"""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 :