diff --git a/openprocurement_client/clients.py b/openprocurement_client/clients.py index 4e82db5..cf8df63 100755 --- a/openprocurement_client/clients.py +++ b/openprocurement_client/clients.py @@ -68,12 +68,13 @@ def _obtain_cookie(self): def _get_access_token(obj): return getattr(getattr(obj, 'access', ''), 'token', '') + @retry(stop_max_attempt_number=5) 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 + if ((response_item.status_code in (200, 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) @@ -109,6 +110,7 @@ def _get_resource_items(self, params=None, feed='changes'): raise InvalidResponse(response) + @retry(stop_max_attempt_number=5) def _patch_resource_item(self, url, payload, headers=None): _headers = self.headers.copy() _headers.update(headers or {}) @@ -117,6 +119,14 @@ def _patch_resource_item(self, url, payload, headers=None): return munchify(loads(response_item.text)) raise InvalidResponse(response_item) + def _put_resource_item(self, url, payload, headers=None): + _headers = self.headers.copy() + _headers.update(headers or {}) + response_item = self.request('PUT', 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']), @@ -146,6 +156,20 @@ def upload_document(self, file_, resource_item_id, subitem_name=DOCUMENTS, doc_t file_=file_, headers=headers, doc_registration=doc_registration, doc_type=doc_type, use_ds_client=use_ds_client) + @verify_file + def register_and_upload_document(self, file_, use_ds_client=True, doc_registration=True): + headers = None + return self._register_and_upload_file(file_=file_, headers=headers, use_ds_client=use_ds_client, + doc_registration=doc_registration) + + def _register_and_upload_file(self, file_=None, headers=None, use_ds_client=True, doc_registration=True): + if hasattr(self, 'ds_client') and use_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) + return response + def _update_params(self, params): for key in params: if key not in IGNORE_PARAMS: @@ -226,6 +250,23 @@ def create_resource_item_subitem(self, resource_item_id, subitem_obj, subitem_name) return self._create_resource_item(url, subitem_obj, headers=headers) + def create_resource_item_sub_subitem(self, resource_item_id, sub_subitem_obj, + sub_subitem_name, access_token=None, + depth_path=None): + headers = None + if access_token: + headers = {'X-Access-Token': access_token} + if isinstance(resource_item_id, dict): + headers = {'X-Access-Token': self._get_access_token(resource_item_id)} + resource_item_id = resource_item_id['data']['id'] + if depth_path: + url = '{}/{}/{}/{}'.format(self.prefix_path, resource_item_id, + depth_path, sub_subitem_name) + else: + url = '{}/{}/{}'.format(self.prefix_path, resource_item_id, + sub_subitem_name) + return self._create_resource_item(url, sub_subitem_obj, headers=headers) + ########################################################################### # DELETE CLIENT METHODS ########################################################################### @@ -363,6 +404,27 @@ def patch_document(self, resource_item_id, document_data, document_id, access_token, depth_path ) + def put_resource_item_subitem(self, resource_item_id, put_data, subitem_name, subitem_id=None, + access_token=None, depth_path=None): + headers = None + if access_token: + headers = {'X-Access-Token': access_token} + if isinstance(resource_item_id, dict): + headers = {'X-Access-Token': self._get_access_token(resource_item_id)} + resource_item_id = resource_item_id['data']['id'] + if not subitem_id: + subitem_id = put_data['data']['id'] + 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._put_resource_item(url, put_data, headers=headers) + ########################################################################### # UPLOAD CLIENT METHODS ########################################################################### diff --git a/openprocurement_client/constants.py b/openprocurement_client/constants.py index 84fee27..321b63d 100644 --- a/openprocurement_client/constants.py +++ b/openprocurement_client/constants.py @@ -4,6 +4,7 @@ AWARDS = 'awards' BIDS = 'bids' CANCELLATIONS = 'cancellations' +CATEGORIES = 'categories' CHANGES = 'changes' COMPLAINTS = 'complaints' CONTRACTS = 'contracts' @@ -14,10 +15,20 @@ LOTS = 'lots' MILESTONES = 'milestones' PLANS = 'plans' +PROFILES = 'profiles' PROLONGATIONS = 'prolongations' +SUPPLIERS = 'suppliers' QUALIFICATIONS = 'qualifications' QUALIFICATION_DOCUMENTS = 'qualificationDocuments' QUESTIONS = 'questions' TENDERS = 'tenders' TRANSFERS = 'transfers' -AGREEMENTS= 'agreements' +AGREEMENTS = 'agreements' +PUSH = 'push' +CRITERIA = 'criteria' +REQUIREMENT_RESPONSES = 'requirement_responses' +EVIDENCES = 'evidences' +REQUIREMENT_GROUPS = 'requirement_groups' +REQUIREMENTS = 'requirements' +FRAMEWORKS = 'frameworks' +SUBMISSIONS = 'submissions' diff --git a/openprocurement_client/dasu_client.py b/openprocurement_client/dasu_client.py index 1fc31b5..deb4d07 100755 --- a/openprocurement_client/dasu_client.py +++ b/openprocurement_client/dasu_client.py @@ -21,21 +21,22 @@ class DasuClient(APIBaseClient, APITemplateClient): """client for monitorings""" - host_url = 'https://audit-api-sandbox.prozorro.gov.ua' - api_version = '2.4' + host_url = 'https://audit-api-staging.prozorro.gov.ua' + api_version = '2.5' headers = {'Content-Type': 'application/json'} def __init__(self, - key, resource='monitorings', host_url=None, api_version=None, + username=None, + password=None, params=None, ds_client=None, user_agent=None, ds_config=None): - APITemplateClient.__init__(self, login_pass=(key, ''), headers=self.headers, + APITemplateClient.__init__(self, login_pass=(username, password), headers=self.headers, user_agent=user_agent) if ds_config: @@ -85,6 +86,24 @@ def create_party(self, monitoring, party): headers={'X-Access-Token': self._get_access_token(monitoring)} ) + def create_liability(self, monitoring, monitoring_id): + return self._create_resource_item( + '{}/{}/{}'.format( + self.prefix_path, monitoring_id, 'liabilities' + ), + payload=monitoring, + headers={'X-Access-Token': self._get_access_token(monitoring)}, + ) + + def patch_liability(self, monitoring, monitoring_id, liability_id): + return self._patch_resource_item( + '{}/{}/{}/{}'.format( + self.prefix_path, monitoring_id, 'liabilities', liability_id + ), + payload=monitoring, + headers={'X-Access-Token': self._get_access_token(monitoring)} + ) + def patch_monitoring(self, monitoring, monitoring_id): return self._patch_resource_item( '{}/{}'.format(self.prefix_path, monitoring_id), @@ -113,7 +132,7 @@ def patch_eliminationReport(self, monitoring, report): method='PUT' ) - def patch_appeal(self, monitoring, appeal): + def create_appeal(self, monitoring, appeal): return self._create_resource_item( '{}/{}/{}'.format( self.prefix_path, monitoring.data.id, @@ -124,6 +143,16 @@ def patch_appeal(self, monitoring, appeal): method='PUT' ) + def patch_appeal(self, monitoring, appeal): + return self._patch_resource_item( + '{}/{}/{}'.format( + self.prefix_path, monitoring.data.id, + 'appeal' + ), + payload=appeal, + headers={'X-Access-Token': self._get_access_token(monitoring)}, + ) + @verify_file def upload_obj_document(self, file_): response = self.ds_client.document_upload_registered( diff --git a/openprocurement_client/resources/agreements.py b/openprocurement_client/resources/agreements.py index 2b51078..0e8ded5 100644 --- a/openprocurement_client/resources/agreements.py +++ b/openprocurement_client/resources/agreements.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- from zope.deprecation import deprecation +from simplejson import loads +from openprocurement_client.compatibility_utils import munchify_factory +from openprocurement_client.exceptions import InvalidResponse from openprocurement_client.clients import APIResourceClient from openprocurement_client.constants import ( AGREEMENTS, @@ -8,6 +11,8 @@ DOCUMENTS, ) +munchify = munchify_factory() + class AgreementClient(APIResourceClient): """ Client for agreements """ @@ -32,4 +37,28 @@ def patch_change(self, agreement_id, data, change_id, access_token=None): def patch_document(self, agreement_id, data, document_id, access_token=None): return self.patch_resource_item_subitem( - agreement_id, data, DOCUMENTS, document_id, access_token=access_token) \ No newline at end of file + agreement_id, data, DOCUMENTS, document_id, access_token=access_token) + + def find_agreements_by_classification_id(self, classification_id, additional_classifications=()): + url = "{}_by_classification/{}".format(self.prefix_path, classification_id) + params = {} + if additional_classifications: + params["additional_classifications"] = ",".join(additional_classifications) + response = self.request('GET', url, params_dict=params) + if response.status_code == 200: + resource_items_list = munchify(loads(response.text)) + return resource_items_list.data + + raise InvalidResponse(response) + + def find_recursive_agreements_by_classification_id(self, classification_id, additional_classifications=()): + if "-" in classification_id: + classification_id = classification_id[:classification_id.find("-")] + needed_level = 2 + while classification_id[needed_level] != '0': + agreements = self.find_agreements_by_classification_id(classification_id, additional_classifications) + if agreements: + return agreements + + pos = classification_id[1:].find('0') + classification_id = classification_id[:pos] + '0' + classification_id[pos+1:] diff --git a/openprocurement_client/resources/document_service.py b/openprocurement_client/resources/document_service.py index 2a3f599..ca3e39a 100644 --- a/openprocurement_client/resources/document_service.py +++ b/openprocurement_client/resources/document_service.py @@ -14,7 +14,7 @@ class DocumentServiceClient(APITemplateClient): """class for work with Document Service""" - host_url = 'https://upload.docs-sandbox.openprocurement.org' + host_url = 'https://upload-docs-staging.prozorro.gov.ua' url_register_part = 'register' url_upload_part = 'upload' diff --git a/openprocurement_client/resources/ecatalogues.py b/openprocurement_client/resources/ecatalogues.py new file mode 100644 index 0000000..1521a97 --- /dev/null +++ b/openprocurement_client/resources/ecatalogues.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +from openprocurement_client.clients import APIResourceClient +from openprocurement_client.constants import CATEGORIES, PROFILES, SUPPLIERS + + +class CategoriesClient(APIResourceClient): + """ Client for eCatalogues categories """ + resource = CATEGORIES + + def get_category(self, category_id): + return self.get_resource_item(category_id) + + def get_category_suppliers(self, category_id): + return self.get_resource_item_subitem(category_id, SUPPLIERS) + + +class ProfilesClient(APIResourceClient): + """ Client for eCatalogues profiles """ + resource = PROFILES + + def get_profile(self, profile_id): + return self.get_resource_item(profile_id) + + +class ECataloguesClient(object): + + def __init__(self, *args, **kwargs): + self.categories = CategoriesClient(*args, **kwargs) + self.profiles = ProfilesClient(*args, **kwargs) diff --git a/openprocurement_client/resources/edr.py b/openprocurement_client/resources/edr.py index 7333df4..f20a507 100644 --- a/openprocurement_client/resources/edr.py +++ b/openprocurement_client/resources/edr.py @@ -13,8 +13,8 @@ class EDRClient(APITemplateClient): """ Client for validate members by EDR """ - host_url = 'https://api-sandbox.openprocurement.org' - api_version = '2.0' + host_url = 'https://lb-api-staging.prozorro.gov.ua' + api_version = '2.5' def __init__(self, host_url=None, api_version=None, username=None, password=None): diff --git a/openprocurement_client/resources/frameworks.py b/openprocurement_client/resources/frameworks.py new file mode 100644 index 0000000..11a757d --- /dev/null +++ b/openprocurement_client/resources/frameworks.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +import logging +from retrying import retry + +from openprocurement_client.clients import APIResourceClient +from openprocurement_client.constants import (FRAMEWORKS, DOCUMENTS, SUBMISSIONS, QUALIFICATIONS) + +LOGGER = logging.getLogger(__name__) + + +class FrameworksClient(APIResourceClient): + """Client for frameworks""" + + resource = FRAMEWORKS + + ########################################################################### + # GET ITEMS LIST API METHODS + ########################################################################### + + retry(stop_max_attempt_number=5) + def get_qualifications(self, params=None, feed='changes'): + return self.get_resource_items(params=params, feed=feed) + + ########################################################################### + # CREATE ITEM API METHODS + ########################################################################### + + def create_qualification(self, framework): + return self.create_resource_item(framework) + + ########################################################################### + # GET ITEM API METHODS + ########################################################################### + + @retry(stop_max_attempt_number=5) + def get_qualification(self, framework_id): + return self.get_resource_item(framework_id) + + def get_documents(self, framework_id, access_token=None): + return self.get_resource_item_subitem(framework_id, DOCUMENTS, access_token=access_token) + + def get_submissions(self, framework_id, access_token=None): + return self.get_resource_item_subitem(framework_id, SUBMISSIONS, access_token=access_token) + + def get_qualifications(self, framework_id, access_token=None): + return self.get_resource_item_subitem(framework_id, QUALIFICATIONS, access_token=access_token) + + ########################################################################### + # PATCH ITEM API METHODS + ########################################################################### + def patch_framework(self, framework_id, patch_data={}, access_token=None): + return self.patch_resource_item(framework_id, patch_data, access_token=access_token) + + ########################################################################### + # UPLOAD FILE API METHODS + ########################################################################### + def upload_framework_document(self, file, framework_id, use_ds_client=True, + doc_registration=True, access_token=None): + return self.upload_document(file, framework_id, + use_ds_client=use_ds_client, + doc_registration=doc_registration, + access_token=access_token) \ No newline at end of file diff --git a/openprocurement_client/resources/plans.py b/openprocurement_client/resources/plans.py index b5a0636..26430c6 100644 --- a/openprocurement_client/resources/plans.py +++ b/openprocurement_client/resources/plans.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import logging +from retrying import retry from zope.deprecation import deprecation @@ -45,6 +46,7 @@ def _create_plan_resource_item(self, plan, item_obj, items_name): # GET ITEM API METHODS ########################################################################### + @retry(stop_max_attempt_number=5) def get_plan(self, plan_id): return self.get_resource_item(plan_id) diff --git a/openprocurement_client/resources/qualifications.py b/openprocurement_client/resources/qualifications.py new file mode 100644 index 0000000..1e83ae7 --- /dev/null +++ b/openprocurement_client/resources/qualifications.py @@ -0,0 +1,25 @@ +import logging + +from openprocurement_client.clients import APIResourceClient +from openprocurement_client.constants import (QUALIFICATIONS) + +LOGGER = logging.getLogger(__name__) + + +class QualificationClient(APIResourceClient): + """Client for qualification""" + + resource = QUALIFICATIONS + + def get_qualification(self, qualification_id): + return self.get_resource_item(qualification_id) + + def patch_qualification(self, qualification_id, patch_data={}, access_token=None): + return self.patch_resource_item(qualification_id, patch_data, access_token=access_token) + + def upload_qualification_document(self, file, qualification_id, use_ds_client=True, + doc_registration=True, access_token=None): + return self.upload_document(file, qualification_id, + use_ds_client=use_ds_client, + doc_registration=doc_registration, + access_token=access_token) \ No newline at end of file diff --git a/openprocurement_client/resources/submissions.py b/openprocurement_client/resources/submissions.py new file mode 100644 index 0000000..3d2fbb7 --- /dev/null +++ b/openprocurement_client/resources/submissions.py @@ -0,0 +1,30 @@ +import logging + +from openprocurement_client.clients import APIResourceClient +from openprocurement_client.constants import (SUBMISSIONS) + +LOGGER = logging.getLogger(__name__) + + +class SubmissionsClient(APIResourceClient): + """Client for submissions""" + + resource = SUBMISSIONS + + def get_submission(self, submission_id): + return self.get_resource_item(submission_id) + + def create_submission(self, submission): + return self.create_resource_item(submission) + + def patch_submission(self, submission_id, patch_data={}, access_token=None): + return self.patch_resource_item(submission_id, patch_data, access_token=access_token) + + def upload_submission_document(self, file, submission_id, use_ds_client=True, + doc_registration=True, access_token=None): + return self.upload_document(file, submission_id, + use_ds_client=use_ds_client, + doc_registration=doc_registration, + access_token=access_token) + + diff --git a/openprocurement_client/resources/sync.py b/openprocurement_client/resources/sync.py index 4bf886c..414ac17 100644 --- a/openprocurement_client/resources/sync.py +++ b/openprocurement_client/resources/sync.py @@ -9,7 +9,7 @@ from openprocurement_client.clients import APIResourceClientSync from openprocurement_client.utils import get_response - +from copy import deepcopy DEFAULT_RETRIEVERS_PARAMS = { 'down_requests_sleep': 5, @@ -19,8 +19,8 @@ 'queue_size': 101 } -DEFAULT_API_HOST = 'https://lb.api-sandbox.openprocurement.org/' -DEFAULT_API_VERSION = '2.3' +DEFAULT_API_HOST = 'https://lb-api-staging.prozorro.gov.ua' +DEFAULT_API_VERSION = '2.5' DEFAULT_API_KEY = '' DEFAULT_API_EXTRA_PARAMS = { 'opt_fields': 'status', 'mode': '_all_' @@ -36,7 +36,7 @@ class ResourceFeeder(object): def __init__(self, host=DEFAULT_API_HOST, version=DEFAULT_API_VERSION, key=DEFAULT_API_KEY, resource='tenders', extra_params=DEFAULT_API_EXTRA_PARAMS, - retrievers_params=DEFAULT_RETRIEVERS_PARAMS, adaptive=False, + retrievers_params={}, adaptive=False, with_priority=False): super(ResourceFeeder, self).__init__() LOGGER.info('Init Resource Feeder...') @@ -47,8 +47,9 @@ def __init__(self, host=DEFAULT_API_HOST, version=DEFAULT_API_VERSION, self.adaptive = adaptive self.extra_params = extra_params - self.retrievers_params = retrievers_params - self.queue = PriorityQueue(maxsize=retrievers_params['queue_size']) + self.retrievers_params = deepcopy(DEFAULT_RETRIEVERS_PARAMS) + self.retrievers_params.update(retrievers_params) + self.queue = PriorityQueue(maxsize=self.retrievers_params['queue_size']) self.forward_priority = 1 if with_priority else 0 self.backward_priority = 1000 if with_priority else 0 @@ -140,9 +141,13 @@ def get_resource_items(self): LOGGER.info('Stop check backward worker') check_down_worker = False else: + if not self.backward_worker.successful(): + LOGGER.warning('Exception from forward_worker: {}'.format(repr(self.backward_worker.exception))) self.restart_sync() check_down_worker = True if self.forward_worker.ready(): + if not self.forward_worker.successful(): + LOGGER.warning('Exception from forward_worker: {}'.format(repr(self.forward_worker.exception))) self.restart_sync() check_down_worker = True while not self.queue.empty(): @@ -185,9 +190,13 @@ def feeder(self): LOGGER.info('Stop check backward worker') check_down_worker = False else: + if not self.backward_worker.successful(): + LOGGER.warning('Exception from forward_worker: {}'.format(repr(self.backward_worker.exception))) self.restart_sync() check_down_worker = True if self.forward_worker.ready(): + if not self.forward_worker.successful(): + LOGGER.warning('Exception from forward_worker: {}'.format(repr(self.forward_worker.exception))) self.restart_sync() check_down_worker = True LOGGER.debug('Feeder queue size {} items'.format(self.queue.qsize()), @@ -204,6 +213,9 @@ def retriever_backward(self): 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: + LOGGER.info('Backward client Cookies mismatch: {}, {}'.format( + repr(self.cookies), repr(self.backward_client.session.cookies)) + ) raise Exception('LB Server mismatch') while response.data: LOGGER.debug('Backward: Start process data.') @@ -215,6 +227,9 @@ def retriever_backward(self): 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: + LOGGER.info('Backward client Cookies mismatch: {}, {}'.format( + repr(self.cookies), repr(self.backward_client.session.cookies)) + ) raise Exception('LB Server mismatch') LOGGER.info('Backward: pause between requests {} sec.'.format( self.retrievers_params.get('down_requests_sleep', 5))) @@ -228,6 +243,9 @@ def retriever_forward(self): 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: + LOGGER.info('Forward client Cookies mismatch: {}, {}'.format( + repr(self.cookies), repr(self.forward_client.session.cookies)) + ) raise Exception('LB Server mismatch') while 1: self.forward_heartbeat = time() @@ -240,6 +258,9 @@ def retriever_forward(self): 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: + LOGGER.info('Forward client Cookies mismatch: {}, {}'.format( + repr(self.cookies), repr(self.forward_client.session.cookies)) + ) raise Exception('LB Server mismatch') if len(response.data) != 0: LOGGER.info( @@ -264,6 +285,9 @@ def retriever_forward(self): if self.retrievers_params['up_wait_sleep'] < 30: self.retrievers_params['up_wait_sleep'] += 1 if self.cookies != self.forward_client.session.cookies: + LOGGER.info('Forward client Cookies mismatch: {}, {}'.format( + repr(self.cookies), repr(self.forward_client.session.cookies)) + ) raise Exception('LB Server mismatch') return 1 @@ -309,4 +333,4 @@ def get_tenders(host=DEFAULT_API_HOST, version=DEFAULT_API_VERSION, if __name__ == '__main__': for tender_item in get_tenders(): - print('Tender {0[id]}'.format(tender_item)) \ No newline at end of file + print('Tender {0[id]}'.format(tender_item)) diff --git a/openprocurement_client/resources/tenders.py b/openprocurement_client/resources/tenders.py index c211087..d960608 100644 --- a/openprocurement_client/resources/tenders.py +++ b/openprocurement_client/resources/tenders.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import logging +from retrying import retry from retrying import retry from zope.deprecation import deprecation @@ -9,12 +10,33 @@ from openprocurement_client.constants import (AUCTIONS, AWARDS, BIDS, CANCELLATIONS, COMPLAINTS, CONTRACTS, DOCUMENTS, ITEMS, LOTS, PROLONGATIONS, QUALIFICATIONS, QUESTIONS, - TENDERS, AGREEMENTS) + TENDERS, AGREEMENTS, PLANS, PUSH, MILESTONES, + CRITERIA, REQUIREMENT_RESPONSES, EVIDENCES, REQUIREMENT_GROUPS, + REQUIREMENTS) LOGGER = logging.getLogger(__name__) +class CreateTenderClient(APIResourceClient): + """client only for tender creation""" + resource = PLANS + + def create_tender(self, plan_id, tender, access_token=None): + return self.create_resource_item_subitem(plan_id, tender, TENDERS, access_token=access_token) + + +class CreatePaymentClient(APIResourceClient): + """client only for payment creation""" + resource = PUSH + + def create_payment(self, payment): + return self.create_resource_item(payment) + + def _obtain_cookie(self): + pass + + class TendersClient(APIResourceClient): """client for tenders""" resource = TENDERS @@ -72,6 +94,20 @@ def create_award_complaint(self, tender_id, complaint, award_id, depth_path=depth_path, access_token=access_token ) + def create_qualification_complaint(self, tender_id, complaint, qualification_id, access_token=None): + depth_path = '{}/{}'.format(QUALIFICATIONS, qualification_id) + return self.create_resource_item_subitem( + tender_id, complaint, COMPLAINTS, + depth_path=depth_path, access_token=access_token + ) + + def create_cancellations_complaint(self, tender_id, complaint, cancellation_id, access_token=None): + depth_path = '{}/{}'.format(CANCELLATIONS, cancellation_id) + return self.create_resource_item_subitem( + tender_id, complaint, COMPLAINTS, + depth_path=depth_path, access_token=access_token + ) + def create_thin_document(self, tender_id, document_data, access_token=None): return self.create_resource_item_subitem( tender_id, document_data, DOCUMENTS, access_token=access_token @@ -86,6 +122,53 @@ def create_prolongation(self, tender_id, contract_id, prolongation_data, access_ tender_id, prolongation_data, PROLONGATIONS, depth_path=depth_path, access_token=access_token ) + def create_award_milestone(self, tender_id, milestone, award_id, access_token=None): + depth_path = '{}/{}'.format(AWARDS, award_id) + return self.create_resource_item_subitem( + tender_id, milestone, MILESTONES, depth_path=depth_path, access_token=access_token + ) + + def create_qualification_milestone(self, tender_id, milestone, qualification_id, access_token=None): + depth_path = '{}/{}'.format(QUALIFICATIONS, qualification_id) + return self.create_resource_item_subitem( + tender_id, milestone, MILESTONES, depth_path=depth_path, access_token=access_token + ) + + def create_criteria(self, tender_id, criteria, access_token=None): + return self.create_resource_item_subitem( + tender_id, criteria, CRITERIA, access_token=access_token + ) + + def create_bid_criteria_response(self, tender_id, criteria_data, bid_id, access_token=None): + depth_path = '{}/{}'.format(BIDS, bid_id) + return self.create_resource_item_subitem( + tender_id, criteria_data, REQUIREMENT_RESPONSES, depth_path=depth_path, access_token=access_token + ) + + def create_qualification_criteria_response(self, tender_id, criteria_data, qualification_id, access_token=None): + depth_path = '{}/{}'.format(QUALIFICATIONS, qualification_id) + return self.create_resource_item_subitem( + tender_id, criteria_data, REQUIREMENT_RESPONSES, depth_path=depth_path, access_token=access_token + ) + + def create_award_criteria_response(self, tender_id, criteria_data, award_id, access_token=None): + depth_path = '{}/{}'.format(AWARDS, award_id) + return self.create_resource_item_subitem( + tender_id, criteria_data, REQUIREMENT_RESPONSES, depth_path=depth_path, access_token=access_token + ) + + def create_bid_criteria_contract_guarantee_response(self, tender_id, contract_criteria_data, bid_id, + requirement_response_id, access_token=None): + depth_path = '{}/{}/{}/{}'.format(BIDS, bid_id, REQUIREMENT_RESPONSES, requirement_response_id) + return self.create_resource_item_subitem( + tender_id, contract_criteria_data, EVIDENCES, depth_path=depth_path, access_token=access_token + ) + + def aggregate_plans(self, tender_id, plans_data, access_token=None): + return self.create_resource_item_subitem( + tender_id, plans_data, PLANS, access_token=access_token + ) + ########################################################################### # GET ITEMS LIST API METHODS ########################################################################### @@ -110,6 +193,10 @@ def get_questions(self, tender_id, access_token=None): def get_documents(self, tender_id, access_token=None): return self.get_resource_item_subitem(tender_id, DOCUMENTS, access_token=access_token) + def get_bid_documents(self, tender_id, bid_id, access_token=None): + return self.get_resource_item_subitem( + tender_id, DOCUMENTS, depth_path='{}/{}'.format(BIDS, bid_id), access_token=access_token) + 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), @@ -129,6 +216,9 @@ def get_awards(self, tender_id, access_token=None): def get_lots(self, tender_id, access_token=None): return self.get_resource_item_subitem(tender_id, LOTS, access_token=access_token) + def get_bids(self, tender_id, access_token=None): + return self.get_resource_item_subitem(tender_id, BIDS, access_token=access_token) + ########################################################################### # GET ITEM API METHODS ########################################################################### @@ -143,6 +233,7 @@ def _get_tender_resource_item(self, tender, item_id, items_name, headers=headers ) + @retry(stop_max_attempt_number=5) def get_tender(self, tender_id): return self.get_resource_item(tender_id) @@ -200,6 +291,20 @@ def patch_award_document(self, tender_id, document_data, award_id, document_id, depth_path=depth_path, access_token=access_token ) + def patch_qualification_complaint(self, tender_id, complaint, qualification_id, complaint_id='', access_token=None): + return self.patch_resource_item_subitem( + tender_id, complaint, COMPLAINTS, subitem_id=complaint_id, + depth_path='{}/{}'.format(QUALIFICATIONS, qualification_id), + access_token=access_token + ) + + def patch_cancellation_complaint(self, tender_id, complaint, cancellation_id, complaint_id='', access_token=None): + return self.patch_resource_item_subitem( + tender_id, complaint, COMPLAINTS, subitem_id=complaint_id, + depth_path='{}/{}'.format(CANCELLATIONS, cancellation_id), + access_token=access_token + ) + def patch_cancellation(self, tender_id, cancellation, cancellation_id='', access_token=None): return self.patch_resource_item_subitem( tender_id, cancellation, CANCELLATIONS, @@ -344,6 +449,14 @@ def upload_cancellation_document(self, file_, tender_id, cancellation_id, use_ds doc_registration=doc_registration, depth_path=depth_path, access_token=access_token) + def upload_cancellation_complaint_document(self, file_, tender_id, cancellation_id, complaint_id, use_ds_client=True, + doc_registration=True, access_token=None): + depth_path = '{}/{}/{}/{}'.format(CANCELLATIONS, cancellation_id, COMPLAINTS, complaint_id) + return self.upload_document(file_, tender_id, + use_ds_client=use_ds_client, + doc_registration=doc_registration, + depth_path=depth_path, access_token=access_token) + def upload_complaint_document(self, file_, tender_id, complaint_id, use_ds_client=True, doc_registration=True, access_token=None): depth_path = '{}/{}'.format(COMPLAINTS, complaint_id) @@ -368,6 +481,14 @@ def upload_qualification_document(self, file_, tender_id, qualification_id, use_ doc_registration=doc_registration, depth_path=depth_path, access_token=access_token) + def upload_qualification_complaint_document(self, file_, tender_id, qualification_id, complaint_id, use_ds_client=True, + doc_registration=True, access_token=None): + depth_path = '{}/{}/{}/{}'.format(QUALIFICATIONS, qualification_id, COMPLAINTS, complaint_id) + return self.upload_document(file_, tender_id, + use_ds_client=use_ds_client, + doc_registration=doc_registration, + depth_path=depth_path, access_token=access_token) + def upload_prolongation_document(self, file_, tender_id, contract_id, prolongation_id, use_ds_client=True, doc_registration=True, access_token=None): depth_path = "{}/{}/{}/{}".format(CONTRACTS, contract_id, PROLONGATIONS, prolongation_id) @@ -376,6 +497,24 @@ def upload_prolongation_document(self, file_, tender_id, contract_id, prolongati doc_registration=doc_registration, depth_path=depth_path, access_token=access_token) + def patch_requirement(self, tender_id, requirement_data, criteria_id, requirement_groups_id, requirement_id='', + access_token=None): + return self.put_resource_item_subitem( + tender_id, requirement_data, REQUIREMENTS, + subitem_id=requirement_id, + depth_path='{}/{}/{}/{}'.format(CRITERIA, criteria_id, REQUIREMENT_GROUPS, requirement_groups_id), + access_token=access_token + ) + + def patch_evidence(self, tender_id, evidence_data, criteria_id, requirement_groups_id, requirement_id='', + access_token=None): + return self.put_resource_item_subitem( + tender_id, evidence_data, REQUIREMENTS, + subitem_id=requirement_id, + depth_path='{}/{}/{}/{}'.format(CRITERIA, criteria_id, REQUIREMENT_GROUPS, requirement_groups_id), + access_token=access_token + ) + ########################################################################### # UPDATE FILE API METHODS ########################################################################### @@ -427,6 +566,10 @@ def delete_lot(self, tender_id, lot_id, access_token=None): ########################################################################### +class PaymentClient(CreatePaymentClient): + """client for payment push only""" + + class Client(TendersClient): """client for tenders for backward compatibility""" @@ -437,3 +580,7 @@ class TendersClientSync(APIResourceClientSync): sync_tenders = APIResourceClientSync.sync_resource_items get_tender = APIResourceClientSync.get_resource_item + + +class TenderCreateClient(CreateTenderClient): + """client for tender publication only""" diff --git a/openprocurement_client/tests/_server.py b/openprocurement_client/tests/_server.py index 2bc2f71..b1fa9af 100644 --- a/openprocurement_client/tests/_server.py +++ b/openprocurement_client/tests/_server.py @@ -9,8 +9,9 @@ TEST_CONTRACT_KEYS, TEST_ASSET_KEYS, TEST_LOT_KEYS, - TEST_AGREEMENT_KEYS -) + TEST_AGREEMENT_KEYS, + TEST_CATEGORY_KEYS, + TEST_PROFILE_KEYS) import magic import os @@ -31,6 +32,7 @@ CONTRACTS_PATH = API_PATH.format('contracts') AGREEMENTS_PATH = API_PATH.format('agreements') SPORE_PATH = API_PATH.format('spore') +CATEGORIES_PATH = API_PATH.format('categories') DOWNLOAD_URL_EXTENSION = 'some_key_etc' RESOURCE_DICT = { 'tender': {'sublink': 'tenders', 'data': TEST_TENDER_KEYS}, @@ -39,6 +41,8 @@ 'asset': {'sublink': 'assets', 'data': TEST_ASSET_KEYS}, 'lot': {'sublink': 'lots', 'data': TEST_LOT_KEYS}, 'agreement': {'sublink': 'agreements', 'data': TEST_AGREEMENT_KEYS}, + 'category': {'sublink': 'categories', 'data': TEST_CATEGORY_KEYS}, + 'profile': {'sublink': 'profiles', 'data': TEST_PROFILE_KEYS} } @@ -347,6 +351,12 @@ def contract_patch_milestone(contract_id, milestone_id): milestone.update(request.json['data']) return dumps({'data': milestone}) +#categories operations + +def category_suppliers(category_id): + subpage = resource_partition(category_id, resource_name="category", part="suppliers") + return dumps({'data': subpage}) + # Routes @@ -410,6 +420,15 @@ def contract_patch_milestone(contract_id, milestone_id): "agreement_subpage_item_create": (AGREEMENTS_PATH + "//", 'POST', resource_subpage_item_create), "agreement_change_patch": (API_PATH.format('agreements') + '//changes/', 'PATCH', agreement_change_patch), "agreement_document_patch": (API_PATH.format('agreements') + '//documents/', 'PATCH', agreement_document_patch), + "categories_head": (CATEGORIES_PATH, 'HEAD', spore), + "category": (API_PATH.format( + '') + '/', + 'GET', resource_page), + "category_suppliers": (CATEGORIES_PATH + "//suppliers", 'GET', category_suppliers), + "profiles_head": (API_PATH.format("profiles"), 'HEAD', spore), + "profile": (API_PATH.format( + '') + '/', + 'GET', resource_page), } diff --git a/openprocurement_client/tests/data/category_33140000-560716-42000777.json b/openprocurement_client/tests/data/category_33140000-560716-42000777.json new file mode 100644 index 0000000..dfb37d1 --- /dev/null +++ b/openprocurement_client/tests/data/category_33140000-560716-42000777.json @@ -0,0 +1,114 @@ +{ + "data": { + "classification": { + "description": "Текстильні матеріали", + "id": "33140000-3", + "scheme": "ДК021" + }, + "dateModified": "2019-08-06T06:16:41.391680+03:00", + "description": "", + "id": "33140000-560716-42000777", + "images": [ + { + "sizes": "738x652", + "url": "/static/images/cotton_wool.png" + } + ], + "procuringEntity": { + "address": { + "countryName": "Україна", + "locality": "м. Київ", + "postalCode": "01001", + "region": "м. Київ", + "streetAddress": "вул. Дубова, буд. 54" + }, + "contactPoint": { + "email": "user1@domain.org", + "faxNumber": "", + "name": "Юзер 1", + "telephone": "", + "url": "http://fb.me/user1_profile" + }, + "identifier": { + "id": "42000777", + "legalName": "ПРИВАТНЕ ПІДПРИЄМСТВО «ТЕКСТИЛЬНІ ЗАКУПІВЛІ УКРАЇНИ»", + "scheme": "UA-EDR" + }, + "kind": "central", + "name": "ПРИВАТНЕ ПІДПРИЄМСТВО «ТЕКСТИЛЬНІ ЗАКУПІВЛІ УКРАЇНИ»" + }, + "status": "active", + "suppliers": [ + { + "address": { + "countryName": "Україна", + "locality": "м.Ірпінь", + "postalCode": "08200", + "region": "Київська область", + "streetAddress": "вул. Липова, 23" + }, + "contactPoint": { + "email": "user2@domain.org", + "name": "Юзер 2", + "telephone": "067-000-00-00" + }, + "id": "UA-EDR-38571763", + "identifier": { + "id": "38571763", + "legalName": "ТОВ «ВИГАДАЙКА УА»", + "scheme": "UA-EDR" + }, + "name": "ТОВ «ВИГАДАЙКА УА»", + "scale": "sme", + "status": "active" + }, + { + "address": { + "countryName": "Україна", + "locality": "м. Дубровиця", + "postalCode": "34100", + "region": "", + "streetAddress": "вул. Смерекова, 16" + }, + "contactPoint": { + "email": "user3@domain.org", + "name": "Юзер 3", + "telephone": "38-050-000-00-11" + }, + "id": "UA-EDR-40551463", + "identifier": { + "id": "40551463", + "legalName": "ТОВ «МОРШИН ТЕКСТИЛЬ»", + "scheme": "UA-EDR" + }, + "name": "ТОВ «МОРШИН ТЕКСТИЛЬ»", + "scale": "micro", + "status": "active" + }, + { + "address": { + "countryName": "Україна", + "locality": "м. Дніпро", + "postalCode": "49000", + "region": "Дніпропетровська область", + "streetAddress": "вул. Річкова 71" + }, + "contactPoint": { + "email": "user4@domain.org", + "name": "Юзер 4", + "telephone": "(063) 985-10-75" + }, + "id": "UA-EDR-39273420", + "identifier": { + "id": "39273420", + "legalName": "ТОВ «Дніпрові кручі", + "scheme": "UA-EDR" + }, + "name": "ТОВ «Дніпрові кручі", + "scale": "mid", + "status": "active" + } + ], + "title": "Вата будівельна" + } +} \ No newline at end of file diff --git a/openprocurement_client/tests/data/profile_195333-32420000-214469-40000777.json b/openprocurement_client/tests/data/profile_195333-32420000-214469-40000777.json new file mode 100644 index 0000000..b2d5a64 --- /dev/null +++ b/openprocurement_client/tests/data/profile_195333-32420000-214469-40000777.json @@ -0,0 +1,114 @@ +{ + "data": { + "classification": { + "description": "Мережеве обладнання", + "id": "32420000-3", + "scheme": "ДК021" + }, + "criteria": [ + { + "code": "OCDS-FORM-FACTOR", + "description": "Форм-фактор", + "id": "214469-0001", + "requirementGroups": [ + { + "description": "Форм-фактор", + "id": "214469-0001-001", + "requirements": [ + { + "dataType": "string", + "expectedValue": "настільний", + "id": "214469-0001-001-01", + "title": "Форм-фактор" + } + ] + } + ], + "title": "Форм-фактор" + }, + { + "code": "OCDS-TYPE-SWITCH", + "description": "Тип комутатора", + "id": "214469-0002", + "requirementGroups": [ + { + "description": "Тип комутатора", + "id": "214469-0002-001", + "requirements": [ + { + "dataType": "string", + "expectedValue": "некерований", + "id": "214469-0002-001-01", + "title": "Тип комутатора" + } + ] + } + ], + "title": "Тип комутатора" + }, + { + "code": "OCDS-PORT-FASTETHERNET", + "description": "Кількість портів Fast Ethernet (10/100 Мбит/c)", + "id": "214469-0003", + "requirementGroups": [ + { + "description": "Кількість портів Fast Ethernet (10/100 Мбит/c), не менше 5", + "id": "214469-0003-001", + "requirements": [ + { + "dataType": "integer", + "id": "214469-0003-001-01", + "minValue": 5, + "title": "Кількість портів Fast Ethernet (10/100 Мбит/c)", + "unit": { + "code": "H87", + "name": "штук" + } + } + ] + } + ], + "title": "Кількість портів Fast Ethernet (10/100 Мбит/c)" + }, + { + "code": "OCDS-SWITCH-GUARANTEE", + "description": "Гарантія", + "id": "214469-0099", + "requirementGroups": [ + { + "description": "Гарантія, не менше 12 місяців", + "id": "214469-0099-001", + "requirements": [ + { + "dataType": "integer", + "id": "214469-0099-001-01", + "minValue": 12, + "title": "Гарантія", + "unit": { + "code": "MON", + "name": "місяців" + } + } + ] + } + ], + "title": "Гарантія" + } + ], + "dateModified": "2019-08-06T06:17:07.618531+03:00", + "description": "Комутатор настільний 5-портовий 10/100 Мбіт/с", + "id": "195333-32420000-214469-40000777", + "relatedCategory": "32420000-214469-40000777", + "status": "active", + "title": "Комутатор настільний 5-портовий 10/100 Мбіт/с", + "unit": { + "code": "H87", + "name": "штуки" + }, + "value": { + "amount": 200, + "currency": "UAH", + "valueAddedTaxIncluded": true + } + } +} \ No newline at end of file diff --git a/openprocurement_client/tests/data_dict.py b/openprocurement_client/tests/data_dict.py index cd41918..987cfbd 100644 --- a/openprocurement_client/tests/data_dict.py +++ b/openprocurement_client/tests/data_dict.py @@ -87,3 +87,11 @@ "name": 'The State Audit Service of Ukraine', "roles": 'sas' }) + +TEST_CATEGORY_KEYS = munchify({ + "category_id": '33140000-560716-42000777' +}) + +TEST_PROFILE_KEYS = munchify({ + "profile_id": '195333-32420000-214469-40000777' +}) diff --git a/openprocurement_client/tests/main.py b/openprocurement_client/tests/main.py index a6e892f..6d50aa6 100644 --- a/openprocurement_client/tests/main.py +++ b/openprocurement_client/tests/main.py @@ -3,7 +3,9 @@ from openprocurement_client.tests import ( tests_resources, tests_sync, - test_registry_client + test_registry_client, + tests_dasu, + tests_utils ) @@ -12,6 +14,8 @@ def suite(): suite.addTest(tests_sync.suite()) suite.addTest(test_registry_client.suite()) suite.addTest(tests_resources.suite()) + suite.addTest(tests_dasu.suite()) + suite.addTest(tests_utils.suite()) return suite diff --git a/openprocurement_client/tests/tests_dasu.py b/openprocurement_client/tests/tests_dasu.py index 6aecab5..f65bbd7 100644 --- a/openprocurement_client/tests/tests_dasu.py +++ b/openprocurement_client/tests/tests_dasu.py @@ -34,7 +34,7 @@ class TestUtilsFunctions(unittest.TestCase): 'auth_ds': AUTH_DS_FAKE } - client = DasuClient('', ds_config=ds_config) + client = DasuClient(resource='monitorings', ds_config=ds_config) def setUp(self): with open(ROOT + 'monitoring_' + TEST_MONITORING_KEYS.monitoring_id + '.json') as monitoring: diff --git a/openprocurement_client/tests/tests_resources.py b/openprocurement_client/tests/tests_resources.py index 3b33db2..e021ad1 100644 --- a/openprocurement_client/tests/tests_resources.py +++ b/openprocurement_client/tests/tests_resources.py @@ -24,6 +24,10 @@ from openprocurement_client.resources.tenders import ( TendersClient, TendersClientSync ) +from openprocurement_client.resources.ecatalogues import ( + CategoriesClient, + ProfilesClient, + ECataloguesClient) from openprocurement_client.tests.data_dict import ( TEST_CONTRACT_KEYS, TEST_PLAN_KEYS, @@ -31,7 +35,8 @@ TEST_TENDER_KEYS_LIMITED, TEST_TENDER_KEYS_AGREEMENT, TEST_AGREEMENT_KEYS, -) + TEST_CATEGORY_KEYS, + TEST_PROFILE_KEYS) from openprocurement_client.tests._server import ( API_KEY, API_VERSION, @@ -247,6 +252,90 @@ def test_sync_tenders(self): self.assertEqual(tenders.data, self.tenders.data) +class CategoriesClientTestCase(BaseTestClass): + + def setUp(self): + self.setting_up(client=CategoriesClient) + + with open(ROOT + 'category_' + TEST_CATEGORY_KEYS.category_id + '.json') as category: + self.category = munchify(load(category)) + + def tearDown(self): + self.server.stop() + + def test_get_category(self): + setup_routing(self.app, routes=['category']) + category = self.client.get_category(TEST_CATEGORY_KEYS.category_id) + self.assertEqual(category, self.category) + + def test_get_suppliers(self): + setup_routing(self.app, routes=['category_suppliers']) + suppliers = self.client.get_category_suppliers(TEST_CATEGORY_KEYS.category_id) + self.assertEqual(suppliers["data"], self.category["data"]["suppliers"]) + + +class ProfileCLientTestCase(BaseTestClass): + + def setUp(self): + self.setting_up(client=ProfilesClient) + + with open(ROOT + 'profile_' + TEST_PROFILE_KEYS.profile_id + '.json') as profile: + self.profile = munchify(load(profile)) + + def tearDown(self): + self.server.stop() + + def test_get_profile(self): + setup_routing(self.app, routes=['profile']) + profile = self.client.get_profile(TEST_PROFILE_KEYS.profile_id) + self.assertEqual(profile, self.profile) + + +class ECataloguesClientTestCase(BaseTestClass): + + def setting_up(self, client, resource_clients): + self.app = Bottle() + self.app.router.add_filter('resource_filter', resource_filter) + for resource_client in resource_clients: + setup_routing(self.app, routes=['{}_head'.format(resource_client.resource), 'spore']) + 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 + + self.client = client(host_url=HOST_URL, api_version=API_VERSION) + + def setUp(self): + self.setting_up(client=ECataloguesClient, resource_clients=[CategoriesClient, ProfilesClient]) + + with open(ROOT + 'category_' + TEST_CATEGORY_KEYS.category_id + '.json') as category: + self.category = munchify(load(category)) + + with open(ROOT + 'profile_' + TEST_PROFILE_KEYS.profile_id + '.json') as profile: + self.profile = munchify(load(profile)) + + def tearDown(self): + self.server.stop() + + def test_get_category(self): + setup_routing(self.app, routes=['category']) + category = self.client.categories.get_category(TEST_CATEGORY_KEYS.category_id) + self.assertEqual(category, self.category) + + def test_get_suppliers(self): + setup_routing(self.app, routes=['category_suppliers']) + suppliers = self.client.categories.get_category_suppliers(TEST_CATEGORY_KEYS.category_id) + self.assertEqual(suppliers["data"], self.category["data"]["suppliers"]) + + def test_get_profile(self): + setup_routing(self.app, routes=['profile']) + profile = self.client.profiles.get_profile(TEST_PROFILE_KEYS.profile_id) + self.assertEqual(profile, self.profile) + + class UserTestCase(BaseTestClass): """""" def setUp(self): diff --git a/openprocurement_client/tests/tests_sync.py b/openprocurement_client/tests/tests_sync.py index 232e074..f181ebc 100644 --- a/openprocurement_client/tests/tests_sync.py +++ b/openprocurement_client/tests/tests_sync.py @@ -166,8 +166,8 @@ 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.version, '2.3') + 'https://lb-api-staging.prozorro.gov.ua') + self.assertEqual(self.resource_feeder.version, '2.5') self.assertEqual(self.resource_feeder.resource, 'tenders') self.assertEqual(self.resource_feeder.adaptive, False) self.assertEqual(self.resource_feeder.extra_params, diff --git a/openprocurement_client/tests/tests_utils.py b/openprocurement_client/tests/tests_utils.py index 4dfe360..d572ba7 100644 --- a/openprocurement_client/tests/tests_utils.py +++ b/openprocurement_client/tests/tests_utils.py @@ -114,7 +114,7 @@ def test_get_agreement_id_by_uaid(self, mock_get_agreements): 'get_monitorings') def test_get_monitoring_id_by_uaid(self, mock_get_monitorings): mock_get_monitorings.side_effect = [self.response.data, []] - client = DasuClient('') + client = DasuClient(resource='monitorings') with self.assertRaises(IdNotFound): result = get_monitoring_id_by_uaid('f3849ade33534174b8402579152a5f41', client, id_field='dateModified') diff --git a/openprocurement_client/utils.py b/openprocurement_client/utils.py index 60b5418..ec844f8 100644 --- a/openprocurement_client/utils.py +++ b/openprocurement_client/utils.py @@ -107,6 +107,18 @@ def get_plan_id_by_uaid(ua_id, client, descending=True, id_field='planID'): raise IdNotFound +def get_qualification_id_by_uaid(ua_id, client, descending=True, id_field='qualificationID'): + params = {'offset': '', 'opt_fields': id_field, 'descending': descending} + qualification_list = True + client._update_params(params) + while qualification_list: + qualification_list = client.get_qualifications() + for qualification in qualification_list: + if qualification[id_field] == ua_id: + return qualification.id + raise IdNotFound + + def get_monitoring_id_by_uaid(ua_id, client, descending=True, id_field='monitoring_id'): params = {'offset': '', 'opt_fields': id_field, 'descending': descending} monitoring_list = True diff --git a/setup.py b/setup.py index fee5b68..5ee3cd6 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import find_packages, setup -version = '2.0.0' +version = '2.1.1+dp' install_requires = [ 'gevent',