diff --git a/README.md b/README.md new file mode 100644 index 000000000..9a76f72f8 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +openprocurement.client.python +============================= + +Reference implementation of a client for OpenProcurement API. + +This product may contain traces of nuts. diff --git a/README.txt b/README.txt deleted file mode 100644 index 329413e04..000000000 --- a/README.txt +++ /dev/null @@ -1,6 +0,0 @@ -Introduction -============ - - - -This product may contain traces of nuts. diff --git a/openprocurement_client/client.py b/openprocurement_client/client.py index 1b238d802..8bf392394 100644 --- a/openprocurement_client/client.py +++ b/openprocurement_client/client.py @@ -1,17 +1,14 @@ -# from gevent import monkey -# monkey.patch_all() -from StringIO import StringIO from functools import wraps from iso8601 import parse_date from munch import munchify -from restkit import BasicAuth, Resource, request, errors +from restkit import BasicAuth, errors, request, Resource from retrying import retry from simplejson import dumps, loads -from tempfile import NamedTemporaryFile +from StringIO import StringIO from urlparse import parse_qs, urlparse -import sys -IGNORE_PARAMS = ('uri', 'path',) +IGNORE_PARAMS = ('uri', 'path') + def verify_file(fn): @wraps(fn) @@ -27,10 +24,15 @@ def wrapper(self, file_, *args, **kwargs): 'file-like object, got {}'.format(type(file_))) return wrapper + class InvalidResponse(Exception): pass +class NoToken(Exception): + pass + + class Client(Resource): """docstring for API""" def __init__(self, key, @@ -44,10 +46,10 @@ def __init__(self, key, self.params = {"mode": "_all_"} self.headers = {"Content-Type": "application/json"} - def request(self, method, path=None, payload=None, headers={}, + def request(self, method, path=None, payload=None, headers=None, params_dict=None, **params): _headers = dict(self.headers) - _headers.update(headers) + _headers.update(headers or {}) try: response = super(Client, self).request( method, path=path, payload=payload, headers=_headers, @@ -61,7 +63,6 @@ def request(self, method, path=None, payload=None, headers={}, self.headers['Cookie'] = e.response.headers['Set-Cookie'] raise e - def patch(self, path=None, payload=None, headers=None, params_dict=None, **params): """ HTTP PATCH @@ -76,29 +77,26 @@ def patch(self, path=None, payload=None, headers=None, return self.request("PATCH", path=path, payload=payload, headers=headers, params_dict=params_dict, **params) - def delete(self, path=None, headers=None, - ): + def delete(self, path=None, headers=None): """ HTTP DELETE - path: string additionnal path to the uri - headers: dict, optionnal headers that will be added to HTTP request. - params: Optionnal parameterss added to the request """ - return self.request("DELETE", path=path, headers=headers, - ) + return self.request("DELETE", path=path, headers=headers) def _update_params(self, params): for key in params: if key not in IGNORE_PARAMS: self.params[key] = params[key] - ############################################################################ + ########################################################################### # GET ITEMS LIST API METHODS - ############################################################################ + ########################################################################### @retry(stop_max_attempt_number=5) def get_tenders(self, params={}, feed='changes'): - #import pdb; pdb.Pdb(stdout=sys.__stdout__).set_trace() params['feed'] = feed try: self._update_params(params) @@ -110,19 +108,22 @@ def get_tenders(self, params={}, feed='changes'): self._update_params(tender_list.next_page) return tender_list.data - except errors.ResourceNotFound as e: + except errors.ResourceNotFound: del self.params['offset'] raise raise InvalidResponse - def get_latest_tenders(self, date, tenderID): - iso_dt=parse_date(date) + def get_latest_tenders(self, date, tender_id): + iso_dt = parse_date(date) dt = iso_dt.strftime("%Y-%m-%d") tm = iso_dt.strftime("%H:%M:%S") response = self._get_resource_item( - self.prefix_path + '?offset={}T{}&opt_fields=tenderID&mode=test'.format(dt, tm), - + '{}?offset={}T{}&opt_fields=tender_id&mode=test'.format( + self.prefix_path, + dt, + tm + ) ) if response.status_int == 200: tender_list = munchify(loads(response.body_string())) @@ -132,8 +133,9 @@ def get_latest_tenders(self, date, tenderID): def _get_tender_resource_list(self, tender, items_name): return self._get_resource_item( - self.prefix_path + '/{}/{}'.format(tender.data.id, items_name), - headers={'X-Access-Token': getattr(getattr(tender, 'access', ''), 'token', '')} + '{}/{}/{}'.format(self.prefix_path, tender.data.id, items_name), + headers={'X-Access-Token': + getattr(getattr(tender, 'access', ''), 'token', '')} ) def get_questions(self, tender, params={}): @@ -145,9 +147,9 @@ def get_documents(self, tender, params={}): def get_awards(self, tender, params={}): return self._get_tender_resource_list(tender, "awards") - ############################################################################ + ########################################################################### # CREATE ITEM API METHODS - ############################################################################ + ########################################################################### def _create_resource_item(self, url, payload, headers={}): headers.update(self.headers) response_item = self.post( @@ -159,9 +161,10 @@ def _create_resource_item(self, url, payload, headers={}): def _create_tender_resource_item(self, tender, item_obj, items_name): return self._create_resource_item( - self.prefix_path + '/{}/'.format(tender.data.id) + items_name, + '{}/{}/{}'.format(self.prefix_path, tender.data.id, items_name), item_obj, - headers={'X-Access-Token': getattr(getattr(tender, 'access', ''), 'token', '')} + headers={'X-Access-Token': + getattr(getattr(tender, 'access', ''), 'token', '')} ) def create_tender(self, tender): @@ -173,9 +176,9 @@ def create_question(self, tender, question): def create_bid(self, tender, bid): return self._create_tender_resource_item(tender, bid, "bids") - ############################################################################ + ########################################################################### # GET ITEM API METHODS - ############################################################################ + ########################################################################### def _get_resource_item(self, url, headers={}): headers.update(self.headers) @@ -185,16 +188,20 @@ def _get_resource_item(self, url, headers={}): raise InvalidResponse def get_tender(self, id): - return self._get_resource_item(self.prefix_path + '/{}'.format(id)) + return self._get_resource_item('{}/{}'.format(self.prefix_path, id)) def _get_tender_resource_item(self, tender, item_id, items_name, access_token=""): if access_token: headers = {'X-Access-Token': access_token} else: - headers = {'X-Access-Token': getattr(getattr(tender, 'access', ''), 'token', '')} + headers = {'X-Access-Token': + getattr(getattr(tender, 'access', ''), 'token', '')} return self._get_resource_item( - self.prefix_path + '/{}/{}/{}'.format(tender.data.id, items_name, item_id), + '{}/{}/{}/{}'.format(self.prefix_path, + tender.data.id, + items_name, + item_id), headers=headers ) @@ -202,7 +209,8 @@ def get_question(self, tender, question_id): return self._get_tender_resource_item(tender, question_id, "questions") def get_bid(self, tender, bid_id, access_token): - return self._get_tender_resource_item(tender, bid_id, "bids", access_token) + return self._get_tender_resource_item(tender, bid_id, "bids", + access_token) def get_file(self, tender, url, access_token): parsed_url = urlparse(url) @@ -219,12 +227,14 @@ def get_file(self, tender, url, access_token): if response_item.status_int == 302: response_obj = request(response_item.headers['location']) if response_obj.status_int == 200: - return response_obj.body_string(), response_obj.headers['Content-Disposition'].split(";")[1].split('"')[1] + return response_obj.body_string(), \ + response_obj.headers['Content-Disposition'] \ + .split(";")[1].split('"')[1] raise InvalidResponse - ############################################################################ + ########################################################################### # PATCH ITEM API METHODS - ############################################################################ + ########################################################################### def _patch_resource_item(self, url, payload, headers={}): headers.update(self.headers) @@ -237,15 +247,19 @@ def _patch_resource_item(self, url, payload, headers={}): def _patch_tender_resource_item(self, tender, item_obj, items_name): return self._patch_resource_item( - self.prefix_path + '/{}/{}/{}'.format( - tender.data.id, items_name, item_obj.data.id - ), item_obj, headers={'X-Access-Token': getattr(getattr(tender, 'access', ''), 'token', '')} + '{}/{}/{}/{}'.format( + self.prefix_path, tender.data.id, items_name, item_obj.data.id + ), + item_obj, + headers={'X-Access-Token': + getattr(getattr(tender, 'access', ''), 'token', '')} ) def patch_tender(self, tender): return self._patch_resource_item( - self.prefix_path + '/{}'.format(tender["data"]["id"]), tender, - headers={'X-Access-Token': getattr(getattr(tender, 'access', ''), 'token', '')} + '{}/{}'.format(self.prefix_path, tender["data"]["id"]), tender, + headers={'X-Access-Token': + getattr(getattr(tender, 'access', ''), 'token', '')} ) def patch_question(self, tender, question): @@ -253,12 +267,13 @@ def patch_question(self, tender, question): def patch_bid(self, tender, bid): return self._patch_tender_resource_item(tender, bid, "bids") + def patch_award(self, tender, award): return self._patch_tender_resource_item(tender, award, "awards") - ############################################################################ + ########################################################################### # UPLOAD FILE API METHODS - ############################################################################ + ########################################################################### def _upload_resource_file(self, url, data, headers={}, method='post'): file_headers = {} file_headers.update(self.headers) @@ -274,9 +289,10 @@ def _upload_resource_file(self, url, data, headers={}, method='post'): @verify_file def upload_document(self, file_, tender): return self._upload_resource_file( - self.prefix_path + '/{}/documents'.format(tender.data.id), + '{}/{}/documents'.format(self.prefix_path, tender.data.id), {"file": file_}, - headers={'X-Access-Token': getattr(getattr(tender, 'access', ''), 'token', '')} + headers={'X-Access-Token': + getattr(getattr(tender, 'access', ''), 'token', '')} ) def upload_tender_document(self, filename, tender): @@ -289,9 +305,14 @@ def upload_tender_document(self, filename, tender): @verify_file def upload_bid_document(self, file_, tender, bid_id): return self._upload_resource_file( - self.prefix_path + '/{}/'.format(tender.data.id)+"bids/"+bid_id+'/documents', + '{}/{}/bids/{}/documents'.format( + self.prefix_path, + tender.data.id, + bid_id + ), {"file": file_}, - headers={'X-Access-Token': getattr(getattr(tender, 'access', ''), 'token', '')} + headers={'X-Access-Token': + getattr(getattr(tender, 'access', ''), 'token', '')} ) def update_bid_document(self, filename, tender, bid_id, document_id): @@ -300,27 +321,37 @@ def update_bid_document(self, filename, tender, bid_id, document_id): file_.write("fixed text data") file_.seek(0) return self._upload_resource_file( - self.prefix_path + '/{}/'.format(tender.data.id)+"bids/"+bid_id+'/documents/'+document_id, + '{}/{}/bids/{}/documents/{}'.format( + self.prefix_path, + tender.data.id, + bid_id, + document_id + ), {"file": file_}, - headers={'X-Access-Token': getattr(getattr(tender, 'access', ''), 'token', '')}, + headers={'X-Access-Token': + getattr(getattr(tender, 'access', ''), 'token', '')}, method='put' ) - ############################################################################ + ########################################################################### # DELETE ITEMS LIST API METHODS - ############################################################################ + ########################################################################### def _delete_resource_item(self, url, headers={}): - response_item = self.delete( - url, headers=headers) + response_item = self.delete(url, headers=headers) if response_item.status_int == 200: return munchify(loads(response_item.body_string())) raise InvalidResponse def delete_bid(self, tender, bid): return self._delete_resource_item( - self.prefix_path + '/{}/'.format(tender.data.id)+"bids/"+bid.data.id, - headers={'X-Access-Token': getattr(getattr(bid, 'access', ''), 'token', '')} + '{}/{}/bids/{}'.format( + self.prefix_path, + tender.data.id, + bid.data.id + ), + headers={'X-Access-Token': + getattr(getattr(bid, 'access', ''), 'token', '')} ) - ############################################################################ + ########################################################################### diff --git a/openprocurement_client/utils.py b/openprocurement_client/utils.py index c1b8d5fb2..ca42970b8 100644 --- a/openprocurement_client/utils.py +++ b/openprocurement_client/utils.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from openprocurement_client.client import Client -import logging from time import sleep +import logging logger = logging.getLogger() diff --git a/setup.py b/setup.py index 0bb16f61d..ee2cec06e 100644 --- a/setup.py +++ b/setup.py @@ -1,18 +1,20 @@ -from setuptools import setup, find_packages +from setuptools import find_packages, setup import os -version = '1.0.a1' +version = '1.0a2' setup(name='openprocurement_client', version=version, description="", - long_description=open("README.txt").read() + "\n" + - open(os.path.join("docs", "HISTORY.txt")).read(), + long_description="{0}\n{1}".format( + open("README.txt").read(), + open(os.path.join("docs", "HISTORY.txt")).read() + ), # Get more strings from # http://pypi.python.org/pypi?:action=list_classifiers classifiers=[ - "Programming Language :: Python", - ], + "Programming Language :: Python", + ], keywords='', author='', author_email='',