From 25b757dd2dae950eade75398076616b6cc7dae36 Mon Sep 17 00:00:00 2001 From: VDigitall Date: Tue, 6 Jun 2017 08:55:16 +0300 Subject: [PATCH 01/20] Added client renew cookies method --- openprocurement_client/api_base_client.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openprocurement_client/api_base_client.py b/openprocurement_client/api_base_client.py index 3ccc095..8a3f1e2 100644 --- a/openprocurement_client/api_base_client.py +++ b/openprocurement_client/api_base_client.py @@ -248,3 +248,21 @@ def patch_credentials(self, id, access_token): payload=None, headers={'X-Access-Token': access_token} ) + + def renew_cookies(self): + old_cookies = 'Old cookies:\n' + for k in self.session.cookies.keys(): + old_cookies += '{}={}\n'.format(k, self.session.cookies[k]) + logger.debug(old_cookies.strip()) + + self.session.cookies.clear() + + response = self.session.request( + 'HEAD', '{}/api/{}/spore'.format(self.host_url, self.api_version) + ) + response.raise_for_status() + + new_cookies = 'New cookies:\n' + for k in self.session.cookies.keys(): + new_cookies += '{}={}\n'.format(k, self.session.cookies[k]) + logger.debug(new_cookies) From 1262b3c80a6cbf888eac9bb2043a4df7cccca4ef Mon Sep 17 00:00:00 2001 From: VDigitall Date: Mon, 21 Aug 2017 13:00:27 +0300 Subject: [PATCH 02/20] Added methods create..., patch... ...resource_items and new Client Classes LotsClient and AssetsClient --- openprocurement_client/api_base_client.py | 28 + openprocurement_client/registry_client.py | 59 ++ openprocurement_client/tests/_server.py | 96 ++-- ...sset_668c3156c8cb496fb28359909cde6e96.json | 272 ++++++++++ ...sset_823d50b3236247adad28a5a66f74db42.json | 504 ++++++++++++++++++ openprocurement_client/tests/data/assets.json | 153 ++++++ .../lot_668c3156c8cb496fb28359909cde6e96.json | 272 ++++++++++ .../lot_823d50b3236247adad28a5a66f74db42.json | 504 ++++++++++++++++++ openprocurement_client/tests/data/lots.json | 153 ++++++ openprocurement_client/tests/data_dict.py | 18 +- openprocurement_client/tests/main.py | 7 +- .../tests/test_registry_client.py | 169 ++++++ 12 files changed, 2192 insertions(+), 43 deletions(-) create mode 100644 openprocurement_client/registry_client.py create mode 100644 openprocurement_client/tests/data/asset_668c3156c8cb496fb28359909cde6e96.json create mode 100644 openprocurement_client/tests/data/asset_823d50b3236247adad28a5a66f74db42.json create mode 100644 openprocurement_client/tests/data/assets.json create mode 100644 openprocurement_client/tests/data/lot_668c3156c8cb496fb28359909cde6e96.json create mode 100644 openprocurement_client/tests/data/lot_823d50b3236247adad28a5a66f74db42.json create mode 100644 openprocurement_client/tests/data/lots.json create mode 100644 openprocurement_client/tests/test_registry_client.py diff --git a/openprocurement_client/api_base_client.py b/openprocurement_client/api_base_client.py index 3ccc095..0d265ed 100644 --- a/openprocurement_client/api_base_client.py +++ b/openprocurement_client/api_base_client.py @@ -10,6 +10,7 @@ from requests import Session from requests.auth import HTTPBasicAuth as BasicAuth from simplejson import loads +from retrying import retry logger = logging.getLogger(__name__) IGNORE_PARAMS = ('uri', 'path') @@ -154,6 +155,23 @@ def _get_resource_item(self, url, headers=None): return munchify(loads(response_item.text)) raise InvalidResponse(response_item) + @retry(stop_max_attempt_number=5) + def _get_resource_items(self, params=None, feed='changes'): + _params = (params or {}).copy() + _params['feed'] = feed + self._update_params(_params) + response = self.request('GET', + self.prefix_path, + params_dict=self.params) + if response.status_code == 200: + resource_items_list = munchify(loads(response.text)) + self._update_params(resource_items_list.next_page) + return resource_items_list.data + elif response.status_code == 404: + del self.params['offset'] + + raise InvalidResponse(response) + def _patch_resource_item(self, url, payload, headers=None): _headers = self.headers.copy() _headers.update(headers or {}) @@ -248,3 +266,13 @@ def patch_credentials(self, id, access_token): payload=None, headers={'X-Access-Token': access_token} ) + + def create_resource_item(self, resource_item): + return self._create_resource_item(self.prefix_path, resource_item) + + def patch_resource_item(self, resource_item): + return self._patch_resource_item( + '{}/{}'.format(self.prefix_path, resource_item['data']['id']), + payload=resource_item, + headers={'X-Access-Token': self._get_access_token(resource_item)} + ) diff --git a/openprocurement_client/registry_client.py b/openprocurement_client/registry_client.py new file mode 100644 index 0000000..1431e86 --- /dev/null +++ b/openprocurement_client/registry_client.py @@ -0,0 +1,59 @@ +import logging + +from .api_base_client import APIBaseClient +from .exceptions import InvalidResponse + +from iso8601 import parse_date +from munch import munchify +from retrying import retry +from simplejson import loads + + +class LotsClient(APIBaseClient): + """ Client for Openregistry Lots """ + + api_version = '0.1' + + def __init__(self, + key='', + resource='lots', + host_url=None, + api_version=None, + params=None, + ds_client=None, + user_agent=None): + super(LotsClient, self).__init__( + key=key, resource=resource, host_url=host_url, params=params, + api_version=api_version, ds_client=ds_client, + user_agent=user_agent) + + def get_lot(self, lot_id, headers=None): + return self.get_resource_item(lot_id, headers=headers) + + def get_lots(self, params=None, feed='changes'): + return self._get_resource_items(params=params, feed=feed) + + +class AssetsClient(APIBaseClient): + """ Client for Openregistry Assets """ + + api_version = '0.1' + + def __init__(self, + key='', + resource='assets', + host_url=None, + api_version=None, + params=None, + ds_client=None, + user_agent=None): + super(AssetsClient, self).__init__( + key=key, resource=resource, host_url=host_url, params=params, + api_version=api_version, ds_client=ds_client, + user_agent=user_agent) + + def get_asset(self, asset_id, headers=None): + return self.get_resource_item(asset_id, headers=headers) + + def get_assets(self, params=None, feed='changes'): + return self._get_resource_items(params=params, feed=feed) diff --git a/openprocurement_client/tests/_server.py b/openprocurement_client/tests/_server.py index 62e9dbf..9ba746b 100755 --- a/openprocurement_client/tests/_server.py +++ b/openprocurement_client/tests/_server.py @@ -3,8 +3,13 @@ from simplejson import dumps, load from openprocurement_client.document_service_client \ import DocumentServiceClient -from openprocurement_client.tests.data_dict import TEST_TENDER_KEYS, \ - TEST_PLAN_KEYS, TEST_CONTRACT_KEYS +from openprocurement_client.tests.data_dict import ( + TEST_TENDER_KEYS, + TEST_PLAN_KEYS, + TEST_CONTRACT_KEYS, + TEST_ASSET_KEYS, + TEST_LOT_KEYS +) import magic import os @@ -23,10 +28,13 @@ CONTRACTS_PATH = API_PATH.format('contracts') SPORE_PATH = API_PATH.format('spore') DOWNLOAD_URL_EXTENSION = 'some_key_etc' -RESOURCE_DICT = \ - {'tender': {'sublink': 'tenders', 'data': TEST_TENDER_KEYS}, - 'contract': {'sublink': 'contracts', 'data': TEST_CONTRACT_KEYS}, - 'plan': {'sublink': 'plans', 'data': TEST_PLAN_KEYS}} +RESOURCE_DICT = { + 'tender': {'sublink': 'tenders', 'data': TEST_TENDER_KEYS}, + 'contract': {'sublink': 'contracts', 'data': TEST_CONTRACT_KEYS}, + 'plan': {'sublink': 'plans', 'data': TEST_PLAN_KEYS}, + 'asset': {'sublink': 'assets', 'data': TEST_ASSET_KEYS}, + 'lot': {'sublink': 'lots', 'data': TEST_LOT_KEYS} +} def resource_filter(resource_name): @@ -296,41 +304,47 @@ def contract_change_patch(contract_id, change_id): routes_dict = { - "spore": (SPORE_PATH, 'HEAD', spore), - "offset_error": (API_PATH.format(''), 'GET', offset_error), - "tenders": (API_PATH.format(''), 'GET', resource_page_get), - "tender_create": (TENDERS_PATH, 'POST', resource_create), - "tender": (API_PATH.format('') + '/', 'GET', resource_page), - "tender_patch": (API_PATH.format('') + "/", 'PATCH', resource_patch), - "tender_document_create": (TENDERS_PATH + "//documents", 'POST', tender_document_create), - "tender_subpage": (TENDERS_PATH + "//", 'GET', tender_subpage), - "tender_subpage_item_create": (TENDERS_PATH + "//", 'POST', resource_subpage_item_create), - "tender_award_documents": (TENDERS_PATH + "//awards//documents", 'GET', tender_award_documents), - "tender_qualification_documents": (TENDERS_PATH + "//qualifications//documents", 'GET', tender_qualification_documents), - "tender_subpage_document_create": (TENDERS_PATH + "////", 'POST', tender_subpage_document_create), - "tender_subpage_document_update": (TENDERS_PATH + "/////", 'PUT', tender_subpage_document_update), - "tender_subpage_document_patch": (TENDERS_PATH + "/////", 'PATCH', tender_subpage_document_patch), - "tender_subpage_item": (TENDERS_PATH + "///", 'GET', tender_subpage_item), - "tender_subpage_item_patch": (API_PATH.format('') + '///', 'PATCH', object_subpage_item_patch), - "tender_subpage_item_delete": (TENDERS_PATH + "///", 'DELETE', tender_subpage_item_delete), - "tender_patch_credentials": (API_PATH.format('') + '//credentials', 'PATCH', patch_credentials), - "redirect": ('/redirect/', 'GET', get_file), - "download": ('/download/', 'GET', download_file), - "plans": (API_PATH.format(''), 'GET', resource_page_get), - "plan_create": (PLANS_PATH, 'POST', resource_create), - "plan_patch": (API_PATH.format('') + "/", 'PATCH', resource_patch), - "plan": (API_PATH.format('') + '/', 'GET', resource_page), - "plan_offset_error": (API_PATH.format(''), 'GET', offset_error), - "contracts": (API_PATH.format(''), 'GET', resource_page_get), - "contract_create": (CONTRACTS_PATH, 'POST', resource_create), - "contract_document_create": (CONTRACTS_PATH + "//documents", 'POST', contract_document_create), - "contract": (API_PATH.format('') + '/', 'GET', resource_page), - "contract_subpage_item_create": (CONTRACTS_PATH + "//", 'POST', resource_subpage_item_create), - "contract_subpage_item_patch": (API_PATH.format('') + '///', 'PATCH', object_subpage_item_patch), - "contract_change_patch": (API_PATH.format('contracts') + '//changes/', 'PATCH', contract_change_patch), - "contract_patch": (API_PATH.format('') + "/", 'PATCH', resource_patch), - "contract_patch_credentials": (API_PATH.format('') + '//credentials', 'PATCH', patch_credentials), - } + "spore": (SPORE_PATH, 'HEAD', spore), + "offset_error": (API_PATH.format(''), 'GET', offset_error), + "tenders": (API_PATH.format(''), 'GET', resource_page_get), + "tender_create": (TENDERS_PATH, 'POST', resource_create), + "tender": (API_PATH.format('') + '/', 'GET', resource_page), + "tender_patch": (API_PATH.format('') + "/", 'PATCH', resource_patch), + "tender_document_create": (TENDERS_PATH + "//documents", 'POST', tender_document_create), + "tender_subpage": (TENDERS_PATH + "//", 'GET', tender_subpage), + "tender_subpage_item_create": (TENDERS_PATH + "//", 'POST', resource_subpage_item_create), + "tender_award_documents": (TENDERS_PATH + "//awards//documents", 'GET', tender_award_documents), + "tender_qualification_documents": (TENDERS_PATH + "//qualifications//documents", 'GET', tender_qualification_documents), + "tender_subpage_document_create": (TENDERS_PATH + "////", 'POST', tender_subpage_document_create), + "tender_subpage_document_update": (TENDERS_PATH + "/////", 'PUT', tender_subpage_document_update), + "tender_subpage_document_patch": (TENDERS_PATH + "/////", 'PATCH', tender_subpage_document_patch), + "tender_subpage_item": (TENDERS_PATH + "///", 'GET', tender_subpage_item), + "tender_subpage_item_patch": (API_PATH.format('') + '///', 'PATCH', object_subpage_item_patch), + "tender_subpage_item_delete": (TENDERS_PATH + "///", 'DELETE', tender_subpage_item_delete), + "tender_patch_credentials": (API_PATH.format('') + '//credentials', 'PATCH', patch_credentials), + "redirect": ('/redirect/', 'GET', get_file), + "download": ('/download/', 'GET', download_file), + "plans": (API_PATH.format(''), 'GET', resource_page_get), + "plan_create": (PLANS_PATH, 'POST', resource_create), + "plan_patch": (API_PATH.format('') + "/", 'PATCH', resource_patch), + "plan": (API_PATH.format('') + '/', 'GET', resource_page), + "plan_offset_error": (API_PATH.format(''), 'GET', offset_error), + "contracts": (API_PATH.format(''), 'GET', resource_page_get), + "contract_create": (CONTRACTS_PATH, 'POST', resource_create), + "contract_document_create": (CONTRACTS_PATH + "//documents", 'POST', contract_document_create), + "contract": (API_PATH.format('') + '/', 'GET', resource_page), + "contract_subpage_item_create": (CONTRACTS_PATH + "//", 'POST', resource_subpage_item_create), + "contract_subpage_item_patch": (API_PATH.format('') + '///', 'PATCH', object_subpage_item_patch), + "contract_change_patch": (API_PATH.format('contracts') + '//changes/', 'PATCH', contract_change_patch), + "contract_patch": (API_PATH.format('') + "/", 'PATCH', resource_patch), + "contract_patch_credentials": (API_PATH.format('') + '//credentials', 'PATCH', patch_credentials), + "assets": (API_PATH.format(''), 'GET', resource_page_get), + "asset": (API_PATH.format('') + '/', 'GET', resource_page), + "asset_patch": (API_PATH.format('') + "/", 'PATCH', resource_patch), + "lots": (API_PATH.format(''), 'GET', resource_page_get), + "lot": (API_PATH.format('') + '/', 'GET', resource_page), + "lot_patch": (API_PATH.format('') + "/", 'PATCH', resource_patch), +} def file_info(file_): diff --git a/openprocurement_client/tests/data/asset_668c3156c8cb496fb28359909cde6e96.json b/openprocurement_client/tests/data/asset_668c3156c8cb496fb28359909cde6e96.json new file mode 100644 index 0000000..27e133b --- /dev/null +++ b/openprocurement_client/tests/data/asset_668c3156c8cb496fb28359909cde6e96.json @@ -0,0 +1,272 @@ +{ + "access": { + "token": "0fc58c83963d42a692db4987df86640a" + }, + "data": { + "procurementMethod": "limited", + "status": "cancelled", + "documents": [ + { + "format": "text/plain", + "url": "https://api-sandbox.openprocurement.org/api/0.12/tenders/668c3156c8cb496fb28359909cde6e96/documents/3f82c7ef80a745119b6a6dec2670e5bc?download=0fc58c83963d42a692db4987df86640a", + "title": "/tmp/tmp5k4QEL", + "documentOf": "tender", + "datePublished": "2016-02-18T14:45:40.348569+02:00", + "dateModified": "2016-02-18T14:45:40.348612+02:00", + "id": "3f82c7ef80a745119b6a6dec2670e5bc" + } + ], + "title": "\u041f\u043e\u0441\u043b\u0443\u0433\u0438 \u0448\u043a\u0456\u043b\u044c\u043d\u0438\u0445 \u0457\u0434\u0430\u043b\u0435\u043d\u044c", + "contracts": [ + { + "status": "pending", + "awardID": "70eaaceee81e4f378e333c1ed8258652", + "id": "c65d3526cdc44f75b2457b4489970a7c" + } + ], + "items": [ + { + "description": "\u041f\u043e\u0441\u043b\u0443\u0433\u0438 \u0448\u043a\u0456\u043b\u044c\u043d\u0438\u0445 \u0457\u0434\u0430\u043b\u0435\u043d\u044c", + "classification": { + "scheme": "CPV", + "description": "\u041f\u043e\u0441\u043b\u0443\u0433\u0438 \u0437 \u0445\u0430\u0440\u0447\u0443\u0432\u0430\u043d\u043d\u044f \u0443 \u0448\u043a\u043e\u043b\u0430\u0445", + "id": "55523100-3" + }, + "additionalClassifications": [ + { + "scheme": "\u0414\u041a\u041f\u041f", + "id": "55.51.10.300", + "description": "\u041f\u043e\u0441\u043b\u0443\u0433\u0438 \u0448\u043a\u0456\u043b\u044c\u043d\u0438\u0445 \u0457\u0434\u0430\u043b\u0435\u043d\u044c" + } + ], + "deliveryLocation": { + "latitude": 49.85, + "longitude": 24.0167 + }, + "deliveryAddress": { + "locality": "\u043c. \u041a\u0438\u0457\u0432", + "region": "\u043c. \u041a\u0438\u0457\u0432", + "countryName_en": "Ukraine", + "countryName": "\u0423\u043a\u0440\u0430\u0457\u043d\u0430", + "streetAddress": "481 John Pine Suite 803", + "countryName_ru": "\u0423\u043a\u0440\u0430\u0438\u043d\u0430", + "postalCode": "58662" + }, + "deliveryDate": { + "endDate": "2016-02-23T14:42:28.297330+02:00" + }, + "id": "2dc54675d6364e2baffbc0f8e74432ac", + "unit": { + "code": "MON", + "name": "month" + }, + "quantity": 9 + } + ], + "complaints": [ + { + "status": "resolved", + "documents": [ + { + "author": "complaint_owner", + "format": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "url": "https://lb.api-sandbox.openprocurement.org/api/0.12/tenders/cdec9f009be44f53817754b12d292ef2/complaints/22b086222a7a4bc6a9cc8adeaa91a57f/documents/129f4013b33a45b8bc70699a62a81499?download=7a6787929a624dd8b7c067974b9e8a96", + "title": "/tmp/tmpcFrLvz.docx", + "documentOf": "tender", + "datePublished": "2016-02-29T15:05:54.557614+02:00", + "dateModified": "2016-02-29T15:05:54.557656+02:00", + "id": "129f4013b33a45b8bc70699a62a81499" + } + ], + "description": "Consequuntur sint cupiditate impedit et magnam aut odit eligendi dicta magnam dicta eveniet.", + "tendererActionDate": "2016-02-29T15:06:05.212783+02:00", + "title": "Hic laboriosam magni cupiditate enim placeat in dolorem.", + "resolutionType": "resolved", + "resolution": "Non ipsa accusantium iste et dolorum quo temporibus deserunt et provident suscipit.", + "satisfied": true, + "dateAnswered": "2016-02-29T15:06:05.212741+02:00", + "tendererAction": "Labore adipisci sequi corporis neque delectus ea aut dicta molestiae omnis voluptatem qui et.", + "dateSubmitted": "2016-02-29T15:06:01.956017+02:00", + "date": "2016-02-29T15:05:53.084688+02:00", + "type": "claim", + "id": "22b086222a7a4bc6a9cc8adeaa91a57f" + }, + { + "status": "cancelled", + "description": "Sit cumque iste eius corporis exercitationem voluptate.", + "title": "Commodi voluptatem earum sapiente doloremque.", + "cancellationReason": "prosto tak :)", + "dateCanceled": "2016-02-29T15:06:19.281225+02:00", + "date": "2016-02-29T15:06:17.694889+02:00", + "type": "claim", + "id": "d26fc2f475514652a53c0c551d984be3" + }, + { + "status": "cancelled", + "description": "Eos officia vel aliquid id qui provident suscipit eos nam amet.", + "title": "Voluptas debitis cupiditate deserunt inventore fugit quo nemo dicta.", + "cancellationReason": "prosto tak :)", + "dateCanceled": "2016-02-29T15:06:28.419955+02:00", + "dateSubmitted": "2016-02-29T15:06:26.927454+02:00", + "date": "2016-02-29T15:06:24.962751+02:00", + "type": "claim", + "id": "816eb0529d6d41148dac0edabc119407" + }, + { + "status": "cancelled", + "dateCanceled": "2016-02-29T15:06:37.390093+02:00", + "tendererActionDate": "2016-02-29T15:06:35.767209+02:00", + "description": "Non voluptas ipsa ex quibusdam dolorem rem dolores sequi.", + "title": "Et esse eos unde molestiae cum sunt.", + "resolutionType": "resolved", + "resolution": "Cumque magni vitae officiis odit et mollitia neque dolores reprehenderit.", + "cancellationReason": "prosto tak :)", + "dateAnswered": "2016-02-29T15:06:35.767171+02:00", + "tendererAction": "Architecto dolor dolorem ut labore esse possimus odit a unde hic quas error alias nulla.", + "dateSubmitted": "2016-02-29T15:06:34.170608+02:00", + "date": "2016-02-29T15:06:32.488624+02:00", + "type": "claim", + "id": "ec053962ce334bdaaab8b3bf2e6b3173" + }, + { + "status": "cancelled", + "dateCanceled": "2016-02-29T15:06:55.181473+02:00", + "tendererActionDate": "2016-02-29T15:06:45.067477+02:00", + "description": "Et autem reiciendis eaque dolorem adipisci aut.", + "title": "Ipsum cupiditate ipsam ut non.", + "resolutionType": "resolved", + "resolution": "Qui vitae placeat rerum officia quia aperiam illo nisi velit et fuga ducimus dolor.", + "author": { + "contactPoint": { + "name": "Варвара Талан", + "telephone": "+38 (268) 501-16-45" + }, + "identifier": { + "scheme": "UA-EDR", + "id": "00007460", + "uri": "http://dummyimage.com/506x880" + }, + "name": "Терещук-Приймак", + "address": { + "locality": "м. Вінниця", + "region": "Вінницька область", + "countryName_en": "Ukraine", + "countryName": "Україна", + "streetAddress": "73741 John Pass", + "countryName_ru": "Украина", + "postalCode": "21100" + } + }, + "satisfied": false, + "dateEscalated": "2016-02-29T15:06:46.828487+02:00", + "dateAnswered": "2016-02-29T15:06:45.067440+02:00", + "tendererAction": "Ex optio cumque assumenda iure consectetur unde iste et consequatur at quaerat dolor.", + "dateSubmitted": "2016-02-29T15:06:43.326331+02:00", + "date": "2016-02-29T15:06:41.513902+02:00", + "cancellationReason": "prosto tak :)", + "type": "complaint", + "id": "76f0f99cb643434da21b6a6350cc2d0e" + } + ], + "procurementMethodType": "reporting", + "cancellations": [ + { + "status": "active", + "documents": [ + { + "description": "test description", + "format": "text/plain", + "url": "https://api-sandbox.openprocurement.org/api/0.12/tenders/668c3156c8cb496fb28359909cde6e96/cancellations/0dd6f9e8cc4f4d1c9c404d842b56d0d7/documents/1afca9faaf2b4f9489ee264b136371c6?download=57e135c2f8d342c3a506be20940a3f3c", + "title": "/tmp/tmpdhdoax", + "documentOf": "tender", + "datePublished": "2016-02-18T14:46:05.348204+02:00", + "dateModified": "2016-02-18T14:46:05.348246+02:00", + "id": "1afca9faaf2b4f9489ee264b136371c6" + }, + { + "description": "test description", + "format": "text/plain", + "url": "https://api-sandbox.openprocurement.org/api/0.12/tenders/668c3156c8cb496fb28359909cde6e96/cancellations/0dd6f9e8cc4f4d1c9c404d842b56d0d7/documents/1afca9faaf2b4f9489ee264b136371c6?download=9208a5fa7cf140a2bb4751a42a1f1ea9", + "title": "/tmp/tmpOBUlWP", + "documentOf": "tender", + "datePublished": "2016-02-18T14:46:05.348204+02:00", + "dateModified": "2016-02-18T14:46:08.516130+02:00", + "id": "1afca9faaf2b4f9489ee264b136371c6" + } + ], + "reason": "prost :))", + "date": "2016-02-18T14:46:04.344125+02:00", + "cancellationOf": "tender", + "id": "0dd6f9e8cc4f4d1c9c404d842b56d0d7" + } + ], + "value": { + "currency": "UAH", + "amount": 500000.0, + "valueAddedTaxIncluded": true + }, + "id": "668c3156c8cb496fb28359909cde6e96", + "awards": [ + { + "status": "active", + "complaintPeriod": { + "startDate": "2016-02-18T14:45:41.212240+02:00", + "endDate": "2016-02-19T14:45:42.178932+02:00" + }, + "suppliers": [ + { + "contactPoint": { + "telephone": "+380 (432) 21-69-30", + "name": "\u0421\u0435\u0440\u0433\u0456\u0439 \u041e\u043b\u0435\u043a\u0441\u044e\u043a", + "email": "soleksuk@gmail.com" + }, + "identifier": { + "scheme": "UA-EDR", + "id": "13313462", + "uri": "http://sch10.edu.vn.ua/", + "legalName": "\u0414\u0435\u0440\u0436\u0430\u0432\u043d\u0435 \u043a\u043e\u043c\u0443\u043d\u0430\u043b\u044c\u043d\u0435 \u043f\u0456\u0434\u043f\u0440\u0438\u0454\u043c\u0441\u0442\u0432\u043e \u0433\u0440\u043e\u043c\u0430\u0434\u0441\u044c\u043a\u043e\u0433\u043e \u0445\u0430\u0440\u0447\u0443\u0432\u0430\u043d\u043d\u044f \u00ab\u0428\u043a\u043e\u043b\u044f\u0440\u00bb" + }, + "name": "\u0414\u041a\u041f \u00ab\u0428\u043a\u043e\u043b\u044f\u0440\u00bb", + "address": { + "postalCode": "21100", + "countryName": "\u0423\u043a\u0440\u0430\u0457\u043d\u0430", + "streetAddress": "\u0432\u0443\u043b. \u041e\u0441\u0442\u0440\u043e\u0432\u0441\u044c\u043a\u043e\u0433\u043e, 33", + "region": "\u043c. \u0412\u0456\u043d\u043d\u0438\u0446\u044f", + "locality": "\u043c. \u0412\u0456\u043d\u043d\u0438\u0446\u044f" + } + } + ], + "value": { + "currency": "UAH", + "amount": 475000.0, + "valueAddedTaxIncluded": true + }, + "date": "2016-02-18T14:45:41.210928+02:00", + "id": "70eaaceee81e4f378e333c1ed8258652" + } + ], + "tenderID": "UA-2016-02-18-000031", + "owner": "test.quintagroup.com", + "dateModified": "2016-02-18T14:46:09.940361+02:00", + "procuringEntity": { + "contactPoint": { + "url": "http://sch10.edu.vn.ua/", + "name": "\u041a\u0443\u0446\u0430 \u0421\u0432\u0456\u0442\u043b\u0430\u043d\u0430 \u0412\u0430\u043b\u0435\u043d\u0442\u0438\u043d\u0456\u0432\u043d\u0430", + "telephone": "+380 (432) 46-53-02" + }, + "identifier": { + "scheme": "UA-EDR", + "id": "21725150", + "legalName": "\u0417\u0430\u043a\u043b\u0430\u0434 \"\u0417\u0430\u0433\u0430\u043b\u044c\u043d\u043e\u043e\u0441\u0432\u0456\u0442\u043d\u044f \u0448\u043a\u043e\u043b\u0430 \u0406-\u0406\u0406\u0406 \u0441\u0442\u0443\u043f\u0435\u043d\u0456\u0432 \u2116 10 \u0412\u0456\u043d\u043d\u0438\u0446\u044c\u043a\u043e\u0457 \u043c\u0456\u0441\u044c\u043a\u043e\u0457 \u0440\u0430\u0434\u0438\"" + }, + "name": "\u0417\u041e\u0421\u0428 #10 \u043c.\u0412\u0456\u043d\u043d\u0438\u0446\u0456", + "address": { + "postalCode": "21027", + "countryName": "\u0423\u043a\u0440\u0430\u0457\u043d\u0430", + "streetAddress": "\u0432\u0443\u043b. \u0421\u0442\u0430\u0445\u0443\u0440\u0441\u044c\u043a\u043e\u0433\u043e. 22", + "region": "\u043c. \u0412\u0456\u043d\u043d\u0438\u0446\u044f", + "locality": "\u043c. \u0412\u0456\u043d\u043d\u0438\u0446\u044f" + } + } + } +} diff --git a/openprocurement_client/tests/data/asset_823d50b3236247adad28a5a66f74db42.json b/openprocurement_client/tests/data/asset_823d50b3236247adad28a5a66f74db42.json new file mode 100644 index 0000000..27b7f37 --- /dev/null +++ b/openprocurement_client/tests/data/asset_823d50b3236247adad28a5a66f74db42.json @@ -0,0 +1,504 @@ +{ + "data":{ + "procurementMethod":"open", + "status":"complete", + "tenderPeriod":{ + "startDate":"2015-12-30T03:05:00+02:00", + "endDate":"2015-12-31T03:05:00+02:00" + }, + "qualifications": [ + { + "status": "pending", + "id": "cec4b82d2708465291fb4af79f8a3e52", + "bidID": "c41709780fa8434399c62a1facf369cb", + "documents": [ + { + "confidentiality": "public", + "language": "uk", + "title": "Proposal.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/e3e16eb63b584378b75d9eb01dc2f8b9/bids/c41709780fa8434399c62a1facf369cb/documents/a05aff4e91c942bbad7e49e551399ec7?download=d0984048f149465699fd402bfe20881c", + "format": "application/pdf", + "documentOf": "tender", + "datePublished": "2016-02-24T14:13:36.127526+02:00", + "id": "a05aff4e91c942bbad7e49e551399ec7", + "dateModified": "2016-02-24T14:13:36.127580+02:00" + }] + }, + { + "status": "pending", + "id": "9d562bbbf05e44f8a6a1f07ddb0415b1", + "bidID": "dda102997a4d4d5d8b5dac6ce2225e1c" + }, + { + "status": "pending", + "id": "36ac59607c434c8198730cc372672c8c", + "bidID": "6e0d218c8e964b42ac4a8ca086c100f9" + } + ], + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.10/tenders/823d50b3236247adad28a5a66f74db42/documents/330822cbbd724671a1d2ff7c3a51dd52?download=5ff4917f29954e5f8dfea1feed8f7455", + "title":"test1.txt", + "documentOf":"tender", + "datePublished":"2015-12-12T03:06:53.209628+02:00", + "dateModified":"2015-12-12T03:06:53.209670+02:00", + "id":"330822cbbd724671a1d2ff7c3a51dd52" + }, + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.10/tenders/823d50b3236247adad28a5a66f74db42/documents/6badc6f313d2499794bd39a3a7182fc0?download=ab2d7cdd346342a6a0d7b40af39145e2", + "title":"test3.txt", + "documentOf":"tender", + "datePublished":"2015-12-12T03:06:53.724784+02:00", + "dateModified":"2015-12-12T03:06:53.724826+02:00", + "id":"6badc6f313d2499794bd39a3a7182fc0" + }, + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.10/tenders/823d50b3236247adad28a5a66f74db42/documents/08bcd56de9aa43faa684f8f8e7ab1c98?download=c7afbf3686cc441db7735d5ec284a0de", + "title":"test2.txt", + "documentOf":"tender", + "datePublished":"2015-12-12T03:06:54.280311+02:00", + "dateModified":"2015-12-12T03:06:54.280354+02:00", + "id":"08bcd56de9aa43faa684f8f8e7ab1c98" + } + ], + "description":"Multilot 4 descr", + "title":"Multilot 4 title", + "submissionMethodDetails":"quick", + "items":[ + { + "relatedLot":"7d774fbf1e86420484c7d1a005cc283f", + "description":"itelm descr", + "classification":{ + "scheme":"CPV", + "description":"\u041f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f \u0441\u0456\u043b\u044c\u0441\u044c\u043a\u043e\u0433\u043e \u0433\u043e\u0441\u043f\u043e\u0434\u0430\u0440\u0441\u0442\u0432\u0430, \u0444\u0435\u0440\u043c\u0435\u0440\u0441\u0442\u0432\u0430, \u0440\u0438\u0431\u0430\u043b\u044c\u0441\u0442\u0432\u0430, \u043b\u0456\u0441\u043d\u0438\u0446\u0442\u0432\u0430 \u0442\u0430 \u0441\u0443\u043c\u0456\u0436\u043d\u0430 \u043f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f", + "id":"03000000-1" + }, + "additionalClassifications":[ + { + "scheme":"\u0414\u041a\u041f\u041f", + "id":"01", + "description":"\u041f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f \u0441\u0456\u043b\u044c\u0441\u044c\u043a\u043e\u0433\u043e \u0433\u043e\u0441\u043f\u043e\u0434\u0430\u0440\u0441\u0442\u0432\u0430, \u043c\u0438\u0441\u043b\u0438\u0432\u0441\u0442\u0432\u0430 \u0442\u0430 \u043f\u043e\u0432\u2019\u044f\u0437\u0430\u043d\u0456 \u0437 \u0446\u0438\u043c \u043f\u043e\u0441\u043b\u0443\u0433\u0438" + } + ], + "deliveryDate":{ + "startDate":"2016-06-01T00:00:00+03:00" + }, + "id":"7caa3587fe9041da98f2744ef531d7ce", + "unit":{ + "code":"LO", + "name":"\u043b\u043e\u0442" + }, + "quantity":4 + }, + { + "relatedLot":"7d774fbf1e86420484c7d1a005cc283f", + "description":"itelm descr", + "classification":{ + "scheme":"CPV", + "description":"\u041f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f \u0441\u0456\u043b\u044c\u0441\u044c\u043a\u043e\u0433\u043e \u0433\u043e\u0441\u043f\u043e\u0434\u0430\u0440\u0441\u0442\u0432\u0430, \u0444\u0435\u0440\u043c\u0435\u0440\u0441\u0442\u0432\u0430, \u0440\u0438\u0431\u0430\u043b\u044c\u0441\u0442\u0432\u0430, \u043b\u0456\u0441\u043d\u0438\u0446\u0442\u0432\u0430 \u0442\u0430 \u0441\u0443\u043c\u0456\u0436\u043d\u0430 \u043f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f", + "id":"03000000-1" + }, + "additionalClassifications":[ + { + "scheme":"\u0414\u041a\u041f\u041f", + "id":"01", + "description":"\u041f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f \u0441\u0456\u043b\u044c\u0441\u044c\u043a\u043e\u0433\u043e \u0433\u043e\u0441\u043f\u043e\u0434\u0430\u0440\u0441\u0442\u0432\u0430, \u043c\u0438\u0441\u043b\u0438\u0432\u0441\u0442\u0432\u0430 \u0442\u0430 \u043f\u043e\u0432\u2019\u044f\u0437\u0430\u043d\u0456 \u0437 \u0446\u0438\u043c \u043f\u043e\u0441\u043b\u0443\u0433\u0438" + } + ], + "deliveryDate":{ + "startDate":"2016-06-01T00:00:00+03:00" + }, + "id":"563ef5d999f34d36a5a0e4e4d91d7be1", + "unit":{ + "code":"LO", + "name":"\u043b\u043e\u0442" + }, + "quantity":4 + } + ], + "id":"823d50b3236247adad28a5a66f74db42", + "value":{ + "currency":"UAH", + "amount":1000.0, + "valueAddedTaxIncluded":false + }, + "submissionMethod":"electronicAuction", + "procuringEntity":{ + "contactPoint":{ + "telephone":"", + "name":"Dmitry", + "email":"d.karnaukh+merchant1@smartweb.com.ua" + }, + "identifier":{ + "scheme":"UA-EDR", + "id":"66666692", + "legalName":"\u041d\u0410\u041a \"Testov Test\"" + }, + "name":"\u041d\u0410\u041a \"Testov Test\"", + "address":{ + "postalCode":"02121", + "countryName":"\u0423\u043a\u0440\u0430\u0438\u043d\u0430", + "streetAddress":"", + "region":"\u041a\u0438\u0435\u0432\u0441\u043a\u0430\u044f \u043e\u0431\u043b\u0430\u0441\u0442\u044c", + "locality":"\u041a\u0438\u0435\u0432" + } + }, + "minimalStep":{ + "currency":"UAH", + "amount":1.0, + "valueAddedTaxIncluded":false + }, + "tenderID":"UA-2015-12-12-000003-1", + "questions":[ + { + "description":"Test lot question descr", + "title":"Test lot question title", + "relatedItem":"563ef5d999f34d36a5a0e4e4d91d7be1", + "answer":"Test answer", + "date":"2015-12-21T17:31:19.650440+02:00", + "id":"615ff8be8eba4a81b300036d6bec991c", + "questionOf":"lot" + }, + { + "description":"lot 2 question descr?", + "title":"lot 2 question title", + "relatedItem":"563ef5d999f34d36a5a0e4e4d91d7be1", + "answer":"lot 2 answer", + "date":"2015-12-21T17:36:30.680576+02:00", + "id":"278f269ee482434da6615a21f3deccf0", + "questionOf":"lot" + }, + { + "description":"lot 3 q descr?", + "title":"lot 3 q title", + "relatedItem":"563ef5d999f34d36a5a0e4e4d91d7be1", + "answer":"lot 3 answer", + "date":"2015-12-21T17:57:28.108596+02:00", + "id":"da6c1228f81e4c2e8451479d57b685dd", + "questionOf":"lot" + }, + { + "description":"Tender q 1 descr?", + "title":"Tender q 1 title", + "relatedItem":"ad681d050d0e42169a80b86d5b591c20", + "answer":"Tender q 1 answer", + "date":"2015-12-21T17:58:24.489846+02:00", + "id":"937b4b9ef3444782b9a3019fcc5c072a", + "questionOf":"tender" + }, + { + "description":"Item descr?", + "title":"Item title?", + "relatedItem":"7caa3587fe9041da98f2744ef531d7ce", + "answer":"Item answer", + "date":"2015-12-21T18:04:35.349936+02:00", + "id":"a2d14f4faf9b4ba180b40f08a2be3bd9", + "questionOf":"item" + } + ], + "enquiryPeriod":{ + "startDate":"2015-12-12T03:06:52.664385+02:00", + "endDate":"2015-12-30T03:05:00+02:00" + }, + "owner":"prom.ua", + "lots":[ + { + "status":"complete", + "description":"lot descr1", + "title":"lot title", + "minimalStep":{ + "currency":"UAH", + "amount":1.0, + "valueAddedTaxIncluded":false + }, + "value":{ + "currency":"UAH", + "amount":1000.0, + "valueAddedTaxIncluded":false + }, + "id":"7d774fbf1e86420484c7d1a005cc283f" + }, + { + "status":"active", + "description":"lot descr2", + "title":"lot title", + "minimalStep":{ + "currency":"UAH", + "amount":1.0, + "valueAddedTaxIncluded":false + }, + "value":{ + "currency":"UAH", + "amount":2000.0, + "valueAddedTaxIncluded":false + }, + "id":"563ef5d999f34d36a5a0e4e4d91d7be1" + } + ], + "bids":[ + { + "date": "2014-12-16T04:44:23.569815+02:00", + "documents": [ + { + "dateModified": "2014-12-16T04:44:25.010930+02:00", + "datePublished": "2014-12-16T04:44:25.010885+02:00", + "format": "text/plain", + "id": "ff001412c60c4164a0f57101e4eaf8aa", + "title": "Proposal.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0/tenders/6f73bf0f7f734f459f7e37e3787054a0/bids/f7fc1212f9f140bba5c4e3cd4f2b62d9/documents/ff001412c60c4164a0f57101e4eaf8aa?download=4f45bbd414104cd78faf620208efd824" + } + ], + "qualificationDocuments": [ + { + "confidentiality": "public", + "language": "uk", + "title": "qualification_document.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/e3e16eb63b584378b75d9eb01dc2f8b9/bids/c41709780fa8434399c62a1facf369cb/financial_documents/7519d21b32af432396acd6e2c9e18ee5?download=5db59ae05361427b84c4caddb0b6d92b", + "format": "application/pdf", + "documentOf": "tender", + "datePublished": "2016-02-24T14:13:36.625252+02:00", + "id": "7519d21b32af432396acd6e2c9e18ee5", + "dateModified": "2016-02-24T14:13:36.625290+02:00" + } + ], + "financial_documens": [ + { + "confidentiality": "public", + "language": "uk", + "title": "financial_doc.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/e3e16eb63b584378b75d9eb01dc2f8b9/bids/c41709780fa8434399c62a1facf369cb/financial_documents/7519d21b32af432396acd6e2c9e18ee5?download=5db59ae05361427b84c4caddb0b6d92b", + "format": "application/pdf", + "documentOf": "tender", + "datePublished": "2016-02-24T14:13:36.625252+02:00", + "id": "7519d21b32af432396acd6e2c9e18ee5", + "dateModified": "2016-02-24T14:13:36.625290+02:00" + } + ], + "eligibility_documents": [ + { + "confidentiality": "public", + "language": "uk", + "title": "eligibility_doc.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/e3e16eb63b584378b75d9eb01dc2f8b9/bids/c41709780fa8434399c62a1facf369cb/financial_documents/7519d21b32af432396acd6e2c9e18ee5?download=5db59ae05361427b84c4caddb0b6d92b", + "format": "application/pdf", + "documentOf": "tender", + "datePublished": "2016-02-24T14:13:36.625252+02:00", + "id": "7519d21b32af432396acd6e2c9e18ee5", + "dateModified": "2016-02-24T14:13:36.625290+02:00" + } + ], + "id": "f7fc1212f9f140bba5c4e3cd4f2b62d9", + "lotValues": [ + { + "value": {"currency": "UAH", "amount": 7750.0, "valueAddedTaxIncluded": true}, + "reatedLot": "7d774fbf1e86420484c7d1a005cc283f", + "date": "2015-11-01T12:43:12.482645+02:00" + }, { + "value": {"currency": "UAH", "amount": 8125.0, "valueAddedTaxIncluded": true}, + "reatedLot": "563ef5d999f34d36a5a0e4e4d91d7be1", + "date": "2015-11-01T12:43:12.482645+02:00" + } + ], + "tenderers": [ + { + "address": { + "countryName": "Україна", + "locality": "м. Вінниця", + "postalCode": "21100", + "region": "м. Вінниця", + "streetAddress": "вул. Островського, 33" + }, + "contactPoint": { + "email": "soleksuk@gmail.com", + "name": "Сергій Олексюк", + "telephone": "+380 (432) 21-69-30" + }, + "identifier": { + "id": "13313462", + "legalName": "Державне комунальне підприємство громадського харчування «Школяр»", + "scheme": "UA-EDR", + "uri": "http://sch10.edu.vn.ua/" + }, + "name": "ДКП «Школяр»" + } + ] + }, + { + "date": "2014-12-16T04:44:27.976478+02:00", + "id": "7ec725815ef448a9b857129024395638", + "lotValues": [ + { + "value": {"currency": "UAH", "amount": 7750.0, "valueAddedTaxIncluded": true}, + "reatedLot": "7d774fbf1e86420484c7d1a005cc283f", + "date": "2015-11-01T12:43:12.482645+02:00" + }, { + "value": {"currency": "UAH", "amount": 8125.0, "valueAddedTaxIncluded": true}, + "reatedLot": "563ef5d999f34d36a5a0e4e4d91d7be1", + "date": "2015-11-01T12:43:12.482645+02:00" + } + ], + "tenderers": [ + { + "address": { + "countryName": "Україна", + "locality": "м. Вінниця", + "postalCode": "21018", + "region": "м. Вінниця", + "streetAddress": "вул. Юності, 30" + }, + "contactPoint": { + "email": "alla.myhailova@i.ua", + "name": "Алла Михайлова", + "telephone": "+380 (432) 460-665" + }, + "identifier": { + "id": "13306232", + "legalName": "Державне комунальне підприємство громадського харчування «Меридіан»", + "scheme": "UA-EDR", + "uri": "http://sch10.edu.vn.ua/" + }, + "name": "ДКП «Меридіан2»" + } + ] + } + + ], + "awards":[ + { + "status":"active", + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.9/tenders/3216137f17fb452db202daad1f9e73ad/awards/7054491a5e514699a56e44d32e23edf7/documents/c7ac894e4ca34e618e03a02d8799dc69?download=8c4053b0942949f4bd6e998a51e177c5", + "title":"2.jpg", + "datePublished":"2015-11-13T16:31:03.691781+02:00", + "dateModified":"2015-11-13T16:31:03.691829+02:00", + "id":"c7ac894e4ca34e618e03a02d8799dc69" + } + ], + "complaintPeriod":{ + "startDate":"2015-11-13T16:17:00.375880+02:00", + "endDate":"2015-11-14T16:31:06.979761+02:00" + }, + "contracts":[ + { + "status":"active", + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.9/tenders/3216137f17fb452db202daad1f9e73ad/awards/7054491a5e514699a56e44d32e23edf7/contracts/045c478807c04215950b4d74e6861cb1/documents/14dbf158b9ed4a478a59d19c90dbdf5d?download=9d948173e65441409f06e0b81674b39a", + "title":"3.jpg", + "datePublished":"2015-11-13T19:01:16.748785+02:00", + "dateModified":"2015-11-13T19:01:16.748829+02:00", + "id":"14dbf158b9ed4a478a59d19c90dbdf5d" + } + ], + "awardID":"7054491a5e514699a56e44d32e23edf7", + "id":"045c478807c04215950b4d74e6861cb1" + } + ], + "suppliers":[ + { + "contactPoint":{ + "telephone":"", + "name":"some6", + "email":"some6@mailinator.com" + }, + "identifier":{ + "scheme":"UA-EDR", + "id":"62123463", + "legalName":"some6" + }, + "name":"some6", + "address":{ + "postalCode":"04119", + "countryName":"\u0423\u043a\u0440\u0430\u0438\u043d\u0430", + "streetAddress":"", + "region":"\u0410\u0420 \u041a\u0440\u044b\u043c", + "locality":"ffff" + } + } + ], + "bid_id":"ad9ac917ff0049178acc3613bb14a691", + "value":{ + "currency":"UAH", + "amount":300.0, + "valueAddedTaxIncluded":false + }, + "date":"2015-11-13T16:17:00.376251+02:00", + "id":"7054491a5e514699a56e44d32e23edf7", + "lotId": "563ef5d999f34d36a5a0e4e4d91d7be1" + }, + { + "status":"active", + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.9/tenders/3216137f17fb452db202daad1f9e73ad/awards/7054491a5e514699a56e44d32e23edf7/documents/c7ac894e4ca34e618e03a02d8799dc69?download=8c4053b0942949f4bd6e998a51e177c5", + "title":"2.jpg", + "datePublished":"2015-11-13T16:31:03.691781+02:00", + "dateModified":"2015-11-13T16:31:03.691829+02:00", + "id":"c7ac894e4ca34e618e03a02d8799dc69" + } + ], + "complaintPeriod":{ + "startDate":"2015-11-13T16:17:00.375880+02:00", + "endDate":"2015-11-14T16:31:06.979761+02:00" + }, + "contracts":[ + { + "status":"active", + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.9/tenders/3216137f17fb452db202daad1f9e73ad/awards/7054491a5e514699a56e44d32e23edf7/contracts/045c478807c04215950b4d74e6861cb1/documents/14dbf158b9ed4a478a59d19c90dbdf5d?download=9d948173e65441409f06e0b81674b39a", + "title":"3.jpg", + "datePublished":"2015-11-13T19:01:16.748785+02:00", + "dateModified":"2015-11-13T19:01:16.748829+02:00", + "id":"14dbf158b9ed4a478a59d19c90dbdf5d" + } + ], + "awardID":"7054491a5e514699a56e44d32e23edf7", + "id":"045c478807c04215950b4d74e6861cb1" + } + ], + "suppliers":[ + { + "contactPoint":{ + "telephone":"", + "name":"some6", + "email":"some6@mailinator.com" + }, + "identifier":{ + "scheme":"UA-EDR", + "id":"62123463", + "legalName":"some6" + }, + "name":"some6", + "address":{ + "postalCode":"04119", + "countryName":"\u0423\u043a\u0440\u0430\u0438\u043d\u0430", + "streetAddress":"", + "region":"\u0410\u0420 \u041a\u0440\u044b\u043c", + "locality":"ffff" + } + } + ], + "bid_id":"ad9ac917ff0049178acc3613bb14a691", + "value":{ + "currency":"UAH", + "amount":300.0, + "valueAddedTaxIncluded":false + }, + "date":"2015-11-13T16:17:00.376251+02:00", + "id":"7054491a5e514699a56e44d32e23edf7", + "lotId": "7d774fbf1e86420484c7d1a005cc283f" + } + ], + "dateModified":"2015-12-12T03:06:54.370277+02:00", + "awardCriteria":"lowestCost" + } +} diff --git a/openprocurement_client/tests/data/assets.json b/openprocurement_client/tests/data/assets.json new file mode 100644 index 0000000..384ef8a --- /dev/null +++ b/openprocurement_client/tests/data/assets.json @@ -0,0 +1,153 @@ +{ + "next_page":{ + "path":"/api/0.10/tenders?offset=2015-12-25T18%3A04%3A36.264176%2B02%3A00", + "uri":"https://lb.api-sandbox.openprocurement.org/api/0.10/tenders?offset=2015-12-25T18%3A04%3A36.264176%2B02%3A00", + "offset":"2015-12-25T18:04:36.264176+02:00" + }, + "data":[ + { + "id":"823d50b3236247adad28a5a66f74db42", + "dateModified":"2015-11-13T18:50:00.753811+02:00" + }, + { + "id":"f3849ade33534174b8402579152a5f41", + "dateModified":"2015-11-16T01:15:00.469896+02:00" + }, + { + "id":"f3849ade33534174b8402579152a5f41", + "dateModified":"2015-11-16T12:00:00.960077+02:00" + }, + { + "id":"3be3664065994d1e8847beb597c014bf", + "dateModified":"2015-11-16T21:15:00.404214+02:00" + }, + { + "id":"711cfa82c54147b686f6755adb22364d", + "dateModified":"2015-11-17T18:02:00.456050+02:00" + }, + { + "id":"730fe73cb7be4f92bce472484236e9ee", + "dateModified":"2015-11-18T18:30:00.410770+02:00" + }, + { + "id":"496ec128323f45679daf9ebcb1ec62f6", + "dateModified":"2015-11-20T16:20:37.977312+02:00" + }, + { + "id":"18d566bed8cc4f2e846ca5d64c6bdfed", + "dateModified":"2015-12-01T11:48:33.784585+02:00" + }, + { + "id":"21733c5ed0be4b259c3345310f425a82", + "dateModified":"2015-12-02T13:54:13.656648+02:00" + }, + { + "id":"b35a1151b7a84562addb47f66193a309", + "dateModified":"2015-12-12T02:45:36.355426+02:00" + }, + { + "id":"ce9751da26a84c2390368ffa5adfd74c", + "dateModified":"2015-12-12T03:04:16.059667+02:00" + }, + { + "id":"823d50b3236247adad28a5a66f74db42", + "dateModified":"2015-12-12T03:06:54.370277+02:00" + }, + { + "id":"0085cf76ad444ad0ae625fc17f366dd2", + "dateModified":"2015-12-12T14:30:38.596246+02:00" + }, + { + "id":"d97613278dd547f2be5523cdc5614880", + "dateModified":"2015-12-12T14:31:51.342309+02:00" + }, + { + "id":"71161344890c4231b1487296f56aa910", + "dateModified":"2015-12-14T13:10:38.771625+02:00" + }, + { + "id":"759a81e58cf04b5ea0587942d0925faa", + "dateModified":"2015-12-16T19:56:53.874070+02:00" + }, + { + "id":"ad681d050d0e42169a80b86d5b591c20", + "dateModified":"2015-12-22T13:14:25.253453+02:00" + }, + { + "id":"a2e4464a08d64addb6c7a63da48ddb87", + "dateModified":"2015-12-23T17:09:15.145867+02:00" + }, + { + "id":"eea0c2f3cf3f4206b8cbba813b9a527b", + "dateModified":"2015-12-23T17:15:23.479628+02:00" + }, + { + "id":"847411c7b03f4f91b97fecb70c6e6275", + "dateModified":"2015-12-23T17:54:39.470396+02:00" + }, + { + "id":"599e63e6fb1349078fb01e4f9e8a812c", + "dateModified":"2015-12-23T17:55:36.317529+02:00" + }, + { + "id":"d63a7302316c4d15b46aeb7bcd4f1dbe", + "dateModified":"2015-12-23T17:56:01.709307+02:00" + }, + { + "id":"6496299a3c8e482f9017d855483a97b5", + "dateModified":"2015-12-23T17:57:57.376623+02:00" + }, + { + "id":"a513848583cc4d1d806c78e5131ad068", + "dateModified":"2015-12-23T17:59:52.101031+02:00" + }, + { + "id":"79aa9fd2bb43471cbcb0b97f6965e5f6", + "dateModified":"2015-12-24T11:39:09.071184+02:00" + }, + { + "id":"b2cdcda417434c7b889c82a81927bb73", + "dateModified":"2015-12-24T11:40:20.172257+02:00" + }, + { + "id":"58eca935e0e24805b561a1588ae64ef3", + "dateModified":"2015-12-24T11:48:57.584694+02:00" + }, + { + "id":"78b889ec869544b9b47c8740549ed20a", + "dateModified":"2015-12-24T11:51:07.149689+02:00" + }, + { + "id":"258ef598759946dbb30e7ed67bfc2346", + "dateModified":"2015-12-24T11:52:16.196052+02:00" + }, + { + "id":"639ecaca9db24228a9fe0eb3494aa0e3", + "dateModified":"2015-12-25T13:28:22.136939+02:00" + }, + { + "id":"ca10599dfcbe416bb513416c13073c5f", + "dateModified":"2015-12-25T15:19:53.913160+02:00" + }, + { + "id":"21e551bf7f194d8aaba29bf2e4949b90", + "dateModified":"2015-12-25T16:00:00.319589+02:00" + }, + { + "id":"dd7b13a956b44a34bbaeb2bfa1910c03", + "dateModified":"2015-12-25T16:07:19.809837+02:00" + }, + { + "id":"f8f21e8b0c104747b8abf5ece4fb5d72", + "dateModified":"2015-12-25T17:06:32.885207+02:00" + }, + { + "id":"48dcb014d003454b8875975bb531147f", + "dateModified":"2015-12-25T17:16:05.208447+02:00" + }, + { + "id":"7f35a555d5c64b34a7cbc454fab5628c", + "dateModified":"2015-12-25T18:04:36.264176+02:00" + } + ] +} diff --git a/openprocurement_client/tests/data/lot_668c3156c8cb496fb28359909cde6e96.json b/openprocurement_client/tests/data/lot_668c3156c8cb496fb28359909cde6e96.json new file mode 100644 index 0000000..27e133b --- /dev/null +++ b/openprocurement_client/tests/data/lot_668c3156c8cb496fb28359909cde6e96.json @@ -0,0 +1,272 @@ +{ + "access": { + "token": "0fc58c83963d42a692db4987df86640a" + }, + "data": { + "procurementMethod": "limited", + "status": "cancelled", + "documents": [ + { + "format": "text/plain", + "url": "https://api-sandbox.openprocurement.org/api/0.12/tenders/668c3156c8cb496fb28359909cde6e96/documents/3f82c7ef80a745119b6a6dec2670e5bc?download=0fc58c83963d42a692db4987df86640a", + "title": "/tmp/tmp5k4QEL", + "documentOf": "tender", + "datePublished": "2016-02-18T14:45:40.348569+02:00", + "dateModified": "2016-02-18T14:45:40.348612+02:00", + "id": "3f82c7ef80a745119b6a6dec2670e5bc" + } + ], + "title": "\u041f\u043e\u0441\u043b\u0443\u0433\u0438 \u0448\u043a\u0456\u043b\u044c\u043d\u0438\u0445 \u0457\u0434\u0430\u043b\u0435\u043d\u044c", + "contracts": [ + { + "status": "pending", + "awardID": "70eaaceee81e4f378e333c1ed8258652", + "id": "c65d3526cdc44f75b2457b4489970a7c" + } + ], + "items": [ + { + "description": "\u041f\u043e\u0441\u043b\u0443\u0433\u0438 \u0448\u043a\u0456\u043b\u044c\u043d\u0438\u0445 \u0457\u0434\u0430\u043b\u0435\u043d\u044c", + "classification": { + "scheme": "CPV", + "description": "\u041f\u043e\u0441\u043b\u0443\u0433\u0438 \u0437 \u0445\u0430\u0440\u0447\u0443\u0432\u0430\u043d\u043d\u044f \u0443 \u0448\u043a\u043e\u043b\u0430\u0445", + "id": "55523100-3" + }, + "additionalClassifications": [ + { + "scheme": "\u0414\u041a\u041f\u041f", + "id": "55.51.10.300", + "description": "\u041f\u043e\u0441\u043b\u0443\u0433\u0438 \u0448\u043a\u0456\u043b\u044c\u043d\u0438\u0445 \u0457\u0434\u0430\u043b\u0435\u043d\u044c" + } + ], + "deliveryLocation": { + "latitude": 49.85, + "longitude": 24.0167 + }, + "deliveryAddress": { + "locality": "\u043c. \u041a\u0438\u0457\u0432", + "region": "\u043c. \u041a\u0438\u0457\u0432", + "countryName_en": "Ukraine", + "countryName": "\u0423\u043a\u0440\u0430\u0457\u043d\u0430", + "streetAddress": "481 John Pine Suite 803", + "countryName_ru": "\u0423\u043a\u0440\u0430\u0438\u043d\u0430", + "postalCode": "58662" + }, + "deliveryDate": { + "endDate": "2016-02-23T14:42:28.297330+02:00" + }, + "id": "2dc54675d6364e2baffbc0f8e74432ac", + "unit": { + "code": "MON", + "name": "month" + }, + "quantity": 9 + } + ], + "complaints": [ + { + "status": "resolved", + "documents": [ + { + "author": "complaint_owner", + "format": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "url": "https://lb.api-sandbox.openprocurement.org/api/0.12/tenders/cdec9f009be44f53817754b12d292ef2/complaints/22b086222a7a4bc6a9cc8adeaa91a57f/documents/129f4013b33a45b8bc70699a62a81499?download=7a6787929a624dd8b7c067974b9e8a96", + "title": "/tmp/tmpcFrLvz.docx", + "documentOf": "tender", + "datePublished": "2016-02-29T15:05:54.557614+02:00", + "dateModified": "2016-02-29T15:05:54.557656+02:00", + "id": "129f4013b33a45b8bc70699a62a81499" + } + ], + "description": "Consequuntur sint cupiditate impedit et magnam aut odit eligendi dicta magnam dicta eveniet.", + "tendererActionDate": "2016-02-29T15:06:05.212783+02:00", + "title": "Hic laboriosam magni cupiditate enim placeat in dolorem.", + "resolutionType": "resolved", + "resolution": "Non ipsa accusantium iste et dolorum quo temporibus deserunt et provident suscipit.", + "satisfied": true, + "dateAnswered": "2016-02-29T15:06:05.212741+02:00", + "tendererAction": "Labore adipisci sequi corporis neque delectus ea aut dicta molestiae omnis voluptatem qui et.", + "dateSubmitted": "2016-02-29T15:06:01.956017+02:00", + "date": "2016-02-29T15:05:53.084688+02:00", + "type": "claim", + "id": "22b086222a7a4bc6a9cc8adeaa91a57f" + }, + { + "status": "cancelled", + "description": "Sit cumque iste eius corporis exercitationem voluptate.", + "title": "Commodi voluptatem earum sapiente doloremque.", + "cancellationReason": "prosto tak :)", + "dateCanceled": "2016-02-29T15:06:19.281225+02:00", + "date": "2016-02-29T15:06:17.694889+02:00", + "type": "claim", + "id": "d26fc2f475514652a53c0c551d984be3" + }, + { + "status": "cancelled", + "description": "Eos officia vel aliquid id qui provident suscipit eos nam amet.", + "title": "Voluptas debitis cupiditate deserunt inventore fugit quo nemo dicta.", + "cancellationReason": "prosto tak :)", + "dateCanceled": "2016-02-29T15:06:28.419955+02:00", + "dateSubmitted": "2016-02-29T15:06:26.927454+02:00", + "date": "2016-02-29T15:06:24.962751+02:00", + "type": "claim", + "id": "816eb0529d6d41148dac0edabc119407" + }, + { + "status": "cancelled", + "dateCanceled": "2016-02-29T15:06:37.390093+02:00", + "tendererActionDate": "2016-02-29T15:06:35.767209+02:00", + "description": "Non voluptas ipsa ex quibusdam dolorem rem dolores sequi.", + "title": "Et esse eos unde molestiae cum sunt.", + "resolutionType": "resolved", + "resolution": "Cumque magni vitae officiis odit et mollitia neque dolores reprehenderit.", + "cancellationReason": "prosto tak :)", + "dateAnswered": "2016-02-29T15:06:35.767171+02:00", + "tendererAction": "Architecto dolor dolorem ut labore esse possimus odit a unde hic quas error alias nulla.", + "dateSubmitted": "2016-02-29T15:06:34.170608+02:00", + "date": "2016-02-29T15:06:32.488624+02:00", + "type": "claim", + "id": "ec053962ce334bdaaab8b3bf2e6b3173" + }, + { + "status": "cancelled", + "dateCanceled": "2016-02-29T15:06:55.181473+02:00", + "tendererActionDate": "2016-02-29T15:06:45.067477+02:00", + "description": "Et autem reiciendis eaque dolorem adipisci aut.", + "title": "Ipsum cupiditate ipsam ut non.", + "resolutionType": "resolved", + "resolution": "Qui vitae placeat rerum officia quia aperiam illo nisi velit et fuga ducimus dolor.", + "author": { + "contactPoint": { + "name": "Варвара Талан", + "telephone": "+38 (268) 501-16-45" + }, + "identifier": { + "scheme": "UA-EDR", + "id": "00007460", + "uri": "http://dummyimage.com/506x880" + }, + "name": "Терещук-Приймак", + "address": { + "locality": "м. Вінниця", + "region": "Вінницька область", + "countryName_en": "Ukraine", + "countryName": "Україна", + "streetAddress": "73741 John Pass", + "countryName_ru": "Украина", + "postalCode": "21100" + } + }, + "satisfied": false, + "dateEscalated": "2016-02-29T15:06:46.828487+02:00", + "dateAnswered": "2016-02-29T15:06:45.067440+02:00", + "tendererAction": "Ex optio cumque assumenda iure consectetur unde iste et consequatur at quaerat dolor.", + "dateSubmitted": "2016-02-29T15:06:43.326331+02:00", + "date": "2016-02-29T15:06:41.513902+02:00", + "cancellationReason": "prosto tak :)", + "type": "complaint", + "id": "76f0f99cb643434da21b6a6350cc2d0e" + } + ], + "procurementMethodType": "reporting", + "cancellations": [ + { + "status": "active", + "documents": [ + { + "description": "test description", + "format": "text/plain", + "url": "https://api-sandbox.openprocurement.org/api/0.12/tenders/668c3156c8cb496fb28359909cde6e96/cancellations/0dd6f9e8cc4f4d1c9c404d842b56d0d7/documents/1afca9faaf2b4f9489ee264b136371c6?download=57e135c2f8d342c3a506be20940a3f3c", + "title": "/tmp/tmpdhdoax", + "documentOf": "tender", + "datePublished": "2016-02-18T14:46:05.348204+02:00", + "dateModified": "2016-02-18T14:46:05.348246+02:00", + "id": "1afca9faaf2b4f9489ee264b136371c6" + }, + { + "description": "test description", + "format": "text/plain", + "url": "https://api-sandbox.openprocurement.org/api/0.12/tenders/668c3156c8cb496fb28359909cde6e96/cancellations/0dd6f9e8cc4f4d1c9c404d842b56d0d7/documents/1afca9faaf2b4f9489ee264b136371c6?download=9208a5fa7cf140a2bb4751a42a1f1ea9", + "title": "/tmp/tmpOBUlWP", + "documentOf": "tender", + "datePublished": "2016-02-18T14:46:05.348204+02:00", + "dateModified": "2016-02-18T14:46:08.516130+02:00", + "id": "1afca9faaf2b4f9489ee264b136371c6" + } + ], + "reason": "prost :))", + "date": "2016-02-18T14:46:04.344125+02:00", + "cancellationOf": "tender", + "id": "0dd6f9e8cc4f4d1c9c404d842b56d0d7" + } + ], + "value": { + "currency": "UAH", + "amount": 500000.0, + "valueAddedTaxIncluded": true + }, + "id": "668c3156c8cb496fb28359909cde6e96", + "awards": [ + { + "status": "active", + "complaintPeriod": { + "startDate": "2016-02-18T14:45:41.212240+02:00", + "endDate": "2016-02-19T14:45:42.178932+02:00" + }, + "suppliers": [ + { + "contactPoint": { + "telephone": "+380 (432) 21-69-30", + "name": "\u0421\u0435\u0440\u0433\u0456\u0439 \u041e\u043b\u0435\u043a\u0441\u044e\u043a", + "email": "soleksuk@gmail.com" + }, + "identifier": { + "scheme": "UA-EDR", + "id": "13313462", + "uri": "http://sch10.edu.vn.ua/", + "legalName": "\u0414\u0435\u0440\u0436\u0430\u0432\u043d\u0435 \u043a\u043e\u043c\u0443\u043d\u0430\u043b\u044c\u043d\u0435 \u043f\u0456\u0434\u043f\u0440\u0438\u0454\u043c\u0441\u0442\u0432\u043e \u0433\u0440\u043e\u043c\u0430\u0434\u0441\u044c\u043a\u043e\u0433\u043e \u0445\u0430\u0440\u0447\u0443\u0432\u0430\u043d\u043d\u044f \u00ab\u0428\u043a\u043e\u043b\u044f\u0440\u00bb" + }, + "name": "\u0414\u041a\u041f \u00ab\u0428\u043a\u043e\u043b\u044f\u0440\u00bb", + "address": { + "postalCode": "21100", + "countryName": "\u0423\u043a\u0440\u0430\u0457\u043d\u0430", + "streetAddress": "\u0432\u0443\u043b. \u041e\u0441\u0442\u0440\u043e\u0432\u0441\u044c\u043a\u043e\u0433\u043e, 33", + "region": "\u043c. \u0412\u0456\u043d\u043d\u0438\u0446\u044f", + "locality": "\u043c. \u0412\u0456\u043d\u043d\u0438\u0446\u044f" + } + } + ], + "value": { + "currency": "UAH", + "amount": 475000.0, + "valueAddedTaxIncluded": true + }, + "date": "2016-02-18T14:45:41.210928+02:00", + "id": "70eaaceee81e4f378e333c1ed8258652" + } + ], + "tenderID": "UA-2016-02-18-000031", + "owner": "test.quintagroup.com", + "dateModified": "2016-02-18T14:46:09.940361+02:00", + "procuringEntity": { + "contactPoint": { + "url": "http://sch10.edu.vn.ua/", + "name": "\u041a\u0443\u0446\u0430 \u0421\u0432\u0456\u0442\u043b\u0430\u043d\u0430 \u0412\u0430\u043b\u0435\u043d\u0442\u0438\u043d\u0456\u0432\u043d\u0430", + "telephone": "+380 (432) 46-53-02" + }, + "identifier": { + "scheme": "UA-EDR", + "id": "21725150", + "legalName": "\u0417\u0430\u043a\u043b\u0430\u0434 \"\u0417\u0430\u0433\u0430\u043b\u044c\u043d\u043e\u043e\u0441\u0432\u0456\u0442\u043d\u044f \u0448\u043a\u043e\u043b\u0430 \u0406-\u0406\u0406\u0406 \u0441\u0442\u0443\u043f\u0435\u043d\u0456\u0432 \u2116 10 \u0412\u0456\u043d\u043d\u0438\u0446\u044c\u043a\u043e\u0457 \u043c\u0456\u0441\u044c\u043a\u043e\u0457 \u0440\u0430\u0434\u0438\"" + }, + "name": "\u0417\u041e\u0421\u0428 #10 \u043c.\u0412\u0456\u043d\u043d\u0438\u0446\u0456", + "address": { + "postalCode": "21027", + "countryName": "\u0423\u043a\u0440\u0430\u0457\u043d\u0430", + "streetAddress": "\u0432\u0443\u043b. \u0421\u0442\u0430\u0445\u0443\u0440\u0441\u044c\u043a\u043e\u0433\u043e. 22", + "region": "\u043c. \u0412\u0456\u043d\u043d\u0438\u0446\u044f", + "locality": "\u043c. \u0412\u0456\u043d\u043d\u0438\u0446\u044f" + } + } + } +} diff --git a/openprocurement_client/tests/data/lot_823d50b3236247adad28a5a66f74db42.json b/openprocurement_client/tests/data/lot_823d50b3236247adad28a5a66f74db42.json new file mode 100644 index 0000000..27b7f37 --- /dev/null +++ b/openprocurement_client/tests/data/lot_823d50b3236247adad28a5a66f74db42.json @@ -0,0 +1,504 @@ +{ + "data":{ + "procurementMethod":"open", + "status":"complete", + "tenderPeriod":{ + "startDate":"2015-12-30T03:05:00+02:00", + "endDate":"2015-12-31T03:05:00+02:00" + }, + "qualifications": [ + { + "status": "pending", + "id": "cec4b82d2708465291fb4af79f8a3e52", + "bidID": "c41709780fa8434399c62a1facf369cb", + "documents": [ + { + "confidentiality": "public", + "language": "uk", + "title": "Proposal.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/e3e16eb63b584378b75d9eb01dc2f8b9/bids/c41709780fa8434399c62a1facf369cb/documents/a05aff4e91c942bbad7e49e551399ec7?download=d0984048f149465699fd402bfe20881c", + "format": "application/pdf", + "documentOf": "tender", + "datePublished": "2016-02-24T14:13:36.127526+02:00", + "id": "a05aff4e91c942bbad7e49e551399ec7", + "dateModified": "2016-02-24T14:13:36.127580+02:00" + }] + }, + { + "status": "pending", + "id": "9d562bbbf05e44f8a6a1f07ddb0415b1", + "bidID": "dda102997a4d4d5d8b5dac6ce2225e1c" + }, + { + "status": "pending", + "id": "36ac59607c434c8198730cc372672c8c", + "bidID": "6e0d218c8e964b42ac4a8ca086c100f9" + } + ], + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.10/tenders/823d50b3236247adad28a5a66f74db42/documents/330822cbbd724671a1d2ff7c3a51dd52?download=5ff4917f29954e5f8dfea1feed8f7455", + "title":"test1.txt", + "documentOf":"tender", + "datePublished":"2015-12-12T03:06:53.209628+02:00", + "dateModified":"2015-12-12T03:06:53.209670+02:00", + "id":"330822cbbd724671a1d2ff7c3a51dd52" + }, + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.10/tenders/823d50b3236247adad28a5a66f74db42/documents/6badc6f313d2499794bd39a3a7182fc0?download=ab2d7cdd346342a6a0d7b40af39145e2", + "title":"test3.txt", + "documentOf":"tender", + "datePublished":"2015-12-12T03:06:53.724784+02:00", + "dateModified":"2015-12-12T03:06:53.724826+02:00", + "id":"6badc6f313d2499794bd39a3a7182fc0" + }, + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.10/tenders/823d50b3236247adad28a5a66f74db42/documents/08bcd56de9aa43faa684f8f8e7ab1c98?download=c7afbf3686cc441db7735d5ec284a0de", + "title":"test2.txt", + "documentOf":"tender", + "datePublished":"2015-12-12T03:06:54.280311+02:00", + "dateModified":"2015-12-12T03:06:54.280354+02:00", + "id":"08bcd56de9aa43faa684f8f8e7ab1c98" + } + ], + "description":"Multilot 4 descr", + "title":"Multilot 4 title", + "submissionMethodDetails":"quick", + "items":[ + { + "relatedLot":"7d774fbf1e86420484c7d1a005cc283f", + "description":"itelm descr", + "classification":{ + "scheme":"CPV", + "description":"\u041f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f \u0441\u0456\u043b\u044c\u0441\u044c\u043a\u043e\u0433\u043e \u0433\u043e\u0441\u043f\u043e\u0434\u0430\u0440\u0441\u0442\u0432\u0430, \u0444\u0435\u0440\u043c\u0435\u0440\u0441\u0442\u0432\u0430, \u0440\u0438\u0431\u0430\u043b\u044c\u0441\u0442\u0432\u0430, \u043b\u0456\u0441\u043d\u0438\u0446\u0442\u0432\u0430 \u0442\u0430 \u0441\u0443\u043c\u0456\u0436\u043d\u0430 \u043f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f", + "id":"03000000-1" + }, + "additionalClassifications":[ + { + "scheme":"\u0414\u041a\u041f\u041f", + "id":"01", + "description":"\u041f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f \u0441\u0456\u043b\u044c\u0441\u044c\u043a\u043e\u0433\u043e \u0433\u043e\u0441\u043f\u043e\u0434\u0430\u0440\u0441\u0442\u0432\u0430, \u043c\u0438\u0441\u043b\u0438\u0432\u0441\u0442\u0432\u0430 \u0442\u0430 \u043f\u043e\u0432\u2019\u044f\u0437\u0430\u043d\u0456 \u0437 \u0446\u0438\u043c \u043f\u043e\u0441\u043b\u0443\u0433\u0438" + } + ], + "deliveryDate":{ + "startDate":"2016-06-01T00:00:00+03:00" + }, + "id":"7caa3587fe9041da98f2744ef531d7ce", + "unit":{ + "code":"LO", + "name":"\u043b\u043e\u0442" + }, + "quantity":4 + }, + { + "relatedLot":"7d774fbf1e86420484c7d1a005cc283f", + "description":"itelm descr", + "classification":{ + "scheme":"CPV", + "description":"\u041f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f \u0441\u0456\u043b\u044c\u0441\u044c\u043a\u043e\u0433\u043e \u0433\u043e\u0441\u043f\u043e\u0434\u0430\u0440\u0441\u0442\u0432\u0430, \u0444\u0435\u0440\u043c\u0435\u0440\u0441\u0442\u0432\u0430, \u0440\u0438\u0431\u0430\u043b\u044c\u0441\u0442\u0432\u0430, \u043b\u0456\u0441\u043d\u0438\u0446\u0442\u0432\u0430 \u0442\u0430 \u0441\u0443\u043c\u0456\u0436\u043d\u0430 \u043f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f", + "id":"03000000-1" + }, + "additionalClassifications":[ + { + "scheme":"\u0414\u041a\u041f\u041f", + "id":"01", + "description":"\u041f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f \u0441\u0456\u043b\u044c\u0441\u044c\u043a\u043e\u0433\u043e \u0433\u043e\u0441\u043f\u043e\u0434\u0430\u0440\u0441\u0442\u0432\u0430, \u043c\u0438\u0441\u043b\u0438\u0432\u0441\u0442\u0432\u0430 \u0442\u0430 \u043f\u043e\u0432\u2019\u044f\u0437\u0430\u043d\u0456 \u0437 \u0446\u0438\u043c \u043f\u043e\u0441\u043b\u0443\u0433\u0438" + } + ], + "deliveryDate":{ + "startDate":"2016-06-01T00:00:00+03:00" + }, + "id":"563ef5d999f34d36a5a0e4e4d91d7be1", + "unit":{ + "code":"LO", + "name":"\u043b\u043e\u0442" + }, + "quantity":4 + } + ], + "id":"823d50b3236247adad28a5a66f74db42", + "value":{ + "currency":"UAH", + "amount":1000.0, + "valueAddedTaxIncluded":false + }, + "submissionMethod":"electronicAuction", + "procuringEntity":{ + "contactPoint":{ + "telephone":"", + "name":"Dmitry", + "email":"d.karnaukh+merchant1@smartweb.com.ua" + }, + "identifier":{ + "scheme":"UA-EDR", + "id":"66666692", + "legalName":"\u041d\u0410\u041a \"Testov Test\"" + }, + "name":"\u041d\u0410\u041a \"Testov Test\"", + "address":{ + "postalCode":"02121", + "countryName":"\u0423\u043a\u0440\u0430\u0438\u043d\u0430", + "streetAddress":"", + "region":"\u041a\u0438\u0435\u0432\u0441\u043a\u0430\u044f \u043e\u0431\u043b\u0430\u0441\u0442\u044c", + "locality":"\u041a\u0438\u0435\u0432" + } + }, + "minimalStep":{ + "currency":"UAH", + "amount":1.0, + "valueAddedTaxIncluded":false + }, + "tenderID":"UA-2015-12-12-000003-1", + "questions":[ + { + "description":"Test lot question descr", + "title":"Test lot question title", + "relatedItem":"563ef5d999f34d36a5a0e4e4d91d7be1", + "answer":"Test answer", + "date":"2015-12-21T17:31:19.650440+02:00", + "id":"615ff8be8eba4a81b300036d6bec991c", + "questionOf":"lot" + }, + { + "description":"lot 2 question descr?", + "title":"lot 2 question title", + "relatedItem":"563ef5d999f34d36a5a0e4e4d91d7be1", + "answer":"lot 2 answer", + "date":"2015-12-21T17:36:30.680576+02:00", + "id":"278f269ee482434da6615a21f3deccf0", + "questionOf":"lot" + }, + { + "description":"lot 3 q descr?", + "title":"lot 3 q title", + "relatedItem":"563ef5d999f34d36a5a0e4e4d91d7be1", + "answer":"lot 3 answer", + "date":"2015-12-21T17:57:28.108596+02:00", + "id":"da6c1228f81e4c2e8451479d57b685dd", + "questionOf":"lot" + }, + { + "description":"Tender q 1 descr?", + "title":"Tender q 1 title", + "relatedItem":"ad681d050d0e42169a80b86d5b591c20", + "answer":"Tender q 1 answer", + "date":"2015-12-21T17:58:24.489846+02:00", + "id":"937b4b9ef3444782b9a3019fcc5c072a", + "questionOf":"tender" + }, + { + "description":"Item descr?", + "title":"Item title?", + "relatedItem":"7caa3587fe9041da98f2744ef531d7ce", + "answer":"Item answer", + "date":"2015-12-21T18:04:35.349936+02:00", + "id":"a2d14f4faf9b4ba180b40f08a2be3bd9", + "questionOf":"item" + } + ], + "enquiryPeriod":{ + "startDate":"2015-12-12T03:06:52.664385+02:00", + "endDate":"2015-12-30T03:05:00+02:00" + }, + "owner":"prom.ua", + "lots":[ + { + "status":"complete", + "description":"lot descr1", + "title":"lot title", + "minimalStep":{ + "currency":"UAH", + "amount":1.0, + "valueAddedTaxIncluded":false + }, + "value":{ + "currency":"UAH", + "amount":1000.0, + "valueAddedTaxIncluded":false + }, + "id":"7d774fbf1e86420484c7d1a005cc283f" + }, + { + "status":"active", + "description":"lot descr2", + "title":"lot title", + "minimalStep":{ + "currency":"UAH", + "amount":1.0, + "valueAddedTaxIncluded":false + }, + "value":{ + "currency":"UAH", + "amount":2000.0, + "valueAddedTaxIncluded":false + }, + "id":"563ef5d999f34d36a5a0e4e4d91d7be1" + } + ], + "bids":[ + { + "date": "2014-12-16T04:44:23.569815+02:00", + "documents": [ + { + "dateModified": "2014-12-16T04:44:25.010930+02:00", + "datePublished": "2014-12-16T04:44:25.010885+02:00", + "format": "text/plain", + "id": "ff001412c60c4164a0f57101e4eaf8aa", + "title": "Proposal.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0/tenders/6f73bf0f7f734f459f7e37e3787054a0/bids/f7fc1212f9f140bba5c4e3cd4f2b62d9/documents/ff001412c60c4164a0f57101e4eaf8aa?download=4f45bbd414104cd78faf620208efd824" + } + ], + "qualificationDocuments": [ + { + "confidentiality": "public", + "language": "uk", + "title": "qualification_document.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/e3e16eb63b584378b75d9eb01dc2f8b9/bids/c41709780fa8434399c62a1facf369cb/financial_documents/7519d21b32af432396acd6e2c9e18ee5?download=5db59ae05361427b84c4caddb0b6d92b", + "format": "application/pdf", + "documentOf": "tender", + "datePublished": "2016-02-24T14:13:36.625252+02:00", + "id": "7519d21b32af432396acd6e2c9e18ee5", + "dateModified": "2016-02-24T14:13:36.625290+02:00" + } + ], + "financial_documens": [ + { + "confidentiality": "public", + "language": "uk", + "title": "financial_doc.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/e3e16eb63b584378b75d9eb01dc2f8b9/bids/c41709780fa8434399c62a1facf369cb/financial_documents/7519d21b32af432396acd6e2c9e18ee5?download=5db59ae05361427b84c4caddb0b6d92b", + "format": "application/pdf", + "documentOf": "tender", + "datePublished": "2016-02-24T14:13:36.625252+02:00", + "id": "7519d21b32af432396acd6e2c9e18ee5", + "dateModified": "2016-02-24T14:13:36.625290+02:00" + } + ], + "eligibility_documents": [ + { + "confidentiality": "public", + "language": "uk", + "title": "eligibility_doc.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/e3e16eb63b584378b75d9eb01dc2f8b9/bids/c41709780fa8434399c62a1facf369cb/financial_documents/7519d21b32af432396acd6e2c9e18ee5?download=5db59ae05361427b84c4caddb0b6d92b", + "format": "application/pdf", + "documentOf": "tender", + "datePublished": "2016-02-24T14:13:36.625252+02:00", + "id": "7519d21b32af432396acd6e2c9e18ee5", + "dateModified": "2016-02-24T14:13:36.625290+02:00" + } + ], + "id": "f7fc1212f9f140bba5c4e3cd4f2b62d9", + "lotValues": [ + { + "value": {"currency": "UAH", "amount": 7750.0, "valueAddedTaxIncluded": true}, + "reatedLot": "7d774fbf1e86420484c7d1a005cc283f", + "date": "2015-11-01T12:43:12.482645+02:00" + }, { + "value": {"currency": "UAH", "amount": 8125.0, "valueAddedTaxIncluded": true}, + "reatedLot": "563ef5d999f34d36a5a0e4e4d91d7be1", + "date": "2015-11-01T12:43:12.482645+02:00" + } + ], + "tenderers": [ + { + "address": { + "countryName": "Україна", + "locality": "м. Вінниця", + "postalCode": "21100", + "region": "м. Вінниця", + "streetAddress": "вул. Островського, 33" + }, + "contactPoint": { + "email": "soleksuk@gmail.com", + "name": "Сергій Олексюк", + "telephone": "+380 (432) 21-69-30" + }, + "identifier": { + "id": "13313462", + "legalName": "Державне комунальне підприємство громадського харчування «Школяр»", + "scheme": "UA-EDR", + "uri": "http://sch10.edu.vn.ua/" + }, + "name": "ДКП «Школяр»" + } + ] + }, + { + "date": "2014-12-16T04:44:27.976478+02:00", + "id": "7ec725815ef448a9b857129024395638", + "lotValues": [ + { + "value": {"currency": "UAH", "amount": 7750.0, "valueAddedTaxIncluded": true}, + "reatedLot": "7d774fbf1e86420484c7d1a005cc283f", + "date": "2015-11-01T12:43:12.482645+02:00" + }, { + "value": {"currency": "UAH", "amount": 8125.0, "valueAddedTaxIncluded": true}, + "reatedLot": "563ef5d999f34d36a5a0e4e4d91d7be1", + "date": "2015-11-01T12:43:12.482645+02:00" + } + ], + "tenderers": [ + { + "address": { + "countryName": "Україна", + "locality": "м. Вінниця", + "postalCode": "21018", + "region": "м. Вінниця", + "streetAddress": "вул. Юності, 30" + }, + "contactPoint": { + "email": "alla.myhailova@i.ua", + "name": "Алла Михайлова", + "telephone": "+380 (432) 460-665" + }, + "identifier": { + "id": "13306232", + "legalName": "Державне комунальне підприємство громадського харчування «Меридіан»", + "scheme": "UA-EDR", + "uri": "http://sch10.edu.vn.ua/" + }, + "name": "ДКП «Меридіан2»" + } + ] + } + + ], + "awards":[ + { + "status":"active", + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.9/tenders/3216137f17fb452db202daad1f9e73ad/awards/7054491a5e514699a56e44d32e23edf7/documents/c7ac894e4ca34e618e03a02d8799dc69?download=8c4053b0942949f4bd6e998a51e177c5", + "title":"2.jpg", + "datePublished":"2015-11-13T16:31:03.691781+02:00", + "dateModified":"2015-11-13T16:31:03.691829+02:00", + "id":"c7ac894e4ca34e618e03a02d8799dc69" + } + ], + "complaintPeriod":{ + "startDate":"2015-11-13T16:17:00.375880+02:00", + "endDate":"2015-11-14T16:31:06.979761+02:00" + }, + "contracts":[ + { + "status":"active", + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.9/tenders/3216137f17fb452db202daad1f9e73ad/awards/7054491a5e514699a56e44d32e23edf7/contracts/045c478807c04215950b4d74e6861cb1/documents/14dbf158b9ed4a478a59d19c90dbdf5d?download=9d948173e65441409f06e0b81674b39a", + "title":"3.jpg", + "datePublished":"2015-11-13T19:01:16.748785+02:00", + "dateModified":"2015-11-13T19:01:16.748829+02:00", + "id":"14dbf158b9ed4a478a59d19c90dbdf5d" + } + ], + "awardID":"7054491a5e514699a56e44d32e23edf7", + "id":"045c478807c04215950b4d74e6861cb1" + } + ], + "suppliers":[ + { + "contactPoint":{ + "telephone":"", + "name":"some6", + "email":"some6@mailinator.com" + }, + "identifier":{ + "scheme":"UA-EDR", + "id":"62123463", + "legalName":"some6" + }, + "name":"some6", + "address":{ + "postalCode":"04119", + "countryName":"\u0423\u043a\u0440\u0430\u0438\u043d\u0430", + "streetAddress":"", + "region":"\u0410\u0420 \u041a\u0440\u044b\u043c", + "locality":"ffff" + } + } + ], + "bid_id":"ad9ac917ff0049178acc3613bb14a691", + "value":{ + "currency":"UAH", + "amount":300.0, + "valueAddedTaxIncluded":false + }, + "date":"2015-11-13T16:17:00.376251+02:00", + "id":"7054491a5e514699a56e44d32e23edf7", + "lotId": "563ef5d999f34d36a5a0e4e4d91d7be1" + }, + { + "status":"active", + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.9/tenders/3216137f17fb452db202daad1f9e73ad/awards/7054491a5e514699a56e44d32e23edf7/documents/c7ac894e4ca34e618e03a02d8799dc69?download=8c4053b0942949f4bd6e998a51e177c5", + "title":"2.jpg", + "datePublished":"2015-11-13T16:31:03.691781+02:00", + "dateModified":"2015-11-13T16:31:03.691829+02:00", + "id":"c7ac894e4ca34e618e03a02d8799dc69" + } + ], + "complaintPeriod":{ + "startDate":"2015-11-13T16:17:00.375880+02:00", + "endDate":"2015-11-14T16:31:06.979761+02:00" + }, + "contracts":[ + { + "status":"active", + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.9/tenders/3216137f17fb452db202daad1f9e73ad/awards/7054491a5e514699a56e44d32e23edf7/contracts/045c478807c04215950b4d74e6861cb1/documents/14dbf158b9ed4a478a59d19c90dbdf5d?download=9d948173e65441409f06e0b81674b39a", + "title":"3.jpg", + "datePublished":"2015-11-13T19:01:16.748785+02:00", + "dateModified":"2015-11-13T19:01:16.748829+02:00", + "id":"14dbf158b9ed4a478a59d19c90dbdf5d" + } + ], + "awardID":"7054491a5e514699a56e44d32e23edf7", + "id":"045c478807c04215950b4d74e6861cb1" + } + ], + "suppliers":[ + { + "contactPoint":{ + "telephone":"", + "name":"some6", + "email":"some6@mailinator.com" + }, + "identifier":{ + "scheme":"UA-EDR", + "id":"62123463", + "legalName":"some6" + }, + "name":"some6", + "address":{ + "postalCode":"04119", + "countryName":"\u0423\u043a\u0440\u0430\u0438\u043d\u0430", + "streetAddress":"", + "region":"\u0410\u0420 \u041a\u0440\u044b\u043c", + "locality":"ffff" + } + } + ], + "bid_id":"ad9ac917ff0049178acc3613bb14a691", + "value":{ + "currency":"UAH", + "amount":300.0, + "valueAddedTaxIncluded":false + }, + "date":"2015-11-13T16:17:00.376251+02:00", + "id":"7054491a5e514699a56e44d32e23edf7", + "lotId": "7d774fbf1e86420484c7d1a005cc283f" + } + ], + "dateModified":"2015-12-12T03:06:54.370277+02:00", + "awardCriteria":"lowestCost" + } +} diff --git a/openprocurement_client/tests/data/lots.json b/openprocurement_client/tests/data/lots.json new file mode 100644 index 0000000..384ef8a --- /dev/null +++ b/openprocurement_client/tests/data/lots.json @@ -0,0 +1,153 @@ +{ + "next_page":{ + "path":"/api/0.10/tenders?offset=2015-12-25T18%3A04%3A36.264176%2B02%3A00", + "uri":"https://lb.api-sandbox.openprocurement.org/api/0.10/tenders?offset=2015-12-25T18%3A04%3A36.264176%2B02%3A00", + "offset":"2015-12-25T18:04:36.264176+02:00" + }, + "data":[ + { + "id":"823d50b3236247adad28a5a66f74db42", + "dateModified":"2015-11-13T18:50:00.753811+02:00" + }, + { + "id":"f3849ade33534174b8402579152a5f41", + "dateModified":"2015-11-16T01:15:00.469896+02:00" + }, + { + "id":"f3849ade33534174b8402579152a5f41", + "dateModified":"2015-11-16T12:00:00.960077+02:00" + }, + { + "id":"3be3664065994d1e8847beb597c014bf", + "dateModified":"2015-11-16T21:15:00.404214+02:00" + }, + { + "id":"711cfa82c54147b686f6755adb22364d", + "dateModified":"2015-11-17T18:02:00.456050+02:00" + }, + { + "id":"730fe73cb7be4f92bce472484236e9ee", + "dateModified":"2015-11-18T18:30:00.410770+02:00" + }, + { + "id":"496ec128323f45679daf9ebcb1ec62f6", + "dateModified":"2015-11-20T16:20:37.977312+02:00" + }, + { + "id":"18d566bed8cc4f2e846ca5d64c6bdfed", + "dateModified":"2015-12-01T11:48:33.784585+02:00" + }, + { + "id":"21733c5ed0be4b259c3345310f425a82", + "dateModified":"2015-12-02T13:54:13.656648+02:00" + }, + { + "id":"b35a1151b7a84562addb47f66193a309", + "dateModified":"2015-12-12T02:45:36.355426+02:00" + }, + { + "id":"ce9751da26a84c2390368ffa5adfd74c", + "dateModified":"2015-12-12T03:04:16.059667+02:00" + }, + { + "id":"823d50b3236247adad28a5a66f74db42", + "dateModified":"2015-12-12T03:06:54.370277+02:00" + }, + { + "id":"0085cf76ad444ad0ae625fc17f366dd2", + "dateModified":"2015-12-12T14:30:38.596246+02:00" + }, + { + "id":"d97613278dd547f2be5523cdc5614880", + "dateModified":"2015-12-12T14:31:51.342309+02:00" + }, + { + "id":"71161344890c4231b1487296f56aa910", + "dateModified":"2015-12-14T13:10:38.771625+02:00" + }, + { + "id":"759a81e58cf04b5ea0587942d0925faa", + "dateModified":"2015-12-16T19:56:53.874070+02:00" + }, + { + "id":"ad681d050d0e42169a80b86d5b591c20", + "dateModified":"2015-12-22T13:14:25.253453+02:00" + }, + { + "id":"a2e4464a08d64addb6c7a63da48ddb87", + "dateModified":"2015-12-23T17:09:15.145867+02:00" + }, + { + "id":"eea0c2f3cf3f4206b8cbba813b9a527b", + "dateModified":"2015-12-23T17:15:23.479628+02:00" + }, + { + "id":"847411c7b03f4f91b97fecb70c6e6275", + "dateModified":"2015-12-23T17:54:39.470396+02:00" + }, + { + "id":"599e63e6fb1349078fb01e4f9e8a812c", + "dateModified":"2015-12-23T17:55:36.317529+02:00" + }, + { + "id":"d63a7302316c4d15b46aeb7bcd4f1dbe", + "dateModified":"2015-12-23T17:56:01.709307+02:00" + }, + { + "id":"6496299a3c8e482f9017d855483a97b5", + "dateModified":"2015-12-23T17:57:57.376623+02:00" + }, + { + "id":"a513848583cc4d1d806c78e5131ad068", + "dateModified":"2015-12-23T17:59:52.101031+02:00" + }, + { + "id":"79aa9fd2bb43471cbcb0b97f6965e5f6", + "dateModified":"2015-12-24T11:39:09.071184+02:00" + }, + { + "id":"b2cdcda417434c7b889c82a81927bb73", + "dateModified":"2015-12-24T11:40:20.172257+02:00" + }, + { + "id":"58eca935e0e24805b561a1588ae64ef3", + "dateModified":"2015-12-24T11:48:57.584694+02:00" + }, + { + "id":"78b889ec869544b9b47c8740549ed20a", + "dateModified":"2015-12-24T11:51:07.149689+02:00" + }, + { + "id":"258ef598759946dbb30e7ed67bfc2346", + "dateModified":"2015-12-24T11:52:16.196052+02:00" + }, + { + "id":"639ecaca9db24228a9fe0eb3494aa0e3", + "dateModified":"2015-12-25T13:28:22.136939+02:00" + }, + { + "id":"ca10599dfcbe416bb513416c13073c5f", + "dateModified":"2015-12-25T15:19:53.913160+02:00" + }, + { + "id":"21e551bf7f194d8aaba29bf2e4949b90", + "dateModified":"2015-12-25T16:00:00.319589+02:00" + }, + { + "id":"dd7b13a956b44a34bbaeb2bfa1910c03", + "dateModified":"2015-12-25T16:07:19.809837+02:00" + }, + { + "id":"f8f21e8b0c104747b8abf5ece4fb5d72", + "dateModified":"2015-12-25T17:06:32.885207+02:00" + }, + { + "id":"48dcb014d003454b8875975bb531147f", + "dateModified":"2015-12-25T17:16:05.208447+02:00" + }, + { + "id":"7f35a555d5c64b34a7cbc454fab5628c", + "dateModified":"2015-12-25T18:04:36.264176+02:00" + } + ] +} diff --git a/openprocurement_client/tests/data_dict.py b/openprocurement_client/tests/data_dict.py index 2e2938f..f9efa4e 100644 --- a/openprocurement_client/tests/data_dict.py +++ b/openprocurement_client/tests/data_dict.py @@ -7,7 +7,7 @@ "question_id": '615ff8be8eba4a81b300036d6bec991c', "lot_id": '563ef5d999f34d36a5a0e4e4d91d7be1', "bid_id": 'f7fc1212f9f140bba5c4e3cd4f2b62d9', - "bid_document_id":"ff001412c60c4164a0f57101e4eaf8aa", + "bid_document_id": "ff001412c60c4164a0f57101e4eaf8aa", "bid_qualification_document_id": "7519d21b32af432396acd6e2c9e18ee5", "bid_financial_document_id": "7519d21b32af432396acd6e2c9e18ee5", "bid_eligibility_document_id": "7519d21b32af432396acd6e2c9e18ee5", @@ -45,3 +45,19 @@ "patch_change_rationale": u'Друга і третя поставка має бути розфасована', "new_token": 'new0contract0token123412341234' }) + +TEST_LOT_KEYS = munchify({ + "asset_id": '823d50b3236247adad28a5a66f74db42', + "lot_id": '823d50b3236247adad28a5a66f74db42', + "token": 'f6247c315d2744f1aa18c7c3de523bc3', + "new_token": '4fa3e89efbaa46f0a1b86b911bec76e7' + +}) + +TEST_ASSET_KEYS = munchify({ + "asset_id": '823d50b3236247adad28a5a66f74db42', + "lot_id": '823d50b3236247adad28a5a66f74db42', + "token": 'f6247c315d2744f1aa18c7c3de523bc3', + "new_token": '4fa3e89efbaa46f0a1b86b911bec76e7' + +}) diff --git a/openprocurement_client/tests/main.py b/openprocurement_client/tests/main.py index 17c9333..9655dd0 100644 --- a/openprocurement_client/tests/main.py +++ b/openprocurement_client/tests/main.py @@ -1,12 +1,17 @@ import unittest -from openprocurement_client.tests import tests, tests_sync +from openprocurement_client.tests import ( + tests, + tests_sync, + test_registry_client +) def suite(): suite = unittest.TestSuite() suite.addTest(tests.suite()) suite.addTest(tests_sync.suite()) + suite.addTest(test_registry_client.suite()) return suite diff --git a/openprocurement_client/tests/test_registry_client.py b/openprocurement_client/tests/test_registry_client.py new file mode 100644 index 0000000..27355e4 --- /dev/null +++ b/openprocurement_client/tests/test_registry_client.py @@ -0,0 +1,169 @@ +from __future__ import print_function +from gevent import monkey + +monkey.patch_all() +from gevent.pywsgi import WSGIServer +from bottle import Bottle +from StringIO import StringIO +from collections import Iterable +from simplejson import loads, load +from munch import munchify +import mock +import sys +import unittest +from openprocurement_client.registry_client import LotsClient, AssetsClient +from openprocurement_client.document_service_client \ + import DocumentServiceClient +from openprocurement_client.exceptions import InvalidResponse, ResourceNotFound +from openprocurement_client.tests.data_dict import ( + TEST_ASSET_KEYS, + TEST_LOT_KEYS, + TEST_TENDER_KEYS_LIMITED, + TEST_PLAN_KEYS, + TEST_CONTRACT_KEYS +) +from openprocurement_client.tests._server import \ + API_KEY, API_VERSION, AUTH_DS_FAKE, DS_HOST_URL, DS_PORT, \ + HOST_URL, location_error, PORT, ROOT, setup_routing, setup_routing_ds, \ + resource_partition, resource_filter + + +class BaseTestClass(unittest.TestCase): + def setting_up(self, client, resource=None): + self.app = Bottle() + self.app.router.add_filter('resource_filter', resource_filter) + setup_routing(self.app) + self.server = WSGIServer(('localhost', PORT), self.app, log=None) + try: + self.server.start() + except Exception as error: + print(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], + file=sys.stderr) + raise error + ds_client = getattr(self, 'ds_client', None) + self.client = client('', host_url=HOST_URL, api_version=API_VERSION, + ds_client=ds_client) + if resource: + self.client = client( + '', host_url=HOST_URL, api_version=API_VERSION, + ds_client=ds_client, resource=resource) + + @classmethod + def setting_up_ds(cls): + cls.app_ds = Bottle() + cls.server_ds \ + = WSGIServer(('localhost', DS_PORT), cls.app_ds, log=None) + try: + cls.server_ds.start() + except Exception as error: + print(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], + file=sys.stderr) + raise error + + cls.ds_client = DocumentServiceClient(host_url=DS_HOST_URL, + auth_ds=AUTH_DS_FAKE) + # to test units performing file operations outside the DS uncomment + # following lines: + # import logging + # logging.basicConfig() + # cls.ds_client = None + + setup_routing_ds(cls.app_ds) + + @classmethod + def setUpClass(cls): + cls.setting_up_ds() + + @classmethod + def tearDownClass(cls): + cls.server_ds.stop() + + +class AssetsRegistryTestCase(BaseTestClass): + def setUp(self): + self.setting_up(client=AssetsClient, resource='assets') + + with open(ROOT + 'assets.json') as assets: + self.assets = munchify(load(assets)) + with open(ROOT + 'asset_{}.json'.format( + TEST_ASSET_KEYS.asset_id)) as asset: + self.asset = munchify(load(asset)) + + def tearDown(self): + self.server.stop() + + def test_get_assets(self): + setup_routing(self.app, routes=["assets"]) + assets = self.client.get_assets() + self.assertIsInstance(assets, Iterable) + self.assertEqual(assets, self.assets.data) + + @mock.patch('openprocurement_client.registry_client.AssetsClient.request') + def test_get_assets_failed(self, mock_request): + mock_request.return_value = munchify({'status_code': 404}) + with self.assertRaises(InvalidResponse) as e: + self.client.get_assets(params={'offset': 'offset_value'}) + + def test_get_asset(self): + setup_routing(self.app, routes=["asset"]) + asset = self.client.get_asset(TEST_ASSET_KEYS.asset_id) + self.assertEqual(asset, self.asset) + + def test_patch_asset(self): + setup_routing(self.app, routes=["asset_patch"]) + self.asset.data.description = 'test_patch_asset' + + patched_asset = self.client.patch_resource_item(self.asset) + self.assertEqual(patched_asset.data.id, self.asset.data.id) + self.assertEqual(patched_asset.data.description, + self.asset.data.description) + + +class LotsRegistryTestCase(BaseTestClass): + def setUp(self): + self.setting_up(client=LotsClient, resource='lots') + + with open(ROOT + 'lots.json') as lots: + self.lots = munchify(load(lots)) + with open(ROOT + 'lot_{}.json'.format(TEST_LOT_KEYS.lot_id)) as lot: + self.lot = munchify(load(lot)) + + def tearDown(self): + self.server.stop() + + def test_get_lots(self): + setup_routing(self.app, routes=["lots"]) + lots = self.client.get_lots() + self.assertIsInstance(lots, Iterable) + self.assertEqual(lots, self.lots.data) + + @mock.patch('openprocurement_client.registry_client.LotsClient.request') + def test_get_lots_failed(self, mock_request): + mock_request.return_value = munchify({'status_code': 404}) + with self.assertRaises(InvalidResponse) as e: + self.client.get_lots(params={'offset': 'offset_value'}) + + def test_get_lot(self): + setup_routing(self.app, routes=["lot"]) + lot = self.client.get_lot(TEST_LOT_KEYS.lot_id) + self.assertEqual(lot, self.lot) + + def test_patch_lot(self): + setup_routing(self.app, routes=["lot_patch"]) + self.lot.data.description = 'test_patch_lot' + + patched_lot = self.client.patch_resource_item(self.lot) + self.assertEqual(patched_lot.data.id, self.lot.data.id) + self.assertEqual(patched_lot.data.description, + self.lot.data.description) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(AssetsRegistryTestCase)) + suite.addTest(unittest.makeSuite(LotsRegistryTestCase)) + return suite + + +if __name__ == '__main__': + unittest.main(defaultTest='suite') From ca2328a8063bbdcc16766892673160ad8210b3ac Mon Sep 17 00:00:00 2001 From: VDigitall Date: Mon, 21 Aug 2017 13:00:27 +0300 Subject: [PATCH 03/20] Added methods create..., patch... ...resource_items and new Client Classes LotsClient and AssetsClient --- openprocurement_client/api_base_client.py | 153 ++--- openprocurement_client/client.py | 526 +++++++++++------- openprocurement_client/registry_client.py | 51 ++ openprocurement_client/tests/_server.py | 96 ++-- ...sset_668c3156c8cb496fb28359909cde6e96.json | 272 +++++++++ ...sset_823d50b3236247adad28a5a66f74db42.json | 504 +++++++++++++++++ openprocurement_client/tests/data/assets.json | 153 +++++ .../lot_668c3156c8cb496fb28359909cde6e96.json | 272 +++++++++ .../lot_823d50b3236247adad28a5a66f74db42.json | 504 +++++++++++++++++ openprocurement_client/tests/data/lots.json | 153 +++++ openprocurement_client/tests/data_dict.py | 18 +- openprocurement_client/tests/main.py | 7 +- .../tests/test_registry_client.py | 169 ++++++ 13 files changed, 2563 insertions(+), 315 deletions(-) create mode 100644 openprocurement_client/registry_client.py create mode 100644 openprocurement_client/tests/data/asset_668c3156c8cb496fb28359909cde6e96.json create mode 100644 openprocurement_client/tests/data/asset_823d50b3236247adad28a5a66f74db42.json create mode 100644 openprocurement_client/tests/data/assets.json create mode 100644 openprocurement_client/tests/data/lot_668c3156c8cb496fb28359909cde6e96.json create mode 100644 openprocurement_client/tests/data/lot_823d50b3236247adad28a5a66f74db42.json create mode 100644 openprocurement_client/tests/data/lots.json create mode 100644 openprocurement_client/tests/test_registry_client.py diff --git a/openprocurement_client/api_base_client.py b/openprocurement_client/api_base_client.py index 3ccc095..1ea7485 100644 --- a/openprocurement_client/api_base_client.py +++ b/openprocurement_client/api_base_client.py @@ -2,6 +2,7 @@ import uuid from .exceptions import http_exceptions_dict, InvalidResponse, RequestFailed +from .document_service_client import DocumentServiceClient from functools import wraps from io import FileIO @@ -10,30 +11,33 @@ from requests import Session from requests.auth import HTTPBasicAuth as BasicAuth from simplejson import loads +from retrying import retry logger = logging.getLogger(__name__) IGNORE_PARAMS = ('uri', 'path') +# Using FileIO here instead of open() +# to be able to override the filename +# which is later used when uploading the file. +# +# Explanation: +# +# 1) requests reads the filename +# from "name" attribute of a file-like object, +# there is no other way to specify a filename; +# +# 2) The attribute may contain the full path to file, +# which does not work well as a filename; +# +# 3) The attribute is readonly when using open(), +# unlike FileIO object. + + def verify_file(fn): @wraps(fn) def wrapper(self, file_, *args, **kwargs): if isinstance(file_, basestring): - # Using FileIO here instead of open() - # to be able to override the filename - # which is later used when uploading the file. - # - # Explanation: - # - # 1) requests reads the filename - # from "name" attribute of a file-like object, - # there is no other way to specify a filename; - # - # 2) The attribute may contain the full path to file, - # which does not work well as a filename; - # - # 3) The attribute is readonly when using open(), - # unlike FileIO object. file_ = FileIO(file_, 'rb') file_.name = path.basename(file_.name) if hasattr(file_, 'read'): @@ -46,14 +50,15 @@ def wrapper(self, file_, *args, **kwargs): file_.close() except AttributeError: pass - raise TypeError('Expected either a string ' - 'containing a path to file or a ' - 'file-like object, got {}'.format(type(file_))) + raise TypeError( + 'Expected either a string containing a path to file or ' + 'a file-like object, got {}'.format(type(file_)) + ) return wrapper class APITemplateClient(object): - """base class for API""" + """Base class template for API""" def __init__(self, login_pass=None, headers=None, user_agent=None): self.headers = headers or {} @@ -62,8 +67,8 @@ def __init__(self, login_pass=None, headers=None, user_agent=None): self.session.auth = BasicAuth(*login_pass) if user_agent is None: - self.session.headers['User-Agent'] \ - = 'op.client/{}'.format(uuid.uuid4().hex) + self.session.headers['User-Agent'] = 'op.client/{}'.format( + uuid.uuid4().hex) else: self.session.headers['User-Agent'] = user_agent @@ -87,26 +92,27 @@ def request(self, method, path=None, payload=None, json=None, class APIBaseClient(APITemplateClient): - """base class for API""" + """Base class for API""" host_url = 'https://api-sandbox.openprocurement.org' - api_version = '2.0' + api_version = '0' headers = {'Content-Type': 'application/json'} def __init__(self, - key, - resource, + key='', + resource='tenders', host_url=None, api_version=None, params=None, - ds_client=None, + ds_config=None, user_agent=None): - super(APIBaseClient, self)\ - .__init__(login_pass=(key, ''), headers=self.headers, - user_agent=user_agent) - - self.ds_client = ds_client + super(APIBaseClient, self).__init__(login_pass=(key, ''), + headers=self.headers, + user_agent=user_agent) + if ds_config: + self.ds_client = DocumentServiceClient(ds_config['host_url'], + ds_config['auth']) self.host_url = host_url or self.host_url self.api_version = api_version or self.api_version @@ -121,12 +127,8 @@ def __init__(self, ) response.raise_for_status() - self.prefix_path = '{}/api/{}/{}'\ - .format(self.host_url, self.api_version, resource) - - @staticmethod - def _get_access_token(obj): - return getattr(getattr(obj, 'access', ''), 'token', '') + self.prefix_path = '{}/api/{}/{}'.format(self.host_url, + self.api_version, resource) def _update_params(self, params): for key in params: @@ -140,9 +142,9 @@ def _create_resource_item(self, url, payload, headers=None, method='POST'): response_item = self.request( method, url, headers=_headers, json=payload ) - if (response_item.status_code == 201 and method == 'POST') \ - or (response_item.status_code in (200, 204) - and method in ('PUT', 'DELETE')): + if ((response_item.status_code == 201 and method == 'POST') or + (response_item.status_code in (200, 204) and + method in ('PUT', 'DELETE'))): return munchify(loads(response_item.text)) raise InvalidResponse(response_item) @@ -154,6 +156,34 @@ def _get_resource_item(self, url, headers=None): return munchify(loads(response_item.text)) raise InvalidResponse(response_item) + def _get_resource_item_submitem(self, resource_item, subitem_id_or_name, + access_token=None, depth_path=None): + access_token = access_token or self._get_access_token(resource_item) + headers = {'X-Access-Token': access_token} + if depth_path: + url = '{}/{}/{}/{}'.format(self.prefix_path, resource_item.data.id, + depth_path, subitem_id_or_name) + else: + url = '{}/{}/{}'.format(self.prefix_path, resource_item.data.id, + subitem_id_or_name) + + @retry(stop_max_attempt_number=5) + def _get_resource_items(self, params=None, feed='changes'): + _params = (params or {}).copy() + _params['feed'] = feed + self._update_params(_params) + response = self.request('GET', + self.prefix_path, + params_dict=self.params) + if response.status_code == 200: + resource_items_list = munchify(loads(response.text)) + self._update_params(resource_items_list.next_page) + return resource_items_list.data + elif response.status_code == 404: + del self.params['offset'] + + raise InvalidResponse(response) + def _patch_resource_item(self, url, payload, headers=None): _headers = self.headers.copy() _headers.update(headers or {}) @@ -221,30 +251,19 @@ def _patch_obj_resource_item(self, patched_obj, item_obj, items_name): headers={'X-Access-Token': self._get_access_token(patched_obj)} ) - def patch_document(self, obj, document): - return self._patch_obj_resource_item(obj, document, 'documents') - - @verify_file - def upload_document(self, file_, obj, use_ds_client=True, - doc_registration=True): - return self._upload_resource_file( - '{}/{}/documents'.format( - self.prefix_path, - obj.data.id - ), - file_=file_, - headers={'X-Access-Token': self._get_access_token(obj)}, - use_ds_client=use_ds_client, - doc_registration=doc_registration - ) - - def get_resource_item(self, id, headers=None): - return self._get_resource_item('{}/{}'.format(self.prefix_path, id), - headers=headers) - - def patch_credentials(self, id, access_token): - return self._patch_resource_item( - '{}/{}/credentials'.format(self.prefix_path, id), - payload=None, - headers={'X-Access-Token': access_token} - ) + # def patch_document(self, obj, document): + # return self._patch_obj_resource_item(obj, document, 'documents') + # + # @verify_file + # def upload_document(self, file_, obj, use_ds_client=True, + # doc_registration=True): + # return self._upload_resource_file( + # '{}/{}/documents'.format( + # self.prefix_path, + # obj.data.id + # ), + # file_=file_, + # headers={'X-Access-Token': self._get_access_token(obj)}, + # use_ds_client=use_ds_client, + # doc_registration=doc_registration + # ) diff --git a/openprocurement_client/client.py b/openprocurement_client/client.py index 9fef895..db9e335 100755 --- a/openprocurement_client/client.py +++ b/openprocurement_client/client.py @@ -1,7 +1,7 @@ import logging from .api_base_client import APIBaseClient, APITemplateClient, verify_file -from .exceptions import InvalidResponse +from .exceptions import InvalidResponse, IdNotFound from iso8601 import parse_date from munch import munchify @@ -14,42 +14,186 @@ IGNORE_PARAMS = ('uri', 'path') -class TendersClient(APIBaseClient): - """client for tenders""" +class APIClient(APIBaseClient): + """ API Client """ + + def __init__(self, *args, **kwargs): + super(APIClient, self).__init__(*args, **kwargs) + + ########################################################################### + # CREATE CLIENT METHODS + ########################################################################### + + def create_resource_item(self, resource_item): + return self._create_resource_item(self.prefix_path, resource_item) + + def create_resource_item_subitem(self, resource_item, subitem_obj, + subitem_name, resource_item_id=None, + depth_path=None, access_token=None): + resource_item_id = resource_item_id or resource_item['data'].get('id') + access_token = access_token or self._get_access_token(resource_item) + headers = {'X-Access-Token': access_token} + if depth_path: + url = '{}/{}/{}/{}'.format(self.prefix_path, resource_item_id, + depth_path, subitem_name) + else: + url = '{}/{}/{}'.format(self.prefix_path, resource_item_id, + subitem_name) + return self._create_resource_item(url, subitem_obj, headers=headers) + + ########################################################################### + # GET CLIENT METHODS + ########################################################################### + + def get_resource_item(self, resource_item_id, headers=None): + return self._get_resource_item('{}/{}'.format( + self.prefix_path, resource_item_id), headers=headers) + + def get_resource_item_submitem(self, resource_item, subitem_id_or_name, + resource_item_id=None, access_token=None, + depth_path=None): + resource_item_id = resource_item_id or resource_item['data'].get('id') + access_token = access_token or self._get_access_token(resource_item) + headers = {'X-Access-Token': access_token} + if depth_path: + url = '{}/{}/{}/{}'.format(self.prefix_path, resource_item_id, + depth_path, subitem_id_or_name) + else: + url = '{}/{}/{}'.format(self.prefix_path, resource_item_id, + subitem_id_or_name) + return self._get_resource_item(url, headers=headers) + + def get_resource_items(self, params=None, feed='changes'): + return self._get_resource_items(params=params, feed=feed) + + def get_file(self, url, access_token=None): + headers = {'X-Access-Token': access_token} if access_token else {} - def __init__(self, - key, - resource='tenders', # another possible value is 'auctions' - host_url=None, - api_version=None, - params=None, - ds_client=None, - user_agent=None): - super(TendersClient, self).__init__( - key, resource, host_url, api_version, params, ds_client, - user_agent + headers.update(self.headers) + response_item = self.request('GET', url, headers=headers) + + # if response_item.status_code == 302: + # response_obj = request(response_item.headers['location']) + if response_item.status_code == 200: + return response_item.text, \ + response_item.headers['Content-Disposition'] \ + .split("; filename=")[1].strip('"') + raise InvalidResponse(response_item) + + def get_file_properties(self, url, file_hash, access_token=None): + headers = {'X-Access-Token': access_token} if access_token else {} + headers.update(self.headers) + response_item = self.request('GET', url, headers=headers) + if response_item.status_code == 200: + file_properties = { + 'Content_Disposition': + response_item.headers['Content-Disposition'], + 'Content_Type': response_item.headers['Content-Type'], + 'url': url, + 'hash': file_hash + } + return file_properties + raise InvalidResponse(response_item) + + ########################################################################### + # PATCH CLIENT METHODS + ########################################################################### + + def patch_credentials(self, resource_item_id, access_token): + return self._patch_resource_item( + '{}/{}/credentials'.format(self.prefix_path, resource_item_id), + payload=None, + headers={'X-Access-Token': access_token} + ) + + def patch_resource_item(self, resource_item, resource_item_id=None): + resource_item_id = resource_item_id or resource_item['data'].get('id') + return self._patch_resource_item( + '{}/{}'.format(self.prefix_path, resource_item_id), + payload=resource_item, + headers={'X-Access-Token': self._get_access_token(resource_item)} ) + def patch_resource_item_subitem(self, resource_item, subitem_obj, + subitem_name, resource_item_id=None, + subitem_id=None, depth_path=None, + headers=None): + subitem_id = subitem_id or subitem_obj['data'].get('id') + resource_item_id = resource_item_id or resource_item['data'].get('id') + access_token = self._get_access_token(resource_item) + headers = {'X-Access-Token': access_token} + if depth_path: + url = '{}/{}/{}/{}/{}'.format( + self.prefix_path, resource_item_id, depth_path, subitem_name, + subitem_id + ) + else: + url = '{}/{}/{}/{}'.format( + self.prefix_path, resource_item_id, subitem_name, subitem_id + ) + return self._patch_resource_item(url, subitem_obj, headers=headers) + + ########################################################################### + # UPLOAD CLIENT METHODS + ########################################################################### + + @verify_file + def upload_document( + self, file_, resource_item, use_ds_client=True, + doc_registration=True, resource_item_id=None, depth_path=None): + resource_item_id = resource_item_id or resource_item['data'].get('id') + headers = {'X-Access-Token': self._get_access_token(tender)} + if depth_path: + url = '{}/{}/{}/documents'.format( + self.prefix_path, resource_item_id, depth_path + ) + else: + url = '{}/{}/documents'.format(self.prefix_path, resource_item_id) + return self._upload_resource_file( + url, file_=file_, headers=headers, use_ds_client=use_ds_client, + doc_registration=doc_registration + ) + + # def patch_document(self, obj, document): + # return self._patch_obj_resource_item(obj, document, 'documents') + # + # @verify_file + # def upload_document(self, file_, obj, use_ds_client=True, + # doc_registration=True): + # return self._upload_resource_file( + # '{}/{}/documents'.format( + # self.prefix_path, + # obj.data.id + # ), + # file_=file_, + # headers={'X-Access-Token': self._get_access_token(obj)}, + # use_ds_client=use_ds_client, + # doc_registration=doc_registration + # ) + + + + + def extract_credentials(self, resource_item_id): + return self._get_resource_item( + '{}/{}/extract_credentials'.format(self.prefix_path, + resource_item_id) + ) + + +class TendersClient(APIClient): + """client for tenders""" + + def __init__(self, *args, **kwargs): + super(TendersClient, self).__init__(*args, **kwargs) + ########################################################################### # GET ITEMS LIST API METHODS ########################################################################### @retry(stop_max_attempt_number=5) def get_tenders(self, params=None, feed='changes'): - _params = (params or {}).copy() - _params['feed'] = feed - self._update_params(_params) - response = self.request('GET', - self.prefix_path, - params_dict=self.params) - if response.status_code == 200: - tender_list = munchify(loads(response.text)) - self._update_params(tender_list.next_page) - return tender_list.data - elif response.status_code == 404: - del self.params['offset'] - - raise InvalidResponse(response) + return self.get_resource_items(params=params, feed=feed) def get_latest_tenders(self, date, tender_id): iso_dt = parse_date(date) @@ -64,252 +208,224 @@ def get_latest_tenders(self, date, tender_id): ) return data - def _get_tender_resource_list(self, tender, items_name): - return self._get_resource_item( - '{}/{}/{}'.format(self.prefix_path, tender.data.id, items_name), - headers={'X-Access-Token': self._get_access_token(tender)} + def get_questions(self, tender, tender_id=None): + return self.get_resource_item_subitem( + tender, 'questions', resource_item_id=tender_id ) - def get_questions(self, tender): - return self._get_tender_resource_list(tender, 'questions') + def get_documents(self, tender, tender_id=None): + return self.get_resource_item_subitem( + tender, 'documents', resource_item_id=tender_id) - def get_documents(self, tender): - return self._get_tender_resource_list(tender, 'documents') - - def get_awards_documents(self, tender, award_id): - return self._get_resource_item( - '{}/{}/awards/{}/documents'.format(self.prefix_path, tender.data.id, award_id), - headers={'X-Access-Token': - getattr(getattr(tender, 'access', ''), 'token', '')} + def get_awards_documents(self, tender, award_id, tender_id=None): + return self.get_resource_item_subitem( + tender, 'documents', depth_path='awards/{}'.format(award_id), + resource_item_id=tender_id ) - def get_qualification_documents(self, tender, qualification_id): - return self._get_resource_item( - '{}/{}/qualifications/{}/documents'.format(self.prefix_path, tender.data.id, qualification_id), - headers={'X-Access-Token': - getattr(getattr(tender, 'access', ''), 'token', '')} + def get_qualification_documents(self, tender, qualification_id, + tender_id=None): + return self.get_resource_item_subitem( + tender, 'documents', + depth_path='qualifications/{}'.format(qualification_id), + resource_item_id=tender_id ) - def get_awards(self, tender): - return self._get_tender_resource_list(tender, 'awards') + def get_awards(self, tender, tender_id=None): + return self.get_resource_item_subitem( + tender, 'awards', resource_item_id=tender_id + ) - def get_lots(self, tender): - return self._get_tender_resource_list(tender, 'lots') + def get_lots(self, tender, tender_id=None): + return self.get_resource_item_subitem( + tender, 'lots', resource_item_id=tender_id + ) ########################################################################### # CREATE ITEM API METHODS ########################################################################### - def _create_tender_resource_item(self, tender, item_obj, items_name): - return self._create_resource_item( - '{}/{}/{}'.format(self.prefix_path, tender.data.id, items_name), - item_obj, - headers={'X-Access-Token': self._get_access_token(tender)} - ) - def create_tender(self, tender): - return self._create_resource_item(self.prefix_path, tender) + return self.create_resource_item(self.prefix_path, tender) - def create_question(self, tender, question): - return self._create_tender_resource_item(tender, question, 'questions') + def create_question(self, tender, question, tender_id=None): + return self.create_resource_item_subitem( + tender, question, 'questions', resource_item_id=tender_id + ) - def create_bid(self, tender, bid): - return self._create_tender_resource_item(tender, bid, 'bids') + def create_bid(self, tender, bid, tender_id=None): + return self.create_resource_item_subitem( + tender, bid, 'bids', resource_item_id=tender_id + ) - def create_lot(self, tender, lot): - return self._create_tender_resource_item(tender, lot, 'lots') + def create_lot(self, tender, lot, tender_id=None): + return self.create_resource_item_subitem( + tender, lot, 'lots', resource_item_id=tender_id + ) - def create_award(self, tender, award): - return self._create_tender_resource_item(tender, award, 'awards') + def create_award(self, tender, award, tender_id=None): + return self.create_resource_item_subitem( + tender, award, 'awards', resource_item_id=tender_id + ) - def create_cancellation(self, tender, cancellation): - return self._create_tender_resource_item( - tender, cancellation, 'cancellations' + def create_cancellation(self, tender, cancellation, tender_id=None): + return self.create_resource_item_subitem( + tender, cancellation, 'cancellations', resource_item_id=tender_id ) - def create_complaint(self, tender, complaint): - return self\ - ._create_tender_resource_item(tender, complaint, 'complaints') + def create_complaint(self, tender, complaint, tender_id=None): + return self.create_resource_item_subitem( + tender, complaint, 'complaints', resource_item_id=tender_id + ) - def create_award_complaint(self, tender, complaint, award_id): - return self._create_resource_item( - '{}/{}/{}'.format(self.prefix_path, tender.data.id, - 'awards/{0}/complaints'.format(award_id)), - complaint, - headers={'X-Access-Token': self._get_access_token(tender)} + def create_award_complaint(self, tender, complaint, award_id, + tender_id=None): + depth_path = 'awards/{}'.format(award_id) + return self.create_resource_item_subitem( + tender, complaint, 'complaints', depth_path=depth_path, + resource_item_id=tender_id ) - def create_thin_document(self, tender, document_data): - return self._create_resource_item( - '{}/{}/documents'.format( - self.prefix_path, - tender.data.id - ), - document_data, - headers={'X-Access-Token': self._get_access_token(tender)} + def create_thin_document(self, tender, document_data, tender_id=None): + return self.create_resource_item_subitem( + tender, document_data, 'documents', resource_item_id=tender_id ) ########################################################################### # GET ITEM API METHODS ########################################################################### - def get_tender(self, id): - return self._get_resource_item('{}/{}'.format(self.prefix_path, id)) + def get_tender(self, tender_id): + return self.get_resource_item(tender_id) - def _get_tender_resource_item(self, tender, item_id, items_name, - access_token=None): - access_token = access_token or self._get_access_token(tender) - headers = {'X-Access-Token': access_token} - return self._get_resource_item( - '{}/{}/{}/{}'.format(self.prefix_path, - tender.data.id, - items_name, - item_id), - headers=headers + def get_question(self, tender, question_id, tender_id=None): + depth_path = 'questions' + return self.get_resource_item_submitem( + tender, question_id, depth_path=depth_path, + resource_item_id=tender_id ) - 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) - - def get_lot(self, tender, lot_id): - return self._get_tender_resource_item(tender, lot_id, 'lots') - - def get_file(self, url, access_token=None): - headers = {'X-Access-Token': access_token} if access_token else {} - - headers.update(self.headers) - response_item = self.request('GET', url, headers=headers) - - # if response_item.status_code == 302: - # response_obj = request(response_item.headers['location']) - if response_item.status_code == 200: - return response_item.text, \ - response_item.headers['Content-Disposition'] \ - .split("; filename=")[1].strip('"') - raise InvalidResponse(response_item) - - def get_file_properties(self, url, file_hash, access_token=None): - headers = {'X-Access-Token': access_token} if access_token else {} - headers.update(self.headers) - response_item = self.request('GET', url, headers=headers) - if response_item.status_code == 200: - file_properties={ - 'Content_Disposition': response_item.headers['Content-Disposition'], - 'Content_Type': response_item.headers['Content-Type'], - 'url': url, - 'hash': file_hash - } - return file_properties - raise InvalidResponse(response_item) + def get_bid(self, tender, bid_id, access_token=None, tender_id=None): + depth_path = 'bids' + return self.get_resource_item_submitem( + tender, bid_id, depth_path=depth_path, access_token=access_token, + resource_item_id=tender_id + ) - def extract_credentials(self, id): - return self._get_resource_item( - '{}/{}/extract_credentials'.format(self.prefix_path, id) + def get_lot(self, tender, lot_id, tender_id=None): + depth_path = 'lots' + return self.get_resource_item_submitem( + tender, lot_id, depth_path=depth_path, resource_item_id=tender_id ) ########################################################################### # PATCH ITEM API METHODS ########################################################################### - def patch_tender(self, tender): - return self._patch_resource_item( - '{}/{}'.format(self.prefix_path, tender['data']['id']), - payload=tender, - headers={'X-Access-Token': self._get_access_token(tender)} - ) + def patch_tender(self, tender, tender_id=None): + return self.patch_resource_item(tender, resource_item_id=tender_id) - def patch_question(self, tender, question): - return self._patch_obj_resource_item(tender, question, 'questions') + def patch_question(self, tender, question, tender_id=None, + question_id=None): + return self.patch_resource_item_subitem( + tender, question, 'questions', resource_item_id=tender_id, + subitem_id=question_id + ) - def patch_bid(self, tender, bid): - return self._patch_obj_resource_item(tender, bid, 'bids') + def patch_bid(self, tender, bid, tender_id=None, bid_id=None): + return self.patch_resource_item_subitem( + tender, bid, 'bids', resource_item_id=tender_id, subitem_id=bid_id + ) - def patch_bid_document(self, tender, document_data, bid_id, document_id): - return self._patch_resource_item( - '{}/{}/{}/{}/documents/{}'.format( - self.prefix_path, tender.data.id, 'bids', bid_id, document_id - ), - payload=document_data, - headers={'X-Access-Token': self._get_access_token(tender)} + def patch_bid_document(self, tender, document_data, bid_id, + document_id=None, tender_id=None): + depth_path = 'bids/{}'.format(bid_id) + return self.patch_resource_item_subitem( + tender, document_data, 'documents', subitem_id=document_id, + depth_path=depth_path, resource_item_id=tender_id ) - def patch_award(self, tender, award): - return self._patch_obj_resource_item(tender, award, 'awards') + def patch_award(self, tender, award, tender_id=None, award_id=None): + return self.patch_resource_item_subitem( + tender, award, 'awards', resource_item_id=tender_id, + subitem_id=award_id + ) - def patch_award_document(self, tender, document_data, award_id, document_id): - return self._patch_resource_item( - '{}/{}/awards/{}/documents/{}'.format( - self.prefix_path, tender.data.id, award_id, document_id - ), - payload=document_data, - headers={'X-Access-Token': - getattr(getattr(tender, 'access', ''), 'token', '')} + def patch_award_document(self, tender, document_data, award_id, + document_id=None, tender_id=None): + depth_path = 'awards/{}'.format(award_id) + return self.patch_resource_item_subitem( + tender, document_data, 'documents', resource_item_id=tender_id, + subitem_id=document_id, depth_path=depth_path ) - def patch_cancellation(self, tender, cancellation): + def patch_cancellation(self, tender, cancellation, tender_id=None, + cancellation_id=None): return self._patch_obj_resource_item( - tender, cancellation, 'cancellations' + tender, cancellation, 'cancellations', subitem_id=cancellation_id, + resource_item_id=tender_id ) - def patch_cancellation_document( - self, tender, cancellation, cancellation_id, cancellation_doc_id - ): - return self._patch_resource_item( - '{}/{}/{}/{}/documents/{}'.format( - self.prefix_path, tender.data.id, 'cancellations', - cancellation_id, cancellation_doc_id - ), - payload=cancellation, - headers={'X-Access-Token': self._get_access_token(tender)} + def patch_cancellation_document(self, tender, cancellation, + cancellation_id, cancellation_doc_id=None, + tender_id=None): + depth_path = 'cancellations/{}'.format(cancellation_id) + return self.patch_resource_item_subitem( + tender, cancellation, 'documents', subitem_id=cancellation_doc_id, + resource_item_id=tender_id, depth_path=depth_path ) - def patch_complaint(self, tender, complaint): - return self._patch_obj_resource_item( - tender, complaint, 'complaints' + def patch_complaint(self, tender, complaint, tender_id=None, + complaint_id=None): + return self.patch_resource_item_subitem( + tender, complaint, 'complaints', subitem_id=complaint_id, + resource_item_id=tender_id ) - def patch_award_complaint(self, tender, complaint, award_id): - return self._patch_resource_item( - '{}/{}/awards/{}/complaints/{}'.format( - self.prefix_path, tender.data.id, award_id, complaint.data.id - ), - payload=complaint, - headers={'X-Access-Token': self._get_access_token(tender)} + def patch_award_complaint(self, tender, complaint, award_id, + tender_id=None, complaint_id=None): + depth_path = 'awards/{}'.format(award_id) + return self.patch_resource_item_subitem( + tender, complaint, 'complaints', resource_item_id=tender_id, + subitem_id=complaint_id, ) - def patch_lot(self, tender, lot): - return self._patch_obj_resource_item(tender, lot, 'lots') + def patch_lot(self, tender, lot, lot_id=None, tender_id=None): + return self.patch_resource_item_subitem( + tender, lot, 'lots', subitem_id=lot_id, resource_item_id=tender_id + ) - def patch_qualification(self, tender, qualification): - return self._patch_obj_resource_item( - tender, qualification, 'qualifications' + def patch_qualification(self, tender, qualification, qualification_id=None, + tender_id=None): + return self.patch_resource_item_subitem( + tender, qualification, 'qualifications', + resource_item_id=tender_id, subitem_id=qualification_id ) - def patch_contract(self, tender, contract): - return self._patch_obj_resource_item(tender, contract, 'contracts') + def patch_contract(self, tender, contract, contract_id=None, + tender_id=None): + return self.patch_resource_item_subitem( + tender, contract, 'contracts', subitem_id=contract_id, + resource_item_id=tender_id + ) def patch_contract_document(self, tender, document_data, - contract_id, document_id): - return self._patch_resource_item( - '{}/{}/{}/{}/documents/{}'.format( - self.prefix_path, tender.data.id, 'contracts', - contract_id, document_id - ), - payload=document_data, - headers={'X-Access-Token': self._get_access_token(tender)} + contract_id, document_id=None, tender_id=None): + depth_path = 'contracts/{}'.format(contract_id) + return self.patch_resource_item_subitem( + tender, document_data, 'documents', subitem_id=document_id, + resource_item_id=tender_id ) ########################################################################### # UPLOAD FILE API METHODS ########################################################################### - @verify_file def upload_bid_document(self, file_, tender, bid_id, doc_type='documents', - use_ds_client=True, doc_registration=True): + use_ds_client=True, doc_registration=True, + tender_id=None): + depth_path = 'bids/{}'.format(bid_id) + return self.upload_document(file_,) return self._upload_resource_file( '{}/{}/bids/{}/{}'.format( self.prefix_path, diff --git a/openprocurement_client/registry_client.py b/openprocurement_client/registry_client.py new file mode 100644 index 0000000..4a2d807 --- /dev/null +++ b/openprocurement_client/registry_client.py @@ -0,0 +1,51 @@ +import logging + +from .client import APIClient +from .exceptions import InvalidResponse + +from iso8601 import parse_date +from munch import munchify +from retrying import retry +from simplejson import loads + + +class LotsClient(APIClient): + """ Client for Openregistry Lots """ + + api_version = '0.1' + resource = 'lots' + + def __init__(self, *args, **kwargs): + super(LotsClient, self).__init__(resource=self.resource, *args, + **kwargs) + + def get_lot(self, lot_id, headers=None): + return self.get_resource_item(lot_id, headers=headers) + + def get_lots(self, params=None, feed='changes'): + return self._get_resource_items(params=params, feed=feed) + + +class AssetsClient(APIBaseClient): + """ Client for Openregistry Assets """ + + api_version = '0.1' + + def __init__(self, + key='', + resource='assets', + host_url=None, + api_version=None, + params=None, + ds_client=None, + user_agent=None): + super(AssetsClient, self).__init__( + key=key, resource=resource, host_url=host_url, params=params, + api_version=api_version, ds_client=ds_client, + user_agent=user_agent) + + def get_asset(self, asset_id, headers=None): + return self.get_resource_item(asset_id, headers=headers) + + def get_assets(self, params=None, feed='changes'): + return self._get_resource_items(params=params, feed=feed) diff --git a/openprocurement_client/tests/_server.py b/openprocurement_client/tests/_server.py index 62e9dbf..9ba746b 100755 --- a/openprocurement_client/tests/_server.py +++ b/openprocurement_client/tests/_server.py @@ -3,8 +3,13 @@ from simplejson import dumps, load from openprocurement_client.document_service_client \ import DocumentServiceClient -from openprocurement_client.tests.data_dict import TEST_TENDER_KEYS, \ - TEST_PLAN_KEYS, TEST_CONTRACT_KEYS +from openprocurement_client.tests.data_dict import ( + TEST_TENDER_KEYS, + TEST_PLAN_KEYS, + TEST_CONTRACT_KEYS, + TEST_ASSET_KEYS, + TEST_LOT_KEYS +) import magic import os @@ -23,10 +28,13 @@ CONTRACTS_PATH = API_PATH.format('contracts') SPORE_PATH = API_PATH.format('spore') DOWNLOAD_URL_EXTENSION = 'some_key_etc' -RESOURCE_DICT = \ - {'tender': {'sublink': 'tenders', 'data': TEST_TENDER_KEYS}, - 'contract': {'sublink': 'contracts', 'data': TEST_CONTRACT_KEYS}, - 'plan': {'sublink': 'plans', 'data': TEST_PLAN_KEYS}} +RESOURCE_DICT = { + 'tender': {'sublink': 'tenders', 'data': TEST_TENDER_KEYS}, + 'contract': {'sublink': 'contracts', 'data': TEST_CONTRACT_KEYS}, + 'plan': {'sublink': 'plans', 'data': TEST_PLAN_KEYS}, + 'asset': {'sublink': 'assets', 'data': TEST_ASSET_KEYS}, + 'lot': {'sublink': 'lots', 'data': TEST_LOT_KEYS} +} def resource_filter(resource_name): @@ -296,41 +304,47 @@ def contract_change_patch(contract_id, change_id): routes_dict = { - "spore": (SPORE_PATH, 'HEAD', spore), - "offset_error": (API_PATH.format(''), 'GET', offset_error), - "tenders": (API_PATH.format(''), 'GET', resource_page_get), - "tender_create": (TENDERS_PATH, 'POST', resource_create), - "tender": (API_PATH.format('') + '/', 'GET', resource_page), - "tender_patch": (API_PATH.format('') + "/", 'PATCH', resource_patch), - "tender_document_create": (TENDERS_PATH + "//documents", 'POST', tender_document_create), - "tender_subpage": (TENDERS_PATH + "//", 'GET', tender_subpage), - "tender_subpage_item_create": (TENDERS_PATH + "//", 'POST', resource_subpage_item_create), - "tender_award_documents": (TENDERS_PATH + "//awards//documents", 'GET', tender_award_documents), - "tender_qualification_documents": (TENDERS_PATH + "//qualifications//documents", 'GET', tender_qualification_documents), - "tender_subpage_document_create": (TENDERS_PATH + "////", 'POST', tender_subpage_document_create), - "tender_subpage_document_update": (TENDERS_PATH + "/////", 'PUT', tender_subpage_document_update), - "tender_subpage_document_patch": (TENDERS_PATH + "/////", 'PATCH', tender_subpage_document_patch), - "tender_subpage_item": (TENDERS_PATH + "///", 'GET', tender_subpage_item), - "tender_subpage_item_patch": (API_PATH.format('') + '///', 'PATCH', object_subpage_item_patch), - "tender_subpage_item_delete": (TENDERS_PATH + "///", 'DELETE', tender_subpage_item_delete), - "tender_patch_credentials": (API_PATH.format('') + '//credentials', 'PATCH', patch_credentials), - "redirect": ('/redirect/', 'GET', get_file), - "download": ('/download/', 'GET', download_file), - "plans": (API_PATH.format(''), 'GET', resource_page_get), - "plan_create": (PLANS_PATH, 'POST', resource_create), - "plan_patch": (API_PATH.format('') + "/", 'PATCH', resource_patch), - "plan": (API_PATH.format('') + '/', 'GET', resource_page), - "plan_offset_error": (API_PATH.format(''), 'GET', offset_error), - "contracts": (API_PATH.format(''), 'GET', resource_page_get), - "contract_create": (CONTRACTS_PATH, 'POST', resource_create), - "contract_document_create": (CONTRACTS_PATH + "//documents", 'POST', contract_document_create), - "contract": (API_PATH.format('') + '/', 'GET', resource_page), - "contract_subpage_item_create": (CONTRACTS_PATH + "//", 'POST', resource_subpage_item_create), - "contract_subpage_item_patch": (API_PATH.format('') + '///', 'PATCH', object_subpage_item_patch), - "contract_change_patch": (API_PATH.format('contracts') + '//changes/', 'PATCH', contract_change_patch), - "contract_patch": (API_PATH.format('') + "/", 'PATCH', resource_patch), - "contract_patch_credentials": (API_PATH.format('') + '//credentials', 'PATCH', patch_credentials), - } + "spore": (SPORE_PATH, 'HEAD', spore), + "offset_error": (API_PATH.format(''), 'GET', offset_error), + "tenders": (API_PATH.format(''), 'GET', resource_page_get), + "tender_create": (TENDERS_PATH, 'POST', resource_create), + "tender": (API_PATH.format('') + '/', 'GET', resource_page), + "tender_patch": (API_PATH.format('') + "/", 'PATCH', resource_patch), + "tender_document_create": (TENDERS_PATH + "//documents", 'POST', tender_document_create), + "tender_subpage": (TENDERS_PATH + "//", 'GET', tender_subpage), + "tender_subpage_item_create": (TENDERS_PATH + "//", 'POST', resource_subpage_item_create), + "tender_award_documents": (TENDERS_PATH + "//awards//documents", 'GET', tender_award_documents), + "tender_qualification_documents": (TENDERS_PATH + "//qualifications//documents", 'GET', tender_qualification_documents), + "tender_subpage_document_create": (TENDERS_PATH + "////", 'POST', tender_subpage_document_create), + "tender_subpage_document_update": (TENDERS_PATH + "/////", 'PUT', tender_subpage_document_update), + "tender_subpage_document_patch": (TENDERS_PATH + "/////", 'PATCH', tender_subpage_document_patch), + "tender_subpage_item": (TENDERS_PATH + "///", 'GET', tender_subpage_item), + "tender_subpage_item_patch": (API_PATH.format('') + '///', 'PATCH', object_subpage_item_patch), + "tender_subpage_item_delete": (TENDERS_PATH + "///", 'DELETE', tender_subpage_item_delete), + "tender_patch_credentials": (API_PATH.format('') + '//credentials', 'PATCH', patch_credentials), + "redirect": ('/redirect/', 'GET', get_file), + "download": ('/download/', 'GET', download_file), + "plans": (API_PATH.format(''), 'GET', resource_page_get), + "plan_create": (PLANS_PATH, 'POST', resource_create), + "plan_patch": (API_PATH.format('') + "/", 'PATCH', resource_patch), + "plan": (API_PATH.format('') + '/', 'GET', resource_page), + "plan_offset_error": (API_PATH.format(''), 'GET', offset_error), + "contracts": (API_PATH.format(''), 'GET', resource_page_get), + "contract_create": (CONTRACTS_PATH, 'POST', resource_create), + "contract_document_create": (CONTRACTS_PATH + "//documents", 'POST', contract_document_create), + "contract": (API_PATH.format('') + '/', 'GET', resource_page), + "contract_subpage_item_create": (CONTRACTS_PATH + "//", 'POST', resource_subpage_item_create), + "contract_subpage_item_patch": (API_PATH.format('') + '///', 'PATCH', object_subpage_item_patch), + "contract_change_patch": (API_PATH.format('contracts') + '//changes/', 'PATCH', contract_change_patch), + "contract_patch": (API_PATH.format('') + "/", 'PATCH', resource_patch), + "contract_patch_credentials": (API_PATH.format('') + '//credentials', 'PATCH', patch_credentials), + "assets": (API_PATH.format(''), 'GET', resource_page_get), + "asset": (API_PATH.format('') + '/', 'GET', resource_page), + "asset_patch": (API_PATH.format('') + "/", 'PATCH', resource_patch), + "lots": (API_PATH.format(''), 'GET', resource_page_get), + "lot": (API_PATH.format('') + '/', 'GET', resource_page), + "lot_patch": (API_PATH.format('') + "/", 'PATCH', resource_patch), +} def file_info(file_): diff --git a/openprocurement_client/tests/data/asset_668c3156c8cb496fb28359909cde6e96.json b/openprocurement_client/tests/data/asset_668c3156c8cb496fb28359909cde6e96.json new file mode 100644 index 0000000..27e133b --- /dev/null +++ b/openprocurement_client/tests/data/asset_668c3156c8cb496fb28359909cde6e96.json @@ -0,0 +1,272 @@ +{ + "access": { + "token": "0fc58c83963d42a692db4987df86640a" + }, + "data": { + "procurementMethod": "limited", + "status": "cancelled", + "documents": [ + { + "format": "text/plain", + "url": "https://api-sandbox.openprocurement.org/api/0.12/tenders/668c3156c8cb496fb28359909cde6e96/documents/3f82c7ef80a745119b6a6dec2670e5bc?download=0fc58c83963d42a692db4987df86640a", + "title": "/tmp/tmp5k4QEL", + "documentOf": "tender", + "datePublished": "2016-02-18T14:45:40.348569+02:00", + "dateModified": "2016-02-18T14:45:40.348612+02:00", + "id": "3f82c7ef80a745119b6a6dec2670e5bc" + } + ], + "title": "\u041f\u043e\u0441\u043b\u0443\u0433\u0438 \u0448\u043a\u0456\u043b\u044c\u043d\u0438\u0445 \u0457\u0434\u0430\u043b\u0435\u043d\u044c", + "contracts": [ + { + "status": "pending", + "awardID": "70eaaceee81e4f378e333c1ed8258652", + "id": "c65d3526cdc44f75b2457b4489970a7c" + } + ], + "items": [ + { + "description": "\u041f\u043e\u0441\u043b\u0443\u0433\u0438 \u0448\u043a\u0456\u043b\u044c\u043d\u0438\u0445 \u0457\u0434\u0430\u043b\u0435\u043d\u044c", + "classification": { + "scheme": "CPV", + "description": "\u041f\u043e\u0441\u043b\u0443\u0433\u0438 \u0437 \u0445\u0430\u0440\u0447\u0443\u0432\u0430\u043d\u043d\u044f \u0443 \u0448\u043a\u043e\u043b\u0430\u0445", + "id": "55523100-3" + }, + "additionalClassifications": [ + { + "scheme": "\u0414\u041a\u041f\u041f", + "id": "55.51.10.300", + "description": "\u041f\u043e\u0441\u043b\u0443\u0433\u0438 \u0448\u043a\u0456\u043b\u044c\u043d\u0438\u0445 \u0457\u0434\u0430\u043b\u0435\u043d\u044c" + } + ], + "deliveryLocation": { + "latitude": 49.85, + "longitude": 24.0167 + }, + "deliveryAddress": { + "locality": "\u043c. \u041a\u0438\u0457\u0432", + "region": "\u043c. \u041a\u0438\u0457\u0432", + "countryName_en": "Ukraine", + "countryName": "\u0423\u043a\u0440\u0430\u0457\u043d\u0430", + "streetAddress": "481 John Pine Suite 803", + "countryName_ru": "\u0423\u043a\u0440\u0430\u0438\u043d\u0430", + "postalCode": "58662" + }, + "deliveryDate": { + "endDate": "2016-02-23T14:42:28.297330+02:00" + }, + "id": "2dc54675d6364e2baffbc0f8e74432ac", + "unit": { + "code": "MON", + "name": "month" + }, + "quantity": 9 + } + ], + "complaints": [ + { + "status": "resolved", + "documents": [ + { + "author": "complaint_owner", + "format": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "url": "https://lb.api-sandbox.openprocurement.org/api/0.12/tenders/cdec9f009be44f53817754b12d292ef2/complaints/22b086222a7a4bc6a9cc8adeaa91a57f/documents/129f4013b33a45b8bc70699a62a81499?download=7a6787929a624dd8b7c067974b9e8a96", + "title": "/tmp/tmpcFrLvz.docx", + "documentOf": "tender", + "datePublished": "2016-02-29T15:05:54.557614+02:00", + "dateModified": "2016-02-29T15:05:54.557656+02:00", + "id": "129f4013b33a45b8bc70699a62a81499" + } + ], + "description": "Consequuntur sint cupiditate impedit et magnam aut odit eligendi dicta magnam dicta eveniet.", + "tendererActionDate": "2016-02-29T15:06:05.212783+02:00", + "title": "Hic laboriosam magni cupiditate enim placeat in dolorem.", + "resolutionType": "resolved", + "resolution": "Non ipsa accusantium iste et dolorum quo temporibus deserunt et provident suscipit.", + "satisfied": true, + "dateAnswered": "2016-02-29T15:06:05.212741+02:00", + "tendererAction": "Labore adipisci sequi corporis neque delectus ea aut dicta molestiae omnis voluptatem qui et.", + "dateSubmitted": "2016-02-29T15:06:01.956017+02:00", + "date": "2016-02-29T15:05:53.084688+02:00", + "type": "claim", + "id": "22b086222a7a4bc6a9cc8adeaa91a57f" + }, + { + "status": "cancelled", + "description": "Sit cumque iste eius corporis exercitationem voluptate.", + "title": "Commodi voluptatem earum sapiente doloremque.", + "cancellationReason": "prosto tak :)", + "dateCanceled": "2016-02-29T15:06:19.281225+02:00", + "date": "2016-02-29T15:06:17.694889+02:00", + "type": "claim", + "id": "d26fc2f475514652a53c0c551d984be3" + }, + { + "status": "cancelled", + "description": "Eos officia vel aliquid id qui provident suscipit eos nam amet.", + "title": "Voluptas debitis cupiditate deserunt inventore fugit quo nemo dicta.", + "cancellationReason": "prosto tak :)", + "dateCanceled": "2016-02-29T15:06:28.419955+02:00", + "dateSubmitted": "2016-02-29T15:06:26.927454+02:00", + "date": "2016-02-29T15:06:24.962751+02:00", + "type": "claim", + "id": "816eb0529d6d41148dac0edabc119407" + }, + { + "status": "cancelled", + "dateCanceled": "2016-02-29T15:06:37.390093+02:00", + "tendererActionDate": "2016-02-29T15:06:35.767209+02:00", + "description": "Non voluptas ipsa ex quibusdam dolorem rem dolores sequi.", + "title": "Et esse eos unde molestiae cum sunt.", + "resolutionType": "resolved", + "resolution": "Cumque magni vitae officiis odit et mollitia neque dolores reprehenderit.", + "cancellationReason": "prosto tak :)", + "dateAnswered": "2016-02-29T15:06:35.767171+02:00", + "tendererAction": "Architecto dolor dolorem ut labore esse possimus odit a unde hic quas error alias nulla.", + "dateSubmitted": "2016-02-29T15:06:34.170608+02:00", + "date": "2016-02-29T15:06:32.488624+02:00", + "type": "claim", + "id": "ec053962ce334bdaaab8b3bf2e6b3173" + }, + { + "status": "cancelled", + "dateCanceled": "2016-02-29T15:06:55.181473+02:00", + "tendererActionDate": "2016-02-29T15:06:45.067477+02:00", + "description": "Et autem reiciendis eaque dolorem adipisci aut.", + "title": "Ipsum cupiditate ipsam ut non.", + "resolutionType": "resolved", + "resolution": "Qui vitae placeat rerum officia quia aperiam illo nisi velit et fuga ducimus dolor.", + "author": { + "contactPoint": { + "name": "Варвара Талан", + "telephone": "+38 (268) 501-16-45" + }, + "identifier": { + "scheme": "UA-EDR", + "id": "00007460", + "uri": "http://dummyimage.com/506x880" + }, + "name": "Терещук-Приймак", + "address": { + "locality": "м. Вінниця", + "region": "Вінницька область", + "countryName_en": "Ukraine", + "countryName": "Україна", + "streetAddress": "73741 John Pass", + "countryName_ru": "Украина", + "postalCode": "21100" + } + }, + "satisfied": false, + "dateEscalated": "2016-02-29T15:06:46.828487+02:00", + "dateAnswered": "2016-02-29T15:06:45.067440+02:00", + "tendererAction": "Ex optio cumque assumenda iure consectetur unde iste et consequatur at quaerat dolor.", + "dateSubmitted": "2016-02-29T15:06:43.326331+02:00", + "date": "2016-02-29T15:06:41.513902+02:00", + "cancellationReason": "prosto tak :)", + "type": "complaint", + "id": "76f0f99cb643434da21b6a6350cc2d0e" + } + ], + "procurementMethodType": "reporting", + "cancellations": [ + { + "status": "active", + "documents": [ + { + "description": "test description", + "format": "text/plain", + "url": "https://api-sandbox.openprocurement.org/api/0.12/tenders/668c3156c8cb496fb28359909cde6e96/cancellations/0dd6f9e8cc4f4d1c9c404d842b56d0d7/documents/1afca9faaf2b4f9489ee264b136371c6?download=57e135c2f8d342c3a506be20940a3f3c", + "title": "/tmp/tmpdhdoax", + "documentOf": "tender", + "datePublished": "2016-02-18T14:46:05.348204+02:00", + "dateModified": "2016-02-18T14:46:05.348246+02:00", + "id": "1afca9faaf2b4f9489ee264b136371c6" + }, + { + "description": "test description", + "format": "text/plain", + "url": "https://api-sandbox.openprocurement.org/api/0.12/tenders/668c3156c8cb496fb28359909cde6e96/cancellations/0dd6f9e8cc4f4d1c9c404d842b56d0d7/documents/1afca9faaf2b4f9489ee264b136371c6?download=9208a5fa7cf140a2bb4751a42a1f1ea9", + "title": "/tmp/tmpOBUlWP", + "documentOf": "tender", + "datePublished": "2016-02-18T14:46:05.348204+02:00", + "dateModified": "2016-02-18T14:46:08.516130+02:00", + "id": "1afca9faaf2b4f9489ee264b136371c6" + } + ], + "reason": "prost :))", + "date": "2016-02-18T14:46:04.344125+02:00", + "cancellationOf": "tender", + "id": "0dd6f9e8cc4f4d1c9c404d842b56d0d7" + } + ], + "value": { + "currency": "UAH", + "amount": 500000.0, + "valueAddedTaxIncluded": true + }, + "id": "668c3156c8cb496fb28359909cde6e96", + "awards": [ + { + "status": "active", + "complaintPeriod": { + "startDate": "2016-02-18T14:45:41.212240+02:00", + "endDate": "2016-02-19T14:45:42.178932+02:00" + }, + "suppliers": [ + { + "contactPoint": { + "telephone": "+380 (432) 21-69-30", + "name": "\u0421\u0435\u0440\u0433\u0456\u0439 \u041e\u043b\u0435\u043a\u0441\u044e\u043a", + "email": "soleksuk@gmail.com" + }, + "identifier": { + "scheme": "UA-EDR", + "id": "13313462", + "uri": "http://sch10.edu.vn.ua/", + "legalName": "\u0414\u0435\u0440\u0436\u0430\u0432\u043d\u0435 \u043a\u043e\u043c\u0443\u043d\u0430\u043b\u044c\u043d\u0435 \u043f\u0456\u0434\u043f\u0440\u0438\u0454\u043c\u0441\u0442\u0432\u043e \u0433\u0440\u043e\u043c\u0430\u0434\u0441\u044c\u043a\u043e\u0433\u043e \u0445\u0430\u0440\u0447\u0443\u0432\u0430\u043d\u043d\u044f \u00ab\u0428\u043a\u043e\u043b\u044f\u0440\u00bb" + }, + "name": "\u0414\u041a\u041f \u00ab\u0428\u043a\u043e\u043b\u044f\u0440\u00bb", + "address": { + "postalCode": "21100", + "countryName": "\u0423\u043a\u0440\u0430\u0457\u043d\u0430", + "streetAddress": "\u0432\u0443\u043b. \u041e\u0441\u0442\u0440\u043e\u0432\u0441\u044c\u043a\u043e\u0433\u043e, 33", + "region": "\u043c. \u0412\u0456\u043d\u043d\u0438\u0446\u044f", + "locality": "\u043c. \u0412\u0456\u043d\u043d\u0438\u0446\u044f" + } + } + ], + "value": { + "currency": "UAH", + "amount": 475000.0, + "valueAddedTaxIncluded": true + }, + "date": "2016-02-18T14:45:41.210928+02:00", + "id": "70eaaceee81e4f378e333c1ed8258652" + } + ], + "tenderID": "UA-2016-02-18-000031", + "owner": "test.quintagroup.com", + "dateModified": "2016-02-18T14:46:09.940361+02:00", + "procuringEntity": { + "contactPoint": { + "url": "http://sch10.edu.vn.ua/", + "name": "\u041a\u0443\u0446\u0430 \u0421\u0432\u0456\u0442\u043b\u0430\u043d\u0430 \u0412\u0430\u043b\u0435\u043d\u0442\u0438\u043d\u0456\u0432\u043d\u0430", + "telephone": "+380 (432) 46-53-02" + }, + "identifier": { + "scheme": "UA-EDR", + "id": "21725150", + "legalName": "\u0417\u0430\u043a\u043b\u0430\u0434 \"\u0417\u0430\u0433\u0430\u043b\u044c\u043d\u043e\u043e\u0441\u0432\u0456\u0442\u043d\u044f \u0448\u043a\u043e\u043b\u0430 \u0406-\u0406\u0406\u0406 \u0441\u0442\u0443\u043f\u0435\u043d\u0456\u0432 \u2116 10 \u0412\u0456\u043d\u043d\u0438\u0446\u044c\u043a\u043e\u0457 \u043c\u0456\u0441\u044c\u043a\u043e\u0457 \u0440\u0430\u0434\u0438\"" + }, + "name": "\u0417\u041e\u0421\u0428 #10 \u043c.\u0412\u0456\u043d\u043d\u0438\u0446\u0456", + "address": { + "postalCode": "21027", + "countryName": "\u0423\u043a\u0440\u0430\u0457\u043d\u0430", + "streetAddress": "\u0432\u0443\u043b. \u0421\u0442\u0430\u0445\u0443\u0440\u0441\u044c\u043a\u043e\u0433\u043e. 22", + "region": "\u043c. \u0412\u0456\u043d\u043d\u0438\u0446\u044f", + "locality": "\u043c. \u0412\u0456\u043d\u043d\u0438\u0446\u044f" + } + } + } +} diff --git a/openprocurement_client/tests/data/asset_823d50b3236247adad28a5a66f74db42.json b/openprocurement_client/tests/data/asset_823d50b3236247adad28a5a66f74db42.json new file mode 100644 index 0000000..27b7f37 --- /dev/null +++ b/openprocurement_client/tests/data/asset_823d50b3236247adad28a5a66f74db42.json @@ -0,0 +1,504 @@ +{ + "data":{ + "procurementMethod":"open", + "status":"complete", + "tenderPeriod":{ + "startDate":"2015-12-30T03:05:00+02:00", + "endDate":"2015-12-31T03:05:00+02:00" + }, + "qualifications": [ + { + "status": "pending", + "id": "cec4b82d2708465291fb4af79f8a3e52", + "bidID": "c41709780fa8434399c62a1facf369cb", + "documents": [ + { + "confidentiality": "public", + "language": "uk", + "title": "Proposal.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/e3e16eb63b584378b75d9eb01dc2f8b9/bids/c41709780fa8434399c62a1facf369cb/documents/a05aff4e91c942bbad7e49e551399ec7?download=d0984048f149465699fd402bfe20881c", + "format": "application/pdf", + "documentOf": "tender", + "datePublished": "2016-02-24T14:13:36.127526+02:00", + "id": "a05aff4e91c942bbad7e49e551399ec7", + "dateModified": "2016-02-24T14:13:36.127580+02:00" + }] + }, + { + "status": "pending", + "id": "9d562bbbf05e44f8a6a1f07ddb0415b1", + "bidID": "dda102997a4d4d5d8b5dac6ce2225e1c" + }, + { + "status": "pending", + "id": "36ac59607c434c8198730cc372672c8c", + "bidID": "6e0d218c8e964b42ac4a8ca086c100f9" + } + ], + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.10/tenders/823d50b3236247adad28a5a66f74db42/documents/330822cbbd724671a1d2ff7c3a51dd52?download=5ff4917f29954e5f8dfea1feed8f7455", + "title":"test1.txt", + "documentOf":"tender", + "datePublished":"2015-12-12T03:06:53.209628+02:00", + "dateModified":"2015-12-12T03:06:53.209670+02:00", + "id":"330822cbbd724671a1d2ff7c3a51dd52" + }, + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.10/tenders/823d50b3236247adad28a5a66f74db42/documents/6badc6f313d2499794bd39a3a7182fc0?download=ab2d7cdd346342a6a0d7b40af39145e2", + "title":"test3.txt", + "documentOf":"tender", + "datePublished":"2015-12-12T03:06:53.724784+02:00", + "dateModified":"2015-12-12T03:06:53.724826+02:00", + "id":"6badc6f313d2499794bd39a3a7182fc0" + }, + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.10/tenders/823d50b3236247adad28a5a66f74db42/documents/08bcd56de9aa43faa684f8f8e7ab1c98?download=c7afbf3686cc441db7735d5ec284a0de", + "title":"test2.txt", + "documentOf":"tender", + "datePublished":"2015-12-12T03:06:54.280311+02:00", + "dateModified":"2015-12-12T03:06:54.280354+02:00", + "id":"08bcd56de9aa43faa684f8f8e7ab1c98" + } + ], + "description":"Multilot 4 descr", + "title":"Multilot 4 title", + "submissionMethodDetails":"quick", + "items":[ + { + "relatedLot":"7d774fbf1e86420484c7d1a005cc283f", + "description":"itelm descr", + "classification":{ + "scheme":"CPV", + "description":"\u041f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f \u0441\u0456\u043b\u044c\u0441\u044c\u043a\u043e\u0433\u043e \u0433\u043e\u0441\u043f\u043e\u0434\u0430\u0440\u0441\u0442\u0432\u0430, \u0444\u0435\u0440\u043c\u0435\u0440\u0441\u0442\u0432\u0430, \u0440\u0438\u0431\u0430\u043b\u044c\u0441\u0442\u0432\u0430, \u043b\u0456\u0441\u043d\u0438\u0446\u0442\u0432\u0430 \u0442\u0430 \u0441\u0443\u043c\u0456\u0436\u043d\u0430 \u043f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f", + "id":"03000000-1" + }, + "additionalClassifications":[ + { + "scheme":"\u0414\u041a\u041f\u041f", + "id":"01", + "description":"\u041f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f \u0441\u0456\u043b\u044c\u0441\u044c\u043a\u043e\u0433\u043e \u0433\u043e\u0441\u043f\u043e\u0434\u0430\u0440\u0441\u0442\u0432\u0430, \u043c\u0438\u0441\u043b\u0438\u0432\u0441\u0442\u0432\u0430 \u0442\u0430 \u043f\u043e\u0432\u2019\u044f\u0437\u0430\u043d\u0456 \u0437 \u0446\u0438\u043c \u043f\u043e\u0441\u043b\u0443\u0433\u0438" + } + ], + "deliveryDate":{ + "startDate":"2016-06-01T00:00:00+03:00" + }, + "id":"7caa3587fe9041da98f2744ef531d7ce", + "unit":{ + "code":"LO", + "name":"\u043b\u043e\u0442" + }, + "quantity":4 + }, + { + "relatedLot":"7d774fbf1e86420484c7d1a005cc283f", + "description":"itelm descr", + "classification":{ + "scheme":"CPV", + "description":"\u041f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f \u0441\u0456\u043b\u044c\u0441\u044c\u043a\u043e\u0433\u043e \u0433\u043e\u0441\u043f\u043e\u0434\u0430\u0440\u0441\u0442\u0432\u0430, \u0444\u0435\u0440\u043c\u0435\u0440\u0441\u0442\u0432\u0430, \u0440\u0438\u0431\u0430\u043b\u044c\u0441\u0442\u0432\u0430, \u043b\u0456\u0441\u043d\u0438\u0446\u0442\u0432\u0430 \u0442\u0430 \u0441\u0443\u043c\u0456\u0436\u043d\u0430 \u043f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f", + "id":"03000000-1" + }, + "additionalClassifications":[ + { + "scheme":"\u0414\u041a\u041f\u041f", + "id":"01", + "description":"\u041f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f \u0441\u0456\u043b\u044c\u0441\u044c\u043a\u043e\u0433\u043e \u0433\u043e\u0441\u043f\u043e\u0434\u0430\u0440\u0441\u0442\u0432\u0430, \u043c\u0438\u0441\u043b\u0438\u0432\u0441\u0442\u0432\u0430 \u0442\u0430 \u043f\u043e\u0432\u2019\u044f\u0437\u0430\u043d\u0456 \u0437 \u0446\u0438\u043c \u043f\u043e\u0441\u043b\u0443\u0433\u0438" + } + ], + "deliveryDate":{ + "startDate":"2016-06-01T00:00:00+03:00" + }, + "id":"563ef5d999f34d36a5a0e4e4d91d7be1", + "unit":{ + "code":"LO", + "name":"\u043b\u043e\u0442" + }, + "quantity":4 + } + ], + "id":"823d50b3236247adad28a5a66f74db42", + "value":{ + "currency":"UAH", + "amount":1000.0, + "valueAddedTaxIncluded":false + }, + "submissionMethod":"electronicAuction", + "procuringEntity":{ + "contactPoint":{ + "telephone":"", + "name":"Dmitry", + "email":"d.karnaukh+merchant1@smartweb.com.ua" + }, + "identifier":{ + "scheme":"UA-EDR", + "id":"66666692", + "legalName":"\u041d\u0410\u041a \"Testov Test\"" + }, + "name":"\u041d\u0410\u041a \"Testov Test\"", + "address":{ + "postalCode":"02121", + "countryName":"\u0423\u043a\u0440\u0430\u0438\u043d\u0430", + "streetAddress":"", + "region":"\u041a\u0438\u0435\u0432\u0441\u043a\u0430\u044f \u043e\u0431\u043b\u0430\u0441\u0442\u044c", + "locality":"\u041a\u0438\u0435\u0432" + } + }, + "minimalStep":{ + "currency":"UAH", + "amount":1.0, + "valueAddedTaxIncluded":false + }, + "tenderID":"UA-2015-12-12-000003-1", + "questions":[ + { + "description":"Test lot question descr", + "title":"Test lot question title", + "relatedItem":"563ef5d999f34d36a5a0e4e4d91d7be1", + "answer":"Test answer", + "date":"2015-12-21T17:31:19.650440+02:00", + "id":"615ff8be8eba4a81b300036d6bec991c", + "questionOf":"lot" + }, + { + "description":"lot 2 question descr?", + "title":"lot 2 question title", + "relatedItem":"563ef5d999f34d36a5a0e4e4d91d7be1", + "answer":"lot 2 answer", + "date":"2015-12-21T17:36:30.680576+02:00", + "id":"278f269ee482434da6615a21f3deccf0", + "questionOf":"lot" + }, + { + "description":"lot 3 q descr?", + "title":"lot 3 q title", + "relatedItem":"563ef5d999f34d36a5a0e4e4d91d7be1", + "answer":"lot 3 answer", + "date":"2015-12-21T17:57:28.108596+02:00", + "id":"da6c1228f81e4c2e8451479d57b685dd", + "questionOf":"lot" + }, + { + "description":"Tender q 1 descr?", + "title":"Tender q 1 title", + "relatedItem":"ad681d050d0e42169a80b86d5b591c20", + "answer":"Tender q 1 answer", + "date":"2015-12-21T17:58:24.489846+02:00", + "id":"937b4b9ef3444782b9a3019fcc5c072a", + "questionOf":"tender" + }, + { + "description":"Item descr?", + "title":"Item title?", + "relatedItem":"7caa3587fe9041da98f2744ef531d7ce", + "answer":"Item answer", + "date":"2015-12-21T18:04:35.349936+02:00", + "id":"a2d14f4faf9b4ba180b40f08a2be3bd9", + "questionOf":"item" + } + ], + "enquiryPeriod":{ + "startDate":"2015-12-12T03:06:52.664385+02:00", + "endDate":"2015-12-30T03:05:00+02:00" + }, + "owner":"prom.ua", + "lots":[ + { + "status":"complete", + "description":"lot descr1", + "title":"lot title", + "minimalStep":{ + "currency":"UAH", + "amount":1.0, + "valueAddedTaxIncluded":false + }, + "value":{ + "currency":"UAH", + "amount":1000.0, + "valueAddedTaxIncluded":false + }, + "id":"7d774fbf1e86420484c7d1a005cc283f" + }, + { + "status":"active", + "description":"lot descr2", + "title":"lot title", + "minimalStep":{ + "currency":"UAH", + "amount":1.0, + "valueAddedTaxIncluded":false + }, + "value":{ + "currency":"UAH", + "amount":2000.0, + "valueAddedTaxIncluded":false + }, + "id":"563ef5d999f34d36a5a0e4e4d91d7be1" + } + ], + "bids":[ + { + "date": "2014-12-16T04:44:23.569815+02:00", + "documents": [ + { + "dateModified": "2014-12-16T04:44:25.010930+02:00", + "datePublished": "2014-12-16T04:44:25.010885+02:00", + "format": "text/plain", + "id": "ff001412c60c4164a0f57101e4eaf8aa", + "title": "Proposal.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0/tenders/6f73bf0f7f734f459f7e37e3787054a0/bids/f7fc1212f9f140bba5c4e3cd4f2b62d9/documents/ff001412c60c4164a0f57101e4eaf8aa?download=4f45bbd414104cd78faf620208efd824" + } + ], + "qualificationDocuments": [ + { + "confidentiality": "public", + "language": "uk", + "title": "qualification_document.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/e3e16eb63b584378b75d9eb01dc2f8b9/bids/c41709780fa8434399c62a1facf369cb/financial_documents/7519d21b32af432396acd6e2c9e18ee5?download=5db59ae05361427b84c4caddb0b6d92b", + "format": "application/pdf", + "documentOf": "tender", + "datePublished": "2016-02-24T14:13:36.625252+02:00", + "id": "7519d21b32af432396acd6e2c9e18ee5", + "dateModified": "2016-02-24T14:13:36.625290+02:00" + } + ], + "financial_documens": [ + { + "confidentiality": "public", + "language": "uk", + "title": "financial_doc.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/e3e16eb63b584378b75d9eb01dc2f8b9/bids/c41709780fa8434399c62a1facf369cb/financial_documents/7519d21b32af432396acd6e2c9e18ee5?download=5db59ae05361427b84c4caddb0b6d92b", + "format": "application/pdf", + "documentOf": "tender", + "datePublished": "2016-02-24T14:13:36.625252+02:00", + "id": "7519d21b32af432396acd6e2c9e18ee5", + "dateModified": "2016-02-24T14:13:36.625290+02:00" + } + ], + "eligibility_documents": [ + { + "confidentiality": "public", + "language": "uk", + "title": "eligibility_doc.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/e3e16eb63b584378b75d9eb01dc2f8b9/bids/c41709780fa8434399c62a1facf369cb/financial_documents/7519d21b32af432396acd6e2c9e18ee5?download=5db59ae05361427b84c4caddb0b6d92b", + "format": "application/pdf", + "documentOf": "tender", + "datePublished": "2016-02-24T14:13:36.625252+02:00", + "id": "7519d21b32af432396acd6e2c9e18ee5", + "dateModified": "2016-02-24T14:13:36.625290+02:00" + } + ], + "id": "f7fc1212f9f140bba5c4e3cd4f2b62d9", + "lotValues": [ + { + "value": {"currency": "UAH", "amount": 7750.0, "valueAddedTaxIncluded": true}, + "reatedLot": "7d774fbf1e86420484c7d1a005cc283f", + "date": "2015-11-01T12:43:12.482645+02:00" + }, { + "value": {"currency": "UAH", "amount": 8125.0, "valueAddedTaxIncluded": true}, + "reatedLot": "563ef5d999f34d36a5a0e4e4d91d7be1", + "date": "2015-11-01T12:43:12.482645+02:00" + } + ], + "tenderers": [ + { + "address": { + "countryName": "Україна", + "locality": "м. Вінниця", + "postalCode": "21100", + "region": "м. Вінниця", + "streetAddress": "вул. Островського, 33" + }, + "contactPoint": { + "email": "soleksuk@gmail.com", + "name": "Сергій Олексюк", + "telephone": "+380 (432) 21-69-30" + }, + "identifier": { + "id": "13313462", + "legalName": "Державне комунальне підприємство громадського харчування «Школяр»", + "scheme": "UA-EDR", + "uri": "http://sch10.edu.vn.ua/" + }, + "name": "ДКП «Школяр»" + } + ] + }, + { + "date": "2014-12-16T04:44:27.976478+02:00", + "id": "7ec725815ef448a9b857129024395638", + "lotValues": [ + { + "value": {"currency": "UAH", "amount": 7750.0, "valueAddedTaxIncluded": true}, + "reatedLot": "7d774fbf1e86420484c7d1a005cc283f", + "date": "2015-11-01T12:43:12.482645+02:00" + }, { + "value": {"currency": "UAH", "amount": 8125.0, "valueAddedTaxIncluded": true}, + "reatedLot": "563ef5d999f34d36a5a0e4e4d91d7be1", + "date": "2015-11-01T12:43:12.482645+02:00" + } + ], + "tenderers": [ + { + "address": { + "countryName": "Україна", + "locality": "м. Вінниця", + "postalCode": "21018", + "region": "м. Вінниця", + "streetAddress": "вул. Юності, 30" + }, + "contactPoint": { + "email": "alla.myhailova@i.ua", + "name": "Алла Михайлова", + "telephone": "+380 (432) 460-665" + }, + "identifier": { + "id": "13306232", + "legalName": "Державне комунальне підприємство громадського харчування «Меридіан»", + "scheme": "UA-EDR", + "uri": "http://sch10.edu.vn.ua/" + }, + "name": "ДКП «Меридіан2»" + } + ] + } + + ], + "awards":[ + { + "status":"active", + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.9/tenders/3216137f17fb452db202daad1f9e73ad/awards/7054491a5e514699a56e44d32e23edf7/documents/c7ac894e4ca34e618e03a02d8799dc69?download=8c4053b0942949f4bd6e998a51e177c5", + "title":"2.jpg", + "datePublished":"2015-11-13T16:31:03.691781+02:00", + "dateModified":"2015-11-13T16:31:03.691829+02:00", + "id":"c7ac894e4ca34e618e03a02d8799dc69" + } + ], + "complaintPeriod":{ + "startDate":"2015-11-13T16:17:00.375880+02:00", + "endDate":"2015-11-14T16:31:06.979761+02:00" + }, + "contracts":[ + { + "status":"active", + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.9/tenders/3216137f17fb452db202daad1f9e73ad/awards/7054491a5e514699a56e44d32e23edf7/contracts/045c478807c04215950b4d74e6861cb1/documents/14dbf158b9ed4a478a59d19c90dbdf5d?download=9d948173e65441409f06e0b81674b39a", + "title":"3.jpg", + "datePublished":"2015-11-13T19:01:16.748785+02:00", + "dateModified":"2015-11-13T19:01:16.748829+02:00", + "id":"14dbf158b9ed4a478a59d19c90dbdf5d" + } + ], + "awardID":"7054491a5e514699a56e44d32e23edf7", + "id":"045c478807c04215950b4d74e6861cb1" + } + ], + "suppliers":[ + { + "contactPoint":{ + "telephone":"", + "name":"some6", + "email":"some6@mailinator.com" + }, + "identifier":{ + "scheme":"UA-EDR", + "id":"62123463", + "legalName":"some6" + }, + "name":"some6", + "address":{ + "postalCode":"04119", + "countryName":"\u0423\u043a\u0440\u0430\u0438\u043d\u0430", + "streetAddress":"", + "region":"\u0410\u0420 \u041a\u0440\u044b\u043c", + "locality":"ffff" + } + } + ], + "bid_id":"ad9ac917ff0049178acc3613bb14a691", + "value":{ + "currency":"UAH", + "amount":300.0, + "valueAddedTaxIncluded":false + }, + "date":"2015-11-13T16:17:00.376251+02:00", + "id":"7054491a5e514699a56e44d32e23edf7", + "lotId": "563ef5d999f34d36a5a0e4e4d91d7be1" + }, + { + "status":"active", + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.9/tenders/3216137f17fb452db202daad1f9e73ad/awards/7054491a5e514699a56e44d32e23edf7/documents/c7ac894e4ca34e618e03a02d8799dc69?download=8c4053b0942949f4bd6e998a51e177c5", + "title":"2.jpg", + "datePublished":"2015-11-13T16:31:03.691781+02:00", + "dateModified":"2015-11-13T16:31:03.691829+02:00", + "id":"c7ac894e4ca34e618e03a02d8799dc69" + } + ], + "complaintPeriod":{ + "startDate":"2015-11-13T16:17:00.375880+02:00", + "endDate":"2015-11-14T16:31:06.979761+02:00" + }, + "contracts":[ + { + "status":"active", + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.9/tenders/3216137f17fb452db202daad1f9e73ad/awards/7054491a5e514699a56e44d32e23edf7/contracts/045c478807c04215950b4d74e6861cb1/documents/14dbf158b9ed4a478a59d19c90dbdf5d?download=9d948173e65441409f06e0b81674b39a", + "title":"3.jpg", + "datePublished":"2015-11-13T19:01:16.748785+02:00", + "dateModified":"2015-11-13T19:01:16.748829+02:00", + "id":"14dbf158b9ed4a478a59d19c90dbdf5d" + } + ], + "awardID":"7054491a5e514699a56e44d32e23edf7", + "id":"045c478807c04215950b4d74e6861cb1" + } + ], + "suppliers":[ + { + "contactPoint":{ + "telephone":"", + "name":"some6", + "email":"some6@mailinator.com" + }, + "identifier":{ + "scheme":"UA-EDR", + "id":"62123463", + "legalName":"some6" + }, + "name":"some6", + "address":{ + "postalCode":"04119", + "countryName":"\u0423\u043a\u0440\u0430\u0438\u043d\u0430", + "streetAddress":"", + "region":"\u0410\u0420 \u041a\u0440\u044b\u043c", + "locality":"ffff" + } + } + ], + "bid_id":"ad9ac917ff0049178acc3613bb14a691", + "value":{ + "currency":"UAH", + "amount":300.0, + "valueAddedTaxIncluded":false + }, + "date":"2015-11-13T16:17:00.376251+02:00", + "id":"7054491a5e514699a56e44d32e23edf7", + "lotId": "7d774fbf1e86420484c7d1a005cc283f" + } + ], + "dateModified":"2015-12-12T03:06:54.370277+02:00", + "awardCriteria":"lowestCost" + } +} diff --git a/openprocurement_client/tests/data/assets.json b/openprocurement_client/tests/data/assets.json new file mode 100644 index 0000000..384ef8a --- /dev/null +++ b/openprocurement_client/tests/data/assets.json @@ -0,0 +1,153 @@ +{ + "next_page":{ + "path":"/api/0.10/tenders?offset=2015-12-25T18%3A04%3A36.264176%2B02%3A00", + "uri":"https://lb.api-sandbox.openprocurement.org/api/0.10/tenders?offset=2015-12-25T18%3A04%3A36.264176%2B02%3A00", + "offset":"2015-12-25T18:04:36.264176+02:00" + }, + "data":[ + { + "id":"823d50b3236247adad28a5a66f74db42", + "dateModified":"2015-11-13T18:50:00.753811+02:00" + }, + { + "id":"f3849ade33534174b8402579152a5f41", + "dateModified":"2015-11-16T01:15:00.469896+02:00" + }, + { + "id":"f3849ade33534174b8402579152a5f41", + "dateModified":"2015-11-16T12:00:00.960077+02:00" + }, + { + "id":"3be3664065994d1e8847beb597c014bf", + "dateModified":"2015-11-16T21:15:00.404214+02:00" + }, + { + "id":"711cfa82c54147b686f6755adb22364d", + "dateModified":"2015-11-17T18:02:00.456050+02:00" + }, + { + "id":"730fe73cb7be4f92bce472484236e9ee", + "dateModified":"2015-11-18T18:30:00.410770+02:00" + }, + { + "id":"496ec128323f45679daf9ebcb1ec62f6", + "dateModified":"2015-11-20T16:20:37.977312+02:00" + }, + { + "id":"18d566bed8cc4f2e846ca5d64c6bdfed", + "dateModified":"2015-12-01T11:48:33.784585+02:00" + }, + { + "id":"21733c5ed0be4b259c3345310f425a82", + "dateModified":"2015-12-02T13:54:13.656648+02:00" + }, + { + "id":"b35a1151b7a84562addb47f66193a309", + "dateModified":"2015-12-12T02:45:36.355426+02:00" + }, + { + "id":"ce9751da26a84c2390368ffa5adfd74c", + "dateModified":"2015-12-12T03:04:16.059667+02:00" + }, + { + "id":"823d50b3236247adad28a5a66f74db42", + "dateModified":"2015-12-12T03:06:54.370277+02:00" + }, + { + "id":"0085cf76ad444ad0ae625fc17f366dd2", + "dateModified":"2015-12-12T14:30:38.596246+02:00" + }, + { + "id":"d97613278dd547f2be5523cdc5614880", + "dateModified":"2015-12-12T14:31:51.342309+02:00" + }, + { + "id":"71161344890c4231b1487296f56aa910", + "dateModified":"2015-12-14T13:10:38.771625+02:00" + }, + { + "id":"759a81e58cf04b5ea0587942d0925faa", + "dateModified":"2015-12-16T19:56:53.874070+02:00" + }, + { + "id":"ad681d050d0e42169a80b86d5b591c20", + "dateModified":"2015-12-22T13:14:25.253453+02:00" + }, + { + "id":"a2e4464a08d64addb6c7a63da48ddb87", + "dateModified":"2015-12-23T17:09:15.145867+02:00" + }, + { + "id":"eea0c2f3cf3f4206b8cbba813b9a527b", + "dateModified":"2015-12-23T17:15:23.479628+02:00" + }, + { + "id":"847411c7b03f4f91b97fecb70c6e6275", + "dateModified":"2015-12-23T17:54:39.470396+02:00" + }, + { + "id":"599e63e6fb1349078fb01e4f9e8a812c", + "dateModified":"2015-12-23T17:55:36.317529+02:00" + }, + { + "id":"d63a7302316c4d15b46aeb7bcd4f1dbe", + "dateModified":"2015-12-23T17:56:01.709307+02:00" + }, + { + "id":"6496299a3c8e482f9017d855483a97b5", + "dateModified":"2015-12-23T17:57:57.376623+02:00" + }, + { + "id":"a513848583cc4d1d806c78e5131ad068", + "dateModified":"2015-12-23T17:59:52.101031+02:00" + }, + { + "id":"79aa9fd2bb43471cbcb0b97f6965e5f6", + "dateModified":"2015-12-24T11:39:09.071184+02:00" + }, + { + "id":"b2cdcda417434c7b889c82a81927bb73", + "dateModified":"2015-12-24T11:40:20.172257+02:00" + }, + { + "id":"58eca935e0e24805b561a1588ae64ef3", + "dateModified":"2015-12-24T11:48:57.584694+02:00" + }, + { + "id":"78b889ec869544b9b47c8740549ed20a", + "dateModified":"2015-12-24T11:51:07.149689+02:00" + }, + { + "id":"258ef598759946dbb30e7ed67bfc2346", + "dateModified":"2015-12-24T11:52:16.196052+02:00" + }, + { + "id":"639ecaca9db24228a9fe0eb3494aa0e3", + "dateModified":"2015-12-25T13:28:22.136939+02:00" + }, + { + "id":"ca10599dfcbe416bb513416c13073c5f", + "dateModified":"2015-12-25T15:19:53.913160+02:00" + }, + { + "id":"21e551bf7f194d8aaba29bf2e4949b90", + "dateModified":"2015-12-25T16:00:00.319589+02:00" + }, + { + "id":"dd7b13a956b44a34bbaeb2bfa1910c03", + "dateModified":"2015-12-25T16:07:19.809837+02:00" + }, + { + "id":"f8f21e8b0c104747b8abf5ece4fb5d72", + "dateModified":"2015-12-25T17:06:32.885207+02:00" + }, + { + "id":"48dcb014d003454b8875975bb531147f", + "dateModified":"2015-12-25T17:16:05.208447+02:00" + }, + { + "id":"7f35a555d5c64b34a7cbc454fab5628c", + "dateModified":"2015-12-25T18:04:36.264176+02:00" + } + ] +} diff --git a/openprocurement_client/tests/data/lot_668c3156c8cb496fb28359909cde6e96.json b/openprocurement_client/tests/data/lot_668c3156c8cb496fb28359909cde6e96.json new file mode 100644 index 0000000..27e133b --- /dev/null +++ b/openprocurement_client/tests/data/lot_668c3156c8cb496fb28359909cde6e96.json @@ -0,0 +1,272 @@ +{ + "access": { + "token": "0fc58c83963d42a692db4987df86640a" + }, + "data": { + "procurementMethod": "limited", + "status": "cancelled", + "documents": [ + { + "format": "text/plain", + "url": "https://api-sandbox.openprocurement.org/api/0.12/tenders/668c3156c8cb496fb28359909cde6e96/documents/3f82c7ef80a745119b6a6dec2670e5bc?download=0fc58c83963d42a692db4987df86640a", + "title": "/tmp/tmp5k4QEL", + "documentOf": "tender", + "datePublished": "2016-02-18T14:45:40.348569+02:00", + "dateModified": "2016-02-18T14:45:40.348612+02:00", + "id": "3f82c7ef80a745119b6a6dec2670e5bc" + } + ], + "title": "\u041f\u043e\u0441\u043b\u0443\u0433\u0438 \u0448\u043a\u0456\u043b\u044c\u043d\u0438\u0445 \u0457\u0434\u0430\u043b\u0435\u043d\u044c", + "contracts": [ + { + "status": "pending", + "awardID": "70eaaceee81e4f378e333c1ed8258652", + "id": "c65d3526cdc44f75b2457b4489970a7c" + } + ], + "items": [ + { + "description": "\u041f\u043e\u0441\u043b\u0443\u0433\u0438 \u0448\u043a\u0456\u043b\u044c\u043d\u0438\u0445 \u0457\u0434\u0430\u043b\u0435\u043d\u044c", + "classification": { + "scheme": "CPV", + "description": "\u041f\u043e\u0441\u043b\u0443\u0433\u0438 \u0437 \u0445\u0430\u0440\u0447\u0443\u0432\u0430\u043d\u043d\u044f \u0443 \u0448\u043a\u043e\u043b\u0430\u0445", + "id": "55523100-3" + }, + "additionalClassifications": [ + { + "scheme": "\u0414\u041a\u041f\u041f", + "id": "55.51.10.300", + "description": "\u041f\u043e\u0441\u043b\u0443\u0433\u0438 \u0448\u043a\u0456\u043b\u044c\u043d\u0438\u0445 \u0457\u0434\u0430\u043b\u0435\u043d\u044c" + } + ], + "deliveryLocation": { + "latitude": 49.85, + "longitude": 24.0167 + }, + "deliveryAddress": { + "locality": "\u043c. \u041a\u0438\u0457\u0432", + "region": "\u043c. \u041a\u0438\u0457\u0432", + "countryName_en": "Ukraine", + "countryName": "\u0423\u043a\u0440\u0430\u0457\u043d\u0430", + "streetAddress": "481 John Pine Suite 803", + "countryName_ru": "\u0423\u043a\u0440\u0430\u0438\u043d\u0430", + "postalCode": "58662" + }, + "deliveryDate": { + "endDate": "2016-02-23T14:42:28.297330+02:00" + }, + "id": "2dc54675d6364e2baffbc0f8e74432ac", + "unit": { + "code": "MON", + "name": "month" + }, + "quantity": 9 + } + ], + "complaints": [ + { + "status": "resolved", + "documents": [ + { + "author": "complaint_owner", + "format": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "url": "https://lb.api-sandbox.openprocurement.org/api/0.12/tenders/cdec9f009be44f53817754b12d292ef2/complaints/22b086222a7a4bc6a9cc8adeaa91a57f/documents/129f4013b33a45b8bc70699a62a81499?download=7a6787929a624dd8b7c067974b9e8a96", + "title": "/tmp/tmpcFrLvz.docx", + "documentOf": "tender", + "datePublished": "2016-02-29T15:05:54.557614+02:00", + "dateModified": "2016-02-29T15:05:54.557656+02:00", + "id": "129f4013b33a45b8bc70699a62a81499" + } + ], + "description": "Consequuntur sint cupiditate impedit et magnam aut odit eligendi dicta magnam dicta eveniet.", + "tendererActionDate": "2016-02-29T15:06:05.212783+02:00", + "title": "Hic laboriosam magni cupiditate enim placeat in dolorem.", + "resolutionType": "resolved", + "resolution": "Non ipsa accusantium iste et dolorum quo temporibus deserunt et provident suscipit.", + "satisfied": true, + "dateAnswered": "2016-02-29T15:06:05.212741+02:00", + "tendererAction": "Labore adipisci sequi corporis neque delectus ea aut dicta molestiae omnis voluptatem qui et.", + "dateSubmitted": "2016-02-29T15:06:01.956017+02:00", + "date": "2016-02-29T15:05:53.084688+02:00", + "type": "claim", + "id": "22b086222a7a4bc6a9cc8adeaa91a57f" + }, + { + "status": "cancelled", + "description": "Sit cumque iste eius corporis exercitationem voluptate.", + "title": "Commodi voluptatem earum sapiente doloremque.", + "cancellationReason": "prosto tak :)", + "dateCanceled": "2016-02-29T15:06:19.281225+02:00", + "date": "2016-02-29T15:06:17.694889+02:00", + "type": "claim", + "id": "d26fc2f475514652a53c0c551d984be3" + }, + { + "status": "cancelled", + "description": "Eos officia vel aliquid id qui provident suscipit eos nam amet.", + "title": "Voluptas debitis cupiditate deserunt inventore fugit quo nemo dicta.", + "cancellationReason": "prosto tak :)", + "dateCanceled": "2016-02-29T15:06:28.419955+02:00", + "dateSubmitted": "2016-02-29T15:06:26.927454+02:00", + "date": "2016-02-29T15:06:24.962751+02:00", + "type": "claim", + "id": "816eb0529d6d41148dac0edabc119407" + }, + { + "status": "cancelled", + "dateCanceled": "2016-02-29T15:06:37.390093+02:00", + "tendererActionDate": "2016-02-29T15:06:35.767209+02:00", + "description": "Non voluptas ipsa ex quibusdam dolorem rem dolores sequi.", + "title": "Et esse eos unde molestiae cum sunt.", + "resolutionType": "resolved", + "resolution": "Cumque magni vitae officiis odit et mollitia neque dolores reprehenderit.", + "cancellationReason": "prosto tak :)", + "dateAnswered": "2016-02-29T15:06:35.767171+02:00", + "tendererAction": "Architecto dolor dolorem ut labore esse possimus odit a unde hic quas error alias nulla.", + "dateSubmitted": "2016-02-29T15:06:34.170608+02:00", + "date": "2016-02-29T15:06:32.488624+02:00", + "type": "claim", + "id": "ec053962ce334bdaaab8b3bf2e6b3173" + }, + { + "status": "cancelled", + "dateCanceled": "2016-02-29T15:06:55.181473+02:00", + "tendererActionDate": "2016-02-29T15:06:45.067477+02:00", + "description": "Et autem reiciendis eaque dolorem adipisci aut.", + "title": "Ipsum cupiditate ipsam ut non.", + "resolutionType": "resolved", + "resolution": "Qui vitae placeat rerum officia quia aperiam illo nisi velit et fuga ducimus dolor.", + "author": { + "contactPoint": { + "name": "Варвара Талан", + "telephone": "+38 (268) 501-16-45" + }, + "identifier": { + "scheme": "UA-EDR", + "id": "00007460", + "uri": "http://dummyimage.com/506x880" + }, + "name": "Терещук-Приймак", + "address": { + "locality": "м. Вінниця", + "region": "Вінницька область", + "countryName_en": "Ukraine", + "countryName": "Україна", + "streetAddress": "73741 John Pass", + "countryName_ru": "Украина", + "postalCode": "21100" + } + }, + "satisfied": false, + "dateEscalated": "2016-02-29T15:06:46.828487+02:00", + "dateAnswered": "2016-02-29T15:06:45.067440+02:00", + "tendererAction": "Ex optio cumque assumenda iure consectetur unde iste et consequatur at quaerat dolor.", + "dateSubmitted": "2016-02-29T15:06:43.326331+02:00", + "date": "2016-02-29T15:06:41.513902+02:00", + "cancellationReason": "prosto tak :)", + "type": "complaint", + "id": "76f0f99cb643434da21b6a6350cc2d0e" + } + ], + "procurementMethodType": "reporting", + "cancellations": [ + { + "status": "active", + "documents": [ + { + "description": "test description", + "format": "text/plain", + "url": "https://api-sandbox.openprocurement.org/api/0.12/tenders/668c3156c8cb496fb28359909cde6e96/cancellations/0dd6f9e8cc4f4d1c9c404d842b56d0d7/documents/1afca9faaf2b4f9489ee264b136371c6?download=57e135c2f8d342c3a506be20940a3f3c", + "title": "/tmp/tmpdhdoax", + "documentOf": "tender", + "datePublished": "2016-02-18T14:46:05.348204+02:00", + "dateModified": "2016-02-18T14:46:05.348246+02:00", + "id": "1afca9faaf2b4f9489ee264b136371c6" + }, + { + "description": "test description", + "format": "text/plain", + "url": "https://api-sandbox.openprocurement.org/api/0.12/tenders/668c3156c8cb496fb28359909cde6e96/cancellations/0dd6f9e8cc4f4d1c9c404d842b56d0d7/documents/1afca9faaf2b4f9489ee264b136371c6?download=9208a5fa7cf140a2bb4751a42a1f1ea9", + "title": "/tmp/tmpOBUlWP", + "documentOf": "tender", + "datePublished": "2016-02-18T14:46:05.348204+02:00", + "dateModified": "2016-02-18T14:46:08.516130+02:00", + "id": "1afca9faaf2b4f9489ee264b136371c6" + } + ], + "reason": "prost :))", + "date": "2016-02-18T14:46:04.344125+02:00", + "cancellationOf": "tender", + "id": "0dd6f9e8cc4f4d1c9c404d842b56d0d7" + } + ], + "value": { + "currency": "UAH", + "amount": 500000.0, + "valueAddedTaxIncluded": true + }, + "id": "668c3156c8cb496fb28359909cde6e96", + "awards": [ + { + "status": "active", + "complaintPeriod": { + "startDate": "2016-02-18T14:45:41.212240+02:00", + "endDate": "2016-02-19T14:45:42.178932+02:00" + }, + "suppliers": [ + { + "contactPoint": { + "telephone": "+380 (432) 21-69-30", + "name": "\u0421\u0435\u0440\u0433\u0456\u0439 \u041e\u043b\u0435\u043a\u0441\u044e\u043a", + "email": "soleksuk@gmail.com" + }, + "identifier": { + "scheme": "UA-EDR", + "id": "13313462", + "uri": "http://sch10.edu.vn.ua/", + "legalName": "\u0414\u0435\u0440\u0436\u0430\u0432\u043d\u0435 \u043a\u043e\u043c\u0443\u043d\u0430\u043b\u044c\u043d\u0435 \u043f\u0456\u0434\u043f\u0440\u0438\u0454\u043c\u0441\u0442\u0432\u043e \u0433\u0440\u043e\u043c\u0430\u0434\u0441\u044c\u043a\u043e\u0433\u043e \u0445\u0430\u0440\u0447\u0443\u0432\u0430\u043d\u043d\u044f \u00ab\u0428\u043a\u043e\u043b\u044f\u0440\u00bb" + }, + "name": "\u0414\u041a\u041f \u00ab\u0428\u043a\u043e\u043b\u044f\u0440\u00bb", + "address": { + "postalCode": "21100", + "countryName": "\u0423\u043a\u0440\u0430\u0457\u043d\u0430", + "streetAddress": "\u0432\u0443\u043b. \u041e\u0441\u0442\u0440\u043e\u0432\u0441\u044c\u043a\u043e\u0433\u043e, 33", + "region": "\u043c. \u0412\u0456\u043d\u043d\u0438\u0446\u044f", + "locality": "\u043c. \u0412\u0456\u043d\u043d\u0438\u0446\u044f" + } + } + ], + "value": { + "currency": "UAH", + "amount": 475000.0, + "valueAddedTaxIncluded": true + }, + "date": "2016-02-18T14:45:41.210928+02:00", + "id": "70eaaceee81e4f378e333c1ed8258652" + } + ], + "tenderID": "UA-2016-02-18-000031", + "owner": "test.quintagroup.com", + "dateModified": "2016-02-18T14:46:09.940361+02:00", + "procuringEntity": { + "contactPoint": { + "url": "http://sch10.edu.vn.ua/", + "name": "\u041a\u0443\u0446\u0430 \u0421\u0432\u0456\u0442\u043b\u0430\u043d\u0430 \u0412\u0430\u043b\u0435\u043d\u0442\u0438\u043d\u0456\u0432\u043d\u0430", + "telephone": "+380 (432) 46-53-02" + }, + "identifier": { + "scheme": "UA-EDR", + "id": "21725150", + "legalName": "\u0417\u0430\u043a\u043b\u0430\u0434 \"\u0417\u0430\u0433\u0430\u043b\u044c\u043d\u043e\u043e\u0441\u0432\u0456\u0442\u043d\u044f \u0448\u043a\u043e\u043b\u0430 \u0406-\u0406\u0406\u0406 \u0441\u0442\u0443\u043f\u0435\u043d\u0456\u0432 \u2116 10 \u0412\u0456\u043d\u043d\u0438\u0446\u044c\u043a\u043e\u0457 \u043c\u0456\u0441\u044c\u043a\u043e\u0457 \u0440\u0430\u0434\u0438\"" + }, + "name": "\u0417\u041e\u0421\u0428 #10 \u043c.\u0412\u0456\u043d\u043d\u0438\u0446\u0456", + "address": { + "postalCode": "21027", + "countryName": "\u0423\u043a\u0440\u0430\u0457\u043d\u0430", + "streetAddress": "\u0432\u0443\u043b. \u0421\u0442\u0430\u0445\u0443\u0440\u0441\u044c\u043a\u043e\u0433\u043e. 22", + "region": "\u043c. \u0412\u0456\u043d\u043d\u0438\u0446\u044f", + "locality": "\u043c. \u0412\u0456\u043d\u043d\u0438\u0446\u044f" + } + } + } +} diff --git a/openprocurement_client/tests/data/lot_823d50b3236247adad28a5a66f74db42.json b/openprocurement_client/tests/data/lot_823d50b3236247adad28a5a66f74db42.json new file mode 100644 index 0000000..27b7f37 --- /dev/null +++ b/openprocurement_client/tests/data/lot_823d50b3236247adad28a5a66f74db42.json @@ -0,0 +1,504 @@ +{ + "data":{ + "procurementMethod":"open", + "status":"complete", + "tenderPeriod":{ + "startDate":"2015-12-30T03:05:00+02:00", + "endDate":"2015-12-31T03:05:00+02:00" + }, + "qualifications": [ + { + "status": "pending", + "id": "cec4b82d2708465291fb4af79f8a3e52", + "bidID": "c41709780fa8434399c62a1facf369cb", + "documents": [ + { + "confidentiality": "public", + "language": "uk", + "title": "Proposal.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/e3e16eb63b584378b75d9eb01dc2f8b9/bids/c41709780fa8434399c62a1facf369cb/documents/a05aff4e91c942bbad7e49e551399ec7?download=d0984048f149465699fd402bfe20881c", + "format": "application/pdf", + "documentOf": "tender", + "datePublished": "2016-02-24T14:13:36.127526+02:00", + "id": "a05aff4e91c942bbad7e49e551399ec7", + "dateModified": "2016-02-24T14:13:36.127580+02:00" + }] + }, + { + "status": "pending", + "id": "9d562bbbf05e44f8a6a1f07ddb0415b1", + "bidID": "dda102997a4d4d5d8b5dac6ce2225e1c" + }, + { + "status": "pending", + "id": "36ac59607c434c8198730cc372672c8c", + "bidID": "6e0d218c8e964b42ac4a8ca086c100f9" + } + ], + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.10/tenders/823d50b3236247adad28a5a66f74db42/documents/330822cbbd724671a1d2ff7c3a51dd52?download=5ff4917f29954e5f8dfea1feed8f7455", + "title":"test1.txt", + "documentOf":"tender", + "datePublished":"2015-12-12T03:06:53.209628+02:00", + "dateModified":"2015-12-12T03:06:53.209670+02:00", + "id":"330822cbbd724671a1d2ff7c3a51dd52" + }, + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.10/tenders/823d50b3236247adad28a5a66f74db42/documents/6badc6f313d2499794bd39a3a7182fc0?download=ab2d7cdd346342a6a0d7b40af39145e2", + "title":"test3.txt", + "documentOf":"tender", + "datePublished":"2015-12-12T03:06:53.724784+02:00", + "dateModified":"2015-12-12T03:06:53.724826+02:00", + "id":"6badc6f313d2499794bd39a3a7182fc0" + }, + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.10/tenders/823d50b3236247adad28a5a66f74db42/documents/08bcd56de9aa43faa684f8f8e7ab1c98?download=c7afbf3686cc441db7735d5ec284a0de", + "title":"test2.txt", + "documentOf":"tender", + "datePublished":"2015-12-12T03:06:54.280311+02:00", + "dateModified":"2015-12-12T03:06:54.280354+02:00", + "id":"08bcd56de9aa43faa684f8f8e7ab1c98" + } + ], + "description":"Multilot 4 descr", + "title":"Multilot 4 title", + "submissionMethodDetails":"quick", + "items":[ + { + "relatedLot":"7d774fbf1e86420484c7d1a005cc283f", + "description":"itelm descr", + "classification":{ + "scheme":"CPV", + "description":"\u041f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f \u0441\u0456\u043b\u044c\u0441\u044c\u043a\u043e\u0433\u043e \u0433\u043e\u0441\u043f\u043e\u0434\u0430\u0440\u0441\u0442\u0432\u0430, \u0444\u0435\u0440\u043c\u0435\u0440\u0441\u0442\u0432\u0430, \u0440\u0438\u0431\u0430\u043b\u044c\u0441\u0442\u0432\u0430, \u043b\u0456\u0441\u043d\u0438\u0446\u0442\u0432\u0430 \u0442\u0430 \u0441\u0443\u043c\u0456\u0436\u043d\u0430 \u043f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f", + "id":"03000000-1" + }, + "additionalClassifications":[ + { + "scheme":"\u0414\u041a\u041f\u041f", + "id":"01", + "description":"\u041f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f \u0441\u0456\u043b\u044c\u0441\u044c\u043a\u043e\u0433\u043e \u0433\u043e\u0441\u043f\u043e\u0434\u0430\u0440\u0441\u0442\u0432\u0430, \u043c\u0438\u0441\u043b\u0438\u0432\u0441\u0442\u0432\u0430 \u0442\u0430 \u043f\u043e\u0432\u2019\u044f\u0437\u0430\u043d\u0456 \u0437 \u0446\u0438\u043c \u043f\u043e\u0441\u043b\u0443\u0433\u0438" + } + ], + "deliveryDate":{ + "startDate":"2016-06-01T00:00:00+03:00" + }, + "id":"7caa3587fe9041da98f2744ef531d7ce", + "unit":{ + "code":"LO", + "name":"\u043b\u043e\u0442" + }, + "quantity":4 + }, + { + "relatedLot":"7d774fbf1e86420484c7d1a005cc283f", + "description":"itelm descr", + "classification":{ + "scheme":"CPV", + "description":"\u041f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f \u0441\u0456\u043b\u044c\u0441\u044c\u043a\u043e\u0433\u043e \u0433\u043e\u0441\u043f\u043e\u0434\u0430\u0440\u0441\u0442\u0432\u0430, \u0444\u0435\u0440\u043c\u0435\u0440\u0441\u0442\u0432\u0430, \u0440\u0438\u0431\u0430\u043b\u044c\u0441\u0442\u0432\u0430, \u043b\u0456\u0441\u043d\u0438\u0446\u0442\u0432\u0430 \u0442\u0430 \u0441\u0443\u043c\u0456\u0436\u043d\u0430 \u043f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f", + "id":"03000000-1" + }, + "additionalClassifications":[ + { + "scheme":"\u0414\u041a\u041f\u041f", + "id":"01", + "description":"\u041f\u0440\u043e\u0434\u0443\u043a\u0446\u0456\u044f \u0441\u0456\u043b\u044c\u0441\u044c\u043a\u043e\u0433\u043e \u0433\u043e\u0441\u043f\u043e\u0434\u0430\u0440\u0441\u0442\u0432\u0430, \u043c\u0438\u0441\u043b\u0438\u0432\u0441\u0442\u0432\u0430 \u0442\u0430 \u043f\u043e\u0432\u2019\u044f\u0437\u0430\u043d\u0456 \u0437 \u0446\u0438\u043c \u043f\u043e\u0441\u043b\u0443\u0433\u0438" + } + ], + "deliveryDate":{ + "startDate":"2016-06-01T00:00:00+03:00" + }, + "id":"563ef5d999f34d36a5a0e4e4d91d7be1", + "unit":{ + "code":"LO", + "name":"\u043b\u043e\u0442" + }, + "quantity":4 + } + ], + "id":"823d50b3236247adad28a5a66f74db42", + "value":{ + "currency":"UAH", + "amount":1000.0, + "valueAddedTaxIncluded":false + }, + "submissionMethod":"electronicAuction", + "procuringEntity":{ + "contactPoint":{ + "telephone":"", + "name":"Dmitry", + "email":"d.karnaukh+merchant1@smartweb.com.ua" + }, + "identifier":{ + "scheme":"UA-EDR", + "id":"66666692", + "legalName":"\u041d\u0410\u041a \"Testov Test\"" + }, + "name":"\u041d\u0410\u041a \"Testov Test\"", + "address":{ + "postalCode":"02121", + "countryName":"\u0423\u043a\u0440\u0430\u0438\u043d\u0430", + "streetAddress":"", + "region":"\u041a\u0438\u0435\u0432\u0441\u043a\u0430\u044f \u043e\u0431\u043b\u0430\u0441\u0442\u044c", + "locality":"\u041a\u0438\u0435\u0432" + } + }, + "minimalStep":{ + "currency":"UAH", + "amount":1.0, + "valueAddedTaxIncluded":false + }, + "tenderID":"UA-2015-12-12-000003-1", + "questions":[ + { + "description":"Test lot question descr", + "title":"Test lot question title", + "relatedItem":"563ef5d999f34d36a5a0e4e4d91d7be1", + "answer":"Test answer", + "date":"2015-12-21T17:31:19.650440+02:00", + "id":"615ff8be8eba4a81b300036d6bec991c", + "questionOf":"lot" + }, + { + "description":"lot 2 question descr?", + "title":"lot 2 question title", + "relatedItem":"563ef5d999f34d36a5a0e4e4d91d7be1", + "answer":"lot 2 answer", + "date":"2015-12-21T17:36:30.680576+02:00", + "id":"278f269ee482434da6615a21f3deccf0", + "questionOf":"lot" + }, + { + "description":"lot 3 q descr?", + "title":"lot 3 q title", + "relatedItem":"563ef5d999f34d36a5a0e4e4d91d7be1", + "answer":"lot 3 answer", + "date":"2015-12-21T17:57:28.108596+02:00", + "id":"da6c1228f81e4c2e8451479d57b685dd", + "questionOf":"lot" + }, + { + "description":"Tender q 1 descr?", + "title":"Tender q 1 title", + "relatedItem":"ad681d050d0e42169a80b86d5b591c20", + "answer":"Tender q 1 answer", + "date":"2015-12-21T17:58:24.489846+02:00", + "id":"937b4b9ef3444782b9a3019fcc5c072a", + "questionOf":"tender" + }, + { + "description":"Item descr?", + "title":"Item title?", + "relatedItem":"7caa3587fe9041da98f2744ef531d7ce", + "answer":"Item answer", + "date":"2015-12-21T18:04:35.349936+02:00", + "id":"a2d14f4faf9b4ba180b40f08a2be3bd9", + "questionOf":"item" + } + ], + "enquiryPeriod":{ + "startDate":"2015-12-12T03:06:52.664385+02:00", + "endDate":"2015-12-30T03:05:00+02:00" + }, + "owner":"prom.ua", + "lots":[ + { + "status":"complete", + "description":"lot descr1", + "title":"lot title", + "minimalStep":{ + "currency":"UAH", + "amount":1.0, + "valueAddedTaxIncluded":false + }, + "value":{ + "currency":"UAH", + "amount":1000.0, + "valueAddedTaxIncluded":false + }, + "id":"7d774fbf1e86420484c7d1a005cc283f" + }, + { + "status":"active", + "description":"lot descr2", + "title":"lot title", + "minimalStep":{ + "currency":"UAH", + "amount":1.0, + "valueAddedTaxIncluded":false + }, + "value":{ + "currency":"UAH", + "amount":2000.0, + "valueAddedTaxIncluded":false + }, + "id":"563ef5d999f34d36a5a0e4e4d91d7be1" + } + ], + "bids":[ + { + "date": "2014-12-16T04:44:23.569815+02:00", + "documents": [ + { + "dateModified": "2014-12-16T04:44:25.010930+02:00", + "datePublished": "2014-12-16T04:44:25.010885+02:00", + "format": "text/plain", + "id": "ff001412c60c4164a0f57101e4eaf8aa", + "title": "Proposal.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0/tenders/6f73bf0f7f734f459f7e37e3787054a0/bids/f7fc1212f9f140bba5c4e3cd4f2b62d9/documents/ff001412c60c4164a0f57101e4eaf8aa?download=4f45bbd414104cd78faf620208efd824" + } + ], + "qualificationDocuments": [ + { + "confidentiality": "public", + "language": "uk", + "title": "qualification_document.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/e3e16eb63b584378b75d9eb01dc2f8b9/bids/c41709780fa8434399c62a1facf369cb/financial_documents/7519d21b32af432396acd6e2c9e18ee5?download=5db59ae05361427b84c4caddb0b6d92b", + "format": "application/pdf", + "documentOf": "tender", + "datePublished": "2016-02-24T14:13:36.625252+02:00", + "id": "7519d21b32af432396acd6e2c9e18ee5", + "dateModified": "2016-02-24T14:13:36.625290+02:00" + } + ], + "financial_documens": [ + { + "confidentiality": "public", + "language": "uk", + "title": "financial_doc.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/e3e16eb63b584378b75d9eb01dc2f8b9/bids/c41709780fa8434399c62a1facf369cb/financial_documents/7519d21b32af432396acd6e2c9e18ee5?download=5db59ae05361427b84c4caddb0b6d92b", + "format": "application/pdf", + "documentOf": "tender", + "datePublished": "2016-02-24T14:13:36.625252+02:00", + "id": "7519d21b32af432396acd6e2c9e18ee5", + "dateModified": "2016-02-24T14:13:36.625290+02:00" + } + ], + "eligibility_documents": [ + { + "confidentiality": "public", + "language": "uk", + "title": "eligibility_doc.pdf", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/e3e16eb63b584378b75d9eb01dc2f8b9/bids/c41709780fa8434399c62a1facf369cb/financial_documents/7519d21b32af432396acd6e2c9e18ee5?download=5db59ae05361427b84c4caddb0b6d92b", + "format": "application/pdf", + "documentOf": "tender", + "datePublished": "2016-02-24T14:13:36.625252+02:00", + "id": "7519d21b32af432396acd6e2c9e18ee5", + "dateModified": "2016-02-24T14:13:36.625290+02:00" + } + ], + "id": "f7fc1212f9f140bba5c4e3cd4f2b62d9", + "lotValues": [ + { + "value": {"currency": "UAH", "amount": 7750.0, "valueAddedTaxIncluded": true}, + "reatedLot": "7d774fbf1e86420484c7d1a005cc283f", + "date": "2015-11-01T12:43:12.482645+02:00" + }, { + "value": {"currency": "UAH", "amount": 8125.0, "valueAddedTaxIncluded": true}, + "reatedLot": "563ef5d999f34d36a5a0e4e4d91d7be1", + "date": "2015-11-01T12:43:12.482645+02:00" + } + ], + "tenderers": [ + { + "address": { + "countryName": "Україна", + "locality": "м. Вінниця", + "postalCode": "21100", + "region": "м. Вінниця", + "streetAddress": "вул. Островського, 33" + }, + "contactPoint": { + "email": "soleksuk@gmail.com", + "name": "Сергій Олексюк", + "telephone": "+380 (432) 21-69-30" + }, + "identifier": { + "id": "13313462", + "legalName": "Державне комунальне підприємство громадського харчування «Школяр»", + "scheme": "UA-EDR", + "uri": "http://sch10.edu.vn.ua/" + }, + "name": "ДКП «Школяр»" + } + ] + }, + { + "date": "2014-12-16T04:44:27.976478+02:00", + "id": "7ec725815ef448a9b857129024395638", + "lotValues": [ + { + "value": {"currency": "UAH", "amount": 7750.0, "valueAddedTaxIncluded": true}, + "reatedLot": "7d774fbf1e86420484c7d1a005cc283f", + "date": "2015-11-01T12:43:12.482645+02:00" + }, { + "value": {"currency": "UAH", "amount": 8125.0, "valueAddedTaxIncluded": true}, + "reatedLot": "563ef5d999f34d36a5a0e4e4d91d7be1", + "date": "2015-11-01T12:43:12.482645+02:00" + } + ], + "tenderers": [ + { + "address": { + "countryName": "Україна", + "locality": "м. Вінниця", + "postalCode": "21018", + "region": "м. Вінниця", + "streetAddress": "вул. Юності, 30" + }, + "contactPoint": { + "email": "alla.myhailova@i.ua", + "name": "Алла Михайлова", + "telephone": "+380 (432) 460-665" + }, + "identifier": { + "id": "13306232", + "legalName": "Державне комунальне підприємство громадського харчування «Меридіан»", + "scheme": "UA-EDR", + "uri": "http://sch10.edu.vn.ua/" + }, + "name": "ДКП «Меридіан2»" + } + ] + } + + ], + "awards":[ + { + "status":"active", + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.9/tenders/3216137f17fb452db202daad1f9e73ad/awards/7054491a5e514699a56e44d32e23edf7/documents/c7ac894e4ca34e618e03a02d8799dc69?download=8c4053b0942949f4bd6e998a51e177c5", + "title":"2.jpg", + "datePublished":"2015-11-13T16:31:03.691781+02:00", + "dateModified":"2015-11-13T16:31:03.691829+02:00", + "id":"c7ac894e4ca34e618e03a02d8799dc69" + } + ], + "complaintPeriod":{ + "startDate":"2015-11-13T16:17:00.375880+02:00", + "endDate":"2015-11-14T16:31:06.979761+02:00" + }, + "contracts":[ + { + "status":"active", + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.9/tenders/3216137f17fb452db202daad1f9e73ad/awards/7054491a5e514699a56e44d32e23edf7/contracts/045c478807c04215950b4d74e6861cb1/documents/14dbf158b9ed4a478a59d19c90dbdf5d?download=9d948173e65441409f06e0b81674b39a", + "title":"3.jpg", + "datePublished":"2015-11-13T19:01:16.748785+02:00", + "dateModified":"2015-11-13T19:01:16.748829+02:00", + "id":"14dbf158b9ed4a478a59d19c90dbdf5d" + } + ], + "awardID":"7054491a5e514699a56e44d32e23edf7", + "id":"045c478807c04215950b4d74e6861cb1" + } + ], + "suppliers":[ + { + "contactPoint":{ + "telephone":"", + "name":"some6", + "email":"some6@mailinator.com" + }, + "identifier":{ + "scheme":"UA-EDR", + "id":"62123463", + "legalName":"some6" + }, + "name":"some6", + "address":{ + "postalCode":"04119", + "countryName":"\u0423\u043a\u0440\u0430\u0438\u043d\u0430", + "streetAddress":"", + "region":"\u0410\u0420 \u041a\u0440\u044b\u043c", + "locality":"ffff" + } + } + ], + "bid_id":"ad9ac917ff0049178acc3613bb14a691", + "value":{ + "currency":"UAH", + "amount":300.0, + "valueAddedTaxIncluded":false + }, + "date":"2015-11-13T16:17:00.376251+02:00", + "id":"7054491a5e514699a56e44d32e23edf7", + "lotId": "563ef5d999f34d36a5a0e4e4d91d7be1" + }, + { + "status":"active", + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.9/tenders/3216137f17fb452db202daad1f9e73ad/awards/7054491a5e514699a56e44d32e23edf7/documents/c7ac894e4ca34e618e03a02d8799dc69?download=8c4053b0942949f4bd6e998a51e177c5", + "title":"2.jpg", + "datePublished":"2015-11-13T16:31:03.691781+02:00", + "dateModified":"2015-11-13T16:31:03.691829+02:00", + "id":"c7ac894e4ca34e618e03a02d8799dc69" + } + ], + "complaintPeriod":{ + "startDate":"2015-11-13T16:17:00.375880+02:00", + "endDate":"2015-11-14T16:31:06.979761+02:00" + }, + "contracts":[ + { + "status":"active", + "documents":[ + { + "format":"text/plain", + "url":"https://lb.api-sandbox.openprocurement.org/api/0.9/tenders/3216137f17fb452db202daad1f9e73ad/awards/7054491a5e514699a56e44d32e23edf7/contracts/045c478807c04215950b4d74e6861cb1/documents/14dbf158b9ed4a478a59d19c90dbdf5d?download=9d948173e65441409f06e0b81674b39a", + "title":"3.jpg", + "datePublished":"2015-11-13T19:01:16.748785+02:00", + "dateModified":"2015-11-13T19:01:16.748829+02:00", + "id":"14dbf158b9ed4a478a59d19c90dbdf5d" + } + ], + "awardID":"7054491a5e514699a56e44d32e23edf7", + "id":"045c478807c04215950b4d74e6861cb1" + } + ], + "suppliers":[ + { + "contactPoint":{ + "telephone":"", + "name":"some6", + "email":"some6@mailinator.com" + }, + "identifier":{ + "scheme":"UA-EDR", + "id":"62123463", + "legalName":"some6" + }, + "name":"some6", + "address":{ + "postalCode":"04119", + "countryName":"\u0423\u043a\u0440\u0430\u0438\u043d\u0430", + "streetAddress":"", + "region":"\u0410\u0420 \u041a\u0440\u044b\u043c", + "locality":"ffff" + } + } + ], + "bid_id":"ad9ac917ff0049178acc3613bb14a691", + "value":{ + "currency":"UAH", + "amount":300.0, + "valueAddedTaxIncluded":false + }, + "date":"2015-11-13T16:17:00.376251+02:00", + "id":"7054491a5e514699a56e44d32e23edf7", + "lotId": "7d774fbf1e86420484c7d1a005cc283f" + } + ], + "dateModified":"2015-12-12T03:06:54.370277+02:00", + "awardCriteria":"lowestCost" + } +} diff --git a/openprocurement_client/tests/data/lots.json b/openprocurement_client/tests/data/lots.json new file mode 100644 index 0000000..384ef8a --- /dev/null +++ b/openprocurement_client/tests/data/lots.json @@ -0,0 +1,153 @@ +{ + "next_page":{ + "path":"/api/0.10/tenders?offset=2015-12-25T18%3A04%3A36.264176%2B02%3A00", + "uri":"https://lb.api-sandbox.openprocurement.org/api/0.10/tenders?offset=2015-12-25T18%3A04%3A36.264176%2B02%3A00", + "offset":"2015-12-25T18:04:36.264176+02:00" + }, + "data":[ + { + "id":"823d50b3236247adad28a5a66f74db42", + "dateModified":"2015-11-13T18:50:00.753811+02:00" + }, + { + "id":"f3849ade33534174b8402579152a5f41", + "dateModified":"2015-11-16T01:15:00.469896+02:00" + }, + { + "id":"f3849ade33534174b8402579152a5f41", + "dateModified":"2015-11-16T12:00:00.960077+02:00" + }, + { + "id":"3be3664065994d1e8847beb597c014bf", + "dateModified":"2015-11-16T21:15:00.404214+02:00" + }, + { + "id":"711cfa82c54147b686f6755adb22364d", + "dateModified":"2015-11-17T18:02:00.456050+02:00" + }, + { + "id":"730fe73cb7be4f92bce472484236e9ee", + "dateModified":"2015-11-18T18:30:00.410770+02:00" + }, + { + "id":"496ec128323f45679daf9ebcb1ec62f6", + "dateModified":"2015-11-20T16:20:37.977312+02:00" + }, + { + "id":"18d566bed8cc4f2e846ca5d64c6bdfed", + "dateModified":"2015-12-01T11:48:33.784585+02:00" + }, + { + "id":"21733c5ed0be4b259c3345310f425a82", + "dateModified":"2015-12-02T13:54:13.656648+02:00" + }, + { + "id":"b35a1151b7a84562addb47f66193a309", + "dateModified":"2015-12-12T02:45:36.355426+02:00" + }, + { + "id":"ce9751da26a84c2390368ffa5adfd74c", + "dateModified":"2015-12-12T03:04:16.059667+02:00" + }, + { + "id":"823d50b3236247adad28a5a66f74db42", + "dateModified":"2015-12-12T03:06:54.370277+02:00" + }, + { + "id":"0085cf76ad444ad0ae625fc17f366dd2", + "dateModified":"2015-12-12T14:30:38.596246+02:00" + }, + { + "id":"d97613278dd547f2be5523cdc5614880", + "dateModified":"2015-12-12T14:31:51.342309+02:00" + }, + { + "id":"71161344890c4231b1487296f56aa910", + "dateModified":"2015-12-14T13:10:38.771625+02:00" + }, + { + "id":"759a81e58cf04b5ea0587942d0925faa", + "dateModified":"2015-12-16T19:56:53.874070+02:00" + }, + { + "id":"ad681d050d0e42169a80b86d5b591c20", + "dateModified":"2015-12-22T13:14:25.253453+02:00" + }, + { + "id":"a2e4464a08d64addb6c7a63da48ddb87", + "dateModified":"2015-12-23T17:09:15.145867+02:00" + }, + { + "id":"eea0c2f3cf3f4206b8cbba813b9a527b", + "dateModified":"2015-12-23T17:15:23.479628+02:00" + }, + { + "id":"847411c7b03f4f91b97fecb70c6e6275", + "dateModified":"2015-12-23T17:54:39.470396+02:00" + }, + { + "id":"599e63e6fb1349078fb01e4f9e8a812c", + "dateModified":"2015-12-23T17:55:36.317529+02:00" + }, + { + "id":"d63a7302316c4d15b46aeb7bcd4f1dbe", + "dateModified":"2015-12-23T17:56:01.709307+02:00" + }, + { + "id":"6496299a3c8e482f9017d855483a97b5", + "dateModified":"2015-12-23T17:57:57.376623+02:00" + }, + { + "id":"a513848583cc4d1d806c78e5131ad068", + "dateModified":"2015-12-23T17:59:52.101031+02:00" + }, + { + "id":"79aa9fd2bb43471cbcb0b97f6965e5f6", + "dateModified":"2015-12-24T11:39:09.071184+02:00" + }, + { + "id":"b2cdcda417434c7b889c82a81927bb73", + "dateModified":"2015-12-24T11:40:20.172257+02:00" + }, + { + "id":"58eca935e0e24805b561a1588ae64ef3", + "dateModified":"2015-12-24T11:48:57.584694+02:00" + }, + { + "id":"78b889ec869544b9b47c8740549ed20a", + "dateModified":"2015-12-24T11:51:07.149689+02:00" + }, + { + "id":"258ef598759946dbb30e7ed67bfc2346", + "dateModified":"2015-12-24T11:52:16.196052+02:00" + }, + { + "id":"639ecaca9db24228a9fe0eb3494aa0e3", + "dateModified":"2015-12-25T13:28:22.136939+02:00" + }, + { + "id":"ca10599dfcbe416bb513416c13073c5f", + "dateModified":"2015-12-25T15:19:53.913160+02:00" + }, + { + "id":"21e551bf7f194d8aaba29bf2e4949b90", + "dateModified":"2015-12-25T16:00:00.319589+02:00" + }, + { + "id":"dd7b13a956b44a34bbaeb2bfa1910c03", + "dateModified":"2015-12-25T16:07:19.809837+02:00" + }, + { + "id":"f8f21e8b0c104747b8abf5ece4fb5d72", + "dateModified":"2015-12-25T17:06:32.885207+02:00" + }, + { + "id":"48dcb014d003454b8875975bb531147f", + "dateModified":"2015-12-25T17:16:05.208447+02:00" + }, + { + "id":"7f35a555d5c64b34a7cbc454fab5628c", + "dateModified":"2015-12-25T18:04:36.264176+02:00" + } + ] +} diff --git a/openprocurement_client/tests/data_dict.py b/openprocurement_client/tests/data_dict.py index 2e2938f..f9efa4e 100644 --- a/openprocurement_client/tests/data_dict.py +++ b/openprocurement_client/tests/data_dict.py @@ -7,7 +7,7 @@ "question_id": '615ff8be8eba4a81b300036d6bec991c', "lot_id": '563ef5d999f34d36a5a0e4e4d91d7be1', "bid_id": 'f7fc1212f9f140bba5c4e3cd4f2b62d9', - "bid_document_id":"ff001412c60c4164a0f57101e4eaf8aa", + "bid_document_id": "ff001412c60c4164a0f57101e4eaf8aa", "bid_qualification_document_id": "7519d21b32af432396acd6e2c9e18ee5", "bid_financial_document_id": "7519d21b32af432396acd6e2c9e18ee5", "bid_eligibility_document_id": "7519d21b32af432396acd6e2c9e18ee5", @@ -45,3 +45,19 @@ "patch_change_rationale": u'Друга і третя поставка має бути розфасована', "new_token": 'new0contract0token123412341234' }) + +TEST_LOT_KEYS = munchify({ + "asset_id": '823d50b3236247adad28a5a66f74db42', + "lot_id": '823d50b3236247adad28a5a66f74db42', + "token": 'f6247c315d2744f1aa18c7c3de523bc3', + "new_token": '4fa3e89efbaa46f0a1b86b911bec76e7' + +}) + +TEST_ASSET_KEYS = munchify({ + "asset_id": '823d50b3236247adad28a5a66f74db42', + "lot_id": '823d50b3236247adad28a5a66f74db42', + "token": 'f6247c315d2744f1aa18c7c3de523bc3', + "new_token": '4fa3e89efbaa46f0a1b86b911bec76e7' + +}) diff --git a/openprocurement_client/tests/main.py b/openprocurement_client/tests/main.py index 17c9333..9655dd0 100644 --- a/openprocurement_client/tests/main.py +++ b/openprocurement_client/tests/main.py @@ -1,12 +1,17 @@ import unittest -from openprocurement_client.tests import tests, tests_sync +from openprocurement_client.tests import ( + tests, + tests_sync, + test_registry_client +) def suite(): suite = unittest.TestSuite() suite.addTest(tests.suite()) suite.addTest(tests_sync.suite()) + suite.addTest(test_registry_client.suite()) return suite diff --git a/openprocurement_client/tests/test_registry_client.py b/openprocurement_client/tests/test_registry_client.py new file mode 100644 index 0000000..27355e4 --- /dev/null +++ b/openprocurement_client/tests/test_registry_client.py @@ -0,0 +1,169 @@ +from __future__ import print_function +from gevent import monkey + +monkey.patch_all() +from gevent.pywsgi import WSGIServer +from bottle import Bottle +from StringIO import StringIO +from collections import Iterable +from simplejson import loads, load +from munch import munchify +import mock +import sys +import unittest +from openprocurement_client.registry_client import LotsClient, AssetsClient +from openprocurement_client.document_service_client \ + import DocumentServiceClient +from openprocurement_client.exceptions import InvalidResponse, ResourceNotFound +from openprocurement_client.tests.data_dict import ( + TEST_ASSET_KEYS, + TEST_LOT_KEYS, + TEST_TENDER_KEYS_LIMITED, + TEST_PLAN_KEYS, + TEST_CONTRACT_KEYS +) +from openprocurement_client.tests._server import \ + API_KEY, API_VERSION, AUTH_DS_FAKE, DS_HOST_URL, DS_PORT, \ + HOST_URL, location_error, PORT, ROOT, setup_routing, setup_routing_ds, \ + resource_partition, resource_filter + + +class BaseTestClass(unittest.TestCase): + def setting_up(self, client, resource=None): + self.app = Bottle() + self.app.router.add_filter('resource_filter', resource_filter) + setup_routing(self.app) + self.server = WSGIServer(('localhost', PORT), self.app, log=None) + try: + self.server.start() + except Exception as error: + print(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], + file=sys.stderr) + raise error + ds_client = getattr(self, 'ds_client', None) + self.client = client('', host_url=HOST_URL, api_version=API_VERSION, + ds_client=ds_client) + if resource: + self.client = client( + '', host_url=HOST_URL, api_version=API_VERSION, + ds_client=ds_client, resource=resource) + + @classmethod + def setting_up_ds(cls): + cls.app_ds = Bottle() + cls.server_ds \ + = WSGIServer(('localhost', DS_PORT), cls.app_ds, log=None) + try: + cls.server_ds.start() + except Exception as error: + print(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], + file=sys.stderr) + raise error + + cls.ds_client = DocumentServiceClient(host_url=DS_HOST_URL, + auth_ds=AUTH_DS_FAKE) + # to test units performing file operations outside the DS uncomment + # following lines: + # import logging + # logging.basicConfig() + # cls.ds_client = None + + setup_routing_ds(cls.app_ds) + + @classmethod + def setUpClass(cls): + cls.setting_up_ds() + + @classmethod + def tearDownClass(cls): + cls.server_ds.stop() + + +class AssetsRegistryTestCase(BaseTestClass): + def setUp(self): + self.setting_up(client=AssetsClient, resource='assets') + + with open(ROOT + 'assets.json') as assets: + self.assets = munchify(load(assets)) + with open(ROOT + 'asset_{}.json'.format( + TEST_ASSET_KEYS.asset_id)) as asset: + self.asset = munchify(load(asset)) + + def tearDown(self): + self.server.stop() + + def test_get_assets(self): + setup_routing(self.app, routes=["assets"]) + assets = self.client.get_assets() + self.assertIsInstance(assets, Iterable) + self.assertEqual(assets, self.assets.data) + + @mock.patch('openprocurement_client.registry_client.AssetsClient.request') + def test_get_assets_failed(self, mock_request): + mock_request.return_value = munchify({'status_code': 404}) + with self.assertRaises(InvalidResponse) as e: + self.client.get_assets(params={'offset': 'offset_value'}) + + def test_get_asset(self): + setup_routing(self.app, routes=["asset"]) + asset = self.client.get_asset(TEST_ASSET_KEYS.asset_id) + self.assertEqual(asset, self.asset) + + def test_patch_asset(self): + setup_routing(self.app, routes=["asset_patch"]) + self.asset.data.description = 'test_patch_asset' + + patched_asset = self.client.patch_resource_item(self.asset) + self.assertEqual(patched_asset.data.id, self.asset.data.id) + self.assertEqual(patched_asset.data.description, + self.asset.data.description) + + +class LotsRegistryTestCase(BaseTestClass): + def setUp(self): + self.setting_up(client=LotsClient, resource='lots') + + with open(ROOT + 'lots.json') as lots: + self.lots = munchify(load(lots)) + with open(ROOT + 'lot_{}.json'.format(TEST_LOT_KEYS.lot_id)) as lot: + self.lot = munchify(load(lot)) + + def tearDown(self): + self.server.stop() + + def test_get_lots(self): + setup_routing(self.app, routes=["lots"]) + lots = self.client.get_lots() + self.assertIsInstance(lots, Iterable) + self.assertEqual(lots, self.lots.data) + + @mock.patch('openprocurement_client.registry_client.LotsClient.request') + def test_get_lots_failed(self, mock_request): + mock_request.return_value = munchify({'status_code': 404}) + with self.assertRaises(InvalidResponse) as e: + self.client.get_lots(params={'offset': 'offset_value'}) + + def test_get_lot(self): + setup_routing(self.app, routes=["lot"]) + lot = self.client.get_lot(TEST_LOT_KEYS.lot_id) + self.assertEqual(lot, self.lot) + + def test_patch_lot(self): + setup_routing(self.app, routes=["lot_patch"]) + self.lot.data.description = 'test_patch_lot' + + patched_lot = self.client.patch_resource_item(self.lot) + self.assertEqual(patched_lot.data.id, self.lot.data.id) + self.assertEqual(patched_lot.data.description, + self.lot.data.description) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(AssetsRegistryTestCase)) + suite.addTest(unittest.makeSuite(LotsRegistryTestCase)) + return suite + + +if __name__ == '__main__': + unittest.main(defaultTest='suite') From 9513f5685ac6f9e746e8279d2d399c9b5394fb89 Mon Sep 17 00:00:00 2001 From: VDigitall Date: Tue, 29 Aug 2017 08:59:33 +0300 Subject: [PATCH 04/20] Refactored clients --- .coveragerc | 3 +- .gitignore | 1 + openprocurement_client/api_base_client.py | 298 -------- openprocurement_client/client.py | 657 ------------------ openprocurement_client/clients.py | 416 +++++++++++ openprocurement_client/constants.py | 18 + openprocurement_client/contract.py | 75 -- openprocurement_client/plan.py | 121 ---- openprocurement_client/registry_client.py | 43 -- openprocurement_client/resources/__init__.py | 0 openprocurement_client/resources/assets.py | 17 + openprocurement_client/resources/contracts.py | 43 ++ .../document_service.py} | 5 +- openprocurement_client/resources/edr.py | 29 + openprocurement_client/resources/lots.py | 17 + openprocurement_client/resources/plans.py | 57 ++ .../{ => resources}/sync.py | 191 ++--- openprocurement_client/resources/tenders.py | 312 +++++++++ openprocurement_client/templates.py | 39 ++ openprocurement_client/tests/__init__.py | 1 - openprocurement_client/tests/_server.py | 74 +- openprocurement_client/tests/main.py | 4 +- .../tests/test_registry_client.py | 80 +-- .../tests/{tests.py => tests_resources.py} | 547 ++++++++++----- openprocurement_client/tests/tests_sync.py | 235 ++++--- openprocurement_client/tests/tests_utils.py | 24 +- openprocurement_client/utils.py | 122 +++- setup.cfg | 1 + setup.py | 5 +- 29 files changed, 1738 insertions(+), 1697 deletions(-) delete mode 100644 openprocurement_client/api_base_client.py delete mode 100755 openprocurement_client/client.py create mode 100755 openprocurement_client/clients.py create mode 100644 openprocurement_client/constants.py delete mode 100644 openprocurement_client/contract.py delete mode 100644 openprocurement_client/plan.py delete mode 100644 openprocurement_client/registry_client.py create mode 100644 openprocurement_client/resources/__init__.py create mode 100644 openprocurement_client/resources/assets.py create mode 100644 openprocurement_client/resources/contracts.py rename openprocurement_client/{document_service_client.py => resources/document_service.py} (95%) create mode 100644 openprocurement_client/resources/edr.py create mode 100644 openprocurement_client/resources/lots.py create mode 100644 openprocurement_client/resources/plans.py rename openprocurement_client/{ => resources}/sync.py (57%) create mode 100644 openprocurement_client/resources/tenders.py create mode 100644 openprocurement_client/templates.py mode change 100755 => 100644 openprocurement_client/tests/_server.py rename openprocurement_client/tests/{tests.py => tests_resources.py} (63%) mode change 100755 => 100644 diff --git a/.coveragerc b/.coveragerc index 54856c6..e054045 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,9 +1,8 @@ [run] -omit = - *tests* source = openprocurement_client [report] +omit = *tests* exclude_lines = pragma: no cover def __repr__ diff --git a/.gitignore b/.gitignore index c5f8917..447ab9e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ __pycache__/ # Distribution / packaging .Python +.env/ env/ build/ develop-eggs/ diff --git a/openprocurement_client/api_base_client.py b/openprocurement_client/api_base_client.py deleted file mode 100644 index f26176b..0000000 --- a/openprocurement_client/api_base_client.py +++ /dev/null @@ -1,298 +0,0 @@ -import logging -import uuid - -from .exceptions import http_exceptions_dict, InvalidResponse, RequestFailed -from .document_service_client import DocumentServiceClient - -from functools import wraps -from io import FileIO -from munch import munchify -from os import path -from requests import Session -from requests.auth import HTTPBasicAuth as BasicAuth -from simplejson import loads -from retrying import retry - -logger = logging.getLogger(__name__) -IGNORE_PARAMS = ('uri', 'path') - - -# Using FileIO here instead of open() -# to be able to override the filename -# which is later used when uploading the file. -# -# Explanation: -# -# 1) requests reads the filename -# from "name" attribute of a file-like object, -# there is no other way to specify a filename; -# -# 2) The attribute may contain the full path to file, -# which does not work well as a filename; -# -# 3) The attribute is readonly when using open(), -# unlike FileIO object. - - -def verify_file(fn): - @wraps(fn) - def wrapper(self, file_, *args, **kwargs): - if isinstance(file_, basestring): - file_ = FileIO(file_, 'rb') - file_.name = path.basename(file_.name) - if hasattr(file_, 'read'): - # A file-like object must have 'read' method - output = fn(self, file_, *args, **kwargs) - file_.close() - return output - else: - try: - file_.close() - except AttributeError: - pass - raise TypeError( - 'Expected either a string containing a path to file or ' - 'a file-like object, got {}'.format(type(file_)) - ) - return wrapper - - -class APITemplateClient(object): - """Base class template for API""" - - def __init__(self, login_pass=None, headers=None, user_agent=None): - self.headers = headers or {} - self.session = Session() - if login_pass is not None: - self.session.auth = BasicAuth(*login_pass) - - if user_agent is None: - self.session.headers['User-Agent'] = 'op.client/{}'.format( - uuid.uuid4().hex) - else: - self.session.headers['User-Agent'] = user_agent - - def request(self, method, path=None, payload=None, json=None, - headers=None, params_dict=None, file_=None): - _headers = self.headers.copy() - _headers.update(headers or {}) - if file_: - _headers.pop('Content-Type', None) - - response = self.session.request( - method, path, data=payload, json=json, headers=_headers, - params=params_dict, files=file_ - ) - - if response.status_code >= 400: - raise http_exceptions_dict\ - .get(response.status_code, RequestFailed)(response) - - return response - - -class APIBaseClient(APITemplateClient): - """Base class for API""" - - host_url = 'https://api-sandbox.openprocurement.org' - api_version = '0' - headers = {'Content-Type': 'application/json'} - - def __init__(self, - key='', - resource='tenders', - host_url=None, - api_version=None, - params=None, - ds_config=None, - user_agent=None): - - super(APIBaseClient, self).__init__(login_pass=(key, ''), - headers=self.headers, - user_agent=user_agent) - if ds_config: - self.ds_client = DocumentServiceClient(ds_config['host_url'], - ds_config['auth']) - self.host_url = host_url or self.host_url - self.api_version = api_version or self.api_version - - if not isinstance(params, dict): - params = {'mode': '_all_'} - self.params = params or {} - # To perform some operations (e.g. create a tender) - # we first need to obtain a cookie. For that reason, - # here we send a HEAD request to a neutral URL. - response = self.session.request( - 'HEAD', '{}/api/{}/spore'.format(self.host_url, self.api_version) - ) - response.raise_for_status() - - self.prefix_path = '{}/api/{}/{}'.format(self.host_url, - self.api_version, resource) - - def _update_params(self, params): - for key in params: - if key not in IGNORE_PARAMS: - self.params[key] = params[key] - - def _create_resource_item(self, url, payload, headers=None, method='POST'): - _headers = self.headers.copy() - _headers.update(headers or {}) - - response_item = self.request( - method, url, headers=_headers, json=payload - ) - if ((response_item.status_code == 201 and method == 'POST') or - (response_item.status_code in (200, 204) and - method in ('PUT', 'DELETE'))): - return munchify(loads(response_item.text)) - raise InvalidResponse(response_item) - - def _get_resource_item(self, url, headers=None): - _headers = self.headers.copy() - _headers.update(headers or {}) - response_item = self.request('GET', url, headers=_headers) - if response_item.status_code == 200: - return munchify(loads(response_item.text)) - raise InvalidResponse(response_item) - - @retry(stop_max_attempt_number=5) - def _get_resource_items(self, params=None, feed='changes'): - _params = (params or {}).copy() - _params['feed'] = feed - self._update_params(_params) - response = self.request('GET', - self.prefix_path, - params_dict=self.params) - if response.status_code == 200: - resource_items_list = munchify(loads(response.text)) - self._update_params(resource_items_list.next_page) - return resource_items_list.data - elif response.status_code == 404: - del self.params['offset'] - - raise InvalidResponse(response) - - def _patch_resource_item(self, url, payload, headers=None): - _headers = self.headers.copy() - _headers.update(headers or {}) - response_item = self.request( - 'PATCH', url, headers=_headers, json=payload - ) - if response_item.status_code == 200: - return munchify(loads(response_item.text)) - raise InvalidResponse(response_item) - - def _upload_resource_file( - self, url, file_=None, headers=None, method='POST', - use_ds_client=True, doc_registration=True - ): - if use_ds_client and self.ds_client: - if doc_registration: - response = self.ds_client.document_upload_registered( - file_=file_, headers=headers - ) - else: - response = self.ds_client.document_upload_not_registered( - file_=file_, headers=headers - ) - payload = {'data': response['data']} - response = self._create_resource_item( - url, - headers=headers, - payload=payload, - method=method - ) - else: - if use_ds_client: - logger.warning('use_ds_client parameter is True while ' - 'DS-client is not passed to the client ' - 'constructor.') - logger.warning( - 'File upload/download/delete outside of the Document Service ' - 'is deprecated' - ) - response = self.request( - method, url, headers=headers, file_={'file': file_} - ) - if response.status_code in (201, 200): - response = munchify(loads(response.text)) - else: - raise InvalidResponse(response) - - return response - - def _delete_resource_item(self, url, headers=None): - _headers = self.headers.copy() - _headers.update(headers or {}) - response_item = self.request('DELETE', url, headers=_headers) - if response_item.status_code == 200: - return munchify(loads(response_item.text)) - raise InvalidResponse(response_item) - - def _patch_obj_resource_item(self, patched_obj, item_obj, items_name): - return self._patch_resource_item( - '{}/{}/{}/{}'.format( - self.prefix_path, patched_obj.data.id, - items_name, item_obj['data']['id'] - ), - payload=item_obj, - headers={'X-Access-Token': self._get_access_token(patched_obj)} - ) - - def patch_document(self, obj, document): - return self._patch_obj_resource_item(obj, document, 'documents') - - @verify_file - def upload_document(self, file_, obj, use_ds_client=True, - doc_registration=True): - return self._upload_resource_file( - '{}/{}/documents'.format( - self.prefix_path, - obj.data.id - ), - file_=file_, - headers={'X-Access-Token': self._get_access_token(obj)}, - use_ds_client=use_ds_client, - doc_registration=doc_registration - ) - - def get_resource_item(self, id, headers=None): - return self._get_resource_item('{}/{}'.format(self.prefix_path, id), - headers=headers) - - def patch_credentials(self, id, access_token): - return self._patch_resource_item( - '{}/{}/credentials'.format(self.prefix_path, id), - payload=None, - headers={'X-Access-Token': access_token} - ) - - - def renew_cookies(self): - old_cookies = 'Old cookies:\n' - for k in self.session.cookies.keys(): - old_cookies += '{}={}\n'.format(k, self.session.cookies[k]) - logger.debug(old_cookies.strip()) - - self.session.cookies.clear() - - response = self.session.request( - 'HEAD', '{}/api/{}/spore'.format(self.host_url, self.api_version) - ) - response.raise_for_status() - - new_cookies = 'New cookies:\n' - for k in self.session.cookies.keys(): - new_cookies += '{}={}\n'.format(k, self.session.cookies[k]) - logger.debug(new_cookies) - - def create_resource_item(self, resource_item): - return self._create_resource_item(self.prefix_path, resource_item) - - def patch_resource_item(self, resource_item): - return self._patch_resource_item( - '{}/{}'.format(self.prefix_path, resource_item['data']['id']), - payload=resource_item, - headers={'X-Access-Token': self._get_access_token(resource_item)} - ) diff --git a/openprocurement_client/client.py b/openprocurement_client/client.py deleted file mode 100755 index d20e7d5..0000000 --- a/openprocurement_client/client.py +++ /dev/null @@ -1,657 +0,0 @@ -import logging - -from .api_base_client import APIBaseClient, APITemplateClient, verify_file -from .exceptions import InvalidResponse, IdNotFound - -from iso8601 import parse_date -from munch import munchify -from retrying import retry -from simplejson import loads - - -logger = logging.getLogger(__name__) - -IGNORE_PARAMS = ('uri', 'path') - - -class APIClient(APIBaseClient): - """ API Client """ - - def __init__(self, *args, **kwargs): - super(APIClient, self).__init__(*args, **kwargs) - - ########################################################################### - # CREATE CLIENT METHODS - ########################################################################### - - def create_resource_item(self, resource_item): - return self._create_resource_item(self.prefix_path, resource_item) - - def create_resource_item_subitem(self, resource_item_id, subitem_obj, - subitem_name, depth_path=None, - access_token=None): - headers = None - if access_token: - headers = {'X-Access-Token': access_token} - if depth_path: - url = '{}/{}/{}/{}'.format(self.prefix_path, resource_item_id, - depth_path, subitem_name) - else: - url = '{}/{}/{}'.format(self.prefix_path, resource_item_id, - subitem_name) - return self._create_resource_item(url, subitem_obj, headers=headers) - - ########################################################################### - # GET CLIENT METHODS - ########################################################################### - - def get_resource_item(self, resource_item_id): - return self._get_resource_item( - '{}/{}'.format(self.prefix_path, resource_item_id) - ) - - def get_resource_item_submitem(self, resource_item_id, subitem_id_or_name, - depth_path=None, access_token=None): - resource_item_id = resource_item_id or resource_item['data'].get('id') - access_token = access_token or self._get_access_token(resource_item) - headers = {'X-Access-Token': access_token} - if depth_path: - url = '{}/{}/{}/{}'.format(self.prefix_path, resource_item_id, - depth_path, subitem_id_or_name) - else: - url = '{}/{}/{}'.format(self.prefix_path, resource_item_id, - subitem_id_or_name) - return self._get_resource_item(url, headers=headers) - - def get_resource_items(self, params=None, feed='changes'): - return self._get_resource_items(params=params, feed=feed) - - def get_file(self, url, access_token=None): - headers = {'X-Access-Token': access_token} if access_token else {} - - headers.update(self.headers) - response_item = self.request('GET', url, headers=headers) - - # if response_item.status_code == 302: - # response_obj = request(response_item.headers['location']) - if response_item.status_code == 200: - return response_item.text, \ - response_item.headers['Content-Disposition'] \ - .split("; filename=")[1].strip('"') - raise InvalidResponse(response_item) - - def get_file_properties(self, url, file_hash, access_token=None): - headers = {'X-Access-Token': access_token} if access_token else {} - headers.update(self.headers) - response_item = self.request('GET', url, headers=headers) - if response_item.status_code == 200: - file_properties = { - 'Content_Disposition': - response_item.headers['Content-Disposition'], - 'Content_Type': response_item.headers['Content-Type'], - 'url': url, - 'hash': file_hash - } - return file_properties - raise InvalidResponse(response_item) - - ########################################################################### - # PATCH CLIENT METHODS - ########################################################################### - - def patch_credentials(self, resource_item_id, access_token): - return self._patch_resource_item( - '{}/{}/credentials'.format(self.prefix_path, resource_item_id), - payload=None, - headers={'X-Access-Token': access_token} - ) - - def patch_resource_item(self, resource_item, resource_item_id=None): - resource_item_id = resource_item_id or resource_item['data'].get('id') - return self._patch_resource_item( - '{}/{}'.format(self.prefix_path, resource_item_id), - payload=resource_item, - headers={'X-Access-Token': self._get_access_token(resource_item)} - ) - - def patch_resource_item_subitem(self, resource_item, subitem_obj, - subitem_name, resource_item_id=None, - subitem_id=None, depth_path=None, - headers=None): - subitem_id = subitem_id or subitem_obj['data'].get('id') - resource_item_id = resource_item_id or resource_item['data'].get('id') - access_token = self._get_access_token(resource_item) - headers = {'X-Access-Token': access_token} - if depth_path: - url = '{}/{}/{}/{}/{}'.format( - self.prefix_path, resource_item_id, depth_path, subitem_name, - subitem_id - ) - else: - url = '{}/{}/{}/{}'.format( - self.prefix_path, resource_item_id, subitem_name, subitem_id - ) - return self._patch_resource_item(url, subitem_obj, headers=headers) - - ########################################################################### - # UPLOAD CLIENT METHODS - ########################################################################### - - @verify_file - def upload_document( - self, file_, resource_item, use_ds_client=True, - doc_registration=True, resource_item_id=None, depth_path=None): - resource_item_id = resource_item_id or resource_item['data'].get('id') - headers = {'X-Access-Token': self._get_access_token(tender)} - if depth_path: - url = '{}/{}/{}/documents'.format( - self.prefix_path, resource_item_id, depth_path - ) - else: - url = '{}/{}/documents'.format(self.prefix_path, resource_item_id) - return self._upload_resource_file( - url, file_=file_, headers=headers, use_ds_client=use_ds_client, - doc_registration=doc_registration - ) - - # def patch_document(self, obj, document): - # return self._patch_obj_resource_item(obj, document, 'documents') - # - # @verify_file - # def upload_document(self, file_, obj, use_ds_client=True, - # doc_registration=True): - # return self._upload_resource_file( - # '{}/{}/documents'.format( - # self.prefix_path, - # obj.data.id - # ), - # file_=file_, - # headers={'X-Access-Token': self._get_access_token(obj)}, - # use_ds_client=use_ds_client, - # doc_registration=doc_registration - # ) - - - - - def extract_credentials(self, resource_item_id): - return self._get_resource_item( - '{}/{}/extract_credentials'.format(self.prefix_path, - resource_item_id) - ) - - -class TendersClient(APIClient): - """client for tenders""" - - def __init__(self, *args, **kwargs): - super(TendersClient, self).__init__(*args, **kwargs) - - ########################################################################### - # GET ITEMS LIST API METHODS - ########################################################################### - - @retry(stop_max_attempt_number=5) - def get_tenders(self, params=None, feed='changes'): - return self.get_resource_items(params=params, feed=feed) - - 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') - data = self._get_resource_item( - '{}?offset={}T{}&opt_fields=tender_id&mode=test'.format( - self.prefix_path, - dt, - tm - ) - ) - return data - - def get_questions(self, tender, tender_id=None): - return self.get_resource_item_subitem( - tender, 'questions', resource_item_id=tender_id - ) - - def get_documents(self, tender, tender_id=None): - return self.get_resource_item_subitem( - tender, 'documents', resource_item_id=tender_id) - - def get_awards_documents(self, tender, award_id, tender_id=None): - return self.get_resource_item_subitem( - tender, 'documents', depth_path='awards/{}'.format(award_id), - resource_item_id=tender_id - ) - - def get_qualification_documents(self, tender, qualification_id, - tender_id=None): - return self.get_resource_item_subitem( - tender, 'documents', - depth_path='qualifications/{}'.format(qualification_id), - resource_item_id=tender_id - ) - - def get_awards(self, tender, tender_id=None): - return self.get_resource_item_subitem( - tender, 'awards', resource_item_id=tender_id - ) - - def get_lots(self, tender, tender_id=None): - return self.get_resource_item_subitem( - tender, 'lots', resource_item_id=tender_id - ) - - ########################################################################### - # CREATE ITEM API METHODS - ########################################################################### - - def create_tender(self, tender): - return self.create_resource_item(self.prefix_path, tender) - - def create_question(self, tender, question, tender_id=None): - return self.create_resource_item_subitem( - tender, question, 'questions', resource_item_id=tender_id - ) - - def create_bid(self, tender, bid, tender_id=None): - return self.create_resource_item_subitem( - tender, bid, 'bids', resource_item_id=tender_id - ) - - def create_lot(self, tender, lot, tender_id=None): - return self.create_resource_item_subitem( - tender, lot, 'lots', resource_item_id=tender_id - ) - - def create_award(self, tender, award, tender_id=None): - return self.create_resource_item_subitem( - tender, award, 'awards', resource_item_id=tender_id - ) - - def create_cancellation(self, tender, cancellation, tender_id=None): - return self.create_resource_item_subitem( - tender, cancellation, 'cancellations', resource_item_id=tender_id - ) - - def create_complaint(self, tender, complaint, tender_id=None): - return self.create_resource_item_subitem( - tender, complaint, 'complaints', resource_item_id=tender_id - ) - - def create_award_complaint(self, tender, complaint, award_id, - tender_id=None): - depth_path = 'awards/{}'.format(award_id) - return self.create_resource_item_subitem( - tender, complaint, 'complaints', depth_path=depth_path, - resource_item_id=tender_id - ) - - def create_thin_document(self, tender, document_data, tender_id=None): - return self.create_resource_item_subitem( - tender, document_data, 'documents', resource_item_id=tender_id - ) - - ########################################################################### - # GET ITEM API METHODS - ########################################################################### - - def get_tender(self, tender_id): - return self.get_resource_item(tender_id) - - def get_question(self, tender, question_id, tender_id=None): - depth_path = 'questions' - return self.get_resource_item_submitem( - tender, question_id, depth_path=depth_path, - resource_item_id=tender_id - ) - - def get_bid(self, tender, bid_id, access_token=None, tender_id=None): - depth_path = 'bids' - return self.get_resource_item_submitem( - tender, bid_id, depth_path=depth_path, access_token=access_token, - resource_item_id=tender_id - ) - - def get_lot(self, tender, lot_id, tender_id=None): - depth_path = 'lots' - return self.get_resource_item_submitem( - tender, lot_id, depth_path=depth_path, resource_item_id=tender_id - ) - - ########################################################################### - # PATCH ITEM API METHODS - ########################################################################### - - def patch_tender(self, tender, tender_id=None): - return self.patch_resource_item(tender, resource_item_id=tender_id) - - def patch_question(self, tender, question, tender_id=None, - question_id=None): - return self.patch_resource_item_subitem( - tender, question, 'questions', resource_item_id=tender_id, - subitem_id=question_id - ) - - def patch_bid(self, tender, bid, tender_id=None, bid_id=None): - return self.patch_resource_item_subitem( - tender, bid, 'bids', resource_item_id=tender_id, subitem_id=bid_id - ) - - def patch_bid_document(self, tender, document_data, bid_id, - document_id=None, tender_id=None): - depth_path = 'bids/{}'.format(bid_id) - return self.patch_resource_item_subitem( - tender, document_data, 'documents', subitem_id=document_id, - depth_path=depth_path, resource_item_id=tender_id - ) - - def patch_award(self, tender, award, tender_id=None, award_id=None): - return self.patch_resource_item_subitem( - tender, award, 'awards', resource_item_id=tender_id, - subitem_id=award_id - ) - - def patch_award_document(self, tender, document_data, award_id, - document_id=None, tender_id=None): - depth_path = 'awards/{}'.format(award_id) - return self.patch_resource_item_subitem( - tender, document_data, 'documents', resource_item_id=tender_id, - subitem_id=document_id, depth_path=depth_path - ) - - def patch_cancellation(self, tender, cancellation, tender_id=None, - cancellation_id=None): - return self._patch_obj_resource_item( - tender, cancellation, 'cancellations', subitem_id=cancellation_id, - resource_item_id=tender_id - ) - - def patch_cancellation_document(self, tender, cancellation, - cancellation_id, cancellation_doc_id=None, - tender_id=None): - depth_path = 'cancellations/{}'.format(cancellation_id) - return self.patch_resource_item_subitem( - tender, cancellation, 'documents', subitem_id=cancellation_doc_id, - resource_item_id=tender_id, depth_path=depth_path - ) - - def patch_complaint(self, tender, complaint, tender_id=None, - complaint_id=None): - return self.patch_resource_item_subitem( - tender, complaint, 'complaints', subitem_id=complaint_id, - resource_item_id=tender_id - ) - - def patch_award_complaint(self, tender, complaint, award_id, - tender_id=None, complaint_id=None): - depth_path = 'awards/{}'.format(award_id) - return self.patch_resource_item_subitem( - tender, complaint, 'complaints', resource_item_id=tender_id, - subitem_id=complaint_id, - ) - - def patch_lot(self, tender, lot, lot_id=None, tender_id=None): - return self.patch_resource_item_subitem( - tender, lot, 'lots', subitem_id=lot_id, resource_item_id=tender_id - ) - - def patch_qualification(self, tender, qualification, qualification_id=None, - tender_id=None): - return self.patch_resource_item_subitem( - tender, qualification, 'qualifications', - resource_item_id=tender_id, subitem_id=qualification_id - ) - - def patch_contract(self, tender, contract, contract_id=None, - tender_id=None): - return self.patch_resource_item_subitem( - tender, contract, 'contracts', subitem_id=contract_id, - resource_item_id=tender_id - ) - - def patch_contract_document(self, tender, document_data, - contract_id, document_id=None, tender_id=None): - depth_path = 'contracts/{}'.format(contract_id) - return self.patch_resource_item_subitem( - tender, document_data, 'documents', subitem_id=document_id, - resource_item_id=tender_id - ) - - ########################################################################### - # UPLOAD FILE API METHODS - ########################################################################### - - def upload_bid_document(self, file_, tender, bid_id, doc_type='documents', - use_ds_client=True, doc_registration=True, - tender_id=None): - depth_path = 'bids/{}'.format(bid_id) - return self.upload_document(file_, tender) - return self._upload_resource_file( - '{}/{}/bids/{}/{}'.format( - self.prefix_path, - tender.data.id, - bid_id, - doc_type - ), - file_=file_, - headers={'X-Access-Token': self._get_access_token(tender)}, - use_ds_client=use_ds_client, - doc_registration=doc_registration - ) - - @verify_file - def update_bid_document(self, file_, tender, bid_id, document_id, - doc_type='documents', use_ds_client=True, - doc_registration=True): - return self._upload_resource_file( - '{}/{}/bids/{}/{}/{}'.format( - self.prefix_path, - tender.data.id, - bid_id, - doc_type, - document_id - ), - file_=file_, - headers={'X-Access-Token': self._get_access_token(tender)}, - method='PUT', - use_ds_client=use_ds_client, - doc_registration=doc_registration - ) - - @verify_file - def upload_cancellation_document(self, file_, tender, cancellation_id, - use_ds_client=True, - doc_registration=True): - return self._upload_resource_file( - '{}/{}/cancellations/{}/documents'.format( - self.prefix_path, - tender.data.id, - cancellation_id, - ), - file_=file_, - headers={'X-Access-Token': self._get_access_token(tender)}, - use_ds_client=use_ds_client, - doc_registration=doc_registration - ) - - @verify_file - def update_cancellation_document(self, file_, tender, cancellation_id, - document_id, use_ds_client=True, - doc_registration=True): - return self._upload_resource_file( - '{}/{}/cancellations/{}/documents/{}'.format( - self.prefix_path, - tender.data.id, - cancellation_id, - document_id - ), - file_=file_, - headers={'X-Access-Token': self._get_access_token(tender)}, - method='PUT', - use_ds_client=use_ds_client, - doc_registration=doc_registration - ) - - @verify_file - def upload_complaint_document(self, file_, tender, complaint_id, - use_ds_client=True, doc_registration=True): - return self._upload_resource_file( - '{}/{}/complaints/{}/documents'.format( - self.prefix_path, - tender.data.id, - complaint_id), - file_=file_, - headers={'X-Access-Token': self._get_access_token(tender)}, - use_ds_client=use_ds_client, - doc_registration=doc_registration - ) - - @verify_file - def upload_award_complaint_document(self, file_, tender, award_id, - complaint_id, use_ds_client=True, - doc_registration=True): - return self._upload_resource_file( - '{}/{}/awards/{}/complaints/{}/documents'.format( - self.prefix_path, - tender.data.id, - award_id, - complaint_id), - file_=file_, - headers={'X-Access-Token': self._get_access_token(tender)}, - use_ds_client=use_ds_client, - doc_registration=doc_registration - ) - - @verify_file - def upload_qualification_document(self, file_, tender, qualification_id, - use_ds_client=True, - doc_registration=True): - return self._upload_resource_file( - '{}/{}/qualifications/{}/documents'.format( - self.prefix_path, - tender.data.id, - qualification_id - ), - file_=file_, - headers={'X-Access-Token': self._get_access_token(tender)}, - use_ds_client=use_ds_client, - doc_registration=doc_registration - ) - - @verify_file - def upload_award_document(self, file_, tender, award_id, - use_ds_client=True, doc_registration=True, - doc_type='documents'): - return self._upload_resource_file( - '{}/{}/awards/{}/{}'.format( - self.prefix_path, - tender.data.id, - award_id, - doc_type - ), - file_=file_, - headers={'X-Access-Token': self._get_access_token(tender)}, - use_ds_client=use_ds_client, - doc_registration=doc_registration - ) - - @verify_file - def upload_contract_document(self, file_, tender, contract_id, - use_ds_client=True, doc_registration=True): - return self._upload_resource_file( - '{}/{}/contracts/{}/documents'.format( - self.prefix_path, - tender.data.id, - contract_id - ), - file_=file_, - headers={'X-Access-Token': self._get_access_token(tender)}, - use_ds_client=use_ds_client, - doc_registration=doc_registration - ) - - ########################################################################### - # DELETE ITEMS LIST API METHODS - ########################################################################### - - def delete_bid(self, tender, bid, access_token=None): - logger.info('delete_lot is deprecated. In next update this function ' - 'will takes bid_id and access_token instead bid.') - if isinstance(bid, basestring): - bid_id = bid - access_token = access_token - else: - bid_id = bid.data.id - access_token = getattr(getattr(bid, 'access', ''), 'token', '') - return self._delete_resource_item( - '{}/{}/bids/{}'.format( - self.prefix_path, - tender.data.id, - bid_id - ), - headers={'X-Access-Token': access_token} - ) - - def delete_lot(self, tender, lot): - logger.info('delete_lot is deprecated. In next update this function ' - 'will takes lot_id instead lot.') - if isinstance(lot, basestring): - lot_id = lot - else: - lot_id = lot.data.id - return self._delete_resource_item( - '{}/{}/lots/{}'.format( - self.prefix_path, - tender.data.id, - lot_id - ), - headers={'X-Access-Token': self._get_access_token(tender)} - ) - ########################################################################### - - -class Client(TendersClient): - """client for tenders for backward compatibility""" - - -class TendersClientSync(TendersClient): - - def sync_tenders(self, params=None, extra_headers=None): - _params = (params or {}).copy() - _params['feed'] = 'changes' - self.headers.update(extra_headers or {}) - - response = self.request('GET', self.prefix_path, - params_dict=_params) - if response.status_code == 200: - tender_list = munchify(loads(response.text)) - return tender_list - - @retry(stop_max_attempt_number=5) - def get_tender(self, id, extra_headers=None): - self.headers.update(extra_headers or {}) - return super(TendersClientSync, self).get_tender(id) - - -class EDRClient(APITemplateClient): - """ Client for validate members by EDR """ - - host_url = 'https://api-sandbox.openprocurement.org' - api_version = '2.0' - - def __init__(self, host_url=None, api_version=None, username=None, - password=None): - super(EDRClient, self).__init__(login_pass=(username, password)) - self.host_url = host_url or self.host_url - self.api_version = api_version or self.api_version - - def verify_member(self, edrpou, extra_headers=None): - self.headers.update(extra_headers or {}) - response = self.request( - 'GET', - '{}/api/{}/verify'.format(self.host_url, self.api_version), - params_dict={'id': edrpou} - ) - if response.status_code == 200: - return munchify(loads(response.text)) - raise InvalidResponse(response) diff --git a/openprocurement_client/clients.py b/openprocurement_client/clients.py new file mode 100755 index 0000000..a84788b --- /dev/null +++ b/openprocurement_client/clients.py @@ -0,0 +1,416 @@ +# -*- coding: utf-8 -*- +import logging +from openprocurement_client.constants import DOCUMENTS +from openprocurement_client.exceptions import InvalidResponse +from openprocurement_client.templates import APITemplateClient +from openprocurement_client.utils import verify_file +from iso8601 import parse_date +from simplejson import loads +from retrying import retry +from munch import munchify + +from openprocurement_client.resources.document_service import \ + DocumentServiceClient + + +LOGGER = logging.getLogger(__name__) +IGNORE_PARAMS = ('uri', 'path') + + +class APIBaseClient(APITemplateClient): + """Base class for API""" + + host_url = 'https://api-sandbox.openprocurement.org' + api_version = '0' + headers = {'Content-Type': 'application/json'} + + def __init__(self, + key='', + resource='tenders', + host_url=None, + api_version=None, + params=None, + ds_config=None, + user_agent=None): + + super(APIBaseClient, self).__init__(login_pass=(key, ''), + headers=self.headers, + user_agent=user_agent) + if ds_config: + self.ds_client = DocumentServiceClient(ds_config['host_url'], + ds_config['auth_ds']) + self.host_url = host_url or self.host_url + self.api_version = api_version or self.api_version + + if not isinstance(params, dict): + params = {'mode': '_all_'} + self.params = params or {} + # To perform some operations (e.g. create a tender) + # we first need to obtain a cookie. For that reason, + # here we send a HEAD request to a neutral URL. + response = self.session.request( + 'HEAD', '{}/api/{}/spore'.format(self.host_url, self.api_version) + ) + response.raise_for_status() + self.resource = resource + self.prefix_path = '{}/api/{}/{}'.format(self.host_url, + self.api_version, resource) + + def _create_resource_item(self, url, payload, headers=None, method='POST'): + _headers = self.headers.copy() + _headers.update(headers or {}) + + response_item = self.request( + method, url, headers=_headers, json=payload + ) + if ((response_item.status_code == 201 and method == 'POST') or + (response_item.status_code in (200, 204) and + method in ('PUT', 'DELETE'))): + return munchify(loads(response_item.text)) + raise InvalidResponse(response_item) + + def _delete_resource_item(self, url, headers=None): + _headers = self.headers.copy() + _headers.update(headers or {}) + response_item = self.request('DELETE', url, headers=_headers) + if response_item.status_code == 200: + return munchify(loads(response_item.text)) + raise InvalidResponse(response_item) + + def _get_resource_item(self, url, headers=None): + _headers = self.headers.copy() + _headers.update(headers or {}) + response_item = self.request('GET', url, headers=_headers) + if response_item.status_code == 200: + return munchify(loads(response_item.text)) + raise InvalidResponse(response_item) + + @retry(stop_max_attempt_number=5) + def _get_resource_items(self, params=None, feed='changes'): + _params = (params or {}).copy() + _params['feed'] = feed + self._update_params(_params) + response = self.request('GET', + self.prefix_path, + params_dict=self.params) + if response.status_code == 200: + resource_items_list = munchify(loads(response.text)) + self._update_params(resource_items_list.next_page) + return resource_items_list.data + elif response.status_code == 404: + del self.params['offset'] + + raise InvalidResponse(response) + + def _patch_resource_item(self, url, payload, headers=None): + _headers = self.headers.copy() + _headers.update(headers or {}) + response_item = self.request( + 'PATCH', url, headers=_headers, json=payload + ) + if response_item.status_code == 200: + return munchify(loads(response_item.text)) + raise InvalidResponse(response_item) + + def _patch_obj_resource_item(self, patched_obj, item_obj, items_name): + return self._patch_resource_item( + '{}/{}/{}/{}'.format( + self.prefix_path, patched_obj.data.id, + items_name, item_obj['data']['id'] + ), + payload=item_obj, + headers={'X-Access-Token': self._get_access_token(patched_obj)} + ) + + def _update_params(self, params): + for key in params: + if key not in IGNORE_PARAMS: + self.params[key] = params[key] + + def _upload_resource_file(self, url, file_=None, headers=None, + method='POST', doc_registration=True): + if hasattr(self, 'ds_client'): + if doc_registration: + response = self.ds_client.document_upload_registered( + file_=file_, headers=headers + ) + else: + response = self.ds_client.document_upload_not_registered( + file_=file_, headers=headers + ) + payload = {'data': response['data']} + response = self._create_resource_item( + url, + headers=headers, + payload=payload, + method=method + ) + else: + LOGGER.warning( + 'File upload/download/delete outside of the Document Service ' + 'is deprecated' + ) + response = self.request( + method, url, headers=headers, file_={'file': file_} + ) + if response.status_code in (201, 200): + response = munchify(loads(response.text)) + else: + raise InvalidResponse(response) + + return response + + def renew_cookies(self): + old_cookies = 'Old cookies:\n' + for k in self.session.cookies.keys(): + old_cookies += '{}={}\n'.format(k, self.session.cookies[k]) + LOGGER.debug(old_cookies.strip()) + + self.session.cookies.clear() + + response = self.session.request( + 'HEAD', '{}/api/{}/spore'.format(self.host_url, self.api_version) + ) + response.raise_for_status() + + new_cookies = 'New cookies:\n' + for k in self.session.cookies.keys(): + new_cookies += '{}={}\n'.format(k, self.session.cookies[k]) + LOGGER.debug(new_cookies) + + +class APIResourceClient(APIBaseClient): + """ API Resource Client """ + + def __init__(self, *args, **kwargs): + super(APIResourceClient, self).__init__(*args, **kwargs) + + ########################################################################### + # CREATE CLIENT METHODS + ########################################################################### + + def create_resource_item(self, resource_item): + return self._create_resource_item(self.prefix_path, resource_item) + + def create_resource_item_subitem(self, resource_item_id, subitem_obj, + subitem_name, access_token=None, + depth_path=None): + headers = None + if access_token: + headers = {'X-Access-Token': access_token} + if depth_path: + url = '{}/{}/{}/{}'.format(self.prefix_path, resource_item_id, + depth_path, subitem_name) + else: + url = '{}/{}/{}'.format(self.prefix_path, resource_item_id, + subitem_name) + return self._create_resource_item(url, subitem_obj, headers=headers) + + ########################################################################### + # DELETE CLIENT METHODS + ########################################################################### + + def delete_resource_item_subitem(self, resource_item_id, subitem_name, + subitem_id, access_token=None, + depth_path=None): + headers = None + if access_token: + headers = {'X-Access-Token': access_token} + if depth_path: + url = '{}/{}/{}/{}/{}'.format( + self.prefix_path, resource_item_id, depth_path, subitem_name, + subitem_id + ) + else: + url = '{}/{}/{}/{}'.format( + self.prefix_path, resource_item_id, subitem_name, subitem_id + ) + return self._delete_resource_item(url, headers=headers) + + ########################################################################### + # GET CLIENT METHODS + ########################################################################### + + def get_resource_item(self, resource_item_id): + return self._get_resource_item( + '{}/{}'.format(self.prefix_path, resource_item_id) + ) + + def get_resource_item_subitem(self, resource_item_id, subitem_id_or_name, + access_token=None, depth_path=None): + headers = None + if access_token: + headers = {'X-Access-Token': access_token} + if depth_path: + url = '{}/{}/{}/{}'.format(self.prefix_path, resource_item_id, + depth_path, subitem_id_or_name) + else: + url = '{}/{}/{}'.format(self.prefix_path, resource_item_id, + subitem_id_or_name) + return self._get_resource_item(url, headers=headers) + + def get_resource_items(self, params=None, feed='changes'): + return self._get_resource_items(params=params, feed=feed) + + def get_latest_resource_items(self, date): + iso_dt = parse_date(date) + dt = iso_dt.strftime('%Y-%m-%d') + tm = iso_dt.strftime('%H:%M:%S') + data = self._get_resource_item( + '{}?offset={}T{}&opt_fields={}_id&mode=test'.format( + self.prefix_path, + dt, + tm, + self.resource[:-1] + ) + ) + return data + + def get_file(self, url, access_token=None): + headers = {'X-Access-Token': access_token} if access_token else {} + + headers.update(self.headers) + response_item = self.request('GET', url, headers=headers) + + # if response_item.status_code == 302: + # response_obj = request(response_item.headers['location']) + if response_item.status_code == 200: + return response_item.text, \ + response_item.headers['Content-Disposition'] \ + .split("; filename=")[1].strip('"') + raise InvalidResponse(response_item) + + def get_file_properties(self, url, file_hash, access_token=None): + headers = {'X-Access-Token': access_token} if access_token else {} + headers.update(self.headers) + response_item = self.request('GET', url, headers=headers) + if response_item.status_code == 200: + file_properties = { + 'Content_Disposition': + response_item.headers['Content-Disposition'], + 'Content_Type': response_item.headers['Content-Type'], + 'url': url, + 'hash': file_hash + } + return file_properties + raise InvalidResponse(response_item) + + ########################################################################### + # PATCH CLIENT METHODS + ########################################################################### + + def patch_credentials(self, resource_item_id, access_token): + return self._patch_resource_item( + '{}/{}/credentials'.format(self.prefix_path, resource_item_id), + payload=None, + headers={'X-Access-Token': access_token} + ) + + def patch_resource_item(self, resource_item_id, patch_data, + access_token=None): + headers = None + if access_token: + headers = {'X-Access-Token': access_token} + return self._patch_resource_item( + '{}/{}'.format(self.prefix_path, resource_item_id), + payload=patch_data, headers=headers + ) + + def patch_resource_item_subitem(self, resource_item_id, patch_data, + subitem_name, subitem_id=None, + access_token=None, depth_path=None): + headers = None + if access_token: + headers = {'X-Access-Token': access_token} + if depth_path: + url = '{}/{}/{}/{}/{}'.format( + self.prefix_path, resource_item_id, depth_path, subitem_name, + subitem_id + ) + else: + url = '{}/{}/{}/{}'.format( + self.prefix_path, resource_item_id, subitem_name, subitem_id + ) + return self._patch_resource_item(url, patch_data, headers=headers) + + def patch_document(self, resource_item_id, document_data, document_id, + access_token=None, depth_path=None): + return self.patch_resource_item_subitem( + resource_item_id, document_data, DOCUMENTS, document_id, + access_token, depth_path + ) + + ########################################################################### + # UPLOAD CLIENT METHODS + ########################################################################### + + @verify_file + def update_document(self, file_, resource_item_id, document_id, + doc_registration=True, access_token=None, + depth_path=None, doc_type=DOCUMENTS): + headers = None + if access_token: + headers = {'X-Access-Token': access_token} + if depth_path: + url = '{}/{}/{}/{}/{}'.format( + self.prefix_path, resource_item_id, depth_path, doc_type, + document_id + ) + else: + url = '{}/{}/{}/{}'.format( + self.prefix_path, resource_item_id, doc_type, document_id + ) + return self._upload_resource_file( + url, file_=file_, headers=headers, method='PUT', + doc_registration=doc_registration + ) + + ########################################################################### + # UPLOAD CLIENT METHODS + ########################################################################### + + @verify_file + def upload_document(self, file_, resource_item_id, doc_registration=True, + access_token=None, depth_path=None, + doc_type=DOCUMENTS): + headers = None + if access_token: + headers = {'X-Access-Token': access_token} + if depth_path: + url = '{}/{}/{}/{}'.format( + self.prefix_path, resource_item_id, depth_path, doc_type + ) + else: + url = '{}/{}/{}'.format( + self.prefix_path, resource_item_id, doc_type + ) + return self._upload_resource_file( + url, file_=file_, headers=headers, + doc_registration=doc_registration + ) + + def extract_credentials(self, resource_item_id): + return self._get_resource_item( + '{}/{}/extract_credentials'.format(self.prefix_path, + resource_item_id) + ) + + +class APIResourceClientSync(APIResourceClient): + + def sync_resource_items(self, params=None, extra_headers=None): + _params = (params or {}).copy() + _params['feed'] = 'changes' + self.headers.update(extra_headers or {}) + + response = self.request('GET', self.prefix_path, + params_dict=_params) + if response.status_code == 200: + tender_list = munchify(loads(response.text)) + return tender_list + + @retry(stop_max_attempt_number=5) + def get_resource_item(self, resource_item_id, extra_headers=None): + self.headers.update(extra_headers or {}) + return super(APIResourceClientSync, self).get_resource_item( + resource_item_id) diff --git a/openprocurement_client/constants.py b/openprocurement_client/constants.py new file mode 100644 index 0000000..4ead9f4 --- /dev/null +++ b/openprocurement_client/constants.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +ASSETS = 'assets' +AUCTIONS = 'auctions' +AWARDS = 'awards' +BIDS = 'bids' +CANCELLATIONS = 'cancellations' +CHANGES = 'changes' +COMPLAINTS = 'complaints' +CONTRACTS = 'contracts' +DOCUMENTS = 'documents' +ELIGIBILITY_DOCUMENTS = 'eligibility_documents' +FINANCIAL_DOCUMENTS = 'financial_documens' +LOTS = 'lots' +PLANS = 'plans' +QUALIFICATION_DOCUMENTS = 'qualificationDocuments' +QUALIFICATIONS = 'qualifications' +QUESTIONS = 'questions' +TENDERS = 'tenders' diff --git a/openprocurement_client/contract.py b/openprocurement_client/contract.py deleted file mode 100644 index 231e553..0000000 --- a/openprocurement_client/contract.py +++ /dev/null @@ -1,75 +0,0 @@ -from simplejson import loads -from munch import munchify -from client import APIBaseClient -from openprocurement_client.exceptions import ResourceNotFound, InvalidResponse - - -class ContractingClient(APIBaseClient): - """ contracting client """ - - def __init__(self, - key, - host_url=None, - api_version=None, - params=None, - ds_client=None): - super(ContractingClient, self).__init__(key, 'contracts', host_url, - api_version, params, ds_client) - - def create_contract(self, contract): - return self._create_resource_item(self.prefix_path, contract) - - def get_contract(self, id): - return self._get_resource_item('{}/{}'.format(self.prefix_path, id)) - - def get_contracts(self, params=None, feed='changes'): - _params = (params or {}).copy() - _params['feed'] = feed - self._update_params(_params) - try: - response = self.request('GET', self.prefix_path, - params_dict=self.params) - except ResourceNotFound as e: - self.params.pop('offset', None) - raise e - if response.status_code == 200: - contracts = munchify(loads(response.text)) - self._update_params(contracts.next_page) - return contracts.data - - raise InvalidResponse(response) - - def _create_contract_resource_item(self, contract_id, access_token, item_obj, items_name): - return self._create_resource_item( - '{}/{}/{}'.format(self.prefix_path, contract_id, items_name), - item_obj, - headers={'X-Access-Token': access_token} - ) - - def create_change(self, contract_id, access_token, change_data): - return self._create_contract_resource_item(contract_id, access_token, change_data, - "changes") - - def retrieve_contract_credentials(self, contract_id, access_token): - # In order to get rights for future contract editing, tender token is passed as access token here. - # Response will contain the new access token for further contract modification. - return self._patch_resource_item( - '{}/{}/credentials'.format(self.prefix_path, contract_id), - payload=None, - headers={'X-Access-Token': access_token} - ) - - def patch_contract(self, contract_id, access_token, data): - return self._patch_resource_item( - '{}/{}'.format(self.prefix_path, contract_id), - payload=data, - headers={'X-Access-Token': access_token} - ) - - def patch_change(self, contract_id, change_id, access_token, data): - return self._patch_resource_item( - '{}/{}/{}/{}'.format(self.prefix_path, contract_id, 'changes', - change_id), - payload=data, - headers={'X-Access-Token': access_token} - ) diff --git a/openprocurement_client/plan.py b/openprocurement_client/plan.py deleted file mode 100644 index a35fbb5..0000000 --- a/openprocurement_client/plan.py +++ /dev/null @@ -1,121 +0,0 @@ -from client import APIBaseClient, InvalidResponse -from iso8601 import parse_date -from munch import munchify -from retrying import retry -from simplejson import loads -import logging - -logger = logging.getLogger(__name__) - - -class PlansClient(APIBaseClient): - """client for plans""" - - api_version = '0.8' - - def __init__(self, - key, - host_url=None, - api_version=None, - params=None, - ds_client=None): - - super(PlansClient, self)\ - .__init__(key, 'plans', host_url, api_version, params, ds_client) - - ########################################################################### - # GET ITEMS LIST API METHODS - ########################################################################### - - @retry(stop_max_attempt_number=5) - def get_plans(self, params=None, feed='changes'): - _params = (params or {}).copy() - _params['feed'] = feed - self._update_params(_params) - response = self.request('GET', - self.prefix_path, - params_dict=self.params) - if response.status_code == 200: - plan_list = munchify(loads(response.text)) - self._update_params(plan_list.next_page) - return plan_list.data - elif response.status_code == 412: - del self.params['offset'] - - raise InvalidResponse - - def get_latest_plans(self, date): - iso_dt = parse_date(date) - dt = iso_dt.strftime('%Y-%m-%d') - tm = iso_dt.strftime('%H:%M:%S') - data = self._get_resource_item( - '{}?offset={}T{}&opt_fields=plan_id&mode=test'.format( - self.prefix_path, - dt, - tm - ) - ) - return data - - def _get_plan_resource_list(self, plan, items_name): - return self._get_resource_item( - '{}/{}/{}'.format(self.prefix_path, plan.data.id, items_name), - headers={'X-Access-Token': self._get_access_token(plan)} - ) - - ########################################################################### - # CREATE ITEM API METHODS - ########################################################################### - - def _create_plan_resource_item(self, plan, item_obj, items_name): - return self._create_resource_item( - '{}/{}/{}'.format(self.prefix_path, plan.data.id, items_name), - item_obj, - headers={'X-Access-Token': self._get_access_token(plan)} - ) - - def create_plan(self, plan): - return self._create_resource_item(self.prefix_path, plan) - - ########################################################################### - # GET ITEM API METHODS - ########################################################################### - - def get_plan(self, plan_id): - return self._get_resource_item('{}/{}' - .format(self.prefix_path, plan_id)) - - def _get_plan_resource_item(self, plan, item_id, items_name, - access_token=''): - if access_token: - headers = {'X-Access-Token': access_token} - else: - headers = {'X-Access-Token': self._get_access_token(plan)} - return self._get_resource_item( - '{}/{}/{}/{}'.format(self.prefix_path, - plan.data.id, - items_name, - item_id), - headers=headers - ) - - ########################################################################### - # PATCH ITEM API METHODS - ########################################################################### - - def _patch_plan_resource_item(self, plan, item_obj, items_name): - return self._patch_resource_item( - '{}/{}/{}/{}'.format( - self.prefix_path, plan.data.id, - items_name, item_obj['data']['id'] - ), - payload=item_obj, - headers={'X-Access-Token': self._get_access_token(plan)} - ) - - def patch_plan(self, plan): - return self._patch_resource_item( - '{}/{}'.format(self.prefix_path, plan['data']['id']), - payload=plan, - headers={'X-Access-Token': self._get_access_token(plan)} - ) diff --git a/openprocurement_client/registry_client.py b/openprocurement_client/registry_client.py deleted file mode 100644 index 07ac3c5..0000000 --- a/openprocurement_client/registry_client.py +++ /dev/null @@ -1,43 +0,0 @@ -import logging - -from .client import APIClient -from .exceptions import InvalidResponse - -from iso8601 import parse_date -from munch import munchify -from retrying import retry -from simplejson import loads - - -class LotsClient(APIClient): - """ Client for Openregistry Lots """ - - api_version = '0.1' - resource = 'lots' - - def __init__(self, *args, **kwargs): - super(LotsClient, self).__init__(resource=self.resource, *args, - **kwargs) - - def get_lot(self, lot_id, headers=None): - return self.get_resource_item(lot_id, headers=headers) - - def get_lots(self, params=None, feed='changes'): - return self._get_resource_items(params=params, feed=feed) - - -class AssetsClient(APIBaseClient): - """ Client for Openregistry Assets """ - - api_version = '0.1' - resource = 'assets' - - def __init__(self, *args, **kwargs): - super(AssetsClient, self).__init__(resource=self.resource, *args, - **kwargs) - - def get_asset(self, asset_id, headers=None): - return self.get_resource_item(asset_id, headers=headers) - - def get_assets(self, params=None, feed='changes'): - return self._get_resource_items(params=params, feed=feed) diff --git a/openprocurement_client/resources/__init__.py b/openprocurement_client/resources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/openprocurement_client/resources/assets.py b/openprocurement_client/resources/assets.py new file mode 100644 index 0000000..fce60b0 --- /dev/null +++ b/openprocurement_client/resources/assets.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +from openprocurement_client.clients import APIResourceClient +from openprocurement_client.constants import ASSETS + + +class AssetsClient(APIResourceClient): + """ Client for Openregistry Assets """ + + resource = ASSETS + + def __init__(self, *args, **kwargs): + super(AssetsClient, self).__init__(resource=self.resource, *args, + **kwargs) + + get_asset = APIResourceClient.get_resource_item + + get_assets = APIResourceClient.get_resource_items diff --git a/openprocurement_client/resources/contracts.py b/openprocurement_client/resources/contracts.py new file mode 100644 index 0000000..8452a0e --- /dev/null +++ b/openprocurement_client/resources/contracts.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +from openprocurement_client.clients import APIResourceClient +from openprocurement_client.constants import CHANGES, CONTRACTS + + +class ContractingClient(APIResourceClient): + """ Contracting client """ + resource = CONTRACTS + + def __init__(self, *args, **kwargs): + super(ContractingClient, self).__init__(resource=self.resource, *args, + **kwargs) + + def create_contract(self, contract): + return self.create_resource_item(contract) + + def get_contract(self, contract_id): + return self.get_resource_item(contract_id) + + def get_contracts(self, params=None, feed=CHANGES): + return self.get_resource_items(params=params, feed=feed) + + def create_change(self, contract_id, access_token, change_data): + return self.create_resource_item_subitem( + contract_id, change_data, CHANGES, access_token=access_token + ) + + def retrieve_contract_credentials(self, contract_id, access_token): + # In order to get rights for future contract editing, tender token + # is passed as access token here. + # Response will contain the new access token for further + # contract modification. + return self.patch_credentials(contract_id, access_token) + + def patch_contract(self, contract_id, access_token, data): + return self.patch_resource_item( + contract_id, data, access_token + ) + + def patch_change(self, contract_id, change_id, access_token, data): + return self.patch_resource_item_subitem( + contract_id, data, CHANGES, change_id, access_token=access_token + ) diff --git a/openprocurement_client/document_service_client.py b/openprocurement_client/resources/document_service.py similarity index 95% rename from openprocurement_client/document_service_client.py rename to openprocurement_client/resources/document_service.py index fc55cc2..b968132 100644 --- a/openprocurement_client/document_service_client.py +++ b/openprocurement_client/resources/document_service.py @@ -1,7 +1,6 @@ import hashlib - -from .api_base_client import APITemplateClient -from .exceptions import InvalidResponse +from openprocurement_client.templates import APITemplateClient +from openprocurement_client.exceptions import InvalidResponse from munch import munchify from simplejson import loads diff --git a/openprocurement_client/resources/edr.py b/openprocurement_client/resources/edr.py new file mode 100644 index 0000000..ee90135 --- /dev/null +++ b/openprocurement_client/resources/edr.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +from openprocurement_client.exceptions import InvalidResponse +from openprocurement_client.templates import APITemplateClient +from munch import munchify +from simplejson import loads + + +class EDRClient(APITemplateClient): + """ Client for validate members by EDR """ + + host_url = 'https://api-sandbox.openprocurement.org' + api_version = '2.0' + + def __init__(self, host_url=None, api_version=None, username=None, + password=None): + super(EDRClient, self).__init__(login_pass=(username, password)) + self.host_url = host_url or self.host_url + self.api_version = api_version or self.api_version + + def verify_member(self, edrpou, extra_headers=None): + self.headers.update(extra_headers or {}) + response = self.request( + 'GET', + '{}/api/{}/verify'.format(self.host_url, self.api_version), + params_dict={'id': edrpou} + ) + if response.status_code == 200: + return munchify(loads(response.text)) + raise InvalidResponse(response) diff --git a/openprocurement_client/resources/lots.py b/openprocurement_client/resources/lots.py new file mode 100644 index 0000000..62b57b4 --- /dev/null +++ b/openprocurement_client/resources/lots.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +from openprocurement_client.clients import APIResourceClient +from openprocurement_client.constants import LOTS + + +class LotsClient(APIResourceClient): + """ Client for Openregistry Lots """ + + resource = LOTS + + def __init__(self, *args, **kwargs): + super(LotsClient, self).__init__(resource=self.resource, *args, + **kwargs) + + get_lot = APIResourceClient.get_resource_item + + get_lots = APIResourceClient.get_resource_items diff --git a/openprocurement_client/resources/plans.py b/openprocurement_client/resources/plans.py new file mode 100644 index 0000000..5d76eb1 --- /dev/null +++ b/openprocurement_client/resources/plans.py @@ -0,0 +1,57 @@ +from openprocurement_client.clients import APIResourceClient +from openprocurement_client.constants import PLANS +import logging + + +LOGGER = logging.getLogger(__name__) + + +class PlansClient(APIResourceClient): + """Client for plans""" + + api_version = '0' + resource = PLANS + + def __init__(self, *args, **kwargs): + + super(PlansClient, self).__init__(resource=self.resource, *args, + **kwargs) + + ########################################################################### + # GET ITEMS LIST API METHODS + ########################################################################### + + def get_plans(self, params=None, feed='changes'): + return self.get_resource_items(params, feed) + + def get_latest_plans(self, date): + return self.get_latest_resource_items(date) + + def _get_plan_resource_list(self, plan, items_name): + return self._get_resource_item( + '{}/{}/{}'.format(self.prefix_path, plan.data.id, items_name), + headers={'X-Access-Token': self._get_access_token(plan)} + ) + + ########################################################################### + # CREATE ITEM API METHODS + ########################################################################### + + def create_plan(self, plan): + return self.create_resource_item(plan) + + ########################################################################### + # GET ITEM API METHODS + ########################################################################### + + def get_plan(self, plan_id): + return self.get_resource_item(plan_id) + + ########################################################################### + # PATCH ITEM API METHODS + ########################################################################### + + def patch_plan(self, plan_id, patch_data, access_token=None): + return self.patch_resource_item( + plan_id, patch_data, access_token=access_token + ) diff --git a/openprocurement_client/sync.py b/openprocurement_client/resources/sync.py similarity index 57% rename from openprocurement_client/sync.py rename to openprocurement_client/resources/sync.py index 15b3905..3f4997d 100644 --- a/openprocurement_client/sync.py +++ b/openprocurement_client/resources/sync.py @@ -2,16 +2,12 @@ monkey.patch_all() import logging -from .client import TendersClientSync -from time import time +from openprocurement_client.clients import APIResourceClientSync +from openprocurement_client.utils import get_response + from gevent import spawn, sleep, idle from gevent.queue import Queue, Empty -from requests.exceptions import ConnectionError -from openprocurement_client.exceptions import ( - RequestFailed, - PreconditionFailed, - ResourceNotFound -) + DEFAULT_RETRIEVERS_PARAMS = { 'down_requests_sleep': 5, @@ -27,67 +23,7 @@ DEFAULT_API_EXTRA_PARAMS = { 'opt_fields': 'status', 'mode': '_all_'} -logger = logging.getLogger(__name__) - - -def get_response(client, params): - response_fail = True - sleep_interval = 0.2 - while response_fail: - try: - start = time() - response = client.sync_tenders(params) - end = time() - start - logger.debug( - 'Request duration {} sec'.format(end), - extra={'FEEDER_REQUEST_DURATION': end * 1000}) - response_fail = False - except PreconditionFailed as e: - logger.error('PreconditionFailed: {}'.format(e.message), - extra={'MESSAGE_ID': 'precondition_failed'}) - continue - except ConnectionError as e: - logger.error('ConnectionError: {}'.format(e.message), - extra={'MESSAGE_ID': 'connection_error'}) - if sleep_interval > 300: - raise e - sleep_interval = sleep_interval * 2 - logger.debug( - 'Client sleeping after ConnectionError {} sec.'.format( - sleep_interval)) - sleep(sleep_interval) - continue - except RequestFailed as e: - logger.error('Request failed. Status code: {}'.format( - e.status_code), extra={'MESSAGE_ID': 'request_failed'}) - if e.status_code == 429: - if sleep_interval > 120: - raise e - logger.debug( - 'Client sleeping after RequestFailed {} sec.'.format( - sleep_interval)) - sleep_interval = sleep_interval * 2 - sleep(sleep_interval) - continue - except ResourceNotFound as e: - logger.error('Resource not found: {}'.format(e.message), - extra={'MESSAGE_ID': 'resource_not_found'}) - logger.debug('Clear offset and client cookies.') - client.session.cookies.clear() - del params['offset'] - continue - except Exception as e: - logger.error('Exception: {}'.format(e.message), - extra={'MESSAGE_ID': 'exceptions'}) - if sleep_interval > 300: - raise e - sleep_interval = sleep_interval * 2 - logger.debug( - 'Client sleeping after Exception: {}, {} sec.'.format( - e.message, sleep_interval)) - sleep(sleep_interval) - continue - return response +LOGGER = logging.getLogger(__name__) class ResourceFeeder(object): @@ -113,24 +49,25 @@ def init_api_clients(self): self.backward_params.update(self.extra_params) self.forward_params = {'feed': 'changes'} self.forward_params.update(self.extra_params) - self.forward_client = TendersClientSync( + self.forward_client = APIResourceClientSync( self.key, resource=self.resource, host_url=self.host, api_version=self.version) - self.backward_client = TendersClientSync( + self.backward_client = APIResourceClientSync( self.key, resource=self.resource, host_url=self.host, api_version=self.version) self.cookies = self.forward_client.session.cookies =\ self.backward_client.session.cookies def handle_response_data(self, data): - for tender in data: + for resource_item in data: # self.idle() - self.queue.put(tender) + self.queue.put(resource_item) def start_sync(self): # self.init_api_clients() - response = self.backward_client.sync_tenders(self.backward_params) + response = self.backward_client.sync_resource_items( + self.backward_params) self.handle_response_data(response.data) @@ -145,7 +82,7 @@ def restart_sync(self): Restart retrieving from Openprocurement API. """ - logger.info('Restart workers') + LOGGER.info('Restart workers') self.forward_worker.kill() self.backward_worker.kill() self.init_api_clients() @@ -156,14 +93,18 @@ def get_resource_items(self): Prepare iterator for retrieving from Openprocurement API. :param: - host (str): Url of Openprocurement API. Defaults is DEFAULT_API_HOST - version (str): Verion of Openprocurement API. Defaults is DEFAULT_API_VERSION - key(str): Access key of broker in Openprocurement API. Defaults is DEFAULT_API_KEY + host (str): Url of Openprocurement API. + Defaults is DEFAULT_API_HOST + version (str): Verion of Openprocurement API. + Defaults is DEFAULT_API_VERSION + key(str): Access key of broker in Openprocurement API. + Defaults is DEFAULT_API_KEY (Empty string) extra_params(dict): Extra params of query :returns: - iterator of tender_object (Munch): object derived from the list of tenders + iterator of tender_object (Munch): object derived from the + list of tenders """ self.init_api_clients() @@ -172,7 +113,7 @@ def get_resource_items(self): while True: if check_down_worker and self.backward_worker.ready(): if self.backward_worker.value == 0: - logger.info('Stop check backward worker') + LOGGER.info('Stop check backward worker') check_down_worker = False else: self.restart_sync() @@ -192,14 +133,18 @@ def feeder(self): Prepare iterator for retrieving from Openprocurement API. :param: - host (str): Url of Openprocurement API. Defaults is DEFAULT_API_HOST - version (str): Verion of Openprocurement API. Defaults is DEFAULT_API_VERSION - key(str): Access key of broker in Openprocurement API. Defaults is DEFAULT_API_KEY + host (str): Url of Openprocurement API. + Defaults is DEFAULT_API_HOST + version (str): Verion of Openprocurement API. + Defaults is DEFAULT_API_VERSION + key(str): Access key of broker in Openprocurement API. + Defaults is DEFAULT_API_KEY (Empty string) extra_params(dict): Extra params of query :returns: - iterator of tender_object (Munch): object derived from the list of tenders + iterator of tender_object (Munch): object derived from the + list of tenders """ self.init_api_clients() @@ -208,7 +153,7 @@ def feeder(self): while 1: if check_down_worker and self.backward_worker.ready(): if self.backward_worker.value == 0: - logger.info('Stop check backward worker') + LOGGER.info('Stop check backward worker') check_down_worker = False else: self.restart_sync() @@ -216,7 +161,7 @@ def feeder(self): if self.forward_worker.ready(): self.restart_sync() check_down_worker = True - logger.debug('Feeder queue size {} items'.format( + LOGGER.debug('Feeder queue size {} items'.format( self.queue.qsize()), extra={'FEEDER_QUEUE_SIZE': self.queue.qsize()}) sleep(2) @@ -226,36 +171,36 @@ def run_feeder(self): return self.queue def retriever_backward(self): - logger.info('Backward: Start worker') + LOGGER.info('Backward: Start worker') response = get_response(self.backward_client, self.backward_params) - logger.debug('Backward response length {} items'.format( + LOGGER.debug('Backward response length {} items'.format( len(response.data)), extra={'BACKWARD_RESPONSE_LENGTH': len(response.data)}) if self.cookies != self.backward_client.session.cookies: raise Exception('LB Server mismatch') while response.data: - logger.debug('Backward: Start process data.') + LOGGER.debug('Backward: Start process data.') self.handle_response_data(response.data) self.backward_params['offset'] = response.next_page.offset self.log_retriever_state( 'Backward', self.backward_client, self.backward_params) - logger.debug('Backward: Start process request.') + LOGGER.debug('Backward: Start process request.') response = get_response(self.backward_client, self.backward_params) - logger.debug('Backward response length {} items'.format( + LOGGER.debug('Backward response length {} items'.format( len(response.data)), extra={'BACKWARD_RESPONSE_LENGTH': len(response.data)}) if self.cookies != self.backward_client.session.cookies: raise Exception('LB Server mismatch') - logger.info('Backward: pause between requests {} sec.'.format( + LOGGER.info('Backward: pause between requests {} sec.'.format( self.retrievers_params.get('down_requests_sleep', 5))) sleep(self.retrievers_params.get('down_requests_sleep', 5)) - logger.info('Backward: finished') + LOGGER.info('Backward: finished') return 0 def retriever_forward(self): - logger.info('Forward: Start worker') + LOGGER.info('Forward: Start worker') response = get_response(self.forward_client, self.forward_params) - logger.debug('Forward response length {} items'.format( + LOGGER.debug('Forward response length {} items'.format( len(response.data)), extra={'FORWARD_RESPONSE_LENGTH': len(response.data)}) if self.cookies != self.forward_client.session.cookies: @@ -268,18 +213,18 @@ def retriever_forward(self): 'Forward', self.forward_client, self.forward_params) response = get_response(self.forward_client, self.forward_params) - logger.debug('Forward response length {} items'.format( + LOGGER.debug('Forward response length {} items'.format( len(response.data)), extra={'FORWARD_RESPONSE_LENGTH': len(response.data)}) if self.cookies != self.forward_client.session.cookies: raise Exception('LB Server mismatch') if len(response.data) != 0: - logger.info( + LOGGER.info( 'Forward: pause between requests {} sec.'.format( self.retrievers_params.get('up_requests_sleep', 5.0))) sleep(self.retrievers_params.get('up_requests_sleep', 5.0)) - logger.info('Forward: pause after empty response {} sec.'.format( + LOGGER.info('Forward: pause after empty response {} sec.'.format( self.retrievers_params.get('up_wait_sleep', 30.0)), extra={'FORWARD_WAIT_SLEEP': self.retrievers_params.get('up_wait_sleep', 30.0)}) @@ -288,7 +233,7 @@ def retriever_forward(self): self.log_retriever_state( 'Forward', self.forward_client, self.forward_params) response = get_response(self.forward_client, self.forward_params) - logger.debug('Forward response length {} items'.format( + LOGGER.debug('Forward response length {} items'.format( len(response.data)), extra={'FORWARD_RESPONSE_LENGTH': len(response.data)}) if self.adaptive: @@ -305,52 +250,12 @@ def retriever_forward(self): return 1 def log_retriever_state(self, name, client, params): - logger.debug('{}: offset {}'.format(name, params.get('offset', ''))) - logger.debug('{}: AWSELB {}'.format( + LOGGER.debug('{}: offset {}'.format(name, params.get('offset', ''))) + LOGGER.debug('{}: AWSELB {}'.format( name, client.session.cookies.get('AWSELB', ' ') )) - logger.debug('{}: SERVER_ID {}'.format( + LOGGER.debug('{}: SERVER_ID {}'.format( name, client.session.cookies.get('SERVER_ID', '') )) - logger.debug('{}: limit {}'.format(name, params.get('limit', ''))) - - -def get_resource_items(host=DEFAULT_API_HOST, version=DEFAULT_API_VERSION, - key=DEFAULT_API_KEY, - extra_params=DEFAULT_API_EXTRA_PARAMS, - retrievers_params=DEFAULT_RETRIEVERS_PARAMS, - resource='tenders'): - """ - Prepare iterator for retrieving from Openprocurement API. - - :param: - host (str): Url of Openprocurement API. Defaults is DEFAULT_API_HOST - version (str): Verion of Openprocurement API. Defaults is DEFAULT_API_VERSION - key(str): Access key of broker in Openprocurement API. Defaults is DEFAULT_API_KEY - (Empty string) - extra_params(dict): Extra params of query - - :returns: - iterator of tender_object (Munch): object derived from the list of tenders - - """ - feeder = ResourceFeeder( - host=host, version=version, - key=key, extra_params=extra_params, - retrievers_params=retrievers_params, resource=resource - ) - return feeder.get_resource_items() - - -def get_tenders(host=DEFAULT_API_HOST, version=DEFAULT_API_VERSION, - key=DEFAULT_API_KEY, extra_params=DEFAULT_API_EXTRA_PARAMS, - retrievers_params=DEFAULT_RETRIEVERS_PARAMS): - return get_resource_items(host=host, version=version, key=key, - resource='tenders', extra_params=extra_params, - retrievers_params=retrievers_params) - - -if __name__ == '__main__': - for tender_item in get_tenders(): - print('Tender {0[id]}'.format(tender_item)) + LOGGER.debug('{}: limit {}'.format(name, params.get('limit', ''))) diff --git a/openprocurement_client/resources/tenders.py b/openprocurement_client/resources/tenders.py new file mode 100644 index 0000000..a2f6cf6 --- /dev/null +++ b/openprocurement_client/resources/tenders.py @@ -0,0 +1,312 @@ +# -*- coding: utf-8 -*- +from openprocurement_client.clients import ( + APIResourceClient, APIResourceClientSync +) +from openprocurement_client.constants import ( + AWARDS, BIDS, CANCELLATIONS, COMPLAINTS, CONTRACTS, DOCUMENTS, + LOTS, QUALIFICATIONS, QUESTIONS +) +from retrying import retry + + +class TendersClient(APIResourceClient): + """client for tenders""" + + def __init__(self, *args, **kwargs): + super(TendersClient, self).__init__(*args, **kwargs) + + ########################################################################### + # CREATE ITEM API METHODS + ########################################################################### + + def create_tender(self, tender): + return self.create_resource_item(tender) + + def create_question(self, tender_id, question): + return self.create_resource_item_subitem( + tender_id, question, QUESTIONS + ) + + def create_bid(self, tender_id, bid): + return self.create_resource_item_subitem(tender_id, bid, BIDS) + + def create_lot(self, tender_id, lot): + return self.create_resource_item_subitem(tender_id, lot, LOTS) + + def create_award(self, tender_id, award): + return self.create_resource_item_subitem(tender_id, award, AWARDS) + + def create_cancellation(self, tender_id, cancellation): + return self.create_resource_item_subitem( + tender_id, cancellation, CANCELLATIONS + ) + + def create_complaint(self, tender_id, complaint): + return self.create_resource_item_subitem( + tender_id, complaint, COMPLAINTS + ) + + def create_award_complaint(self, tender_id, complaint, award_id): + depth_path = '{}/{}'.format(AWARDS, award_id) + return self.create_resource_item_subitem( + tender_id, complaint, COMPLAINTS, depth_path=depth_path + ) + + def create_thin_document(self, tender_id, document_data): + return self.create_resource_item_subitem( + tender_id, document_data, DOCUMENTS + ) + + ########################################################################### + # GET ITEMS LIST API METHODS + ########################################################################### + + @retry(stop_max_attempt_number=5) + def get_tenders(self, params=None, feed='changes'): + return self.get_resource_items(params=params, feed=feed) + + def get_latest_tenders(self, date): + return self.get_latest_resource_items(date) + + def get_questions(self, tender_id): + return self.get_resource_item_subitem(tender_id, QUESTIONS) + + def get_documents(self, tender_id): + return self.get_resource_item_subitem(tender_id, DOCUMENTS) + + def get_awards_documents(self, tender_id, award_id): + return self.get_resource_item_subitem( + tender_id, DOCUMENTS, depth_path='{}/{}'.format(AWARDS, award_id) + ) + + def get_qualification_documents(self, tender_id, qualification_id): + return self.get_resource_item_subitem( + tender_id, DOCUMENTS, + depth_path='{}/{}'.format(QUALIFICATIONS, qualification_id) + ) + + def get_awards(self, tender_id): + return self.get_resource_item_subitem(tender_id, AWARDS) + + def get_lots(self, tender_id): + return self.get_resource_item_subitem(tender_id, LOTS) + + ########################################################################### + # GET ITEM API METHODS + ########################################################################### + + def get_tender(self, tender_id): + return self.get_resource_item(tender_id) + + def get_question(self, tender_id, question_id): + return self.get_resource_item_subitem( + tender_id, question_id, depth_path=QUESTIONS + ) + + def get_bid(self, tender_id, bid_id, access_token=None): + return self.get_resource_item_subitem( + tender_id, bid_id, depth_path=BIDS, access_token=access_token + ) + + def get_lot(self, tender_id, lot_id): + return self.get_resource_item_subitem( + tender_id, lot_id, depth_path=LOTS + ) + + ########################################################################### + # PATCH ITEM API METHODS + ########################################################################### + + def patch_tender(self, tender_id, patch_data): + return self.patch_resource_item(tender_id, patch_data) + + def patch_question(self, tender_id, question, question_id): + return self.patch_resource_item_subitem( + tender_id, question, QUESTIONS, subitem_id=question_id + ) + + def patch_bid(self, tender_id, bid, bid_id): + return self.patch_resource_item_subitem( + tender_id, bid, BIDS, subitem_id=bid_id + ) + + def patch_bid_document(self, tender_id, document_data, bid_id, + document_id): + depth_path = '{}/{}'.format(BIDS, bid_id) + return self.patch_resource_item_subitem( + tender_id, document_data, DOCUMENTS, subitem_id=document_id, + depth_path=depth_path + ) + + def patch_award(self, tender_id, award, award_id): + return self.patch_resource_item_subitem( + tender_id, award, AWARDS, subitem_id=award_id + ) + + def patch_award_document(self, tender_id, document_data, award_id, + document_id): + depth_path = '{}/{}'.format(AWARDS, award_id) + return self.patch_resource_item_subitem( + tender_id, document_data, DOCUMENTS, subitem_id=document_id, + depth_path=depth_path + ) + + def patch_cancellation(self, tender_id, cancellation, cancellation_id): + return self.patch_resource_item_subitem( + tender_id, cancellation, CANCELLATIONS, + subitem_id=cancellation_id + ) + + def patch_cancellation_document(self, tender_id, cancellation, + cancellation_id, cancellation_doc_id): + depth_path = '{}/{}'.format(CANCELLATIONS, cancellation_id) + return self.patch_resource_item_subitem( + tender_id, cancellation, DOCUMENTS, + subitem_id=cancellation_doc_id, depth_path=depth_path + ) + + def patch_complaint(self, tender_id, complaint, complaint_id): + return self.patch_resource_item_subitem( + tender_id, complaint, COMPLAINTS, subitem_id=complaint_id + ) + + def patch_award_complaint(self, tender_id, complaint, award_id, + complaint_id): + return self.patch_resource_item_subitem( + tender_id, complaint, COMPLAINTS, subitem_id=complaint_id, + depth_path='{}/{}'.format(AWARDS, award_id) + ) + + def patch_lot(self, tender_id, lot, lot_id): + return self.patch_resource_item_subitem( + tender_id, lot, LOTS, subitem_id=lot_id + ) + + def patch_qualification(self, tender_id, qualification, qualification_id): + return self.patch_resource_item_subitem( + tender_id, qualification, QUALIFICATIONS, + subitem_id=qualification_id + ) + + def patch_contract(self, tender_id, contract, contract_id): + return self.patch_resource_item_subitem( + tender_id, contract, CONTRACTS, subitem_id=contract_id + ) + + def patch_contract_document(self, tender_id, document_data, contract_id, + document_id): + return self.patch_resource_item_subitem( + tender_id, document_data, DOCUMENTS, subitem_id=document_id, + depth_path='{}/{}'.format(CONTRACTS, contract_id) + ) + + ########################################################################### + # UPLOAD FILE API METHODS + ########################################################################### + + def upload_bid_document(self, file_, tender_id, bid_id, + doc_registration=True, access_token=None, + doc_type=DOCUMENTS): + depth_path = '{}/{}'.format(BIDS, bid_id) + return self.upload_document( + file_, tender_id, doc_registration, + access_token, depth_path, doc_type + ) + + def upload_cancellation_document(self, file_, tender_id, cancellation_id, + doc_registration=True, access_token=None): + depth_path = '{}/{}'.format(CANCELLATIONS, cancellation_id) + return self.upload_document(file_, tender_id, + doc_registration=doc_registration, + access_token=access_token, + depth_path=depth_path) + + def upload_complaint_document(self, file_, tender_id, complaint_id, + doc_registration=True, access_token=None): + depth_path = '{}/{}'.format(COMPLAINTS, complaint_id) + return self.upload_document(file_, tender_id, + doc_registration=doc_registration, + access_token=access_token, + depth_path=depth_path) + + def upload_award_complaint_document(self, file_, tender_id, award_id, + complaint_id, + doc_registration=True, + access_token=None): + depth_path = '{}/{}/{}/{}'.format(AWARDS, award_id, + COMPLAINTS, complaint_id) + return self.upload_document(file_, tender_id, + doc_registration=doc_registration, + access_token=access_token, + depth_path=depth_path) + + def upload_qualification_document(self, file_, tender_id, qualification_id, + doc_registration=True, + access_token=None): + depth_path = '{}/{}'.format(QUALIFICATIONS, qualification_id) + return self.upload_document(file_, tender_id, + doc_registration=doc_registration, + access_token=access_token, + depth_path=depth_path) + + def upload_award_document(self, file_, tender_id, award_id, + doc_registration=True, access_token=None): + depth_path = '{}/{}'.format(AWARDS, award_id) + return self.upload_document(file_, tender_id, + doc_registration=doc_registration, + access_token=access_token, + depth_path=depth_path) + + def upload_contract_document(self, file_, tender_id, contract_id, + doc_registration=True, access_token=None): + depth_path = '{}/{}'.format(CONTRACTS, contract_id) + return self.upload_document(file_, tender_id, doc_registration, + access_token, depth_path) + + ########################################################################### + # UPDATE FILE API METHODS + ########################################################################### + + def update_bid_document(self, file_, tender_id, bid_id, document_id, + doc_registration=True, access_token=None, + doc_type=DOCUMENTS): + depth_path = '{}/{}'.format(BIDS, bid_id) + return self.update_document( + file_, tender_id, document_id, doc_registration, + access_token, depth_path, doc_type + ) + + def update_cancellation_document(self, file_, tender_id, cancellation_id, + document_id, doc_registration=True, + access_token=None): + depth_path = '{}/{}'.format(CANCELLATIONS, cancellation_id) + return self.update_document( + file_, tender_id, document_id, doc_registration, + access_token, depth_path + ) + + ########################################################################### + # DELETE ITEMS LIST API METHODS + ########################################################################### + + def delete_bid(self, tender_id, bid_id, access_token=None): + return self.delete_resource_item_subitem( + tender_id, BIDS, bid_id, access_token=access_token + ) + + def delete_lot(self, tender_id, lot_id, access_token=None): + return self.delete_resource_item_subitem( + tender_id, LOTS, lot_id, access_token=access_token + ) + ########################################################################### + + +class Client(TendersClient): + """client for tenders for backward compatibility""" + + +class TendersClientSync(APIResourceClientSync): + + sync_tenders = APIResourceClientSync.sync_resource_items + + get_tender = APIResourceClientSync.get_resource_item diff --git a/openprocurement_client/templates.py b/openprocurement_client/templates.py new file mode 100644 index 0000000..758d45b --- /dev/null +++ b/openprocurement_client/templates.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +import uuid +from .exceptions import RequestFailed, http_exceptions_dict +from requests import Session +from requests.auth import HTTPBasicAuth as BasicAuth + + +class APITemplateClient(object): + """Base class template for API""" + + def __init__(self, login_pass=None, headers=None, user_agent=None): + self.headers = headers or {} + self.session = Session() + if login_pass is not None: + self.session.auth = BasicAuth(*login_pass) + + if user_agent is None: + self.session.headers['User-Agent'] = 'op.client/{}'.format( + uuid.uuid4().hex) + else: + self.session.headers['User-Agent'] = user_agent + + def request(self, method, path=None, payload=None, json=None, + headers=None, params_dict=None, file_=None): + _headers = self.headers.copy() + _headers.update(headers or {}) + if file_: + _headers.pop('Content-Type', None) + + response = self.session.request( + method, path, data=payload, json=json, headers=_headers, + params=params_dict, files=file_ + ) + + if response.status_code >= 400: + raise http_exceptions_dict\ + .get(response.status_code, RequestFailed)(response) + + return response diff --git a/openprocurement_client/tests/__init__.py b/openprocurement_client/tests/__init__.py index 813cf0d..e69de29 100644 --- a/openprocurement_client/tests/__init__.py +++ b/openprocurement_client/tests/__init__.py @@ -1 +0,0 @@ -# coding: utf8 \ No newline at end of file diff --git a/openprocurement_client/tests/_server.py b/openprocurement_client/tests/_server.py old mode 100755 new mode 100644 index 9ba746b..0c202e4 --- a/openprocurement_client/tests/_server.py +++ b/openprocurement_client/tests/_server.py @@ -1,7 +1,7 @@ from bottle import request, response, redirect, static_file from munch import munchify from simplejson import dumps, load -from openprocurement_client.document_service_client \ +from openprocurement_client.resources.document_service \ import DocumentServiceClient from openprocurement_client.tests.data_dict import ( TEST_TENDER_KEYS, @@ -64,13 +64,14 @@ def get_doc_title_from_request(req): return doc_title -### Base routes -# +# Base routes + def spore(): - response.set_cookie("SERVER_ID", ("a7afc9b1fc79e640f2487ba48243ca071c07a823d27" - "8cf9b7adf0fae467a524747e3c6c6973262130fac2b" - "96a11693fa8bd38623e4daee121f60b4301aef012c")) + response.set_cookie( + "SERVER_ID", + ("a7afc9b1fc79e640f2487ba48243ca071c07a823d278cf9b7adf0fae467a524747" + "e3c6c6973262130fac2b96a11693fa8bd38623e4daee121f60b4301aef012c")) def offset_error(resource_name): @@ -86,9 +87,7 @@ def resource_page_get(resource_name): return dumps(resources) -### Tender operations -# - +# Tender operations def resource_create(): response.status = 201 @@ -106,12 +105,11 @@ def resource_patch(resource_name, resource_id): resource = resource_partition(resource_id, resource_name) if not resource: return location_error(resource_name) - resource.update(request.json['data']) - return dumps({'data': resource}) + resource['data'].update(request.json['data']) + return dumps({'data': resource['data']}) -### Subpage operations -# +# Subpage operations def tender_subpage(tender_id, subpage_name): subpage = resource_partition(tender_id, part=subpage_name) @@ -178,8 +176,7 @@ def patch_credentials(resource_name, resource_id): {'token': RESOURCE_DICT[resource_name]['data']['new_token']} return resource -### Document and file operations -# +# Document and file operations def tender_document_create(tender_id): @@ -190,21 +187,23 @@ def tender_document_create(tender_id): return dumps({"data": document}) -def tender_subpage_document_create(tender_id, subpage_name, subpage_id, document_type): +def tender_subpage_document_create(tender_id, subpage_name, subpage_id, + document_type): response.status = 201 subpage = resource_partition(tender_id, part=subpage_name) if not subpage: return location_error("tender") for unit in subpage: if unit['id'] == subpage_id: - document= unit["documents"][0] + document = unit["documents"][0] document.title = get_doc_title_from_request(request) document.id = TEST_TENDER_KEYS.new_document_id return dumps({"data": document}) return location_error(subpage_name) -def tender_subpage_document_update(tender_id, subpage_name, subpage_id, document_type, document_id): +def tender_subpage_document_update(tender_id, subpage_name, subpage_id, + document_type, document_id): response.status = 200 subpage = resource_partition(tender_id, part=subpage_name) if not subpage: @@ -218,7 +217,8 @@ def tender_subpage_document_update(tender_id, subpage_name, subpage_id, document return location_error(subpage_name) -def tender_subpage_document_patch(tender_id, subpage_name, subpage_id, document_type, document_id): +def tender_subpage_document_patch(tender_id, subpage_name, subpage_id, + document_type, document_id): response.status = 200 subpage = resource_partition(tender_id, part=subpage_name) if not subpage: @@ -235,11 +235,10 @@ def tender_subpage_document_patch(tender_id, subpage_name, subpage_id, document_ def get_file(filename): redirect("/download/" + filename, code=302) + def download_file(filename): return static_file(filename, root=ROOT, download=True) -#### - def resource_partition(resource_id, resource_name='tender', part='all'): try: @@ -255,10 +254,20 @@ def resource_partition(resource_id, resource_name='tender', part='all'): def location_error(name): - return dumps({"status": "error", "errors": [{"location": "url", "name": name + '_id', "description": "Not Found"}]}) + return dumps( + { + "status": "error", + "errors": [ + { + "location": "url", + "name": name + '_id', + "description": "Not Found" + } + ] + } + ) -### Plan operations -# +# Plan operations def plan_patch(plan_id): @@ -273,15 +282,14 @@ def plan_partition(plan_id, part="plan"): try: with open(ROOT + 'plan_' + plan_id + '.json') as json: plan = load(json) - if part=="plan": + if part == "plan": return plan else: return munchify(plan['data'][part]) except (KeyError, IOError): return [] -### Contract operations -# +# Contract operations def contract_document_create(contract_id): @@ -300,15 +308,19 @@ def contract_change_patch(contract_id, change_id): change.data.rationale = TEST_CONTRACT_KEYS.patch_change_rationale return dumps(change) -#### Routes +# Routes routes_dict = { "spore": (SPORE_PATH, 'HEAD', spore), - "offset_error": (API_PATH.format(''), 'GET', offset_error), - "tenders": (API_PATH.format(''), 'GET', resource_page_get), + "offset_error": (API_PATH.format(''), + 'GET', offset_error), + "tenders": (API_PATH.format(''), + 'GET', resource_page_get), "tender_create": (TENDERS_PATH, 'POST', resource_create), - "tender": (API_PATH.format('') + '/', 'GET', resource_page), + "tender": (API_PATH.format( + '') + '/', + 'GET', resource_page), "tender_patch": (API_PATH.format('') + "/", 'PATCH', resource_patch), "tender_document_create": (TENDERS_PATH + "//documents", 'POST', tender_document_create), "tender_subpage": (TENDERS_PATH + "//", 'GET', tender_subpage), diff --git a/openprocurement_client/tests/main.py b/openprocurement_client/tests/main.py index 9655dd0..a6e892f 100644 --- a/openprocurement_client/tests/main.py +++ b/openprocurement_client/tests/main.py @@ -1,7 +1,7 @@ import unittest from openprocurement_client.tests import ( - tests, + tests_resources, tests_sync, test_registry_client ) @@ -9,9 +9,9 @@ def suite(): suite = unittest.TestSuite() - suite.addTest(tests.suite()) suite.addTest(tests_sync.suite()) suite.addTest(test_registry_client.suite()) + suite.addTest(tests_resources.suite()) return suite diff --git a/openprocurement_client/tests/test_registry_client.py b/openprocurement_client/tests/test_registry_client.py index 27355e4..5579718 100644 --- a/openprocurement_client/tests/test_registry_client.py +++ b/openprocurement_client/tests/test_registry_client.py @@ -1,35 +1,30 @@ from __future__ import print_function from gevent import monkey - monkey.patch_all() + +import mock +import sys +import unittest from gevent.pywsgi import WSGIServer from bottle import Bottle -from StringIO import StringIO from collections import Iterable -from simplejson import loads, load +from simplejson import load from munch import munchify -import mock -import sys -import unittest -from openprocurement_client.registry_client import LotsClient, AssetsClient -from openprocurement_client.document_service_client \ - import DocumentServiceClient -from openprocurement_client.exceptions import InvalidResponse, ResourceNotFound +from openprocurement_client.resources.assets import AssetsClient +from openprocurement_client.resources.lots import LotsClient +from openprocurement_client.exceptions import InvalidResponse from openprocurement_client.tests.data_dict import ( TEST_ASSET_KEYS, TEST_LOT_KEYS, - TEST_TENDER_KEYS_LIMITED, - TEST_PLAN_KEYS, - TEST_CONTRACT_KEYS ) from openprocurement_client.tests._server import \ - API_KEY, API_VERSION, AUTH_DS_FAKE, DS_HOST_URL, DS_PORT, \ - HOST_URL, location_error, PORT, ROOT, setup_routing, setup_routing_ds, \ - resource_partition, resource_filter + API_VERSION, AUTH_DS_FAKE, DS_HOST_URL, DS_PORT, \ + HOST_URL, PORT, ROOT, setup_routing, setup_routing_ds, \ + resource_filter class BaseTestClass(unittest.TestCase): - def setting_up(self, client, resource=None): + def setting_up(self, client): self.app = Bottle() self.app.router.add_filter('resource_filter', resource_filter) setup_routing(self.app) @@ -40,13 +35,13 @@ def setting_up(self, client, resource=None): print(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], file=sys.stderr) raise error - ds_client = getattr(self, 'ds_client', None) + + ds_config = { + 'host_url': DS_HOST_URL, + 'auth_ds': AUTH_DS_FAKE + } self.client = client('', host_url=HOST_URL, api_version=API_VERSION, - ds_client=ds_client) - if resource: - self.client = client( - '', host_url=HOST_URL, api_version=API_VERSION, - ds_client=ds_client, resource=resource) + ds_config=ds_config) @classmethod def setting_up_ds(cls): @@ -60,14 +55,6 @@ def setting_up_ds(cls): file=sys.stderr) raise error - cls.ds_client = DocumentServiceClient(host_url=DS_HOST_URL, - auth_ds=AUTH_DS_FAKE) - # to test units performing file operations outside the DS uncomment - # following lines: - # import logging - # logging.basicConfig() - # cls.ds_client = None - setup_routing_ds(cls.app_ds) @classmethod @@ -81,7 +68,7 @@ def tearDownClass(cls): class AssetsRegistryTestCase(BaseTestClass): def setUp(self): - self.setting_up(client=AssetsClient, resource='assets') + self.setting_up(client=AssetsClient) with open(ROOT + 'assets.json') as assets: self.assets = munchify(load(assets)) @@ -98,10 +85,10 @@ def test_get_assets(self): self.assertIsInstance(assets, Iterable) self.assertEqual(assets, self.assets.data) - @mock.patch('openprocurement_client.registry_client.AssetsClient.request') + @mock.patch('openprocurement_client.resources.assets.AssetsClient.request') def test_get_assets_failed(self, mock_request): mock_request.return_value = munchify({'status_code': 404}) - with self.assertRaises(InvalidResponse) as e: + with self.assertRaises(InvalidResponse): self.client.get_assets(params={'offset': 'offset_value'}) def test_get_asset(self): @@ -111,17 +98,18 @@ def test_get_asset(self): def test_patch_asset(self): setup_routing(self.app, routes=["asset_patch"]) - self.asset.data.description = 'test_patch_asset' - - patched_asset = self.client.patch_resource_item(self.asset) + asset_id = self.asset.data.id + patch_data = {'data': {'description': 'test_patch_asset'}} + patched_asset = self.client.patch_resource_item(asset_id, + patch_data) self.assertEqual(patched_asset.data.id, self.asset.data.id) self.assertEqual(patched_asset.data.description, - self.asset.data.description) + patch_data['data']['description']) class LotsRegistryTestCase(BaseTestClass): def setUp(self): - self.setting_up(client=LotsClient, resource='lots') + self.setting_up(client=LotsClient) with open(ROOT + 'lots.json') as lots: self.lots = munchify(load(lots)) @@ -137,10 +125,10 @@ def test_get_lots(self): self.assertIsInstance(lots, Iterable) self.assertEqual(lots, self.lots.data) - @mock.patch('openprocurement_client.registry_client.LotsClient.request') + @mock.patch('openprocurement_client.resources.lots.LotsClient.request') def test_get_lots_failed(self, mock_request): mock_request.return_value = munchify({'status_code': 404}) - with self.assertRaises(InvalidResponse) as e: + with self.assertRaises(InvalidResponse): self.client.get_lots(params={'offset': 'offset_value'}) def test_get_lot(self): @@ -150,12 +138,12 @@ def test_get_lot(self): def test_patch_lot(self): setup_routing(self.app, routes=["lot_patch"]) - self.lot.data.description = 'test_patch_lot' - - patched_lot = self.client.patch_resource_item(self.lot) - self.assertEqual(patched_lot.data.id, self.lot.data.id) + lot_id = self.lot.data.id + patch_data = {'data': {'description': 'test_patch_lot'}} + patched_lot = self.client.patch_resource_item(lot_id, patch_data) + self.assertEqual(patched_lot.data.id, lot_id) self.assertEqual(patched_lot.data.description, - self.lot.data.description) + patch_data['data']['description']) def suite(): diff --git a/openprocurement_client/tests/tests.py b/openprocurement_client/tests/tests_resources.py old mode 100755 new mode 100644 similarity index 63% rename from openprocurement_client/tests/tests.py rename to openprocurement_client/tests/tests_resources.py index 65d96e1..f789f3b --- a/openprocurement_client/tests/tests.py +++ b/openprocurement_client/tests/tests_resources.py @@ -1,5 +1,7 @@ from __future__ import print_function -from gevent import monkey; monkey.patch_all() +from gevent import monkey +monkey.patch_all() + from gevent.pywsgi import WSGIServer from bottle import Bottle from StringIO import StringIO @@ -9,17 +11,20 @@ import mock import sys import unittest -from openprocurement_client.client import TendersClient, TendersClientSync -from openprocurement_client.contract import ContractingClient -from openprocurement_client.document_service_client \ - import DocumentServiceClient +from openprocurement_client.constants import ( + ELIGIBILITY_DOCUMENTS, FINANCIAL_DOCUMENTS, QUALIFICATION_DOCUMENTS +) from openprocurement_client.exceptions import InvalidResponse, ResourceNotFound -from openprocurement_client.plan import PlansClient +from openprocurement_client.resources.contracts import ContractingClient +from openprocurement_client.resources.plans import PlansClient +from openprocurement_client.resources.tenders import ( + TendersClient, TendersClientSync +) from openprocurement_client.tests.data_dict import TEST_TENDER_KEYS, \ TEST_TENDER_KEYS_LIMITED, TEST_PLAN_KEYS, TEST_CONTRACT_KEYS from openprocurement_client.tests._server import \ API_KEY, API_VERSION, AUTH_DS_FAKE, DS_HOST_URL, DS_PORT, \ - HOST_URL, location_error, PORT, ROOT, setup_routing, setup_routing_ds, \ + HOST_URL, location_error, PORT, ROOT, setup_routing, setup_routing_ds, \ resource_partition, resource_filter @@ -44,9 +49,12 @@ def setting_up(self, client): print(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], file=sys.stderr) raise error - ds_client = getattr(self, 'ds_client', None) + ds_config = { + 'host_url': DS_HOST_URL, + 'auth_ds': AUTH_DS_FAKE + } self.client = client('', host_url=HOST_URL, api_version=API_VERSION, - ds_client=ds_client) + ds_config=ds_config) @classmethod def setting_up_ds(cls): @@ -60,8 +68,6 @@ def setting_up_ds(cls): file=sys.stderr) raise error - cls.ds_client = DocumentServiceClient(host_url=DS_HOST_URL, - auth_ds=AUTH_DS_FAKE) # to test units performing file operations outside the DS uncomment # following lines: # import logging @@ -74,7 +80,6 @@ def setting_up_ds(cls): def setUpClass(cls): cls.setting_up_ds() - @classmethod def tearDownClass(cls): cls.server_ds.stop() @@ -87,7 +92,8 @@ def setUp(self): with open(ROOT + 'tenders.json') as tenders: self.tenders = munchify(load(tenders)) - with open(ROOT + 'tender_' + TEST_TENDER_KEYS.tender_id + '.json') as tender: + with open(ROOT + 'tender_' + TEST_TENDER_KEYS.tender_id + '.json') as\ + tender: self.tender = munchify(load(tender)) def tearDown(self): @@ -101,11 +107,13 @@ def test_get_tenders(self): def test_get_latest_tenders(self): setup_routing(self.app, routes=["tenders"]) - tenders = self.client.get_latest_tenders('2015-11-16T12:00:00.960077+02:00', '') + tenders = self.client.get_latest_tenders( + '2015-11-16T12:00:00.960077+02:00') self.assertIsInstance(tenders, Iterable) self.assertEqual(tenders.data, self.tenders.data) - @mock.patch('openprocurement_client.client.TendersClient.request') + @mock.patch('openprocurement_client.resources.tenders.TendersClient.' + 'request') def test_get_tenders_failed(self, mock_request): mock_request.return_value = munchify({'status_code': 404}) self.client.params['offset'] = 'offset_value' @@ -151,17 +159,18 @@ def test_get_plans(self): def test_get_latest_plans(self): setup_routing(self.app, routes=["plans"]) - plans = self.client.get_latest_plans('2015-11-16T12:00:00.960077+02:00') + plans = self.client.get_latest_plans( + '2015-11-16T12:00:00.960077+02:00') self.assertIsInstance(plans, Iterable) self.assertEqual(plans.data, self.plans.data) - @mock.patch('openprocurement_client.plan.PlansClient.request') + @mock.patch('openprocurement_client.resources.plans.PlansClient.request') def test_get_plans_failed(self, mock_request): mock_request.return_value = munchify({'status_code': 412}) self.client.params['offset'] = 'offset_value' - with self.assertRaises(KeyError) as e: + with self.assertRaises(InvalidResponse) as e: self.client.get_plans() - self.assertEqual(e.exception.message, 'offset') + self.assertEqual(e.exception.message, 'Not described error yet.') def test_get_plan(self): setup_routing(self.app, routes=["plan"]) @@ -181,10 +190,11 @@ def test_offset_error(self): def test_patch_plan(self): setup_routing(self.app, routes=['plan_patch']) - self.plan.data.description = 'test_patch_plan' - patched_tender = self.client.patch_plan(self.plan) + patch_data = {'data': {'description': 'test_patch_plan'}} + patched_tender = self.client.patch_plan(self.plan.data.id, patch_data) self.assertEqual(patched_tender.data.id, self.plan.data.id) - self.assertEqual(patched_tender.data.description, self.plan.data.description) + self.assertEqual(patched_tender.data.description, + patch_data['data']['description']) def test_create_plan(self): setup_routing(self.app, routes=["plan_create"]) @@ -199,7 +209,8 @@ def setUp(self): with open(ROOT + 'tenders.json') as tenders: self.tenders = munchify(load(tenders)) - with open(ROOT + 'tender_' + TEST_TENDER_KEYS.tender_id + '.json') as tender: + with open(ROOT + 'tender_' + TEST_TENDER_KEYS.tender_id + '.json') as \ + tender: self.tender = munchify(load(tender)) def tearDown(self): @@ -217,51 +228,78 @@ class UserTestCase(BaseTestClass): def setUp(self): self.setting_up(client=TendersClient) - with open(ROOT + 'tender_' + TEST_TENDER_KEYS.tender_id + '.json') as tender: + with open(ROOT + 'tender_' + TEST_TENDER_KEYS.tender_id + '.json') \ + as tender: self.tender = munchify(load(tender)) - self.tender.update({'access': {'token': TEST_TENDER_KEYS['token']}}) - with open(ROOT + 'tender_' + TEST_TENDER_KEYS.empty_tender + '.json') as tender: + self.tender.update( + {'access': {'token': TEST_TENDER_KEYS['token']}} + ) + with open(ROOT + 'tender_' + TEST_TENDER_KEYS.empty_tender + '.json') \ + as tender: self.empty_tender = munchify(load(tender)) - with open(ROOT + 'tender_' + TEST_TENDER_KEYS_LIMITED.tender_id + '.json') as tender: + with open( + ROOT + 'tender_' + TEST_TENDER_KEYS_LIMITED.tender_id + '.json') \ + as tender: self.limited_tender = munchify(load(tender)) def tearDown(self): self.server.stop() - ########################################################################### # GET ITEMS LIST TEST ########################################################################### def test_get_questions(self): setup_routing(self.app, routes=["tender_subpage"]) - questions = munchify({'data': self.tender['data'].get('questions', [])}) - self.assertEqual(self.client.get_questions(self.tender), questions) + questions = munchify( + {'data': self.tender['data'].get('questions', [])} + ) + self.assertEqual(self.client.get_questions(self.tender.data.id), + questions) def test_get_documents(self): setup_routing(self.app, routes=["tender_subpage"]) - documents = munchify({'data': self.tender['data'].get('documents', [])}) - self.assertEqual(self.client.get_documents(self.tender), documents) + documents = munchify( + {'data': self.tender['data'].get('documents', [])} + ) + self.assertEqual(self.client.get_documents(self.tender.data.id), + documents) def test_get_awards_documents(self): setup_routing(self.app, routes=["tender_award_documents"]) - documents = munchify({'data': self.tender['data']['awards'][0].get('documents', [])}) - self.assertEqual(self.client.get_awards_documents(self.tender, self.tender['data']['awards'][0]['id']), documents) + documents = munchify({ + 'data': self.tender['data']['awards'][0].get('documents', []) + }) + self.assertEqual( + self.client.get_awards_documents( + self.tender.data.id, self.tender['data']['awards'][0]['id'] + ), + documents + ) def test_get_qualification_documents(self): setup_routing(self.app, routes=["tender_qualification_documents"]) - documents = munchify({'data': self.tender['data']['qualifications'][0].get('documents', [])}) - self.assertEqual(self.client.get_qualification_documents(self.tender, self.tender['data']['qualifications'][0]['id']), documents) + documents = munchify({ + 'data': + self.tender['data']['qualifications'][0].get('documents', []) + }) + self.assertEqual( + self.client.get_qualification_documents( + self.tender.data.id, + self.tender['data']['qualifications'][0]['id'] + ), + documents + ) def test_get_awards(self): setup_routing(self.app, routes=["tender_subpage"]) awards = munchify({'data': self.tender['data'].get('awards', [])}) - self.assertEqual(self.client.get_awards(self.tender), awards) + self.assertEqual(self.client.get_awards(self.tender.data.id), awards) def test_get_lots(self): setup_routing(self.app, routes=["tender_subpage"]) lots = munchify({'data': self.tender['data'].get('lots', [])}) - self.assertEqual(self.client.get_lots(self.tender), lots) + self.assertEqual(self.client.get_lots(self.tender.data.id), lots) ########################################################################### # CREATE ITEM TEST @@ -275,32 +313,48 @@ def test_create_tender(self): def test_create_question(self): setup_routing(self.app, routes=["tender_subpage_item_create"]) question = munchify({'data': 'question'}) - self.assertEqual(self.client.create_question(self.tender, question), question) + self.assertEqual( + self.client.create_question(self.tender.data.id, question), + question + ) def test_create_bid(self): setup_routing(self.app, routes=["tender_subpage_item_create"]) bid = munchify({'data': 'bid'}) - self.assertEqual(self.client.create_bid(self.tender, bid), bid) + self.assertEqual(self.client.create_bid(self.tender.data.id, bid), + bid) def test_create_lot(self): setup_routing(self.app, routes=["tender_subpage_item_create"]) lot = munchify({'data': 'lot'}) - self.assertEqual(self.client.create_lot(self.tender, lot), lot) + self.assertEqual(self.client.create_lot(self.tender.data.id, lot), + lot) def test_create_award(self): setup_routing(self.app, routes=["tender_subpage_item_create"]) award = munchify({'data': 'award'}) - self.assertEqual(self.client.create_award(self.limited_tender, award), award) + self.assertEqual( + self.client.create_award(self.limited_tender.data.id, award), + award) def test_create_cancellation(self): setup_routing(self.app, routes=["tender_subpage_item_create"]) cancellation = munchify({'data': 'cancellation'}) - self.assertEqual(self.client.create_cancellation(self.limited_tender, cancellation), cancellation) + self.assertEqual( + self.client.create_cancellation( + self.limited_tender.data.id, cancellation + ), + cancellation) def test_create_complaint(self): setup_routing(self.app, routes=["tender_subpage_item_create"]) complaint = munchify({'data': 'complaint'}) - self.assertEqual(self.client.create_complaint(self.limited_tender, complaint), complaint) + self.assertEqual( + self.client.create_complaint( + self.limited_tender.data.id, complaint + ), + complaint + ) ########################################################################### # GET ITEM TEST @@ -315,7 +369,7 @@ def test_get_question(self): question_ = munchify({"data": question}) break question = self.client.get_question( - self.tender, question_id=TEST_TENDER_KEYS.question_id + self.tender.data.id, question_id=TEST_TENDER_KEYS.question_id ) self.assertEqual(question, question_) @@ -326,7 +380,8 @@ def test_get_lot(self): if lot['id'] == TEST_TENDER_KEYS.lot_id: lot_ = munchify({"data": lot}) break - lot = self.client.get_lot(self.tender, lot_id=TEST_TENDER_KEYS.lot_id) + lot = self.client.get_lot(self.tender.data.id, + lot_id=TEST_TENDER_KEYS.lot_id) self.assertEqual(lot, lot_) def test_get_bid(self): @@ -336,18 +391,31 @@ def test_get_bid(self): if bid['id'] == TEST_TENDER_KEYS.bid_id: bid_ = munchify({"data": bid}) break - bid = self.client.get_bid(self.tender, bid_id=TEST_TENDER_KEYS.bid_id, access_token=API_KEY) + bid = self.client.get_bid(self.tender.data.id, + bid_id=TEST_TENDER_KEYS.bid_id, + access_token=API_KEY) self.assertEqual(bid, bid_) def test_get_location_error(self): setup_routing(self.app, routes=["tender_subpage_item"]) - self.assertEqual(self.client.get_question(self.empty_tender, question_id=TEST_TENDER_KEYS.question_id), - munchify(loads(location_error('questions')))) - self.assertEqual(self.client.get_lot(self.empty_tender, lot_id=TEST_TENDER_KEYS.lot_id), - munchify(loads(location_error('lots')))) - self.assertEqual(self.client.get_bid(self.empty_tender, bid_id=TEST_TENDER_KEYS.bid_id, access_token=API_KEY), - munchify(loads(location_error('bids')))) - + self.assertEqual( + self.client.get_question( + self.empty_tender.data.id, TEST_TENDER_KEYS.question_id + ), + munchify(loads(location_error('questions'))) + ) + self.assertEqual( + self.client.get_lot( + self.empty_tender.data.id, lot_id=TEST_TENDER_KEYS.lot_id + ), + munchify(loads(location_error('lots'))) + ) + self.assertEqual( + self.client.get_bid( + self.empty_tender.data.id, TEST_TENDER_KEYS.bid_id, API_KEY + ), + munchify(loads(location_error('bids'))) + ) ########################################################################### # PATCH ITEM TEST @@ -355,110 +423,231 @@ def test_get_location_error(self): def test_patch_tender(self): setup_routing(self.app, routes=["tender_patch"]) - self.tender.data.description = 'test_patch_tender' - patched_tender = self.client.patch_tender(self.tender) + patch_data = {'data': {'description': 'test_patch_tender'}} + patched_tender = self.client.patch_tender( + self.tender.data.id, patch_data + ) self.assertEqual(patched_tender.data.id, self.tender.data.id) - self.assertEqual(patched_tender.data.description, self.tender.data.description) + self.assertEqual(patched_tender.data.description, + patch_data['data']['description']) def test_patch_question(self): setup_routing(self.app, routes=["tender_subpage_item_patch"]) - question = munchify({"data": {"id": TEST_TENDER_KEYS.question_id, "description": "test_patch_question"}}) - patched_question = self.client.patch_question(self.tender, question) + question = munchify({ + "data": { + "id": TEST_TENDER_KEYS.question_id, + "description": "test_patch_question" + } + }) + patched_question = self.client.patch_question( + self.tender.data.id, question, question.data.id + ) self.assertEqual(patched_question.data.id, question.data.id) - self.assertEqual(patched_question.data.description, question.data.description) + self.assertEqual(patched_question.data.description, + question.data.description) def test_patch_bid(self): setup_routing(self.app, routes=["tender_subpage_item_patch"]) - bid = munchify({"data": {"id": TEST_TENDER_KEYS.bid_id, "description": "test_patch_bid"}}) - patched_bid = self.client.patch_bid(self.tender, bid) + bid = munchify({ + "data": { + "id": TEST_TENDER_KEYS.bid_id, + "description": "test_patch_bid" + } + }) + patched_bid = self.client.patch_bid( + self.tender.data.id, bid, bid.data.id + ) self.assertEqual(patched_bid.data.id, bid.data.id) self.assertEqual(patched_bid.data.description, bid.data.description) def test_patch_award(self): setup_routing(self.app, routes=["tender_subpage_item_patch"]) - award = munchify({"data": {"id": TEST_TENDER_KEYS.award_id, "description": "test_patch_award"}}) - patched_award =self.client.patch_award(self.tender, award) + award = munchify({ + "data": { + "id": TEST_TENDER_KEYS.award_id, + "description": "test_patch_award" + } + }) + patched_award = self.client.patch_award(self.tender.data.id, award, + award.data.id) self.assertEqual(patched_award.data.id, award.data.id) - self.assertEqual(patched_award.data.description, award.data.description) + self.assertEqual(patched_award.data.description, + award.data.description) def test_patch_cancellation(self): setup_routing(self.app, routes=["tender_subpage_item_patch"]) - cancellation = munchify({"data": {"id": TEST_TENDER_KEYS_LIMITED.cancellation_id, "description": "test_patch_cancellation"}}) - patched_cancellation =self.client.patch_cancellation(self.limited_tender, cancellation) + cancellation = munchify({ + "data": { + "id": TEST_TENDER_KEYS_LIMITED.cancellation_id, + "description": "test_patch_cancellation" + } + }) + patched_cancellation = self.client.patch_cancellation( + self.limited_tender.data.id, cancellation, cancellation.data.id + ) self.assertEqual(patched_cancellation.data.id, cancellation.data.id) - self.assertEqual(patched_cancellation.data.description, cancellation.data.description) + self.assertEqual(patched_cancellation.data.description, + cancellation.data.description) def test_patch_cancellation_document(self): setup_routing(self.app, routes=["tender_subpage_document_patch"]) - cancellation_document = munchify({"data": {"id": TEST_TENDER_KEYS_LIMITED.cancellation_document_id, "description": "test_patch_cancellation_document"}}) - patched_cancellation_document =self.client.patch_cancellation_document(self.limited_tender, cancellation_document, TEST_TENDER_KEYS_LIMITED.cancellation_id, TEST_TENDER_KEYS_LIMITED.cancellation_document_id) - self.assertEqual(patched_cancellation_document.data.id, cancellation_document.data.id) - self.assertEqual(patched_cancellation_document.data.description, cancellation_document.data.description) + cancellation_document = munchify({ + "data": { + "id": TEST_TENDER_KEYS_LIMITED.cancellation_document_id, + "description": "test_patch_cancellation_document" + } + }) + patched_cancellation_document = \ + self.client.patch_cancellation_document( + self.limited_tender.data.id, cancellation_document, + TEST_TENDER_KEYS_LIMITED.cancellation_id, + TEST_TENDER_KEYS_LIMITED.cancellation_document_id + ) + self.assertEqual(patched_cancellation_document.data.id, + cancellation_document.data.id) + self.assertEqual(patched_cancellation_document.data.description, + cancellation_document.data.description) def test_patch_complaint(self): setup_routing(self.app, routes=["tender_subpage_item_patch"]) - complaint = munchify({"data": {"id": TEST_TENDER_KEYS_LIMITED.complaint_id, "description": "test_patch_complaint"}}) - patched_complaint =self.client.patch_complaint(self.limited_tender, complaint) + complaint = munchify({ + "data": { + "id": TEST_TENDER_KEYS_LIMITED.complaint_id, + "description": "test_patch_complaint" + } + }) + patched_complaint = self.client.patch_complaint( + self.limited_tender.data.id, complaint, complaint.data.id + ) self.assertEqual(patched_complaint.data.id, complaint.data.id) - self.assertEqual(patched_complaint.data.description, complaint.data.description) + self.assertEqual(patched_complaint.data.description, + complaint.data.description) def test_patch_qualification(self): setup_routing(self.app, routes=["tender_subpage_item_patch"]) - qualification = munchify({"data": {"id": TEST_TENDER_KEYS.qualification_id, "description": "test_patch_qualification"}}) - patched_qualification = self.client.patch_qualification(self.tender, qualification) + qualification = munchify({ + "data": { + "id": TEST_TENDER_KEYS.qualification_id, + "description": "test_patch_qualification" + } + }) + patched_qualification = self.client.patch_qualification( + self.tender.data.id, qualification, qualification.data.id + ) self.assertEqual(patched_qualification.data.id, qualification.data.id) - self.assertEqual(patched_qualification.data.description, qualification.data.description) + self.assertEqual(patched_qualification.data.description, + qualification.data.description) def test_patch_lot(self): setup_routing(self.app, routes=["tender_subpage_item_patch"]) - lot = munchify({"data": {"id": TEST_TENDER_KEYS.lot_id, "description": "test_patch_lot"}}) - patched_lot = self.client.patch_lot(self.tender, lot) + lot = munchify({ + "data": { + "id": TEST_TENDER_KEYS.lot_id, + "description": "test_patch_lot" + } + }) + patched_lot = self.client.patch_lot( + self.tender.data.id, lot, lot.data.id + ) self.assertEqual(patched_lot.data.id, lot.data.id) self.assertEqual(patched_lot.data.description, lot.data.description) def test_patch_document(self): setup_routing(self.app, routes=["tender_subpage_item_patch"]) - document = munchify({"data": {"id": TEST_TENDER_KEYS.document_id, "title": "test_patch_document.txt"}}) - patched_document = self.client.patch_document(self.tender, document) + document = munchify({ + "data": { + "id": TEST_TENDER_KEYS.document_id, + "title": "test_patch_document.txt" + } + }) + patched_document = self.client.patch_document( + self.tender.data.id, document, document.data.id + ) self.assertEqual(patched_document.data.id, document.data.id) self.assertEqual(patched_document.data.title, document.data.title) def test_patch_contract(self): setup_routing(self.app, routes=["tender_subpage_item_patch"]) - contract = munchify({"data": {"id": TEST_TENDER_KEYS_LIMITED.contract_id, "title": "test_patch_contract.txt"}}) - patched_contract = self.client.patch_contract(self.limited_tender, contract) + contract = munchify({ + "data": { + "id": TEST_TENDER_KEYS_LIMITED.contract_id, + "title": "test_patch_contract.txt" + } + }) + patched_contract = self.client.patch_contract( + self.limited_tender.data.id, contract, contract.data.id + ) self.assertEqual(patched_contract.data.id, contract.data.id) self.assertEqual(patched_contract.data.title, contract.data.title) def test_patch_location_error(self): setup_routing(self.app, routes=["tender_subpage_item_patch"]) - error = munchify({"data": {"id": TEST_TENDER_KEYS.error_id}, "access": {"token": API_KEY}}) - self.assertEqual(self.client.patch_question(self.empty_tender, error), - munchify(loads(location_error('questions')))) - self.assertEqual(self.client.patch_bid(self.empty_tender, error), - munchify(loads(location_error('bids')))) - self.assertEqual(self.client.patch_award(self.empty_tender, error), - munchify(loads(location_error('awards')))) - self.assertEqual(self.client.patch_lot(self.empty_tender, error), - munchify(loads(location_error('lots')))) - self.assertEqual(self.client.patch_document(self.empty_tender, error), - munchify(loads(location_error('documents')))) - self.assertEqual(self.client.patch_qualification(self.empty_tender, error), - munchify(loads(location_error('qualifications')))) + error = munchify({ + "data": {"id": TEST_TENDER_KEYS.error_id}, + "access": {"token": API_KEY} + }) + self.assertEqual( + self.client.patch_question( + self.empty_tender.data.id, error, error.data.id + ), + munchify(loads(location_error('questions'))) + ) + self.assertEqual( + self.client.patch_bid( + self.empty_tender.data.id, error, error.data.id + ), + munchify(loads(location_error('bids'))) + ) + self.assertEqual( + self.client.patch_award( + self.empty_tender.data.id, error, error.data.id + ), + munchify(loads(location_error('awards'))) + ) + self.assertEqual( + self.client.patch_lot( + self.empty_tender.data.id, error, error.data.id + ), + munchify(loads(location_error('lots'))) + ) + self.assertEqual( + self.client.patch_document( + self.empty_tender.data.id, error, error.data.id + ), + munchify(loads(location_error('documents'))) + ) + self.assertEqual( + self.client.patch_qualification( + self.empty_tender.data.id, error, error.data.id + ), + munchify(loads(location_error('qualifications'))) + ) def test_patch_bid_document(self): setup_routing(self.app, routes=["tender_subpage_document_patch"]) - document = munchify({"data": {"id": TEST_TENDER_KEYS.document_id, "title": "test_patch_document.txt"}}) - patched_document = self.client.patch_bid_document(self.tender, document, TEST_TENDER_KEYS.bid_id, TEST_TENDER_KEYS.bid_document_id) + document = munchify({ + "data": { + "id": TEST_TENDER_KEYS.document_id, + "title": "test_patch_document.txt" + } + }) + patched_document = self.client.patch_bid_document( + self.tender.data.id, document, TEST_TENDER_KEYS.bid_id, + TEST_TENDER_KEYS.bid_document_id + ) self.assertEqual(patched_document.data.id, document.data.id) self.assertEqual(patched_document.data.title, document.data.title) def test_patch_credentials(self): setup_routing(self.app, routes=['tender_patch_credentials']) - patched_credentials = self.client.patch_credentials(self.tender.data.id, self.tender.access['token']) + patched_credentials = self.client.patch_credentials( + self.tender.data.id, self.tender.access['token'] + ) self.assertEqual(patched_credentials.data.id, self.tender.data.id) - self.assertNotEqual(patched_credentials.access.token, self.tender.access['token']) - self.assertEqual(patched_credentials.access.token, TEST_TENDER_KEYS['new_token']) + self.assertNotEqual(patched_credentials.access.token, + self.tender.access['token']) + self.assertEqual(patched_credentials.access.token, + TEST_TENDER_KEYS['new_token']) ########################################################################### # DOCUMENTS FILE TEST @@ -496,7 +685,10 @@ def test_upload_tender_document(self): file_.name = 'test_document.txt' file_.write("test upload tender document text data") file_.seek(0) - doc = self.client.upload_document(file_, self.tender) + doc = self.client.upload_document( + file_, self.tender.data.id, + access_token=self.tender.access['token'] + ) self.assertEqual(doc.data.title, file_.name) self.assertEqual(doc.data.id, TEST_TENDER_KEYS.new_document_id) @@ -504,18 +696,25 @@ def test_upload_tender_document_path(self): setup_routing(self.app, routes=["tender_document_create"]) file_name = "test_document.txt" file_path = ROOT + file_name - doc = self.client.upload_document(file_path, self.tender) + doc = self.client.upload_document( + file_path, self.tender.data.id, + access_token=self.tender.access['token'] + ) self.assertEqual(doc.data.title, file_name) self.assertEqual(doc.data.id, TEST_TENDER_KEYS.new_document_id) - @mock.patch('openprocurement_client.document_service_client.DocumentServiceClient.request') + @mock.patch('openprocurement_client.resources.document_service.' + 'DocumentServiceClient.request') def test_upload_tender_document_path_failed(self, mock_request): mock_request.return_value = munchify({'status_code': 204}) setup_routing(self.app, routes=["tender_document_create"]) file_name = "test_document.txt" file_path = ROOT + file_name with self.assertRaises(InvalidResponse): - self.client.upload_document(file_path, self.tender) + self.client.upload_document( + file_path, self.tender.data.id, + access_token=self.tender.access['token'] + ) def test_upload_qualification_document(self): setup_routing(self.app, routes=["tender_subpage_document_create"]) @@ -524,7 +723,7 @@ def test_upload_qualification_document(self): file_.write("test upload qualification document text data") file_.seek(0) doc = self.client.upload_qualification_document( - file_, self.tender, TEST_TENDER_KEYS.qualification_id + file_, self.tender.data.id, TEST_TENDER_KEYS.qualification_id ) self.assertEqual(doc.data.title, file_.name) self.assertEqual(doc.data.id, TEST_TENDER_KEYS.new_document_id) @@ -536,48 +735,49 @@ def test_upload_bid_document(self): file_.write("test upload tender document text data") file_.seek(0) doc = self.client.upload_bid_document( - file_, self.tender, TEST_TENDER_KEYS.bid_id + file_, self.tender.data.id, TEST_TENDER_KEYS.bid_id ) self.assertEqual(doc.data.title, file_.name) self.assertEqual(doc.data.id, TEST_TENDER_KEYS.new_document_id) def test_upload_bid_financial_document(self): setup_routing(self.app, routes=["tender_subpage_document_create"]) - document_type = "financial_documents" + document_type = FINANCIAL_DOCUMENTS file_ = StringIO() file_.name = 'test_document.txt' file_.write("test upload tender document text data") file_.seek(0) doc = self.client.upload_bid_document( - file_, self.tender, TEST_TENDER_KEYS.bid_id, - document_type + file_, self.tender.data.id, TEST_TENDER_KEYS.bid_id, + doc_type=document_type ) self.assertEqual(doc.data.title, file_.name) self.assertEqual(doc.data.id, TEST_TENDER_KEYS.new_document_id) def test_upload_bid_qualification_document(self): setup_routing(self.app, routes=["tender_subpage_document_create"]) - document_type = "qualificationDocuments" + document_type = QUALIFICATION_DOCUMENTS file_ = StringIO() file_.name = 'test_document.txt' file_.write("test upload tender document text data") file_.seek(0) doc = self.client.upload_bid_document( - file_, self.tender, TEST_TENDER_KEYS.bid_id, - document_type + file_, self.tender.data.id, TEST_TENDER_KEYS.bid_id, + doc_type=document_type ) self.assertEqual(doc.data.title, file_.name) self.assertEqual(doc.data.id, TEST_TENDER_KEYS.new_document_id) def test_upload_bid_eligibility_document(self): setup_routing(self.app, routes=["tender_subpage_document_create"]) - document_type = "eligibility_documents" + document_type = ELIGIBILITY_DOCUMENTS file_ = StringIO() file_.name = 'test_document.txt' file_.write("test upload tender document text data") file_.seek(0) doc = self.client.upload_bid_document( - file_, self.tender, TEST_TENDER_KEYS.bid_id, document_type + file_, self.tender.data.id, TEST_TENDER_KEYS.bid_id, + doc_type=document_type ) self.assertEqual(doc.data.title, file_.name) self.assertEqual(doc.data.id, TEST_TENDER_KEYS.new_document_id) @@ -589,7 +789,8 @@ def test_upload_cancellation_document(self): file_.write("test upload tender document text data") file_.seek(0) doc = self.client.upload_cancellation_document( - file_, self.limited_tender, TEST_TENDER_KEYS_LIMITED.cancellation_id + file_, self.limited_tender.data.id, + TEST_TENDER_KEYS_LIMITED.cancellation_id ) self.assertEqual(doc.data.title, file_.name) self.assertEqual(doc.data.id, TEST_TENDER_KEYS.new_document_id) @@ -601,7 +802,8 @@ def test_upload_complaint_document(self): file_.write("test upload tender document text data") file_.seek(0) doc = self.client.upload_complaint_document( - file_, self.limited_tender, TEST_TENDER_KEYS_LIMITED.complaint_id + file_, self.limited_tender.data.id, + TEST_TENDER_KEYS_LIMITED.complaint_id ) self.assertEqual(doc.data.title, file_.name) self.assertEqual(doc.data.id, TEST_TENDER_KEYS.new_document_id) @@ -613,14 +815,16 @@ def test_upload_award_document(self): file_.write("test upload award document text data") file_.seek(0) doc = self.client.upload_award_document( - file_, self.tender, TEST_TENDER_KEYS.award_id + file_, self.tender.data.id, TEST_TENDER_KEYS.award_id ) self.assertEqual(doc.data.title, file_.name) self.assertEqual(doc.data.id, TEST_TENDER_KEYS.new_document_id) def test_upload_document_type_error(self): setup_routing(self.app, routes=["tender_document_create"]) - self.assertRaises(TypeError, self.client.upload_document, (object, self.tender)) + self.assertRaises( + TypeError, self.client.upload_document, (object, self.tender) + ) def test_update_bid_document(self): setup_routing(self.app, routes=["tender_subpage_document_update"]) @@ -629,7 +833,7 @@ def test_update_bid_document(self): file_.write("test upload tender document text data") file_.seek(0) doc = self.client.update_bid_document( - file_, self.tender, TEST_TENDER_KEYS.bid_id, + file_, self.tender.data.id, TEST_TENDER_KEYS.bid_id, TEST_TENDER_KEYS.bid_document_id ) self.assertEqual(doc.data.title, file_.name) @@ -641,13 +845,15 @@ def test_update_bid_qualification_document(self): file_.name = 'test_document.txt' file_.write("test upload tender qualification_document text data") file_.seek(0) - document_type = "qualificationDocuments" + document_type = QUALIFICATION_DOCUMENTS doc = self.client.update_bid_document( - file_, self.tender, TEST_TENDER_KEYS.bid_id, - TEST_TENDER_KEYS.bid_qualification_document_id, document_type + file_, self.tender.data.id, TEST_TENDER_KEYS.bid_id, + TEST_TENDER_KEYS.bid_qualification_document_id, + doc_type=document_type ) self.assertEqual(doc.data.title, file_.name) - self.assertEqual(doc.data.id, TEST_TENDER_KEYS.bid_qualification_document_id) + self.assertEqual(doc.data.id, + TEST_TENDER_KEYS.bid_qualification_document_id) def test_update_bid_financial_document(self): setup_routing(self.app, routes=["tender_subpage_document_update"]) @@ -655,13 +861,14 @@ def test_update_bid_financial_document(self): file_.name = 'test_document.txt' file_.write("test upload tender financial_document text data") file_.seek(0) - document_type = "financial_documens" + document_type = FINANCIAL_DOCUMENTS doc = self.client.update_bid_document( - file_, self.tender, TEST_TENDER_KEYS.bid_id, - TEST_TENDER_KEYS.bid_financial_document_id, document_type + file_, self.tender.data.id, TEST_TENDER_KEYS.bid_id, + TEST_TENDER_KEYS.bid_financial_document_id, doc_type=document_type ) self.assertEqual(doc.data.title, file_.name) - self.assertEqual(doc.data.id, TEST_TENDER_KEYS.bid_financial_document_id) + self.assertEqual(doc.data.id, + TEST_TENDER_KEYS.bid_financial_document_id) def test_update_bid_eligibility_document(self): setup_routing(self.app, routes=["tender_subpage_document_update"]) @@ -669,13 +876,15 @@ def test_update_bid_eligibility_document(self): file_.name = 'test_document.txt' file_.write("test upload tender eligibility_document text data") file_.seek(0) - document_type = "eligibility_documents" + document_type = ELIGIBILITY_DOCUMENTS doc = self.client.update_bid_document( - file_, self.tender, TEST_TENDER_KEYS.bid_id, - TEST_TENDER_KEYS.bid_eligibility_document_id, document_type + file_, self.tender.data.id, TEST_TENDER_KEYS.bid_id, + TEST_TENDER_KEYS.bid_eligibility_document_id, + doc_type=document_type ) self.assertEqual(doc.data.title, file_.name) - self.assertEqual(doc.data.id, TEST_TENDER_KEYS.bid_eligibility_document_id) + self.assertEqual(doc.data.id, + TEST_TENDER_KEYS.bid_eligibility_document_id) def test_update_cancellation_document(self): setup_routing(self.app, routes=["tender_subpage_document_update"]) @@ -684,11 +893,13 @@ def test_update_cancellation_document(self): file_.write("test upload tender document text data") file_.seek(0) doc = self.client.update_cancellation_document( - file_, self.limited_tender, TEST_TENDER_KEYS_LIMITED.cancellation_id, + file_, self.limited_tender.data.id, + TEST_TENDER_KEYS_LIMITED.cancellation_id, TEST_TENDER_KEYS_LIMITED.cancellation_document_id ) self.assertEqual(doc.data.title, file_.name) - self.assertEqual(doc.data.id, TEST_TENDER_KEYS_LIMITED.cancellation_document_id) + self.assertEqual(doc.data.id, + TEST_TENDER_KEYS_LIMITED.cancellation_document_id) ########################################################################### # DELETE ITEMS LIST TEST @@ -696,22 +907,33 @@ def test_update_cancellation_document(self): def test_delete_bid(self): setup_routing(self.app, routes=["tender_subpage_item_delete"]) - bid_id = resource_partition(TEST_TENDER_KEYS.tender_id, part="bids")[0]['id'] - deleted_bid = self.client.delete_bid(self.tender, bid_id, API_KEY) + bid_id = resource_partition( + TEST_TENDER_KEYS.tender_id, part="bids")[0]['id'] + deleted_bid = self.client.delete_bid(self.tender.data.id, bid_id, + API_KEY) self.assertFalse(deleted_bid) def test_delete_lot(self): setup_routing(self.app, routes=["tender_subpage_item_delete"]) - lot_id = resource_partition(TEST_TENDER_KEYS.tender_id, part="lots")[0]['id'] - deleted_lot = self.client.delete_lot(self.tender, lot_id) + lot_id = resource_partition( + TEST_TENDER_KEYS.tender_id, part="lots")[0]['id'] + deleted_lot = self.client.delete_lot(self.tender.data.id, lot_id) self.assertFalse(deleted_lot) def test_delete_location_error(self): setup_routing(self.app, routes=["tender_subpage_item_delete"]) - self.assertEqual(self.client.delete_bid(self.empty_tender, TEST_TENDER_KEYS.error_id, API_KEY), - munchify(loads(location_error('bids')))) - self.assertEqual(self.client.delete_lot(self.empty_tender, TEST_TENDER_KEYS.error_id), - munchify(loads(location_error('lots')))) + self.assertEqual( + self.client.delete_bid( + self.empty_tender.data.id, TEST_TENDER_KEYS.error_id, API_KEY + ), + munchify(loads(location_error('bids'))) + ) + self.assertEqual( + self.client.delete_lot( + self.empty_tender, TEST_TENDER_KEYS.error_id + ), + munchify(loads(location_error('lots'))) + ) class ContractingUserTestCase(BaseTestClass): @@ -770,7 +992,10 @@ def test_create_contract(self): def test_create_change(self): setup_routing(self.app, routes=['contract_subpage_item_create']) - change = self.client.create_change(self.contract, self.change) + contract_id = self.contract.data.id + access_token = self.contract.access['token'] + change = self.client.create_change(contract_id, access_token, + self.change) self.assertEqual(change, self.change) ########################################################################### @@ -781,7 +1006,10 @@ def test_upload_contract_document(self): setup_routing(self.app, routes=['contract_document_create']) file_ = generate_file_obj('test_document.txt', 'test upload contract document text data') - doc = self.client.upload_document(file_, self.contract) + doc = self.client.upload_document( + file_, self.contract.data.id, + access_token=self.contract.access['token'] + ) self.assertEqual(doc.data.title, file_.name) self.assertEqual(doc.data.id, TEST_CONTRACT_KEYS.new_document_id) @@ -793,22 +1021,27 @@ def test_patch_document(self): setup_routing(self.app, routes=['contract_subpage_item_patch']) document = munchify({'data': {'id': TEST_CONTRACT_KEYS.document_id, 'title': 'test_patch_document.txt'}}) - patched_document = self.client.patch_document(self.contract, document) + patched_document = self.client.patch_document( + self.contract.data.id, document, document.data.id, + self.contract.access['token']) self.assertEqual(patched_document.data.id, document.data.id) self.assertEqual(patched_document.data.title, document.data.title) def test_patch_change(self): setup_routing(self.app, routes=['contract_change_patch']) - patch_change_data = \ - {'data': {'rationale': - TEST_CONTRACT_KEYS['patch_change_rationale']}} + patch_change_data = { + 'data': { + 'rationale': TEST_CONTRACT_KEYS['patch_change_rationale'] + } + } patched_change = self.change.copy() patched_change['data'].update(patch_change_data['data']) patched_change = munchify(patched_change) - + contract_id = self.contract.data.id + access_token = self.contract.access['token'] + changes_id = self.change.data.id response_change = self.client.patch_change( - self.contract, self.change.data.id, - data=patch_change_data + contract_id, changes_id, access_token, patch_change_data ) self.assertEqual(response_change, patched_change) @@ -827,10 +1060,14 @@ def test_retrieve_contract_credentials(self): def test_patch_contract(self): setup_routing(self.app, routes=["contract_patch"]) - self.contract.data.description = 'test_patch_contract' - patched_contract = self.client.patch_contract(self.contract) + patch_data = {'data': {'description': 'test_patch_contract'}} + access_token = self.contract.access['token'] + patched_contract = self.client.patch_contract( + self.contract.data.id, access_token, patch_data + ) self.assertEqual(patched_contract.data.id, self.contract.data.id) - self.assertEqual(patched_contract.data.description, self.contract.data.description) + self.assertEqual(patched_contract.data.description, + patch_data['data']['description']) def suite(): diff --git a/openprocurement_client/tests/tests_sync.py b/openprocurement_client/tests/tests_sync.py index e8bf964..1aef39b 100644 --- a/openprocurement_client/tests/tests_sync.py +++ b/openprocurement_client/tests/tests_sync.py @@ -1,19 +1,16 @@ from __future__ import print_function -from gevent import monkey; monkey.patch_all() +from gevent import monkey +monkey.patch_all() -from openprocurement_client.client import TendersClientSync +from openprocurement_client.clients import APIResourceClientSync from openprocurement_client.exceptions import ( RequestFailed, PreconditionFailed, ResourceNotFound ) -from openprocurement_client.sync import ( - get_response, - get_resource_items, - get_tenders, - ResourceFeeder, -) -from openprocurement_client.sync import logger +from openprocurement_client.utils import get_response +from openprocurement_client.resources.sync import ResourceFeeder +from openprocurement_client.utils import LOGGER from gevent.queue import Queue from munch import munchify @@ -38,7 +35,7 @@ def __nonzero__(self): return bool(0) -class TestTendersClientSync(TendersClientSync): +class TestAPIResourceClientSync(APIResourceClientSync): def __init__(self): pass @@ -50,66 +47,80 @@ def setUp(self): self.log_capture_string = StringIO() self.ch = logging.StreamHandler(self.log_capture_string) self.ch.setLevel(logging.ERROR) - logger.addHandler(self.ch) - self.logger = logger - - @mock.patch('openprocurement_client.client.TendersClientSync.sync_tenders') - def test_success_response(self, mock_sync_tenders): - mock_sync_tenders.return_value = 'success' - mock_client = TestTendersClientSync() + LOGGER.addHandler(self.ch) + self.logger = LOGGER + + @mock.patch('openprocurement_client.clients.APIResourceClientSync.' + 'sync_resource_items') + def test_success_response(self, mock_sync_resource_items): + mock_sync_resource_items.return_value = 'success' + mock_client = TestAPIResourceClientSync() response = get_response(mock_client, {}) self.assertEqual(response, 'success') - @mock.patch('openprocurement_client.client.TendersClientSync.sync_tenders') - def test_precondition_failed_error(self, mock_sync_tenders): - mock_sync_tenders.side_effect = [PreconditionFailed(), 'success'] - mock_client = TestTendersClientSync() + @mock.patch('openprocurement_client.clients.APIResourceClientSync.' + 'sync_resource_items') + def test_precondition_failed_error(self, mock_sync_resource_items): + mock_sync_resource_items.side_effect = [PreconditionFailed(), + 'success'] + mock_client = TestAPIResourceClientSync() response = get_response(mock_client, {}) log_strings = self.log_capture_string.getvalue().split('\n') - self.assertEqual(log_strings[0], 'PreconditionFailed: Not described error yet.') + self.assertEqual(log_strings[0], + 'PreconditionFailed: Not described error yet.') self.assertEqual(response, 'success') - @mock.patch('openprocurement_client.client.TendersClientSync.sync_tenders') - def test_connection_error(self, mock_sync_tenders): + @mock.patch('openprocurement_client.clients.APIResourceClientSync.' + 'sync_resource_items') + def test_connection_error(self, mock_sync_resource_items): error = ConnectionError('connection error') - mock_sync_tenders.side_effect = [error, error, 'success'] - mock_client = TestTendersClientSync() + mock_sync_resource_items.side_effect = [error, error, 'success'] + mock_client = TestAPIResourceClientSync() response = get_response(mock_client, {}) log_strings = self.log_capture_string.getvalue().split('\n') self.assertEqual(log_strings[0], 'ConnectionError: connection error') self.assertEqual(log_strings[1], 'ConnectionError: connection error') self.assertEqual(response, 'success') - @mock.patch('openprocurement_client.client.TendersClientSync.sync_tenders') - def test_request_failed_error(self, mock_sync_tenders): + @mock.patch('openprocurement_client.clients.APIResourceClientSync.' + 'sync_resource_items') + def test_request_failed_error(self, mock_sync_resource_items): error1 = munchify({'status_code': 429, }) error2 = munchify({'status_code': 404, }) - mock_sync_tenders.side_effect = [RequestFailed(error1), RequestFailed(error2), 'success'] - mock_client = TestTendersClientSync() + mock_sync_resource_items.side_effect = [RequestFailed(error1), + RequestFailed(error2), + 'success'] + mock_client = TestAPIResourceClientSync() response = get_response(mock_client, {}) log_strings = self.log_capture_string.getvalue().split('\n') self.assertEqual(log_strings[0], 'Request failed. Status code: 429') self.assertEqual(log_strings[1], 'Request failed. Status code: 404') self.assertEqual(response, 'success') - @mock.patch('openprocurement_client.client.TendersClientSync.sync_tenders') - def test_resource_not_found_error(self, mock_sync_tenders): - mock_sync_tenders.side_effect = [ResourceNotFound(), 'success'] + @mock.patch('openprocurement_client.clients.APIResourceClientSync.' + 'sync_resource_items') + def test_resource_not_found_error(self, mock_sync_resource_items): + mock_sync_resource_items.side_effect = [ResourceNotFound(), 'success'] params = {'offset': 'offset', 'some_data': 'data'} - mock_client = TestTendersClientSync() + mock_client = TestAPIResourceClientSync() mock_client.session = mock.MagicMock() mock_client.session.cookies.clear = mock.Mock() response = get_response(mock_client, params) log_strings = self.log_capture_string.getvalue().split('\n') - self.assertEqual(log_strings[0], 'Resource not found: Not described error yet.') + self.assertEqual(log_strings[0], + 'Resource not found: Not described error yet.') self.assertEqual(mock_client.session.cookies.clear.call_count, 1) self.assertEqual(params, {'some_data': 'data'}) self.assertEqual(response, 'success') - @mock.patch('openprocurement_client.client.TendersClientSync.sync_tenders') - def test_exception_error(self, mock_sync_tenders): - mock_sync_tenders.side_effect = [InvalidHeader('invalid header'), Exception('exception message'), 'success'] - mock_client = TestTendersClientSync() + @mock.patch('openprocurement_client.clients.APIResourceClientSync.' + 'sync_resource_items') + def test_exception_error(self, mock_sync_resource_items): + mock_sync_resource_items.side_effect = [ + InvalidHeader('invalid header'), Exception('exception message'), + 'success' + ] + mock_client = TestAPIResourceClientSync() response = get_response(mock_client, {}) log_strings = self.log_capture_string.getvalue().split('\n') self.assertEqual(log_strings[0], 'Exception: invalid header') @@ -151,11 +162,13 @@ def setUp(self): def test_instance_initialization(self): self.resource_feeder = ResourceFeeder() self.assertEqual(self.resource_feeder.key, '') - self.assertEqual(self.resource_feeder.host, 'https://lb.api-sandbox.openprocurement.org/') + self.assertEqual(self.resource_feeder.host, + 'https://lb.api-sandbox.openprocurement.org/') self.assertEqual(self.resource_feeder.version, '2.3') self.assertEqual(self.resource_feeder.resource, 'tenders') self.assertEqual(self.resource_feeder.adaptive, False) - self.assertEqual(self.resource_feeder.extra_params, {'opt_fields': 'status', 'mode': '_all_'}) + self.assertEqual(self.resource_feeder.extra_params, + {'opt_fields': 'status', 'mode': '_all_'}) self.assertEqual(self.resource_feeder.retrievers_params, { 'down_requests_sleep': 5, 'up_requests_sleep': 1, @@ -189,39 +202,48 @@ def test_handle_response_data(self): self.assertIn('tender2', list(self.resource_feeder.queue.queue)) self.assertNotIn('tender3', list(self.resource_feeder.queue.queue)) - @mock.patch('openprocurement_client.client.TendersClientSync.sync_tenders') - @mock.patch('openprocurement_client.sync.spawn') - def test_start_sync(self, mock_spawn, mock_sync_tenders): + @mock.patch('openprocurement_client.clients.APIResourceClientSync.' + 'sync_resource_items') + @mock.patch('openprocurement_client.resources.sync.spawn') + def test_start_sync(self, mock_spawn, mock_sync_resource_items): mock_spawn.return_value = 'spawn result' - mock_sync_tenders.return_value = self.response + mock_sync_resource_items.return_value = self.response self.resource_feeder = ResourceFeeder() self.resource_feeder.init_api_clients() self.resource_feeder.start_sync() - self.assertEqual(self.resource_feeder.backward_params['offset'], self.response.next_page.offset) - self.assertEqual(self.resource_feeder.forward_params['offset'], self.response.prev_page.offset) - self.assertEqual(self.resource_feeder.forward_params['offset'], self.response.prev_page.offset) + self.assertEqual(self.resource_feeder.backward_params['offset'], + self.response.next_page.offset) + self.assertEqual(self.resource_feeder.forward_params['offset'], + self.response.prev_page.offset) + self.assertEqual(self.resource_feeder.forward_params['offset'], + self.response.prev_page.offset) self.assertEqual(mock_spawn.call_count, 2) mock_spawn.assert_called_with(self.resource_feeder.retriever_forward) self.assertEqual(self.resource_feeder.backward_worker, 'spawn result') self.assertEqual(self.resource_feeder.forward_worker, 'spawn result') - @mock.patch('openprocurement_client.client.TendersClientSync.sync_tenders') - @mock.patch('openprocurement_client.sync.spawn') - def test_restart_sync(self, mock_spawn, mock_sync_tenders): + @mock.patch('openprocurement_client.clients.APIResourceClientSync.' + 'sync_resource_items') + @mock.patch('openprocurement_client.resources.sync.spawn') + def test_restart_sync(self, mock_spawn, mock_sync_resource_items): mock_spawn.return_value = mock.MagicMock() mock_spawn.return_value.kill = mock.MagicMock('kill result') - mock_sync_tenders.return_value = self.response + mock_sync_resource_items.return_value = self.response self.resource_feeder = ResourceFeeder() self.resource_feeder.init_api_clients() self.resource_feeder.start_sync() self.resource_feeder.restart_sync() self.assertEqual(mock_spawn.return_value.kill.call_count, 2) - @mock.patch('openprocurement_client.client.TendersClientSync.sync_tenders') - @mock.patch('openprocurement_client.sync.spawn') - def test_get_resource_items_zero_value(self, mock_spawn, mock_sync_tenders): - mock_sync_tenders.side_effect = [self.response, munchify( - {'data': {}, 'next_page': {'offset': 'next_page'}, 'prev_page': {'offset': 'next_page'}} + @mock.patch('openprocurement_client.clients.APIResourceClientSync.' + 'sync_resource_items') + @mock.patch('openprocurement_client.resources.sync.spawn') + def test_get_resource_items_zero_value(self, mock_spawn, + mock_sync_resource_items): + mock_sync_resource_items.side_effect = [self.response, munchify( + {'data': {}, + 'next_page': {'offset': 'next_page'}, + 'prev_page': {'offset': 'next_page'}} )] mock_spawn.return_value = mock.MagicMock() mock_spawn.return_value.value = 0 @@ -231,11 +253,15 @@ def test_get_resource_items_zero_value(self, mock_spawn, mock_sync_tenders): result = self.resource_feeder.get_resource_items() self.assertEqual(tuple(result), tuple(self.response.data)) - @mock.patch('openprocurement_client.client.TendersClientSync.sync_tenders') - @mock.patch('openprocurement_client.sync.spawn') - def test_get_resource_items_non_zero_value(self, mock_spawn, mock_sync_tenders): - mock_sync_tenders.side_effect = [self.response, munchify( - {'data': {}, 'next_page': {'offset': 'next_page'}, 'prev_page': {'offset': 'next_page'}} + @mock.patch('openprocurement_client.clients.APIResourceClientSync.' + 'sync_resource_items') + @mock.patch('openprocurement_client.resources.sync.spawn') + def test_get_resource_items_non_zero_value(self, mock_spawn, + mock_sync_resource_items): + mock_sync_resource_items.side_effect = [self.response, munchify( + {'data': {}, + 'next_page': {'offset': 'next_page'}, + 'prev_page': {'offset': 'next_page'}} )] mock_spawn.return_value = mock.MagicMock() mock_spawn.return_value.value = 1 @@ -245,12 +271,15 @@ def test_get_resource_items_non_zero_value(self, mock_spawn, mock_sync_tenders): result = self.resource_feeder.get_resource_items() self.assertEqual(tuple(result), tuple(self.response.data)) - @mock.patch('openprocurement_client.client.TendersClientSync.sync_tenders') - @mock.patch('openprocurement_client.sync.spawn') - @mock.patch('openprocurement_client.sync.sleep') - def test_feeder_zero_value(self, mock_sleep, mock_spawn, mock_sync_tenders): + @mock.patch('openprocurement_client.clients.APIResourceClientSync.' + 'sync_resource_items') + @mock.patch('openprocurement_client.resources.sync.spawn') + @mock.patch('openprocurement_client.resources.sync.sleep') + def test_feeder_zero_value(self, mock_sleep, mock_spawn, + mock_sync_resource_items): mock_sleep.return_value = 'sleeping' - mock_sync_tenders.side_effect = [self.response, self.response, ConnectionError('conn error')] + mock_sync_resource_items.side_effect = [self.response, self.response, + ConnectionError('conn error')] self.resource_feeder = ResourceFeeder() mock_spawn.return_value = mock.MagicMock() mock_spawn.return_value.value = 0 @@ -260,12 +289,14 @@ def test_feeder_zero_value(self, mock_sleep, mock_spawn, mock_sync_tenders): self.assertEqual(e.exception.message, 'conn error') self.assertEqual(mock_sleep.call_count, 1) - @mock.patch('openprocurement_client.client.TendersClientSync.sync_tenders') - @mock.patch('openprocurement_client.sync.spawn') - @mock.patch('openprocurement_client.sync.sleep') - def test_feeder(self, mock_sleep, mock_spawn, mock_sync_tenders): + @mock.patch('openprocurement_client.clients.APIResourceClientSync.' + 'sync_resource_items') + @mock.patch('openprocurement_client.resources.sync.spawn') + @mock.patch('openprocurement_client.resources.sync.sleep') + def test_feeder(self, mock_sleep, mock_spawn, mock_sync_resource_items): mock_sleep.return_value = 'sleeping' - mock_sync_tenders.side_effect = [self.response, self.response, ConnectionError('conn error')] + mock_sync_resource_items.side_effect = [self.response, self.response, + ConnectionError('conn error')] self.resource_feeder = ResourceFeeder() mock_spawn.return_value = mock.MagicMock() mock_spawn.return_value.value = 1 @@ -275,7 +306,7 @@ def test_feeder(self, mock_sleep, mock_spawn, mock_sync_tenders): self.assertEqual(e.exception.message, 'conn error') self.assertEqual(mock_sleep.call_count, 1) - @mock.patch('openprocurement_client.sync.spawn') + @mock.patch('openprocurement_client.resources.sync.spawn') def test_run_feeder(self, mock_spawn): mock_spawn.return_value = mock.MagicMock() self.resource_feeder = ResourceFeeder() @@ -283,18 +314,20 @@ def test_run_feeder(self, mock_spawn): mock_spawn.assert_called_with(self.resource_feeder.feeder) self.assertEqual(result, self.resource_feeder.queue) - @mock.patch('openprocurement_client.sync.get_response') + @mock.patch('openprocurement_client.resources.sync.get_response') def test_retriever_backward(self, mock_get_response): mock_get_response.side_effect = [self.response, munchify({'data': {}})] self.resource_feeder = ResourceFeeder() self.resource_feeder.init_api_clients() self.resource_feeder.backward_params = {"limit": 0} self.resource_feeder.backward_client = mock.MagicMock() - self.resource_feeder.cookies = self.resource_feeder.backward_client.session.cookies + self.resource_feeder.cookies =\ + self.resource_feeder.backward_client.session.cookies self.resource_feeder.retriever_backward() - self.assertEqual(self.resource_feeder.backward_params['offset'], self.response.next_page.offset) + self.assertEqual(self.resource_feeder.backward_params['offset'], + self.response.next_page.offset) - @mock.patch('openprocurement_client.sync.get_response') + @mock.patch('openprocurement_client.resources.sync.get_response') def test_retriever_backward_wrong_cookies(self, mock_get_response): mock_get_response.return_value = self.response self.resource_feeder = ResourceFeeder() @@ -307,7 +340,7 @@ def test_retriever_backward_wrong_cookies(self, mock_get_response): self.resource_feeder.retriever_backward() self.assertEqual(e.exception.message, 'LB Server mismatch') - @mock.patch('openprocurement_client.sync.get_response') + @mock.patch('openprocurement_client.resources.sync.get_response') def test_retriever_forward_wrong_cookies(self, mock_get_response): mock_get_response.return_value = self.response self.resource_feeder = ResourceFeeder() @@ -320,7 +353,7 @@ def test_retriever_forward_wrong_cookies(self, mock_get_response): self.resource_feeder.retriever_forward() self.assertEqual(e.exception.message, 'LB Server mismatch') - @mock.patch('openprocurement_client.sync.get_response') + @mock.patch('openprocurement_client.resources.sync.get_response') def test_retriever_forward(self, mock_get_response): mock_get_response.side_effect = [ self.response, @@ -331,13 +364,15 @@ def test_retriever_forward(self, mock_get_response): self.resource_feeder.init_api_clients() self.resource_feeder.forward_params = {"limit": 0} self.resource_feeder.forward_client = mock.MagicMock() - self.resource_feeder.forward_client.session.cookies = self.resource_feeder.cookies + self.resource_feeder.forward_client.session.cookies = \ + self.resource_feeder.cookies with self.assertRaises(ConnectionError) as e: self.resource_feeder.retriever_forward() self.assertEqual(e.exception.message, 'connection error') - self.assertEqual(self.resource_feeder.forward_params['offset'], self.response.next_page.offset) + self.assertEqual(self.resource_feeder.forward_params['offset'], + self.response.next_page.offset) - @mock.patch('openprocurement_client.sync.get_response') + @mock.patch('openprocurement_client.resources.sync.get_response') def test_retriever_forward_no_data(self, mock_get_response): mock_get_response.side_effect = [ munchify({'data': {}, 'next_page': {'offset': 'next_page'}}), @@ -347,13 +382,15 @@ def test_retriever_forward_no_data(self, mock_get_response): self.resource_feeder.init_api_clients() self.resource_feeder.forward_params = {"limit": 0} self.resource_feeder.forward_client = mock.MagicMock() - self.resource_feeder.forward_client.session.cookies = self.resource_feeder.cookies + self.resource_feeder.forward_client.session.cookies = \ + self.resource_feeder.cookies with self.assertRaises(ConnectionError) as e: self.resource_feeder.retriever_forward() self.assertEqual(e.exception.message, 'connection error') - self.assertEqual(self.resource_feeder.forward_params['offset'], 'next_page') + self.assertEqual(self.resource_feeder.forward_params['offset'], + 'next_page') - @mock.patch('openprocurement_client.sync.get_response') + @mock.patch('openprocurement_client.resources.sync.get_response') def test_retriever_forward_adaptive(self, mock_get_response): mock_get_response.side_effect = [ self.response, @@ -365,13 +402,15 @@ def test_retriever_forward_adaptive(self, mock_get_response): self.resource_feeder.init_api_clients() self.resource_feeder.forward_params = {"limit": 0} self.resource_feeder.forward_client = mock.MagicMock() - self.resource_feeder.forward_client.session.cookies = self.resource_feeder.cookies + self.resource_feeder.forward_client.session.cookies = \ + self.resource_feeder.cookies with self.assertRaises(ConnectionError) as e: self.resource_feeder.retriever_forward() self.assertEqual(e.exception.message, 'connection error') - self.assertEqual(self.resource_feeder.forward_params['offset'], self.response.next_page.offset) + self.assertEqual(self.resource_feeder.forward_params['offset'], + self.response.next_page.offset) - @mock.patch('openprocurement_client.sync.get_response') + @mock.patch('openprocurement_client.resources.sync.get_response') def test_retriever_forward_no_data_adaptive(self, mock_get_response): mock_get_response.side_effect = [ munchify({'data': {}, 'next_page': {'offset': 'next_page'}}), @@ -391,25 +430,13 @@ def test_retriever_forward_no_data_adaptive(self, mock_get_response): self.resource_feeder.init_api_clients() self.resource_feeder.forward_params = {"limit": 0} self.resource_feeder.forward_client = mock.MagicMock() - self.resource_feeder.forward_client.session.cookies = self.resource_feeder.cookies + self.resource_feeder.forward_client.session.cookies = \ + self.resource_feeder.cookies with self.assertRaises(ConnectionError) as e: self.resource_feeder.retriever_forward() self.assertEqual(e.exception.message, 'connection error') - self.assertEqual(self.resource_feeder.forward_params['offset'], 'next_page') - - @mock.patch('openprocurement_client.sync.ResourceFeeder.get_resource_items') - def test_get_resource_items(self, mock_get_resource_items): - mock_get_resource_items.return_value = 'feeder_instance' - result = get_resource_items(resource='tenders') - self.assertEqual(result, 'feeder_instance') - self.assertEqual(mock_get_resource_items.call_count, 1) - - @mock.patch('openprocurement_client.sync.get_resource_items') - def test_get_tenders(self, mock_get_resource_items): - mock_get_resource_items.return_value = 'get_resource_items_call' - result = get_tenders() - self.assertEqual(result, 'get_resource_items_call') - self.assertEqual(mock_get_resource_items.call_count, 1) + self.assertEqual(self.resource_feeder.forward_params['offset'], + 'next_page') def suite(): diff --git a/openprocurement_client/tests/tests_utils.py b/openprocurement_client/tests/tests_utils.py index 61b19bb..4853d0b 100644 --- a/openprocurement_client/tests/tests_utils.py +++ b/openprocurement_client/tests/tests_utils.py @@ -1,9 +1,12 @@ from __future__ import print_function -from gevent import monkey; monkey.patch_all() +from gevent import monkey +monkey.patch_all() -from openprocurement_client.client import TendersClient +from openprocurement_client.resources.tenders import TendersClient from openprocurement_client.exceptions import IdNotFound -from openprocurement_client.utils import tenders_feed, get_tender_id_by_uaid, get_tender_by_uaid +from openprocurement_client.utils import ( + tenders_feed, get_tender_id_by_uaid, get_tender_by_uaid +) from munch import munchify @@ -49,7 +52,8 @@ def setUp(self): ] }""")) - @mock.patch('openprocurement_client.client.TendersClient.get_tenders') + @mock.patch('openprocurement_client.resources.tenders.TendersClient.' + 'get_tenders') def test_tenders_feed(self, mock_get_tenders): mock_get_tenders.side_effect = [self.response.data, []] client = TestTendersClient() @@ -61,8 +65,10 @@ def test_tenders_feed(self, mock_get_tenders): result.next() @mock.patch('openprocurement_client.utils.get_tender_id_by_uaid') - @mock.patch('openprocurement_client.client.TendersClient.get_tender') - def test_get_tender_by_uaid(self, mock_get_tender, mock_get_tender_id_by_uaid): + @mock.patch('openprocurement_client.resources.tenders.TendersClient.' + 'get_tender') + def test_get_tender_by_uaid(self, mock_get_tender, + mock_get_tender_id_by_uaid): mock_get_tender_id_by_uaid.return_value = 'tender_id' mock_get_tender.return_value = 'called get_tender' client = TestTendersClient() @@ -73,12 +79,14 @@ def test_get_tender_by_uaid(self, mock_get_tender, mock_get_tender_id_by_uaid): self.assertEqual(mock_get_tender.call_count, 1) self.assertEqual(result, 'called get_tender') - @mock.patch('openprocurement_client.client.TendersClient.get_tenders') + @mock.patch('openprocurement_client.resources.tenders.TendersClient.' + 'get_tenders') def test_get_tender_id_by_uaid(self, mock_get_tenders): mock_get_tenders.side_effect = [self.response.data, []] client = TestTendersClient() with self.assertRaises(IdNotFound): - result = get_tender_id_by_uaid('f3849ade33534174b8402579152a5f41', client, id_field='dateModified') + result = get_tender_id_by_uaid('f3849ade33534174b8402579152a5f41', + client, id_field='dateModified') self.assertEqual(result, self.response.data[0]['id']) diff --git a/openprocurement_client/utils.py b/openprocurement_client/utils.py index 64a5a6e..a16c8f5 100644 --- a/openprocurement_client/utils.py +++ b/openprocurement_client/utils.py @@ -1,20 +1,72 @@ # -*- coding: utf-8 -*- +from io import FileIO +from functools import wraps from openprocurement_client.exceptions import IdNotFound -from time import sleep +from requests.exceptions import ConnectionError +from openprocurement_client.exceptions import ( + RequestFailed, + PreconditionFailed, + ResourceNotFound +) +from os import path +from time import sleep, time import logging -logger = logging.getLogger() + + +LOGGER = logging.getLogger() + + +# Using FileIO here instead of open() +# to be able to override the filename +# which is later used when uploading the file. +# +# Explanation: +# +# 1) requests reads the filename +# from "name" attribute of a file-like object, +# there is no other way to specify a filename; +# +# 2) The attribute may contain the full path to file, +# which does not work well as a filename; +# +# 3) The attribute is readonly when using open(), +# unlike FileIO object. + + +def verify_file(fn): + """ Decorator for upload or update document methods""" + @wraps(fn) + def wrapper(self, file_, *args, **kwargs): + if isinstance(file_, basestring): + file_ = FileIO(file_, 'rb') + file_.name = path.basename(file_.name) + if hasattr(file_, 'read'): + # A file-like object must have 'read' method + output = fn(self, file_, *args, **kwargs) + file_.close() + return output + else: + try: + file_.close() + except AttributeError: + pass + raise TypeError( + 'Expected either a string containing a path to file or ' + 'a file-like object, got {}'.format(type(file_)) + ) + return wrapper def tenders_feed(client, sleep_time=10): while True: tender_list = True while tender_list: - logger.info("Get next batch") + LOGGER.info("Get next batch") tender_list = client.get_tenders() for tender in tender_list: - logger.debug("Return tender {}".format(str(tender))) + LOGGER.debug("Return tender {}".format(str(tender))) yield tender - logger.info("Wait to get next batch") + LOGGER.info("Wait to get next batch") sleep(sleep_time) @@ -58,3 +110,63 @@ def get_plan_id_by_uaid(ua_id, client, descending=True, id_field='planID'): if tender[id_field] == ua_id: return tender.id raise IdNotFound + + +def get_response(client, params): + response_fail = True + sleep_interval = 0.2 + while response_fail: + try: + start = time() + response = client.sync_resource_items(params) + end = time() - start + LOGGER.debug( + 'Request duration {} sec'.format(end), + extra={'FEEDER_REQUEST_DURATION': end * 1000}) + response_fail = False + except PreconditionFailed as e: + LOGGER.error('PreconditionFailed: {}'.format(e.message), + extra={'MESSAGE_ID': 'precondition_failed'}) + continue + except ConnectionError as e: + LOGGER.error('ConnectionError: {}'.format(e.message), + extra={'MESSAGE_ID': 'connection_error'}) + if sleep_interval > 300: + raise e + sleep_interval = sleep_interval * 2 + LOGGER.debug( + 'Client sleeping after ConnectionError {} sec.'.format( + sleep_interval)) + sleep(sleep_interval) + continue + except RequestFailed as e: + LOGGER.error('Request failed. Status code: {}'.format( + e.status_code), extra={'MESSAGE_ID': 'request_failed'}) + if e.status_code == 429: + if sleep_interval > 120: + raise e + LOGGER.debug( + 'Client sleeping after RequestFailed {} sec.'.format( + sleep_interval)) + sleep_interval = sleep_interval * 2 + sleep(sleep_interval) + continue + except ResourceNotFound as e: + LOGGER.error('Resource not found: {}'.format(e.message), + extra={'MESSAGE_ID': 'resource_not_found'}) + LOGGER.debug('Clear offset and client cookies.') + client.session.cookies.clear() + del params['offset'] + continue + except Exception as e: + LOGGER.error('Exception: {}'.format(e.message), + extra={'MESSAGE_ID': 'exceptions'}) + if sleep_interval > 300: + raise e + sleep_interval = sleep_interval * 2 + LOGGER.debug( + 'Client sleeping after Exception: {}, {} sec.'.format( + e.message, sleep_interval)) + sleep(sleep_interval) + continue + return response diff --git a/setup.cfg b/setup.cfg index 1003930..f2cee2d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,4 @@ [nosetests] +all-modules=1 cover-package=openprocurement_client with-coverage=1 diff --git a/setup.py b/setup.py index 1b64636..35c3ddd 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ from setuptools import find_packages, setup -import os version = '2.0b7' @@ -45,9 +44,9 @@ # -*- Extra requirements: -*- ], tests_require=tests_require, - extras_require = tests_require, + extras_require=tests_require, entry_points=""" # -*- Entry points: -*- """, - test_suite="openprocurement_client.tests" + test_suite="openprocurement_client.tests.main:suite" ) From bb12aefa69992b104d93577ddf4bb1992622d8f4 Mon Sep 17 00:00:00 2001 From: Vitalii Martyniak Date: Thu, 7 Sep 2017 09:31:43 +0300 Subject: [PATCH 05/20] Update README.rst Change coverage badge link --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index f1a0078..d42ce5b 100644 --- a/README.rst +++ b/README.rst @@ -8,5 +8,5 @@ Reference implementation of a client for OpenProcurement API. .. |Build Status| image:: https://travis-ci.org/openprocurement/openprocurement.client.python.svg?branch=use_requests :target: https://travis-ci.org/openprocurement/openprocurement.client.python -.. |Coverage Status| image:: https://coveralls.io/repos/openprocurement/openprocurement.client.python/badge.svg?branch=master&service=github - :target: https://coveralls.io/github/openprocurement/openprocurement.client.python?branch=use_requests +.. |Coverage Status| image:: https://coveralls.io/repos/github/openprocurement/openprocurement.client.python/badge.svg?branch=registry +:target: https://coveralls.io/github/openprocurement/openprocurement.client.python?branch=registry From b1f651d647068340542aa34ff7df0eee69f5cb0a Mon Sep 17 00:00:00 2001 From: Vitalii Martyniak Date: Thu, 7 Sep 2017 09:32:36 +0300 Subject: [PATCH 06/20] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index d42ce5b..cd9e30c 100644 --- a/README.rst +++ b/README.rst @@ -9,4 +9,4 @@ Reference implementation of a client for OpenProcurement API. .. |Build Status| image:: https://travis-ci.org/openprocurement/openprocurement.client.python.svg?branch=use_requests :target: https://travis-ci.org/openprocurement/openprocurement.client.python .. |Coverage Status| image:: https://coveralls.io/repos/github/openprocurement/openprocurement.client.python/badge.svg?branch=registry -:target: https://coveralls.io/github/openprocurement/openprocurement.client.python?branch=registry + :target: https://coveralls.io/github/openprocurement/openprocurement.client.python?branch=registry From e480f03545c9f6848d9228ccc274cccbc5b402a4 Mon Sep 17 00:00:00 2001 From: sasha-kantoriz Date: Thu, 21 Sep 2017 15:39:34 +0300 Subject: [PATCH 07/20] Add patch method for Assets and Lots clients --- openprocurement_client/resources/assets.py | 2 ++ openprocurement_client/resources/lots.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/openprocurement_client/resources/assets.py b/openprocurement_client/resources/assets.py index fce60b0..64f6c1b 100644 --- a/openprocurement_client/resources/assets.py +++ b/openprocurement_client/resources/assets.py @@ -15,3 +15,5 @@ def __init__(self, *args, **kwargs): get_asset = APIResourceClient.get_resource_item get_assets = APIResourceClient.get_resource_items + + patch_asset = APIResourceClient.patch_resource_item diff --git a/openprocurement_client/resources/lots.py b/openprocurement_client/resources/lots.py index 62b57b4..b3202ac 100644 --- a/openprocurement_client/resources/lots.py +++ b/openprocurement_client/resources/lots.py @@ -15,3 +15,5 @@ def __init__(self, *args, **kwargs): get_lot = APIResourceClient.get_resource_item get_lots = APIResourceClient.get_resource_items + + patch_lot = APIResourceClient.patch_resource_item From 7225a5e636b7f15c05dd1f73420d932460dda87d Mon Sep 17 00:00:00 2001 From: Andrew Leitsius Date: Tue, 26 Sep 2017 13:21:49 +0300 Subject: [PATCH 08/20] Update clients --- openprocurement_client/clients.py | 14 +- openprocurement_client/resources/assets.py | 4 - openprocurement_client/resources/auctions.py | 220 ++++++++++++++++++ openprocurement_client/resources/contracts.py | 4 - openprocurement_client/resources/lots.py | 4 - openprocurement_client/resources/plans.py | 5 - openprocurement_client/resources/tenders.py | 7 +- setup.py | 5 +- 8 files changed, 233 insertions(+), 30 deletions(-) create mode 100644 openprocurement_client/resources/auctions.py diff --git a/openprocurement_client/clients.py b/openprocurement_client/clients.py index a84788b..e2611c6 100755 --- a/openprocurement_client/clients.py +++ b/openprocurement_client/clients.py @@ -9,8 +9,7 @@ from retrying import retry from munch import munchify -from openprocurement_client.resources.document_service import \ - DocumentServiceClient +from openprocurement_client.resources.document_service import DocumentServiceClient LOGGER = logging.getLogger(__name__) @@ -23,10 +22,11 @@ class APIBaseClient(APITemplateClient): host_url = 'https://api-sandbox.openprocurement.org' api_version = '0' headers = {'Content-Type': 'application/json'} + resource = '' def __init__(self, key='', - resource='tenders', + resource=None, host_url=None, api_version=None, params=None, @@ -52,9 +52,10 @@ def __init__(self, 'HEAD', '{}/api/{}/spore'.format(self.host_url, self.api_version) ) response.raise_for_status() - self.resource = resource + # print (self.resource, resource) + self.resource = self.resource or resource self.prefix_path = '{}/api/{}/{}'.format(self.host_url, - self.api_version, resource) + self.api_version, self.resource) def _create_resource_item(self, url, payload, headers=None, method='POST'): _headers = self.headers.copy() @@ -182,9 +183,6 @@ def renew_cookies(self): class APIResourceClient(APIBaseClient): """ API Resource Client """ - def __init__(self, *args, **kwargs): - super(APIResourceClient, self).__init__(*args, **kwargs) - ########################################################################### # CREATE CLIENT METHODS ########################################################################### diff --git a/openprocurement_client/resources/assets.py b/openprocurement_client/resources/assets.py index 64f6c1b..e0b5da8 100644 --- a/openprocurement_client/resources/assets.py +++ b/openprocurement_client/resources/assets.py @@ -8,10 +8,6 @@ class AssetsClient(APIResourceClient): resource = ASSETS - def __init__(self, *args, **kwargs): - super(AssetsClient, self).__init__(resource=self.resource, *args, - **kwargs) - get_asset = APIResourceClient.get_resource_item get_assets = APIResourceClient.get_resource_items diff --git a/openprocurement_client/resources/auctions.py b/openprocurement_client/resources/auctions.py new file mode 100644 index 0000000..d429916 --- /dev/null +++ b/openprocurement_client/resources/auctions.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- +from openprocurement_client.clients import ( + APIResourceClient, APIResourceClientSync +) +from openprocurement_client.constants import ( + AUCTIONS, + AWARDS, + BIDS, + CANCELLATIONS, + CONTRACTS, + DOCUMENTS, + QUESTIONS +) +from retrying import retry + + +class AuctionsClient(APIResourceClient): + """client for auctions""" + + resource = AUCTIONS + + ########################################################################### + # CREATE ITEM API METHODS + ########################################################################### + + def create_auction(self, auction): + return self.create_resource_item(auction) + + def create_question(self, auction_id, question): + return self.create_resource_item_subitem( + auction_id, question, QUESTIONS + ) + + def create_bid(self, auction_id, bid): + return self.create_resource_item_subitem(auction_id, bid, BIDS) + + def create_cancellation(self, auction_id, cancellation): + return self.create_resource_item_subitem( + auction_id, cancellation, CANCELLATIONS + ) + + def create_thin_document(self, auction_id, document_data): + return self.create_resource_item_subitem( + auction_id, document_data, DOCUMENTS + ) + + ########################################################################### + # GET ITEMS LIST API METHODS + ########################################################################### + + @retry(stop_max_attempt_number=5) + def get_auctions(self, params=None, feed='changes'): + return self.get_resource_items(params=params, feed=feed) + + def get_latest_auctions(self, date): + return self.get_latest_resource_items(date) + + def get_questions(self, auction_id): + return self.get_resource_item_subitem(auction_id, QUESTIONS) + + def get_documents(self, auction_id): + return self.get_resource_item_subitem(auction_id, DOCUMENTS) + + def get_awards_documents(self, auction_id, award_id): + return self.get_resource_item_subitem( + auction_id, DOCUMENTS, depth_path='{}/{}'.format(AWARDS, award_id) + ) + + def get_awards(self, auction_id): + return self.get_resource_item_subitem(auction_id, AWARDS) + + ########################################################################### + # GET ITEM API METHODS + ########################################################################### + + def get_auction(self, auction_id): + return self.get_resource_item(auction_id) + + def get_question(self, auction_id, question_id): + return self.get_resource_item_subitem( + auction_id, question_id, depth_path=QUESTIONS + ) + + def get_bid(self, auction_id, bid_id, access_token=None): + return self.get_resource_item_subitem( + auction_id, bid_id, depth_path=BIDS, access_token=access_token + ) + + ########################################################################### + # PATCH ITEM API METHODS + ########################################################################### + + def patch_auction(self, auction_id, patch_data): + return self.patch_resource_item(auction_id, patch_data) + + def patch_question(self, auction_id, question, question_id): + return self.patch_resource_item_subitem( + auction_id, question, QUESTIONS, subitem_id=question_id + ) + + def patch_bid(self, auction_id, bid, bid_id): + return self.patch_resource_item_subitem( + auction_id, bid, BIDS, subitem_id=bid_id + ) + + def patch_bid_document(self, auction_id, document_data, bid_id, + document_id): + depth_path = '{}/{}'.format(BIDS, bid_id) + return self.patch_resource_item_subitem( + auction_id, document_data, DOCUMENTS, subitem_id=document_id, + depth_path=depth_path + ) + + def patch_award(self, auction_id, award, award_id): + return self.patch_resource_item_subitem( + auction_id, award, AWARDS, subitem_id=award_id + ) + + def patch_award_document(self, auction_id, document_data, award_id, + document_id): + depth_path = '{}/{}'.format(AWARDS, award_id) + return self.patch_resource_item_subitem( + auction_id, document_data, DOCUMENTS, subitem_id=document_id, + depth_path=depth_path + ) + + def patch_cancellation(self, auction_id, cancellation, cancellation_id): + return self.patch_resource_item_subitem( + auction_id, cancellation, CANCELLATIONS, + subitem_id=cancellation_id + ) + + def patch_cancellation_document(self, auction_id, cancellation, + cancellation_id, cancellation_doc_id): + depth_path = '{}/{}'.format(CANCELLATIONS, cancellation_id) + return self.patch_resource_item_subitem( + auction_id, cancellation, DOCUMENTS, + subitem_id=cancellation_doc_id, depth_path=depth_path + ) + + def patch_contract(self, auction_id, contract, contract_id): + return self.patch_resource_item_subitem( + auction_id, contract, CONTRACTS, subitem_id=contract_id + ) + + def patch_contract_document(self, auction_id, document_data, contract_id, + document_id): + return self.patch_resource_item_subitem( + auction_id, document_data, DOCUMENTS, subitem_id=document_id, + depth_path='{}/{}'.format(CONTRACTS, contract_id) + ) + + ########################################################################### + # UPLOAD FILE API METHODS + ########################################################################### + + def upload_bid_document(self, file_, auction_id, bid_id, + doc_registration=True, access_token=None, + doc_type=DOCUMENTS): + depth_path = '{}/{}'.format(BIDS, bid_id) + return self.upload_document( + file_, auction_id, doc_registration, + access_token, depth_path, doc_type + ) + + def upload_cancellation_document(self, file_, auction_id, cancellation_id, + doc_registration=True, access_token=None): + depth_path = '{}/{}'.format(CANCELLATIONS, cancellation_id) + return self.upload_document(file_, auction_id, + doc_registration=doc_registration, + access_token=access_token, + depth_path=depth_path) + + def upload_award_document(self, file_, auction_id, award_id, + doc_registration=True, access_token=None): + depth_path = '{}/{}'.format(AWARDS, award_id) + return self.upload_document(file_, auction_id, + doc_registration=doc_registration, + access_token=access_token, + depth_path=depth_path) + + def upload_contract_document(self, file_, auction_id, contract_id, + doc_registration=True, access_token=None): + depth_path = '{}/{}'.format(CONTRACTS, contract_id) + return self.upload_document(file_, auction_id, doc_registration, + access_token, depth_path) + + ########################################################################### + # UPDATE FILE API METHODS + ########################################################################### + + def update_bid_document(self, file_, auction_id, bid_id, document_id, + doc_registration=True, access_token=None, + doc_type=DOCUMENTS): + depth_path = '{}/{}'.format(BIDS, bid_id) + return self.update_document( + file_, auction_id, document_id, doc_registration, + access_token, depth_path, doc_type + ) + + def update_cancellation_document(self, file_, auction_id, cancellation_id, + document_id, doc_registration=True, + access_token=None): + depth_path = '{}/{}'.format(CANCELLATIONS, cancellation_id) + return self.update_document( + file_, auction_id, document_id, doc_registration, + access_token, depth_path + ) + + ########################################################################### + # DELETE ITEMS LIST API METHODS + ########################################################################### + + def delete_bid(self, auction_id, bid_id, access_token=None): + return self.delete_resource_item_subitem( + auction_id, BIDS, bid_id, access_token=access_token + ) + + ########################################################################### + diff --git a/openprocurement_client/resources/contracts.py b/openprocurement_client/resources/contracts.py index 8452a0e..8fc555b 100644 --- a/openprocurement_client/resources/contracts.py +++ b/openprocurement_client/resources/contracts.py @@ -7,10 +7,6 @@ class ContractingClient(APIResourceClient): """ Contracting client """ resource = CONTRACTS - def __init__(self, *args, **kwargs): - super(ContractingClient, self).__init__(resource=self.resource, *args, - **kwargs) - def create_contract(self, contract): return self.create_resource_item(contract) diff --git a/openprocurement_client/resources/lots.py b/openprocurement_client/resources/lots.py index b3202ac..2ae7255 100644 --- a/openprocurement_client/resources/lots.py +++ b/openprocurement_client/resources/lots.py @@ -8,10 +8,6 @@ class LotsClient(APIResourceClient): resource = LOTS - def __init__(self, *args, **kwargs): - super(LotsClient, self).__init__(resource=self.resource, *args, - **kwargs) - get_lot = APIResourceClient.get_resource_item get_lots = APIResourceClient.get_resource_items diff --git a/openprocurement_client/resources/plans.py b/openprocurement_client/resources/plans.py index 5d76eb1..1da6bcf 100644 --- a/openprocurement_client/resources/plans.py +++ b/openprocurement_client/resources/plans.py @@ -12,11 +12,6 @@ class PlansClient(APIResourceClient): api_version = '0' resource = PLANS - def __init__(self, *args, **kwargs): - - super(PlansClient, self).__init__(resource=self.resource, *args, - **kwargs) - ########################################################################### # GET ITEMS LIST API METHODS ########################################################################### diff --git a/openprocurement_client/resources/tenders.py b/openprocurement_client/resources/tenders.py index a2f6cf6..ba33c76 100644 --- a/openprocurement_client/resources/tenders.py +++ b/openprocurement_client/resources/tenders.py @@ -4,16 +4,14 @@ ) from openprocurement_client.constants import ( AWARDS, BIDS, CANCELLATIONS, COMPLAINTS, CONTRACTS, DOCUMENTS, - LOTS, QUALIFICATIONS, QUESTIONS + LOTS, QUALIFICATIONS, QUESTIONS, TENDERS ) from retrying import retry class TendersClient(APIResourceClient): """client for tenders""" - - def __init__(self, *args, **kwargs): - super(TendersClient, self).__init__(*args, **kwargs) + resource = TENDERS ########################################################################### # CREATE ITEM API METHODS @@ -306,6 +304,7 @@ class Client(TendersClient): class TendersClientSync(APIResourceClientSync): + resource = TENDERS sync_tenders = APIResourceClientSync.sync_resource_items diff --git a/setup.py b/setup.py index 35c3ddd..75accef 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,10 @@ 'retrying', 'simplejson', 'requests', - 'python-magic' + 'python-magic', + 'bottle', + 'mock', + 'nose' # -*- Extra requirements: -*- ], tests_require=tests_require, From c2437593ab201a354d813fc9d9847fa001c3be7e Mon Sep 17 00:00:00 2001 From: Andrew Leitsius Date: Mon, 6 Nov 2017 14:07:12 +0200 Subject: [PATCH 09/20] Prepare openprocurement_client 2.1+sale. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 75accef..6924c24 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import find_packages, setup -version = '2.0b7' +version = '2.1+sale' tests_require = { 'test': [ From ad8b97e56b309aff23aeeef3ee7984bd3496e79d Mon Sep 17 00:00:00 2001 From: andrii-shchudlo Date: Tue, 12 Dec 2017 14:40:49 +0200 Subject: [PATCH 10/20] Add acc_token --- openprocurement_client/resources/auctions.py | 96 +++++++----- openprocurement_client/resources/tenders.py | 151 +++++++++++-------- 2 files changed, 146 insertions(+), 101 deletions(-) diff --git a/openprocurement_client/resources/auctions.py b/openprocurement_client/resources/auctions.py index d429916..eb1242f 100644 --- a/openprocurement_client/resources/auctions.py +++ b/openprocurement_client/resources/auctions.py @@ -26,22 +26,26 @@ class AuctionsClient(APIResourceClient): def create_auction(self, auction): return self.create_resource_item(auction) - def create_question(self, auction_id, question): + def create_question(self, auction_id, question, access_token=None): return self.create_resource_item_subitem( - auction_id, question, QUESTIONS + auction_id, question, QUESTIONS, access_token=access_token ) - def create_bid(self, auction_id, bid): - return self.create_resource_item_subitem(auction_id, bid, BIDS) + def create_bid(self, auction_id, bid, access_token=None): + return self.create_resource_item_subitem( + auction_id, bid, BIDS, access_token=access_token + ) - def create_cancellation(self, auction_id, cancellation): + def create_cancellation(self, auction_id, cancellation, + access_token=None): return self.create_resource_item_subitem( - auction_id, cancellation, CANCELLATIONS + auction_id, cancellation, CANCELLATIONS, + access_token=access_token ) - def create_thin_document(self, auction_id, document_data): + def create_thin_document(self, auction_id, document_data, access_token=None): return self.create_resource_item_subitem( - auction_id, document_data, DOCUMENTS + auction_id, document_data, DOCUMENTS, access_token=access_token ) ########################################################################### @@ -55,19 +59,23 @@ def get_auctions(self, params=None, feed='changes'): def get_latest_auctions(self, date): return self.get_latest_resource_items(date) - def get_questions(self, auction_id): - return self.get_resource_item_subitem(auction_id, QUESTIONS) + def get_questions(self, auction_id, access_token=None): + return self.get_resource_item_subitem(auction_id, QUESTIONS, + access_token=access_token) - def get_documents(self, auction_id): - return self.get_resource_item_subitem(auction_id, DOCUMENTS) + def get_documents(self, auction_id, access_token=None): + return self.get_resource_item_subitem(auction_id, DOCUMENTS, + access_token=access_token) - def get_awards_documents(self, auction_id, award_id): + def get_awards_documents(self, auction_id, award_id, access_token=None): return self.get_resource_item_subitem( - auction_id, DOCUMENTS, depth_path='{}/{}'.format(AWARDS, award_id) + auction_id, DOCUMENTS, depth_path='{}/{}'.format(AWARDS, award_id), + access_token=access_token ) - def get_awards(self, auction_id): - return self.get_resource_item_subitem(auction_id, AWARDS) + def get_awards(self, auction_id, access_token=None): + return self.get_resource_item_subitem(auction_id, AWARDS, + access_token=access_token) ########################################################################### # GET ITEM API METHODS @@ -76,9 +84,10 @@ def get_awards(self, auction_id): def get_auction(self, auction_id): return self.get_resource_item(auction_id) - def get_question(self, auction_id, question_id): + def get_question(self, auction_id, question_id, access_token=None): return self.get_resource_item_subitem( - auction_id, question_id, depth_path=QUESTIONS + auction_id, question_id, depth_path=QUESTIONS, + access_token=access_token ) def get_bid(self, auction_id, bid_id, access_token=None): @@ -90,64 +99,71 @@ def get_bid(self, auction_id, bid_id, access_token=None): # PATCH ITEM API METHODS ########################################################################### - def patch_auction(self, auction_id, patch_data): - return self.patch_resource_item(auction_id, patch_data) + def patch_auction(self, auction_id, patch_data, access_token=None): + return self.patch_resource_item(auction_id, patch_data, + access_token=access_token) - def patch_question(self, auction_id, question, question_id): + def patch_question(self, auction_id, question, question_id, + access_token=None): return self.patch_resource_item_subitem( - auction_id, question, QUESTIONS, subitem_id=question_id + auction_id, question, QUESTIONS, + subitem_id=question_id, access_token=access_token ) - def patch_bid(self, auction_id, bid, bid_id): + def patch_bid(self, auction_id, bid, bid_id, access_token=None): return self.patch_resource_item_subitem( - auction_id, bid, BIDS, subitem_id=bid_id + auction_id, bid, BIDS, subitem_id=bid_id, access_token=access_token ) def patch_bid_document(self, auction_id, document_data, bid_id, - document_id): + document_id, access_token=None): depth_path = '{}/{}'.format(BIDS, bid_id) return self.patch_resource_item_subitem( auction_id, document_data, DOCUMENTS, subitem_id=document_id, - depth_path=depth_path + depth_path=depth_path, access_token=access_token ) - def patch_award(self, auction_id, award, award_id): + def patch_award(self, auction_id, award, award_id, access_token=None): return self.patch_resource_item_subitem( - auction_id, award, AWARDS, subitem_id=award_id + auction_id, award, AWARDS, subitem_id=award_id, + access_token=access_token ) def patch_award_document(self, auction_id, document_data, award_id, - document_id): + document_id, access_token=None): depth_path = '{}/{}'.format(AWARDS, award_id) return self.patch_resource_item_subitem( auction_id, document_data, DOCUMENTS, subitem_id=document_id, - depth_path=depth_path + depth_path=depth_path, access_token=access_token ) - def patch_cancellation(self, auction_id, cancellation, cancellation_id): + def patch_cancellation(self, auction_id, cancellation, cancellation_id, + access_token=None): return self.patch_resource_item_subitem( auction_id, cancellation, CANCELLATIONS, - subitem_id=cancellation_id + subitem_id=cancellation_id, access_token=access_token ) - def patch_cancellation_document(self, auction_id, cancellation, - cancellation_id, cancellation_doc_id): + def patch_cancellation_document(self, auction_id, cancellation, cancellation_id, + cancellation_doc_id, access_token=None): depth_path = '{}/{}'.format(CANCELLATIONS, cancellation_id) return self.patch_resource_item_subitem( - auction_id, cancellation, DOCUMENTS, - subitem_id=cancellation_doc_id, depth_path=depth_path + auction_id, cancellation, DOCUMENTS, subitem_id=cancellation_doc_id, + depth_path=depth_path, access_token=access_token ) - def patch_contract(self, auction_id, contract, contract_id): + def patch_contract(self, auction_id, contract, contract_id, access_token=None): return self.patch_resource_item_subitem( - auction_id, contract, CONTRACTS, subitem_id=contract_id + auction_id, contract, CONTRACTS, subitem_id=contract_id, + access_token=access_token ) def patch_contract_document(self, auction_id, document_data, contract_id, - document_id): + document_id, access_token=None): return self.patch_resource_item_subitem( auction_id, document_data, DOCUMENTS, subitem_id=document_id, - depth_path='{}/{}'.format(CONTRACTS, contract_id) + depth_path='{}/{}'.format(CONTRACTS, contract_id), + access_token=access_token ) ########################################################################### diff --git a/openprocurement_client/resources/tenders.py b/openprocurement_client/resources/tenders.py index ba33c76..a3e9b4c 100644 --- a/openprocurement_client/resources/tenders.py +++ b/openprocurement_client/resources/tenders.py @@ -20,39 +20,47 @@ class TendersClient(APIResourceClient): def create_tender(self, tender): return self.create_resource_item(tender) - def create_question(self, tender_id, question): + def create_question(self, tender_id, question, access_token=None): return self.create_resource_item_subitem( - tender_id, question, QUESTIONS + tender_id, question, QUESTIONS, access_token=access_token ) - def create_bid(self, tender_id, bid): - return self.create_resource_item_subitem(tender_id, bid, BIDS) + def create_bid(self, tender_id, bid, access_token=None): + return self.create_resource_item_subitem( + tender_id, bid, BIDS, access_token=access_token + ) - def create_lot(self, tender_id, lot): - return self.create_resource_item_subitem(tender_id, lot, LOTS) + def create_lot(self, tender_id, lot, access_token=None): + return self.create_resource_item_subitem( + tender_id, lot, LOTS, access_token=access_token + ) - def create_award(self, tender_id, award): - return self.create_resource_item_subitem(tender_id, award, AWARDS) + def create_award(self, tender_id, award, access_token=None): + return self.create_resource_item_subitem( + tender_id, award, AWARDS, access_token=access_token + ) - def create_cancellation(self, tender_id, cancellation): + def create_cancellation(self, tender_id, cancellation, access_token=None): return self.create_resource_item_subitem( - tender_id, cancellation, CANCELLATIONS + tender_id, cancellation, CANCELLATIONS, access_token=access_token ) - def create_complaint(self, tender_id, complaint): + def create_complaint(self, tender_id, complaint, access_token=None): return self.create_resource_item_subitem( - tender_id, complaint, COMPLAINTS + tender_id, complaint, COMPLAINTS, access_token=access_token ) - def create_award_complaint(self, tender_id, complaint, award_id): + def create_award_complaint(self, tender_id, complaint, award_id, + access_token=None): depth_path = '{}/{}'.format(AWARDS, award_id) return self.create_resource_item_subitem( - tender_id, complaint, COMPLAINTS, depth_path=depth_path + tender_id, complaint, COMPLAINTS, + depth_path=depth_path, access_token=access_token ) - def create_thin_document(self, tender_id, document_data): + def create_thin_document(self, tender_id, document_data, access_token=None): return self.create_resource_item_subitem( - tender_id, document_data, DOCUMENTS + tender_id, document_data, DOCUMENTS, access_token=access_token ) ########################################################################### @@ -66,28 +74,35 @@ def get_tenders(self, params=None, feed='changes'): def get_latest_tenders(self, date): return self.get_latest_resource_items(date) - def get_questions(self, tender_id): - return self.get_resource_item_subitem(tender_id, QUESTIONS) + def get_questions(self, tender_id, access_token=None): + return self.get_resource_item_subitem(tender_id, QUESTIONS, + access_token=access_token) - def get_documents(self, tender_id): - return self.get_resource_item_subitem(tender_id, DOCUMENTS) + def get_documents(self, tender_id, access_token=None): + return self.get_resource_item_subitem(tender_id, DOCUMENTS, + access_token=access_token) - def get_awards_documents(self, tender_id, award_id): + def get_awards_documents(self, tender_id, award_id, access_token=None): return self.get_resource_item_subitem( - tender_id, DOCUMENTS, depth_path='{}/{}'.format(AWARDS, award_id) + tender_id, DOCUMENTS, depth_path='{}/{}'.format(AWARDS, award_id), + access_token=access_token ) - def get_qualification_documents(self, tender_id, qualification_id): + def get_qualification_documents(self, tender_id, qualification_id, + access_token=None): return self.get_resource_item_subitem( tender_id, DOCUMENTS, - depth_path='{}/{}'.format(QUALIFICATIONS, qualification_id) + depth_path='{}/{}'.format(QUALIFICATIONS, qualification_id), + access_token=access_token ) - def get_awards(self, tender_id): - return self.get_resource_item_subitem(tender_id, AWARDS) + def get_awards(self, tender_id, access_token=None): + return self.get_resource_item_subitem(tender_id, AWARDS, + access_token=access_token) - def get_lots(self, tender_id): - return self.get_resource_item_subitem(tender_id, LOTS) + def get_lots(self, tender_id, access_token=None): + return self.get_resource_item_subitem(tender_id, LOTS, + access_token=access_token) ########################################################################### # GET ITEM API METHODS @@ -96,9 +111,10 @@ def get_lots(self, tender_id): def get_tender(self, tender_id): return self.get_resource_item(tender_id) - def get_question(self, tender_id, question_id): + def get_question(self, tender_id, question_id, access_token=None): return self.get_resource_item_subitem( - tender_id, question_id, depth_path=QUESTIONS + tender_id, question_id, depth_path=QUESTIONS, + access_token=access_token ) def get_bid(self, tender_id, bid_id, access_token=None): @@ -106,96 +122,109 @@ def get_bid(self, tender_id, bid_id, access_token=None): tender_id, bid_id, depth_path=BIDS, access_token=access_token ) - def get_lot(self, tender_id, lot_id): + def get_lot(self, tender_id, lot_id, access_token=None): return self.get_resource_item_subitem( - tender_id, lot_id, depth_path=LOTS + tender_id, lot_id, depth_path=LOTS, access_token=access_token ) ########################################################################### # PATCH ITEM API METHODS ########################################################################### - def patch_tender(self, tender_id, patch_data): - return self.patch_resource_item(tender_id, patch_data) + def patch_tender(self, tender_id, patch_data, access_token=None): + return self.patch_resource_item(tender_id, patch_data, + access_token=access_token) - def patch_question(self, tender_id, question, question_id): + def patch_question(self, tender_id, question, question_id, access_token=None): return self.patch_resource_item_subitem( - tender_id, question, QUESTIONS, subitem_id=question_id + tender_id, question, QUESTIONS, + subitem_id=question_id, access_token=access_token ) - def patch_bid(self, tender_id, bid, bid_id): + def patch_bid(self, tender_id, bid, bid_id, access_token=None): return self.patch_resource_item_subitem( - tender_id, bid, BIDS, subitem_id=bid_id + tender_id, bid, BIDS, subitem_id=bid_id, access_token=access_token ) def patch_bid_document(self, tender_id, document_data, bid_id, - document_id): + document_id, access_token=None): depth_path = '{}/{}'.format(BIDS, bid_id) return self.patch_resource_item_subitem( tender_id, document_data, DOCUMENTS, subitem_id=document_id, - depth_path=depth_path + depth_path=depth_path, access_token=access_token ) - def patch_award(self, tender_id, award, award_id): + def patch_award(self, tender_id, award, award_id, access_token=None): return self.patch_resource_item_subitem( - tender_id, award, AWARDS, subitem_id=award_id + tender_id, award, AWARDS, subitem_id=award_id, + access_token=access_token ) def patch_award_document(self, tender_id, document_data, award_id, - document_id): + document_id, access_token=None): depth_path = '{}/{}'.format(AWARDS, award_id) return self.patch_resource_item_subitem( tender_id, document_data, DOCUMENTS, subitem_id=document_id, - depth_path=depth_path + depth_path=depth_path, access_token=access_token ) - def patch_cancellation(self, tender_id, cancellation, cancellation_id): + def patch_cancellation(self, tender_id, cancellation, cancellation_id, + access_token=None): return self.patch_resource_item_subitem( tender_id, cancellation, CANCELLATIONS, - subitem_id=cancellation_id + subitem_id=cancellation_id, access_token=access_token ) def patch_cancellation_document(self, tender_id, cancellation, - cancellation_id, cancellation_doc_id): + cancellation_id, cancellation_doc_id, + access_token=None): depth_path = '{}/{}'.format(CANCELLATIONS, cancellation_id) return self.patch_resource_item_subitem( tender_id, cancellation, DOCUMENTS, - subitem_id=cancellation_doc_id, depth_path=depth_path + subitem_id=cancellation_doc_id, depth_path=depth_path, + access_token=access_token ) - def patch_complaint(self, tender_id, complaint, complaint_id): + def patch_complaint(self, tender_id, complaint, complaint_id, + access_token=None): return self.patch_resource_item_subitem( - tender_id, complaint, COMPLAINTS, subitem_id=complaint_id + tender_id, complaint, COMPLAINTS, subitem_id=complaint_id, + access_token=access_token ) def patch_award_complaint(self, tender_id, complaint, award_id, - complaint_id): + complaint_id, access_token=None): return self.patch_resource_item_subitem( tender_id, complaint, COMPLAINTS, subitem_id=complaint_id, - depth_path='{}/{}'.format(AWARDS, award_id) + depth_path='{}/{}'.format(AWARDS, award_id), + access_token=access_token ) - def patch_lot(self, tender_id, lot, lot_id): + def patch_lot(self, tender_id, lot, lot_id, access_token=None): return self.patch_resource_item_subitem( - tender_id, lot, LOTS, subitem_id=lot_id + tender_id, lot, LOTS, subitem_id=lot_id, access_token=access_token ) - def patch_qualification(self, tender_id, qualification, qualification_id): + def patch_qualification(self, tender_id, qualification, qualification_id, + access_token=None): return self.patch_resource_item_subitem( tender_id, qualification, QUALIFICATIONS, - subitem_id=qualification_id + subitem_id=qualification_id, access_token=access_token ) - def patch_contract(self, tender_id, contract, contract_id): + def patch_contract(self, tender_id, contract, contract_id, access_token=None): return self.patch_resource_item_subitem( - tender_id, contract, CONTRACTS, subitem_id=contract_id + tender_id, contract, CONTRACTS, subitem_id=contract_id, + access_token=access_token ) def patch_contract_document(self, tender_id, document_data, contract_id, - document_id): + document_id, access_token=None): return self.patch_resource_item_subitem( - tender_id, document_data, DOCUMENTS, subitem_id=document_id, - depth_path='{}/{}'.format(CONTRACTS, contract_id) + tender_id, document_data, DOCUMENTS, + subitem_id=document_id, + depth_path='{}/{}'.format(CONTRACTS, contract_id), + access_token=access_token ) ########################################################################### From 64b3a51358f61284a552b09ac917ffadc8bae665 Mon Sep 17 00:00:00 2001 From: Scandie Date: Fri, 15 Dec 2017 17:26:52 +0200 Subject: [PATCH 11/20] Update setup.py --- setup.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 6924c24..65e2d4b 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,8 @@ from setuptools import find_packages, setup -version = '2.1+sale' +version = '2.1.1+sale' -tests_require = { +extras_require = { 'test': [ 'bottle', 'mock', @@ -46,8 +46,7 @@ 'nose' # -*- Extra requirements: -*- ], - tests_require=tests_require, - extras_require=tests_require, + extras_require=extras_require, entry_points=""" # -*- Entry points: -*- """, From 6d7960fb1d42499a231996d853ab3ebd2e2632ab Mon Sep 17 00:00:00 2001 From: Andrew Leitsius Date: Wed, 23 May 2018 17:44:28 +0300 Subject: [PATCH 12/20] Add patch auction to lot client --- openprocurement_client/resources/lots.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openprocurement_client/resources/lots.py b/openprocurement_client/resources/lots.py index 2ae7255..e2db820 100644 --- a/openprocurement_client/resources/lots.py +++ b/openprocurement_client/resources/lots.py @@ -13,3 +13,8 @@ class LotsClient(APIResourceClient): get_lots = APIResourceClient.get_resource_items patch_lot = APIResourceClient.patch_resource_item + + def patch_auction(self, lot_id, data, auction_id, access_token): + return self.patch_resource_item_subitem( + lot_id, {'data': data}, 'auctions', auction_id, access_token=access_token + ) From 4207388a9b95011e06eceec5f49c02f8b5f3f75e Mon Sep 17 00:00:00 2001 From: Andrew Leitsius Date: Thu, 7 Jun 2018 14:07:47 +0300 Subject: [PATCH 13/20] Add transfers client and change_ownership method --- openprocurement_client/clients.py | 10 ++++++++++ openprocurement_client/constants.py | 1 + openprocurement_client/resources/transfers.py | 15 +++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 openprocurement_client/resources/transfers.py diff --git a/openprocurement_client/clients.py b/openprocurement_client/clients.py index e2611c6..eab51bf 100755 --- a/openprocurement_client/clients.py +++ b/openprocurement_client/clients.py @@ -393,6 +393,16 @@ def extract_credentials(self, resource_item_id): resource_item_id) ) + def change_ownership(self, obj_id, transfer, access_token=None): + try: + self.create_resource_item_subitem( + obj_id, transfer, 'ownership', access_token=access_token + ) + except InvalidResponse as e: + if e.status_code == 200: + return munchify(loads(e.response.text)) + raise e + class APIResourceClientSync(APIResourceClient): diff --git a/openprocurement_client/constants.py b/openprocurement_client/constants.py index 4ead9f4..5452604 100644 --- a/openprocurement_client/constants.py +++ b/openprocurement_client/constants.py @@ -16,3 +16,4 @@ QUALIFICATIONS = 'qualifications' QUESTIONS = 'questions' TENDERS = 'tenders' +TRANSFERS = 'transfers' diff --git a/openprocurement_client/resources/transfers.py b/openprocurement_client/resources/transfers.py new file mode 100644 index 0000000..88e3fe0 --- /dev/null +++ b/openprocurement_client/resources/transfers.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from openprocurement_client.clients import APIResourceClient +from openprocurement_client.constants import TRANSFERS + + +class TransfersClient(APIResourceClient): + """ Client for OpenProcurement Transfers """ + + resource = TRANSFERS + + get_transfer = APIResourceClient.get_resource_item + + create_transfer = APIResourceClient.create_resource_item + + patch_transfer = APIResourceClient.patch_resource_item From ed2a80f492fab61bfc5b07e7baae91e700d6bbc3 Mon Sep 17 00:00:00 2001 From: Andrew Leitsius Date: Fri, 15 Jun 2018 19:29:28 +0300 Subject: [PATCH 14/20] Add patch_milestone for contract client --- openprocurement_client/resources/contracts.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openprocurement_client/resources/contracts.py b/openprocurement_client/resources/contracts.py index 8fc555b..1a74597 100644 --- a/openprocurement_client/resources/contracts.py +++ b/openprocurement_client/resources/contracts.py @@ -37,3 +37,8 @@ def patch_change(self, contract_id, change_id, access_token, data): return self.patch_resource_item_subitem( contract_id, data, CHANGES, change_id, access_token=access_token ) + + def patch_milestone(self, contract_id, milestone_id, access_token, data): + return self.patch_resource_item_subitem( + contract_id, data, 'milestones', milestone_id, access_token=access_token + ) From b6627b95d93769c47cbac75ba75f840ca4d2635e Mon Sep 17 00:00:00 2001 From: Andrew Leitsius Date: Wed, 20 Jun 2018 16:52:04 +0300 Subject: [PATCH 15/20] Prepare openprocurement_client 2.1.2+sale. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 65e2d4b..fc76429 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import find_packages, setup -version = '2.1.1+sale' +version = '2.1.2+sale' extras_require = { 'test': [ From 9e8d6449806f9a6f66e8bf7e58ca18b15b3ea88d Mon Sep 17 00:00:00 2001 From: oleksiyVeretiuk <32167775+oleksiyVeretiuk@users.noreply.github.com> Date: Thu, 21 Jun 2018 11:18:39 +0300 Subject: [PATCH 16/20] Mock Session to remove dependency on real sandbox (#94) --- openprocurement_client/tests/tests_sync.py | 42 ++++++++++++++-------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/openprocurement_client/tests/tests_sync.py b/openprocurement_client/tests/tests_sync.py index 1aef39b..e37f791 100644 --- a/openprocurement_client/tests/tests_sync.py +++ b/openprocurement_client/tests/tests_sync.py @@ -178,7 +178,8 @@ def test_instance_initialization(self): }) self.assertIsInstance(self.resource_feeder.queue, Queue) - def test_init_api_clients(self): + @mock.patch('openprocurement_client.templates.Session') + def test_init_api_clients(self, mock_session): self.resource_feeder = ResourceFeeder() self.resource_feeder.init_api_clients() self.assertEqual(self.resource_feeder.backward_params, { @@ -202,10 +203,11 @@ def test_handle_response_data(self): self.assertIn('tender2', list(self.resource_feeder.queue.queue)) self.assertNotIn('tender3', list(self.resource_feeder.queue.queue)) + @mock.patch('openprocurement_client.templates.Session') @mock.patch('openprocurement_client.clients.APIResourceClientSync.' 'sync_resource_items') @mock.patch('openprocurement_client.resources.sync.spawn') - def test_start_sync(self, mock_spawn, mock_sync_resource_items): + def test_start_sync(self, mock_spawn, mock_sync_resource_items, mock_session): mock_spawn.return_value = 'spawn result' mock_sync_resource_items.return_value = self.response self.resource_feeder = ResourceFeeder() @@ -222,10 +224,11 @@ def test_start_sync(self, mock_spawn, mock_sync_resource_items): self.assertEqual(self.resource_feeder.backward_worker, 'spawn result') self.assertEqual(self.resource_feeder.forward_worker, 'spawn result') + @mock.patch('openprocurement_client.templates.Session') @mock.patch('openprocurement_client.clients.APIResourceClientSync.' 'sync_resource_items') @mock.patch('openprocurement_client.resources.sync.spawn') - def test_restart_sync(self, mock_spawn, mock_sync_resource_items): + def test_restart_sync(self, mock_spawn, mock_sync_resource_items, mock_session): mock_spawn.return_value = mock.MagicMock() mock_spawn.return_value.kill = mock.MagicMock('kill result') mock_sync_resource_items.return_value = self.response @@ -235,11 +238,12 @@ def test_restart_sync(self, mock_spawn, mock_sync_resource_items): self.resource_feeder.restart_sync() self.assertEqual(mock_spawn.return_value.kill.call_count, 2) + @mock.patch('openprocurement_client.templates.Session') @mock.patch('openprocurement_client.clients.APIResourceClientSync.' 'sync_resource_items') @mock.patch('openprocurement_client.resources.sync.spawn') def test_get_resource_items_zero_value(self, mock_spawn, - mock_sync_resource_items): + mock_sync_resource_items, mock_session): mock_sync_resource_items.side_effect = [self.response, munchify( {'data': {}, 'next_page': {'offset': 'next_page'}, @@ -253,11 +257,12 @@ def test_get_resource_items_zero_value(self, mock_spawn, result = self.resource_feeder.get_resource_items() self.assertEqual(tuple(result), tuple(self.response.data)) + @mock.patch('openprocurement_client.templates.Session') @mock.patch('openprocurement_client.clients.APIResourceClientSync.' 'sync_resource_items') @mock.patch('openprocurement_client.resources.sync.spawn') def test_get_resource_items_non_zero_value(self, mock_spawn, - mock_sync_resource_items): + mock_sync_resource_items, mock_session): mock_sync_resource_items.side_effect = [self.response, munchify( {'data': {}, 'next_page': {'offset': 'next_page'}, @@ -271,12 +276,13 @@ def test_get_resource_items_non_zero_value(self, mock_spawn, result = self.resource_feeder.get_resource_items() self.assertEqual(tuple(result), tuple(self.response.data)) + @mock.patch('openprocurement_client.templates.Session') @mock.patch('openprocurement_client.clients.APIResourceClientSync.' 'sync_resource_items') @mock.patch('openprocurement_client.resources.sync.spawn') @mock.patch('openprocurement_client.resources.sync.sleep') def test_feeder_zero_value(self, mock_sleep, mock_spawn, - mock_sync_resource_items): + mock_sync_resource_items, mock_session): mock_sleep.return_value = 'sleeping' mock_sync_resource_items.side_effect = [self.response, self.response, ConnectionError('conn error')] @@ -289,11 +295,12 @@ def test_feeder_zero_value(self, mock_sleep, mock_spawn, self.assertEqual(e.exception.message, 'conn error') self.assertEqual(mock_sleep.call_count, 1) + @mock.patch('openprocurement_client.templates.Session') @mock.patch('openprocurement_client.clients.APIResourceClientSync.' 'sync_resource_items') @mock.patch('openprocurement_client.resources.sync.spawn') @mock.patch('openprocurement_client.resources.sync.sleep') - def test_feeder(self, mock_sleep, mock_spawn, mock_sync_resource_items): + def test_feeder(self, mock_sleep, mock_spawn, mock_sync_resource_items, mock_session): mock_sleep.return_value = 'sleeping' mock_sync_resource_items.side_effect = [self.response, self.response, ConnectionError('conn error')] @@ -314,8 +321,9 @@ def test_run_feeder(self, mock_spawn): mock_spawn.assert_called_with(self.resource_feeder.feeder) self.assertEqual(result, self.resource_feeder.queue) + @mock.patch('openprocurement_client.templates.Session') @mock.patch('openprocurement_client.resources.sync.get_response') - def test_retriever_backward(self, mock_get_response): + def test_retriever_backward(self, mock_get_response, mock_session): mock_get_response.side_effect = [self.response, munchify({'data': {}})] self.resource_feeder = ResourceFeeder() self.resource_feeder.init_api_clients() @@ -327,8 +335,9 @@ def test_retriever_backward(self, mock_get_response): self.assertEqual(self.resource_feeder.backward_params['offset'], self.response.next_page.offset) + @mock.patch('openprocurement_client.templates.Session') @mock.patch('openprocurement_client.resources.sync.get_response') - def test_retriever_backward_wrong_cookies(self, mock_get_response): + def test_retriever_backward_wrong_cookies(self, mock_get_response, mock_session): mock_get_response.return_value = self.response self.resource_feeder = ResourceFeeder() self.resource_feeder.init_api_clients() @@ -340,8 +349,9 @@ def test_retriever_backward_wrong_cookies(self, mock_get_response): self.resource_feeder.retriever_backward() self.assertEqual(e.exception.message, 'LB Server mismatch') + @mock.patch('openprocurement_client.templates.Session') @mock.patch('openprocurement_client.resources.sync.get_response') - def test_retriever_forward_wrong_cookies(self, mock_get_response): + def test_retriever_forward_wrong_cookies(self, mock_get_response, mock_session): mock_get_response.return_value = self.response self.resource_feeder = ResourceFeeder() self.resource_feeder.init_api_clients() @@ -353,8 +363,9 @@ def test_retriever_forward_wrong_cookies(self, mock_get_response): self.resource_feeder.retriever_forward() self.assertEqual(e.exception.message, 'LB Server mismatch') + @mock.patch('openprocurement_client.templates.Session') @mock.patch('openprocurement_client.resources.sync.get_response') - def test_retriever_forward(self, mock_get_response): + def test_retriever_forward(self, mock_get_response, mock_session): mock_get_response.side_effect = [ self.response, self.response, @@ -372,8 +383,9 @@ def test_retriever_forward(self, mock_get_response): self.assertEqual(self.resource_feeder.forward_params['offset'], self.response.next_page.offset) + @mock.patch('openprocurement_client.templates.Session') @mock.patch('openprocurement_client.resources.sync.get_response') - def test_retriever_forward_no_data(self, mock_get_response): + def test_retriever_forward_no_data(self, mock_get_response, mock_session): mock_get_response.side_effect = [ munchify({'data': {}, 'next_page': {'offset': 'next_page'}}), ConnectionError('connection error') @@ -390,8 +402,9 @@ def test_retriever_forward_no_data(self, mock_get_response): self.assertEqual(self.resource_feeder.forward_params['offset'], 'next_page') + @mock.patch('openprocurement_client.templates.Session') @mock.patch('openprocurement_client.resources.sync.get_response') - def test_retriever_forward_adaptive(self, mock_get_response): + def test_retriever_forward_adaptive(self, mock_get_response, mock_session): mock_get_response.side_effect = [ self.response, munchify({'data': {}, 'next_page': {'offset': 'next_page'}}), @@ -410,8 +423,9 @@ def test_retriever_forward_adaptive(self, mock_get_response): self.assertEqual(self.resource_feeder.forward_params['offset'], self.response.next_page.offset) + @mock.patch('openprocurement_client.templates.Session') @mock.patch('openprocurement_client.resources.sync.get_response') - def test_retriever_forward_no_data_adaptive(self, mock_get_response): + def test_retriever_forward_no_data_adaptive(self, mock_get_response, mock_session): mock_get_response.side_effect = [ munchify({'data': {}, 'next_page': {'offset': 'next_page'}}), munchify({'data': {}, 'next_page': {'offset': 'next_page'}}), From 2ce6b75770b0a94de437ff1538f71a3a36b41daf Mon Sep 17 00:00:00 2001 From: Andrew Leitsius Date: Thu, 21 Jun 2018 19:18:48 +0300 Subject: [PATCH 17/20] Prepare openprocurement_client 2.1.3+sale. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fc76429..53b5225 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import find_packages, setup -version = '2.1.2+sale' +version = '2.1.3+sale' extras_require = { 'test': [ From d7dc686e161ab952a16a52a74f61faddc8454240 Mon Sep 17 00:00:00 2001 From: Bohdan Dmytriv Date: Wed, 27 Jun 2018 17:22:38 +0300 Subject: [PATCH 18/20] Use constant instead of string literal (#95) --- openprocurement_client/constants.py | 3 ++- openprocurement_client/resources/contracts.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/openprocurement_client/constants.py b/openprocurement_client/constants.py index 5452604..ceb41ac 100644 --- a/openprocurement_client/constants.py +++ b/openprocurement_client/constants.py @@ -11,9 +11,10 @@ ELIGIBILITY_DOCUMENTS = 'eligibility_documents' FINANCIAL_DOCUMENTS = 'financial_documens' LOTS = 'lots' +MILESTONES = 'milestones' PLANS = 'plans' -QUALIFICATION_DOCUMENTS = 'qualificationDocuments' QUALIFICATIONS = 'qualifications' +QUALIFICATION_DOCUMENTS = 'qualificationDocuments' QUESTIONS = 'questions' TENDERS = 'tenders' TRANSFERS = 'transfers' diff --git a/openprocurement_client/resources/contracts.py b/openprocurement_client/resources/contracts.py index 1a74597..a02e731 100644 --- a/openprocurement_client/resources/contracts.py +++ b/openprocurement_client/resources/contracts.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from openprocurement_client.clients import APIResourceClient -from openprocurement_client.constants import CHANGES, CONTRACTS +from openprocurement_client.constants import CHANGES, CONTRACTS, MILESTONES class ContractingClient(APIResourceClient): @@ -40,5 +40,5 @@ def patch_change(self, contract_id, change_id, access_token, data): def patch_milestone(self, contract_id, milestone_id, access_token, data): return self.patch_resource_item_subitem( - contract_id, data, 'milestones', milestone_id, access_token=access_token + contract_id, data, MILESTONES, milestone_id, access_token=access_token ) From 61c876b32bb108098289d791f1a1e22c1827d54c Mon Sep 17 00:00:00 2001 From: Bohdan Dmytriv Date: Fri, 6 Jul 2018 14:17:13 +0300 Subject: [PATCH 19/20] Add support of lot's contract GET & PATCH (#97) --- openprocurement_client/resources/lots.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/openprocurement_client/resources/lots.py b/openprocurement_client/resources/lots.py index e2db820..383ad9a 100644 --- a/openprocurement_client/resources/lots.py +++ b/openprocurement_client/resources/lots.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from openprocurement_client.clients import APIResourceClient -from openprocurement_client.constants import LOTS +from openprocurement_client.constants import LOTS, CONTRACTS class LotsClient(APIResourceClient): @@ -18,3 +18,14 @@ def patch_auction(self, lot_id, data, auction_id, access_token): return self.patch_resource_item_subitem( lot_id, {'data': data}, 'auctions', auction_id, access_token=access_token ) + + def patch_contract(self, lot_id, contract_id, access_token, data): + if not access_token: + # used by caravan + return self.patch_resource_item_subitem(lot_id, data, CONTRACTS, contract_id) + return self.patch_resource_item_subitem( + lot_id, data, CONTRACTS, contract_id, access_token=access_token + ) + + def get_contract(self, lot_id, contract_id): + return self.get_resource_item_subitem(lot_id, contract_id, depth_path=CONTRACTS) From 1d8a8ede4b808779ff25b28938b89fea9add3b23 Mon Sep 17 00:00:00 2001 From: Andrew Leitsius Date: Fri, 21 Sep 2018 15:15:03 +0300 Subject: [PATCH 20/20] Prepare openprocurement_client 2.1.4+sale. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 53b5225..5e50dc8 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import find_packages, setup -version = '2.1.3+sale' +version = '2.1.4+sale' extras_require = { 'test': [