"""Script to Bulk update Pricing data for Physical Releases.""" import uuid import os import dotenv import physical_pricing_bulk_utils as ppbu from logging import INFO from owslogger import logger import requests dotenv.load_dotenv('../.env') ENVIRONMENT = os.environ.get('Environment', 'dev') if ENVIRONMENT == 'prod': OWS_PRICING_URL = 'https://ows-pricing.theorchard.io' OWS_PRODUCT_URL = 'https://ows-product.theorchard.io' else: OWS_PRICING_URL = 'https://qa-ows-pricing.theorchard.io' OWS_PRODUCT_URL = 'https://qa-ows-product.theorchard.io' S3_BUCKET = os.environ.get('S3_BUCKET', 'dev-cucumbers') INGEST_FOLDER = os.environ.get( 'INGEST_FOLDER', 'bulk-physical-pricing-update-ingest') ARCHIVE_FOLDER = os.environ.get( 'ARCHIVE_FOLDER', 'bulk-physical-pricing-update-archive') physical_product_exists_call = '{}/product/{}' LOGGING_LEVEL = INFO LOGGER_DSN = os.environ.get('LOGGER_DSN') SCRIPT_NAME = 'bulk-update-physical-pricing' APPLICATION_NAME = 'bulk_update_physical_pricing' app_logger = logger.setup( ENVIRONMENT, SCRIPT_NAME, LOGGING_LEVEL, APPLICATION_NAME, '1.0.0', dsn=LOGGER_DSN ) def get_current_logger(correlation_id=None): """Get logger with a correlation_id attached. Args: correlation_id: correlation_id that will be sent along with log record Returns: logger.OwsLoggingAdapter: instance of OwsLoggingAdapter with correlation_id attached. """ return logger.OwsLoggingAdapter( app_logger, {'correlation_id': correlation_id or uuid.uuid4()}) def get_physical_pricing_tiers(): """Get pricing tiers for Physical products.""" GET_PRICING_TIER_URL = '{}/pricing-family/4/orchard-pricing-tier'.format( OWS_PRICING_URL) pricing_tiers_response = requests.get(GET_PRICING_TIER_URL) return pricing_tiers_response pricing_tiers_response = get_physical_pricing_tiers().json()['items'] physical_pricing_tiers = {tier['name'].lower(): tier['orchard_pricing_tier_id'] for tier in pricing_tiers_response} chunk_size = 20 def get_pricing_override(product_id): """Get pricing overrides for a product.""" PRICING_OVERRIDE_URL = '{}/product/{}/override'.format( OWS_PRICING_URL, product_id) pricing_overrides_response = requests.get(PRICING_OVERRIDE_URL) return pricing_overrides_response def validate(pricing_row): """Check if the pricing data provided is valid. 1. Pricing tier should be a physical pricing tier. 2. Release should be a physical release. Args: pricing_row (dict): Pricing detail row from CSV. Returns: Pricing Tier ID or False. """ pricing_tier_name = pricing_row['orchard_price_tier']['name'].strip() release_id = pricing_row['release_id'] validation_message = '' if pricing_tier_name.lower() not in physical_pricing_tiers and \ pricing_tier_name: validation_message = 'Invalid Physical pricing tier - {}'.format( pricing_tier_name) return False, validation_message if not release_id or not release_id.isdigit(): validation_message = 'Not a valid release' return False, validation_message product_response = requests.get(physical_product_exists_call.format( OWS_PRODUCT_URL, release_id)) if not product_response: validation_message = 'Release not found' return False, validation_message if product_response.json()['context_type'] != 'physical': validation_message = 'Not valid physical release' return False, validation_message return pricing_tier_name.lower(), validation_message def chunks(l, n): """Yield successive n-sized chunks from l.""" for i in range(0, len(l), n): yield l[i:i + n] def put_overrides(pricing_row, valid_pricing_tier, existing_overrides, logger): """Read JSON batch and make PUT call to create or update overrides.""" product_id = pricing_row['release_id'] create_override_url = '{0}/product/{1}/override/bulk'.format( OWS_PRICING_URL, product_id) data = {'items': []} override_stores = [] sort_order = 1 for store_id, store_pricing in pricing_row['store_pricing'].items(): store_pricing = store_pricing.strip() if store_pricing: override_stores.append(store_id) print('going to set up an override for store_id {0} to have ' 'price_code: {1}'.format(store_id, store_pricing)) override_request_body = { 'sort_order': sort_order, 'price_code': store_pricing, 'product_id': pricing_row['release_id'], 'applies_worldwide': True, 'activated': True, 'stores': [int(store_id)], 'pricing_family_id': 4} if store_pricing.lower() in physical_pricing_tiers: override_request_body['orchard_pricing_tier_id'] = \ physical_pricing_tiers[store_pricing.lower()] del(override_request_body['price_code']) sort_order = sort_order + 1 data['items'].append(override_request_body) if not override_stores: print('Store overrides empty for release-id ', product_id) return 200 if override_stores: increment_value = len(override_stores) print('Updating existing overrides...', override_stores) existing_override_chunks = chunks(existing_overrides, chunk_size) chunk_num = 1 response_statuses = [] for override_chunk in existing_override_chunks: print('Update chunk no.', chunk_num) chunk_num += 1 override_updates = [] for override in override_chunk: if override['activated']: override_request_body = { 'sort_order': override['sort_order'] + increment_value, 'product_id': override['product_id'], 'pricing_family_id': override['pricing_family_id'], 'product_pricing_override_id': override[ 'product_pricing_override_id']} override_updates.append(override_request_body) change_priority_response = requests.put( create_override_url, json={'items': override_updates}) response_statuses.append(change_priority_response.status_code) print('Updating priority for existing overrides', create_override_url) print(change_priority_response, override_updates) if data['items']: override_chunks = chunks(data['items'], chunk_size) chunk_num = 1 for override_chunk in override_chunks: try: print('\nInsert chunk no.', chunk_num) chunk_num += 1 print('Making call to do bulk override as above URL:', create_override_url, override_chunk) create_override_response = requests.put( create_override_url, json={'items': override_chunk}) response_statuses.append(create_override_response.status_code) except Exception as e: logger.info( 'ows-pricing request to post pricing overrides failed: ' '{}'.format(str(e))) print('Error calling ows-pricing to create product pricing' 'override:', str(e)) print('\n\nresponse from create override: ') print(response_statuses) response_status = 200 if all( [status == 200 for status in response_statuses]) else 400 return response_status def ingest_pricing_row(pricing_row, logger): """Ingest pricing row.""" print('\n\n********** Validating pricing entry **********') pricing_tier, validation_message = validate(pricing_row) product_id = pricing_row['release_id'] if not pricing_tier and pricing_tier != '': print('Pricing entry invalid -> ', validation_message, pricing_row) return False, validation_message valid_pricing_tier = physical_pricing_tiers.get(pricing_tier) print('\n---------- Ingesting pricing row ----------') set_tier_response = 200 if valid_pricing_tier: set_tier_url = \ '{}/product/{}/pricing-family/4/orchard_pricing_tier'.format( OWS_PRICING_URL, product_id) print('Going to set orchard tier on URL: ', set_tier_url) data = {'orchard_pricing_tier_id': valid_pricing_tier} print('about to send data:', data) try: set_tier_response = requests.put(set_tier_url, json=data) set_tier_response = set_tier_response.status_code except Exception as e: logger.info( 'Request to set orchard pricing tier failed:{}'.format(str(e))) print('Error while setting orchard pricing tier :', str(e)) print('response from set tier: ', set_tier_response) print('GET call to get override ids for release id.') override_response = get_pricing_override(product_id) override_response = override_response.json()['items'] overrides_status = put_overrides( pricing_row, valid_pricing_tier, override_response, logger) if set_tier_response == overrides_status == 200: response_status = 200 else: response_status = 400 validation_message = 'System error' print('\n---------- Ingestion complete for ', pricing_row, ' ----------') logger.info(f'Ingestion complete for {pricing_row}') return response_status, validation_message if __name__ == '__main__': try: currentLogger = get_current_logger() mail_body = """ Hi There!

The physical pricing batch update has completed for the release ids \ listed below.

""" session_res = ppbu.getS3connection() bucket = session_res.Bucket(S3_BUCKET) ingest_batch = ppbu.get_oldest_batch_to_ingest(bucket, INGEST_FOLDER) if not ingest_batch: print('No Batch to ingest') exit() ingest_ready_filename = ingest_batch.key batch_content = ppbu.get_pricing_data(ingest_batch) currentLogger.info('Batch Content:: ', batch_content) successfull_overrides = [] failed_overrides = [] filename_running = ingest_ready_filename.replace('ready', 'running') currentLogger.info(f'Renaming {ingest_ready_filename} to {filename_running}') ppbu.rename_ingest_batch( bucket, session_res, ingest_ready_filename, filename_running) if batch_content['override_data']: for row in batch_content['override_data']: override_status, validation_message = ingest_pricing_row(row, currentLogger) if override_status == 200 or override_status is None: if row['release_id'] not in successfull_overrides: successfull_overrides.append(row['release_id']) else: failed_overrides.append( {'id': row['release_id'], 'validation': validation_message}) currentLogger.info(f'Done ingesting all pricing rows with release_ids: {successfull_overrides}') else: mail_body += 'No release ids found.' filename_complete = filename_running.replace('running', 'complete') currentLogger.info(f'Renaming {filename_running} to {filename_complete}') ppbu.rename_ingest_batch( bucket, session_res, filename_running, filename_complete) archive_filename = filename_complete.replace( INGEST_FOLDER, ARCHIVE_FOLDER) ppbu.archive_batch( bucket, session_res, filename_complete, archive_filename) subject = 'Physical Pricing Batch Update Completed' if successfull_overrides: mail_body += 'Successfully ingested :
' release_ids = successfull_overrides if failed_overrides: mail_body += '
Failed :
' release_ids = failed_overrides ppbu.send_email( subject, mail_body, [batch_content['oa_user']['email']], release_ids, currentLogger) except Exception as e: currentLogger.info( 'Physical Pricing bulk updater failed due to {}' ' for batch {}'.format(str(e), ingest_ready_filename))