def process_single_auction(self, auction_id): try: auction = self.auctions_client.get_auction(auction_id) except ResourceNotFound: LOGGER.warning('Auction object {} not found'.format(auction_id)) else: self.process_auction(auction['data'])
def _form_auction(self, lot, auction_doc): # Convert assets to items items, documents = self._create_items_from_assets(lot.assets) if not items: self.switch_lot_status(lot.id, 'active.salable') self.invalidate_auction(auction_doc.id) return False api_auction_doc = self.auctions_client.get_resource_item( auction_doc['id']).data LOGGER.info('Received auction {} from CDB'.format(auction_doc['id'])) # Add items to CDB patch_data = {'data': {'items': items, 'dgfID': lot.lotIdentifier}} message = 'Auction: {} was formed from lot: {}'.format( auction_doc['id'], lot.id) self._patch_resource_item(self.auctions_client, api_auction_doc.id, patch_data, message) # Add documents to CDB for document in documents: self.auctions_client.create_resource_item_subitem( api_auction_doc.id, {'data': document}, DOCUMENTS) LOGGER.info( 'Added document with hash {} to auction id: {} item id:' ' {} in CDB'.format(document['hash'], auction_doc['id'], document['relatedItem'])) return True
def _extract_transfer_token(self, auction_id): credentials = self.auctions_client.extract_credentials( resource_item_id=auction_id) LOGGER.info( "Successfully extracted tranfer_token from auction {})".format( auction_id)) return credentials['data']['transfer_token']
def prepare_auction(self, auction_doc): LOGGER.info('Prepare auction {}'.format(auction_doc.id)) lot = self._receive_lot(auction_doc) if lot: auction_formed = self._form_auction(lot, auction_doc) if auction_formed: self._activate_auction(lot, auction_doc)
def report_results(self, auction_doc): LOGGER.info('Report auction results {}'.format(auction_doc.id)) lot_id = auction_doc.merchandisingObject # Get lot try: lot = self.lots_client.get_lot(lot_id).data except ResourceNotFound: LOGGER.warning( 'Lot {} not found when report auction {} results'.format( lot_id, auction_doc.id)) return if lot.status != 'active.auction': LOGGER.info('Auction {} results already reported to lot {}'.format( auction_doc.id, lot_id)) return LOGGER.info('Received lot {} from CDB'.format(lot_id)) if auction_doc.status == 'complete': next_lot_status = 'pending.sold' else: next_lot_status = 'active.salable' # Report results try: self.switch_lot_status(lot['id'], next_lot_status) except Exception as e: LOGGER.error('Failed update lot info {}. {}'.format( lot_id, e.message))
def _get_documents(self, item): if not hasattr(self.auctions_client, 'ds_client'): return [] documents = [] for doc in item.get('documents', []): item_document = {k: doc[k] for k in self.document_keys if k in doc} try: registered_doc = self.auctions_client.ds_client.register_document_upload( doc['hash']) LOGGER.info('Registered document upload for item {} with hash' ' {}'.format(item.id, doc['hash'])) except: LOGGER.error('While registering document upload ' 'something went wrong :(') continue transfer_item = { 'get_url': doc.url, 'upload_url': registered_doc['upload_url'] } self.documents_transfer_queue.put(transfer_item) item_document['url'] = registered_doc['data']['url'] item_document['documentOf'] = 'item' item_document['relatedItem'] = item.id documents.append(item_document) return documents
def _post_contract(self, contract_data): contract = self.contracts_client.create_contract(contract_data).data log_msg = "Successfully created contract {}".format(contract.id) if 'merchandisingObject' in contract_data['data']: log_msg += " from lot {}".format( contract_data['data']['merchandisingObject']) LOGGER.info(log_msg, extra={'MESSAGE_ID': CREATE_CONTRACT_MESSAGE_ID}) return contract
def _patch_lot_contract(self, contract_data, lot_id, contract_id): self.lots_client.patch_resource_item_subitem( resource_item_id=lot_id, patch_data={'data': contract_data}, subitem_name='contracts', subitem_id=contract_id) LOGGER.info('Update lot\'s {} contract data'.format(lot_id), extra={'MESSAGE_ID': UPDATE_CONTRACT_MESSAGE_ID})
def _patch_resource_item(self, client, resource_id, patch_data, message, extra=None): resource = client.patch_resource_item(resource_id, patch_data) LOGGER.info(message, extra=extra) return resource
def run(self): self.transmitter = spawn(self.file_bridge) sleep(1) LOGGER.info('Getting auctions') for auction in continuous_changes_feed(self.db, self.killer, self.timeout): self.process_auction(auction) if self.killer.kill_now: break
def _get_lot(self, auction_doc): lot_id = auction_doc.merchandisingObject try: lot = self.lots_client.get_lot(lot_id).data except ResourceNotFound: LOGGER.warning( 'Lot {} not found when report auction {} results'.format( lot_id, auction_doc.id)) return LOGGER.info('Received lot {} from CDB'.format(lot_id)) return lot
def _check_lot_auction(self, lot, auction_doc): lot_auction = next( (auction for auction in lot.auctions if auction_doc.id == auction.get('relatedProcessID')), None) if not lot_auction: LOGGER.warning('Auction object {} not found in lot {}'.format( auction_doc.id, lot.id)) return if lot_auction['status'] != 'active': LOGGER.info('Auction {} results already reported to lot {}'.format( auction_doc.id, lot.id)) return return lot_auction
def _switch_auction_status(self, status, lot_id, auction_id): self.lots_client.patch_resource_item_subitem( resource_item_id=lot_id, patch_data={'data': { 'status': status }}, subitem_name='auctions', subitem_id=auction_id) LOGGER.info('Switch lot\'s {} auction {} to ({}) status'.format( lot_id, auction_id, status), extra={ 'MESSAGE_ID': SWITCH_LOT_AUCTION_STATUS_MESSAGE_ID, 'STATUS': status })
def _receive_lot(self, auction_doc): lot_id = auction_doc.merchandisingObject # Get lot try: lot = self.lots_client.get_lot(lot_id).data except ResourceNotFound: self.invalidate_auction(auction_doc.id) return LOGGER.info('Received lot {} from CDB'.format(lot_id)) is_lot_unusable = bool( (lot.status == u'active.awaiting' and auction_doc.id != lot.auctions[-1]) or lot.status not in [u'active.salable', u'active.awaiting', u'active.auction']) if is_lot_unusable: # lot['status'] = 'active.salable' # self.lots_client.patch_resource_item(lot) LOGGER.warning( 'Lot status \'{}\' not equal \'active.salable\''.format( lot.status), extra={'MESSAGE_ID': 'invalid_lot_status'}) self.invalidate_auction(auction_doc.id) return elif lot.status == u'active.auction' and auction_doc.id == lot.auctions[ -1]: # Switch auction self.switch_auction_status(auction_doc['id'], 'active.tendering') return elif lot.status == u'active.auction' and auction_doc.id != lot.auctions[ -1]: self.invalidate_auction(auction_doc.id) elif lot.status == u'active.awaiting' and auction_doc.id == lot.auctions[ -1]: return lot # Lock lot auctions_list = lot.get('auctions', []) auctions_list.append(auction_doc.id) lot_patch_data = { 'data': { 'status': 'active.awaiting', 'auctions': auctions_list } } self._patch_resource_item(self.lots_client, lot.id, lot_patch_data, 'Lock lot {}'.format(lot.id), {'MESSAGE_ID': 'lock_lot'}) return lot
def process_auction(self, auction): LOGGER.info('Received auction {} in status {}'.format( auction['id'], auction['status']), extra={ 'MESSAGE_ID': GET_AUCTION_MESSAGE_ID, 'STATUS': auction['status'] }) if auction[ 'procurementMethodType'] not in self.auction_type_processing_configurator: LOGGER.warning( 'Such procurementMethodType %s is not supported by this' ' convoy configuration' % auction['procurementMethodType']) return processing = self.auction_type_processing_configurator.get( auction['procurementMethodType']) processing.process_auction(auction)
def report_results(self, auction_doc): LOGGER.info('Report auction results {}'.format(auction_doc.id)) lot_processing = 'merchandisingObject' in auction_doc contract_processing = 'contractTerms' in auction_doc if lot_processing: # lot ID, that auctions sells lot = self._get_lot(auction_doc) # get lot from the registry if not lot: return lot_auction = self._check_lot_auction( lot, auction_doc) # search for the auction in the lot if not lot_auction: return # terminalize status of lot auction terminalized_status = PRE_TERMINAL_MAPPING.get(auction_doc.status, auction_doc.status) # update lot's auction status with actual auction status if auction_doc.status in UNSUCCESSFUL_TERMINAL_STATUSES + UNSUCCESSFUL_PRE_TERMINAL_STATUSES: if lot_processing: self._switch_auction_status(terminalized_status, lot.id, lot_auction.id) self.auctions_mapping.put(str(auction_doc.id), True) elif auction_doc.status in SUCCESSFUL_TERMINAL_STATUSES + SUCCESSFUL_PRE_TERMINAL_STATUSES: if contract_processing and lot_processing: # build contract regarding obligatoriness of it's fields contract_data = make_contract(auction_doc) try: contract_data[ 'transfer_token'] = self._extract_transfer_token( auction_doc['id']) except EXCEPTIONS as e: message = 'Server error: {}'.format( e.status_code) if e.status_code >= 500 else e.message LOGGER.error( "Failed to extract transfer token from auction {} ({})" .format(auction_doc.id, message)) return # create contract, if none of them are associated with lot if lot.contracts[0].get('relatedProcessID') is None: contract = self._post_contract({'data': contract_data}) else: LOGGER.info( 'Contract {} has already created, and patched to lot {}' .format(lot.contracts[0].get('relatedProcessID'), lot.id)) return if lot_processing: # update lot's auction status with actual auction status self._switch_auction_status(terminalized_status, lot.id, lot_auction.id) if lot_processing and contract_processing: self.update_lot_contract(lot, contract) self.auctions_mapping.put(str(auction_doc.id), True)
def __init__(self, convoy_conf): LOGGER.info('Init Convoy...') self.auction_type_processing_configurator = {} self.auction_types_for_filter = {} self.convoy_conf = convoy_conf self.killer = GracefulKiller() self.stop_transmitting = False self.transmitter_timeout = self.convoy_conf.get( 'transmitter_timeout', 10) created_clients = init_clients(convoy_conf) for key, item in created_clients.items(): setattr(self, key, item) self.documents_transfer_queue = Queue() self.timeout = self.convoy_conf.get('timeout', 10) self.keys = KEYS self.document_keys = DOCUMENT_KEYS if convoy_conf['lots'].get('loki'): process_loki = ProcessingLoki(convoy_conf['lots']['loki'], created_clients, self.keys, self.document_keys, self.documents_transfer_queue) self._register_aliases(process_loki, 'loki') if convoy_conf['lots'].get('basic'): process_basic = ProcessingBasic(convoy_conf['lots']['basic'], created_clients, self.keys, self.document_keys, self.documents_transfer_queue) self._register_aliases(process_basic, 'basic') push_filter_doc(self.db, self.auction_types_for_filter)
def _create_items_from_assets(self, assets_ids): items = [] documents = [] for index, asset_id in enumerate(assets_ids): asset = self.assets_client.get_asset(asset_id).data LOGGER.info('Received asset {} with status {}'.format( asset.id, asset.status)) # Convert asset to item item = {k: asset[k] for k in self.keys if k in asset} item['description'] = asset.title items.append(item) # Get documents from asset for doc in self._get_documents(asset): documents.append(doc) # Get items and items documents from complex asset for item in asset.get('items', []): items.append(item) for doc in self._get_documents(item): documents.append(doc) return items, documents
def file_bridge(self): while not self.stop_transmitting: try: transfer_item = self.documents_transfer_queue.get(timeout=2) try: file_, _ = self.auctions_client.get_file( transfer_item['get_url']) LOGGER.debug('Received document file from asset DS') # TODO: Fill headers valid data if needed headers = {} self.auctions_client.ds_client.document_upload_not_register( file_, headers) LOGGER.debug('Uploaded document file to auction DS') except Exception: LOGGER.error('While receiving or uploading document ' 'something went wrong :(') self.documents_transfer_queue.put(transfer_item) sleep(1) continue except Empty: sleep(self.transmitter_timeout)