""" Tasks for generating custom accounting statement export from Avro files ======================================================================== """ import codecs import datetime from decimal import Decimal import functools import glob import os import shutil import time import zipfile from babel.numbers import format_decimal import fastavro as avro from garcon import task from slugify import slugify from processing_accounting.flows.custom_export import setting from processing_accounting.flows.custom_export.util import datetime as dt_util from processing_accounting.flows.custom_export.util import report from processing_accounting.util import db from processing_accounting.util import dynamodb from processing_accounting.util import logging from processing_accounting.util import s3 from processing_accounting.util import ses def _get_user_id_type(user_id, user_type): """Get user id type code Args: user_id (str): user id. Example: 8869 user_type (str): label or subaccount Return: str: user_id_type code. Example: 8869L """ return '{}{}'.format(user_id, user_type[0].upper()) def _get_user_params(**kwargs): """Get user_params code Args: kwargs (dict): dictionary of params Returns: str: sorted user_params code generated from the values in the dictionary input """ assert 'period_ids' in kwargs assert 'transaction_types' in kwargs assert 'file_format' in kwargs assert 'locale' in kwargs return '__'.join(sorted([value for name, value in kwargs.items()])) def _flush_dir(full_file_path): """Prepare directory by emptying it or creating it Args: full_file_path (str): full path to the local file """ if os.path.exists(full_file_path): os.remove(full_file_path) if not os.path.exists(os.path.dirname(full_file_path)): os.makedirs(os.path.dirname(full_file_path), exist_ok=True) @functools.lru_cache(maxsize=setting.FORMAT_CACHE_SIZE) def apply_format(data_locale, format='#,##0.000000'): """Apply number format according to specified locale Taking in pipe limited string so that the string can be a key for caching Args: data_locale (str): '|' delimited string with a decimal number as the first term and locale as the second term. Example: 4.598673|en_US """ return format_decimal( float(data_locale.split('|')[0]), format=format, locale=data_locale.split('|')[1]) def _get_excluded_columns( user_id, user_type, report_version=setting.DEFAULT_REPORT_VERSION, transaction_types=''): """Get excluded columns Args: user_id (str): vendor id or subaccount id user_type (str): label or subaccount report_version (int): version of the report transaction_types (str): comma separated list of transaction types Returns: list: list of excluded column names """ excluded_columns = ['statement_detail_id', 'isdistributor', 'user_id_type'] if report.is_physical_report(transaction_types): extra_exclude_columns = setting.PHYSICAL_REPORT_EXCLUDED_COLUMNS else: extra_exclude_columns = setting.EXCLUDE_COLUMNS_BY_VERSION.get( report_version, []) excluded_columns.extend(extra_exclude_columns) if user_type == 'label': sql = ( 'select isdistributor from dim_label where labelid = {}').format( user_id) result = [r for r in db.snowflake_query(sql)].pop() if result.get('ISDISTRIBUTOR') == 'N': excluded_columns.append('subaccount') excluded_columns.append('subaccount_label_share_net_receipts') else: excluded_columns.append('subaccount_label_share_net_receipts') else: excluded_columns.append('subaccount') excluded_columns.append('unit_price') excluded_columns.append('gross') excluded_columns.append('adjusted_gross') excluded_columns.append('split_rate') excluded_columns.append('ringtone_publishing') excluded_columns.append('cloud_publishing') excluded_columns.append('publishing') excluded_columns.append('mech_administrative_fee') excluded_columns.append('label_share_net_receipts') excluded_columns.append('original_price') excluded_columns.append('discount') return excluded_columns def calculate_subaccount_split(subaccount_split, gross): """Calculate the spit (gross/net) for subaccounts. Args: subaccount_split (dict): split type and commision override gross (str): gross value from AVRO Returns: str: calculated split """ acc_type = subaccount_split.get('subaccount_split_type') if acc_type != 'Gross': return '' gross = Decimal(gross) commission = Decimal(subaccount_split.get('commissionoverride')) return str(gross * commission) def _write_files( file_write, filename, record, locale, money_columns, integer_columns, part, index, report_schema, user_id_type, user_params, header, file_format, subaccount_split=None): """Extracting data from AVRO into partitioned JSON files. This task extract and put data from AVRO into multiple JSON files. Each JSON file will be read and transformed by a process. Args: file_write (file pointer): file pointer for file write filename (str): file name of a data chunk record (dict): a record from a read iteration of the avro file reader locale (str): locale code. Example: en_US money_columns (list): list of column names for monetary columns integer_columns (list): list of column names for integer values part (int): current partition number index (int): current row number report_schema (collections.OrderedDict): ordered report schema user_id_type (str): user_id and user_type. Example: 18805L user_params (str): __ delimited string of user params. header (list): list of column headers file_format (str): xls or txt subaccount_split (dict): dictionarry of subaccount split type and value Returns: tuple: tuple of file handle of the JSON file to be written to, current row number and current partition number. """ if subaccount_split is None: subaccount_split = {} row = [] for field_name in report_schema.keys(): column = record.get(field_name) column = str(column or '') column = column.replace(setting.REPORT_DELIMITER, '').replace( '\r', '').replace('\n', '').replace('\\\\N', '') if field_name == 'subaccount_label_share_net_receipts': gross_net_split = calculate_subaccount_split( subaccount_split, record['gross']) column = gross_net_split or column value_locale = '{}|{}'.format(column, locale) skip_apply_format = ( field_name in ('original_price', 'discount') and column == '') if field_name in integer_columns: column = apply_format(value_locale, '#,###') if field_name in money_columns and not skip_apply_format: column = apply_format(value_locale) enclose_character = setting.ENCLOSE_CHARACTER[file_format] if enclose_character: column = '{ec}{value_locale}{ec}'.format( ec=enclose_character, value_locale=column.replace( enclose_character, '{}{}'.format( enclose_character, enclose_character))) row.append(column) file_write.write(setting.REPORT_DELIMITER.join(row) + '\n') index += 1 if index % (setting.LINES_PER_FILE / setting.STATUS_UPDATE_INTERVALS) == 0: logging.logger.info('Scanned rows: {} '.format(index)) additional_params = dict( lines_scanned=str(index), number_of_partitions=str(part)) dynamodb.set_status( user_id_type, user_params, 'GENERATING', **additional_params) if index % setting.LINES_PER_FILE == 0: file_write.close() part += 1 filename = '{}_part{}.{}'.format( filename.split('_part')[0], part, file_format) file_write = codecs.open( filename, 'a', setting.FILE_ENCODING.get(file_format)) file_write.write(setting.REPORT_DELIMITER.join(header) + '\n') return file_write, index, part def _get_report_file_name_parts( period_ids, user_id, user_type, locale, transaction_types): """Get final report name Args: period_ids (str): comma delimited string of period ids user_id (str): vendor id or subaccount id user_type (str): label or subaccount locale (str): standard locale code. Example: en_US transaction_types (str): comma delimited string of transaction type codes Returns: dict: report file name parameters """ today = time.strftime('%Y-%m-%d', time.gmtime()).replace('-', '') user_id_sql = ' ds.subaccountid = {}'.format(user_id) if user_type == 'label': user_id_sql = ' dl.labelid = {}'.format(user_id) sql = ( 'select min(labelname) as label_name, ' 'min(ds.subaccountname) as subaccount_name, ' 'ss.payment_interval, dp.year, dp.quarter, dp.month ' 'from dim_label dl ' 'inner join dim_release dr on dr.labelid = dl.labelid ' 'left join dim_subaccount ds on ds.subaccountid = dr.subaccountid ' 'inner join booked_vendor_contract_snapshot ss ' ' on ss.vendor_id = dl.labelid ' 'inner join dim_period dp on dp.periodid = ss.period_id ' 'where {user_id_sql} and dp.periodid in({period_ids}) ' 'group by ss.payment_interval, dp.year, dp.quarter, ' 'dp.month').format( user_id_sql=user_id_sql, period_ids=period_ids) results = list(db.snowflake_query(sql)) result = results.pop() reporting_period = 'Q{}{}'.format( result.get('QUARTER'), result.get('YEAR')) user_name = result.get('LABEL_NAME') number_format = 'US' if locale != 'en_US': number_format = 'EU' if user_type == 'subaccount': user_name = result.get('SUBACCOUNT_NAME') if result.get('PAYMENT_INTERVAL') == 'month': month_name = datetime.date( 1900, int(result.get('MONTH')), 1).strftime('%B') reporting_period = '{}{}'.format(month_name[:3], result.get('YEAR')) report_type = setting.FILE_REPORT_TYPES.get( transaction_types, setting.DEFAULT_FILE_REPORT_TYPE) return { 'date': today, 'reporting_period': reporting_period, 'report_type': report_type, 'user_name': user_name, 'number_format': number_format } def _get_avro_local_path( user_id, user_type, period_ids, transaction_types, locale, file_format): """Get local path from user params Args: user_id (str): user_id. Example 8869 user_type (str): label or subaccount period_ids (str): comma delimited string of period ids transaction_types (str): comma delimited string of transaction type codes locale (str): en_US or es_ES file_format (str): xls or txt Returns: str: full path to the local downloaded avro file """ user_id_type = _get_user_id_type(user_id, user_type) key = _get_user_params( user_id=user_id, user_type=user_type, period_ids=period_ids, transaction_types=transaction_types, locale=locale, file_format=file_format) avro_file_path = setting.LOCAL_AVRO_FILE_PATH.format( user_id_type=user_id_type, user_params_key=key) if not os.path.exists(os.path.dirname(avro_file_path)): os.makedirs(os.path.dirname(avro_file_path), exist_ok=True) return avro_file_path def _get_local_path( user_id, user_type, period_ids, transaction_types, locale, file_format): """Get local path from user params Args: user_id (str): user_id. Example 8869 user_type (str): label or subaccount period_ids (str): comma delimited string of period ids transaction_types (str): comma delimited string of transaction type codes locale (str): standard locale code. Example: en_US file_format (str): format of the file in setting (.txt or .xls) Returns: str: full path to the local file """ user_id_type = _get_user_id_type(user_id, user_type) key = _get_user_params( user_id=user_id, user_type=user_type, period_ids=period_ids, transaction_types=transaction_types, locale=locale, file_format=file_format) file_name_parts = _get_report_file_name_parts( period_ids, user_id, user_type, locale, transaction_types) file_name = setting.FINAL_REPORT_NAME.format( date=file_name_parts.get('date'), reporting_period=file_name_parts.get('reporting_period'), report_type=file_name_parts.get('report_type'), user_name=slugify(file_name_parts.get('user_name'), separator='_'), number_format=file_name_parts.get('number_format')) local_file_path = setting.LOCAL_FILE_PATH.format( user_id_type=user_id_type, user_params_key=key, file_name=file_name) _flush_dir(local_file_path) return local_file_path def _get_header(report_schema): """Get header list with excluded columns excluded Args: report_schema (collections.OrderedDict): ordered fields with headers Returns: list: list of column headers """ headers = list(report_schema.values()) return headers @task.decorate(timeout=2000) def bootstrap( activity, period_ids, user_type, user_id, transaction_types, locale, file_format, report_version): """Setup context variables for custom report workflow Args: activity (ActivityWorker): the activity worker. period_ids (str): comma delimited string of period ids user_type (str): label or subaccount user_id (str): vendor id or subaccount id transaction_types (str): comma delimited string of transaction type codes locale (str): standard locale code. Example: en_US file_format (str): format of the file in setting (.txt or .xls) report_version (int): version of the report, will be set to setting.DEFAULT_REPORT_VERSION if None was provided Returns: dict: context variables to be passed along in the workflow """ assert file_format, 'Missing: file_format' assert locale, 'Missing: locale' assert transaction_types, 'Missing: transaction_types' assert user_id, 'Missing: user_id' assert user_type, 'Missing: user_type' assert period_ids, 'Missing: period_ids' if report_version is None: report_version = setting.DEFAULT_REPORT_VERSION resp = {} # Sort comma delimited params transaction_types = ','.join(sorted(transaction_types.split(','))) period_ids = ','.join(sorted(period_ids.split(','))) # Get s3_path from Dynamodb user_id_type = _get_user_id_type(user_id, user_type) items = dynamodb.get_items( user_id_type, file_type='AVRO', period_ids=period_ids) items_list = [i for i in items] if len(items_list) == 0: resp['stop'] = True return resp item = items_list.pop() resp['avro_file_s3_path'] = item.get('s3_path') # Setup transaction type parameters if transaction_types is not None: resp['transaction_types'] = transaction_types else: resp['transaction_types'] = 'all' resp['money_columns'] = setting.money_columns resp['period_ids'] = period_ids resp['file_format'] = file_format.lower() resp['user_id_type'] = user_id_type user_params = _get_user_params( period_ids=period_ids, transaction_types=transaction_types, locale=locale, file_format=file_format) resp['user_params'] = user_params resp['report_version'] = report_version additional_params = dict( file_type=file_format, number_format=locale, period_ids=period_ids, transaction_types=transaction_types, account_id=user_id, account_type=user_type) dynamodb.set_status( user_id_type, user_params, 'PENDING', **additional_params) return resp def check_active_workflows(activity, user_id_type): """Check active workflows based on DynamoDB record. Workflows that are > 2 days old considered to be inactive. Args: activity (ActivityWorker): the activity worker user_id_type (str): user ID and type which is a PK for DynamoDB Returns: list: resulting DynamodDB records """ # Check if there is any workflow reading the shared avro file. active_workflows = [] for item in dynamodb.get_items(user_id_type): if item.get('status') == 'GENERATED': continue if item.get('file_type') == 'AVRO': continue download_start = item.get('download_avro_file_start') if download_start is None: activity.logger.info('Deleting invalid record: {}'.format( item.items())) dynamodb.delete_item_object(item) continue download_datetime = dt_util.formatted_str_to_datetime(download_start) if dt_util.is_expired_workflow(download_datetime): activity.logger.info('Deleting expired record: {}'.format( item.items())) dynamodb.delete_item_object(item) continue active_workflows.append(item) return active_workflows @task.decorate(timeout=18000) def clean_shared_avro_file(activity, user_id, user_type, period_ids): """Clean downloaded AVRO file if no workflow is reading it Avro file is downloaded and is kept in local file system so that multiple workflows can read it and generate report. This will cut the download time, however, accumulation of this kind of files will potentially introduce disk space issue. This task is to clean up avro file if and only if no one is reading it. Args: activity (ActivityWorker): the activity worker. user_id (str): vendor id or subaccount id. user_type (str): label or subaccount. period_ids (str): comma delimited period ids. """ user_id_type = _get_user_id_type(user_id, user_type) active_workflows = check_active_workflows(activity, user_id_type) has_workflow_reading = len(active_workflows) > 0 if not has_workflow_reading: path_to_remove = os.path.join( setting.EXPORT_GENERATION_DIR, user_id_type) if os.path.exists(path_to_remove): shutil.rmtree(path_to_remove) activity.logger.info( 'Deleted local directory: {}'.format(path_to_remove)) def get_subaccount_split(user_type, user_id): """Get sub account split type and commision override from Snowflake. Args: user_type (str): user type label or subaccount user_id (str): vendor id or subaccount id Returns: dict: query result """ if user_type != 'subaccount': return {} sql = ( 'select subaccount_split_type, commissionoverride ' 'from dim_subaccount ' 'where subaccountid = {}').format(user_id) subaccount_split = [r for r in db.snowflake_query(sql)].pop() subaccount_split = {k.lower(): v for k, v in subaccount_split.items()} return subaccount_split @task.decorate(timeout=18000) def generate_report( activity, user_id, user_type, avro_file_s3_path, transaction_types, locale, file_format, period_ids, redownload, report_version): """Generate actual accounting statement export according to user params. @todo(pkuong): Look into managing download in progress state a little more atomically, perhaps via a field in dynamodb and using transactions. Args: activity (ActivityWorker): the activity worker. user_id (str): vendor id or subaccount id. user_type (str): label or subaccount. avro_file_s3_path (str): s3 path to avro file. transaction_types (str): comma delimited transaction types. locale (str): standard locale code. Example: en_US. file_format (str): format of the file in setting (.txt or .xls). period_ids (str): comma delimited period ids. redownload (bool): True for redownloading AVRO file, False otherwise. report_version (int): report version, defines which columns names and values will be used in the report. Returns: dict: dictionary contains: start_time, end_time, total_time, final_text_report_path=, final_zipped_report_path, file_name """ # Prepare local file directory local_file_path = _get_local_path( user_id, user_type, period_ids, transaction_types, locale, file_format) local_avro_file_path = _get_avro_local_path( user_id, user_type, period_ids, transaction_types, locale, file_format) if redownload is not None: _flush_dir(local_avro_file_path) start_time = time.time() excluded_columns = _get_excluded_columns( user_id, user_type, report_version, transaction_types) report_schema = report.get_report_schema( excluded_columns, report_version, transaction_types) money_columns = setting.money_columns integer_columns = setting.integer_columns user_id_type = _get_user_id_type(user_id, user_type) user_params = _get_user_params( period_ids=period_ids, transaction_types=transaction_types, locale=locale, file_format=file_format) # Save current progress information additional_params = dict( download_avro_file_start=time.strftime( '%Y-%m-%d %H:%M:%S', time.gmtime())) dynamodb.set_status( user_id_type, user_params, 'GENERATING', **additional_params) # Get avro tmp local path shared_local_avro_path = setting.LOCAL_AVRO_FILE_PATH.format( user_id_type=user_id_type, user_params_key='__'.join([user_id, user_type, period_ids])) tmp_downloading_file_path = '{}.tmp'.format(shared_local_avro_path) shared_local_avro_dir = os.path.dirname(shared_local_avro_path) # Wait for file to be downloaded if there is any download operation. is_downloading = len( glob.glob('{}/*tmp*'.format(shared_local_avro_dir))) > 0 while is_downloading: logging.logger.info( 'File: {} is being downloaded. Waiting...'.format( shared_local_avro_path)) time.sleep(10) is_downloading = len( glob.glob('{}/*tmp*'.format(shared_local_avro_dir))) > 0 # If there is no download operation and avro file does not exists, download # it. if not os.path.exists(shared_local_avro_path): tmp_files = s3.download_boto3( avro_file_s3_path, tmp_downloading_file_path) if len(tmp_files) > 1: error_msg = 'Multiple AVRO files are not supported!' files_names = ', '.join(tmp_files) full_error = '{} : {}'.format(error_msg, files_names) logging.logger.critical(full_error) assert len(tmp_files) > 1, full_error os.rename(tmp_downloading_file_path, shared_local_avro_path) # Copy Avro file for read. This is necessary because single avro file # is not able to be read concurrently. shutil.copyfile(shared_local_avro_path, local_avro_file_path) logging.logger.info( 'Copied from {} to {}'.format( shared_local_avro_path, local_avro_file_path)) # Save current progress information additional_params = dict( download_avro_file_end=time.strftime( '%Y-%m-%d %H:%M:%S', time.gmtime())) dynamodb.set_status( user_id_type, user_params, 'GENERATING', **additional_params) index = 0 part = 1 filename = '{}.{}'.format(local_file_path, file_format) zip_filename = filename _flush_dir(filename) file_write = codecs.open( filename, 'a', setting.FILE_ENCODING.get(file_format)) filter_record = transaction_types != 'all' if report.is_physical_report(transaction_types): allowed_transaction_types = setting.PHYSICAL_TRANSACTION_TYPES else: allowed_transaction_types = set(sorted(transaction_types.split(','))) subaccount_split = get_subaccount_split(user_type, user_id) with open(local_avro_file_path, 'rb') as file_read: reader = avro.reader(file_read) header = _get_header(report_schema) file_write.write(setting.REPORT_DELIMITER.join(header) + '\n') for record in reader: trans_type = record.get('trans_type') if filter_record and trans_type not in allowed_transaction_types: continue file_write, index, part = _write_files( file_write, filename, record, locale, money_columns, integer_columns, part, index, report_schema, user_id_type, user_params, header, file_format, subaccount_split ) file_write.close() os.remove(local_avro_file_path) # Save progress information additional_params = dict( lines_scanned=str(index), number_of_partitions=str(part)) dynamodb.set_status( user_id_type, user_params, 'GENERATING', **additional_params) end_time = time.time() return dict( start_time=start_time, end_time=end_time, total_time=str(end_time - start_time), final_text_report_path='{dir}/*.{file_format}'.format( dir=os.path.dirname(filename), file_format=file_format), final_zipped_report_path='{dir}/{file_name}.zip'.format( dir=os.path.dirname(filename), file_name=os.path.basename(zip_filename))) @task.decorate(timeout=18000) def zip_up_files(activity, source_path, local_zip_path): """Zip up files on local file system Args: activity (ActivityWorker): the activity worker. source_path (str): local dir to the files to be compressed local_zip_path (str): full path to the compressed file Returns: dict: dictionary contains name of the zip file """ files_to_be_zipped = glob.glob(source_path) if len(files_to_be_zipped) == 0: return { 'stop': True, 'message': '"No file in {}".'.format( os.path.dirname(local_zip_path))} z = zipfile.ZipFile(local_zip_path, 'w', zipfile.ZIP_DEFLATED) for i in files_to_be_zipped: z.write(i, os.path.basename(i)) z.close() return dict(zip_file_name=os.path.basename(local_zip_path)) @task.decorate(timeout=18000) def upload_to_s3( activity, local_zip_path, period_ids, user_type, user_id, transaction_types, locale, file_format, key_name): """Upload final report to s3 for customer to consume Args: activity (ActivityWorker): the activity worker. period_ids (str): comma delimited string of period ids user_type (str): label or subaccount user_id (str): vendor id or subaccount id transaction_types (str): comma delimited string of transaction type codes locale (str): standard locale code. Example: en_US file_format (str): format of the file in setting (.txt or .xls) key_name (str): name of the key on S3 Returns: dict: dictionary contains path to the final zipped file on s3 """ user_id_type = _get_user_id_type(user_id, user_type) user_params = _get_user_params( period_ids=period_ids, transaction_types=transaction_types, locale=locale, file_format=file_format) s3_path = setting.FINAL_REPORT_S3_PATH.format( user_id_type=user_id_type, user_params=user_params, key_name=key_name) s3.upload_boto3(local_zip_path, s3_path) # clean up the temp files afterward shutil.rmtree(os.path.dirname(local_zip_path)) return {'final_zipped_report_s3_path': s3_path} @task.decorate(timeout=1800) def update_status( activity, period_ids, user_id, user_type, status, file_format, locale, s3_path, transaction_types, start_time, end_time): """Update dynamodb status for a custom report generation Args: activity (ActivityWorker): the activity worker. period_ids (str): comma delimited string of period ids user_id (str): vendor id or subaccount id user_type (str): label or subaccount status (str): status of the report generation file_format (str): format of the file in setting (.txt or .xls) locale (str): standard locale code. Example: en_US s3_path (str): full s3 path to the generated report transaction_types (str): comma delimited string of transaction type codes start_time (float): start time of the generation process since epoch end_time (float): end time of the generation process since epoch """ user_id_type = _get_user_id_type(user_id, user_type) user_params = _get_user_params( period_ids=period_ids, transaction_types=transaction_types, locale=locale, file_format=file_format) payment_interval = 'month' if len(period_ids.split(',')) > 1: payment_interval = 'quarter' total_time = str(end_time - start_time) generation_start = time.strftime( '%Y-%m-%d %H:%M:%S', time.gmtime(start_time)) generation_end = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(end_time)) additional_params = dict( file_type=file_format, generation_start=generation_start, generation_end=generation_end, generation_total_time=total_time, number_format=locale, payment_interval=payment_interval, period_ids=period_ids, s3_path=s3_path, transaction_types=transaction_types ) dynamodb.set_status(user_id_type, user_params, status, **additional_params) @task.decorate(timeout=1000) def send_email( activity, client_email, period_ids, user_id, user_type, locale, transaction_types, file_format): """Send file is ready email to client Args: client_email (str): client's email address. period_ids (str): comma delimited string of period ids user_id (str): vendor id or subaccount id user_type (str): label or subaccount locale (str): standard locale code. Example: en_US transaction_types (str): comma delimited string of transaction type codes file_format (str): format of the file in setting (.txt or .xls) """ file_name_parts = _get_report_file_name_parts( period_ids, user_id, user_type, locale, transaction_types) reporting_period = file_name_parts.get('reporting_period') report_type = file_name_parts.get('report_type') user_name = file_name_parts.get('user_name') number_format = file_name_parts.get('number_format') subject = 'Accounting Statement Ready For Download' body = ( '

Your accounting statement has been generated. Please ' '' 'click here' ' to download the statement from the Workstation.

' '

Label Name: {user_name}
' 'Reporting Period: {reporting_period}
' 'Report Type: {report_type}
' 'Number Format: {number_format}
' 'File Type: {file_type}

').format( user_name=user_name, reporting_period=reporting_period, report_type=report_type, number_format=number_format, file_type=file_format, url=_get_history_page_url(period_ids, reporting_period)) # overwrite email address if it is not a production environment. if setting.ENV != "prod": client_email = setting.FILE_IS_READY_EMAIL ses.send_email(client_email, setting.EMAIL_SENDER, subject, body) def _get_history_page_url(period_ids, reporting_period): """Get url to the history page of the react app. Args: period_ids (str): comma delimited string of period ids reporting_period (str): reporting period. eg: Feb2016 or Q12016 Returns: str: url to the history page of the react app. """ periods = [i for i in map(lambda p: int(p), period_ids.split(','))] period_txt = '{}_{}'.format(reporting_period[:-4], reporting_period[-4:]) return setting.HISTORY_PAGE_URL.format( host=setting.HISTORY_PAGE_URL_HOST[setting.ENV], period=period_txt, min_period=min(periods), max_period=max(periods))