"""Amazon Digital Services Workflow.""" from garcon.param import StaticParam from garcon_contrib.aws import garcon_s3 from garcon_contrib.dynamo_feed_status import garcon_feed_status from snowflake_connector.etl_connector import SQLLoader from feed_ingestion.flows import base from feed_ingestion.flows.amazon_digital_services import config from feed_ingestion.flows.amazon_digital_services import tasks from feed_ingestion.flows.amazon_digital_services.stage_loader \ import AmazonDSSL from feed_ingestion.tasks import feed_status_tasks from feed_ingestion.tasks import overall_status_tasks class Flow(base.FlowBase, base.FlowConfigMixin, base.FlowLoadFactMixinSF): """Class representing the workflow.""" def __init__(self): """Initialize flow object.""" super(Flow, self).__init__(feed_name=config.feed_name, version='2.0') self.timeout = 60 * 60 * 4 # override default to 4 hours def decider(self, schedule, context): """Activity decider. Args: schedule (callable): The scheduler method. """ bootstrap = schedule('bootstrap', self.bootstrap) # Stop flow if feed already ingested if bootstrap.result.get('bootstrap.stop'): return fetch_from_drop_location = schedule( 'fetch_from_drop_location', self.fetch_from_drop_location, requires=[bootstrap]) # Stop flow if files aren't available yet if fetch_from_drop_location.result.get( 'fetch_from_drop_location.stop'): return # Status updated to DOWNLOADED if files are present, else NOT_AVAILABLE set_status_to_downloaded = schedule( 'set_status_to_downloaded', self.set_status_to_downloaded, requires=[fetch_from_drop_location]) check_files_on_s3 = schedule( 'check_files_on_s3', self.check_files_on_s3, requires=[set_status_to_downloaded]) if check_files_on_s3.result.get('check_files_on_s3.stop'): return reset_dynamo_db_status = schedule( 'reset_dynamo_db_status', self.reset_dynamo_db_status, requires=[check_files_on_s3]) prepare_files_for_ingestion = schedule( 'prepare_files_for_ingestion', self.prepare_files_for_ingestion, requires=[reset_dynamo_db_status]) load_staging_raw_table = schedule( 'load_staging_raw_table', self.load_staging_raw_table, requires=[prepare_files_for_ingestion]) # if staging_raw_only flag passed in initial context, skip fact load if 'staging_raw_only' in context: return set_status_to_populated_raw_table = schedule( 'set_status_to_populated_raw_table', self.set_status_to_populated_raw_table, requires=[load_staging_raw_table]) load_staging_fact_table = schedule( 'load_staging_fact_table', self.load_staging_fact_table, requires=[set_status_to_populated_raw_table]) load_fact_tables = schedule( 'load_fact_tables', self.load_fact_tables, requires=[load_staging_fact_table]) schedule( 'mark_ingested_files', self.mark_ingested_files, requires=[load_fact_tables]) def workflow_id(self, initial_context): """Generate workflow id. Args: initial_context (dict): The initial context for the flow. Returns: str: A unique identifier for a workflow being executed. """ licensor = initial_context['licensor'] flow_name = self.name date = initial_context['context_date'] return '{flow_name}-{licensor}-{date}'.format( flow_name=flow_name, licensor=licensor, date=date) def contextified_feed_name(self, context): """Get feed_name in context. Args: context (dict): The context of the flow. Returns: str: Contextified feed name. """ assert 'licensor' in context, 'There is no licensor in context' assert context['licensor'] in config.licensors, \ 'There is no such licensor in config file' return '_'.join([ self.feed_name, context['licensor'], ]) @property def bootstrap(self): """Bootstrap initial configuration.""" return self.create( name='bootstrap', tasks=base.SyncRunner( tasks.bootstrap.fill( namespace='bootstrap', date='context_date', reload='reload', licensor='licensor'))) @property def fetch_from_drop_location(self): """Move file from any source to S3 archive bucket.""" return self.create( name='fetch_from_drop_location', tasks=base.SyncRunner( tasks.fetch_from_drop_location.fill( namespace='fetch_from_drop_location', feed_name='bootstrap.feed_name', date='bootstrap.date', licensor='bootstrap.licensor', s3_tmp_path='bootstrap.s3_tmp_path', use_s3='use_s3'))) @property def prepare_files_for_ingestion(self): """Prepare files for ingestion into Snowflake.""" return self.create( name='prepare_files_for_ingestion', tasks=base.SyncRunner( tasks.prepare_files_for_ingestion.fill( namespace='prepare_files_for_ingestion', date='bootstrap.date', s3_tmp_path='bootstrap.s3_tmp_path', s3_archive_path='bootstrap.s3_archive_path', licensor='bootstrap.licensor', source_files_dict=( 'fetch_from_drop_location.source_files_dict')), # cleanup original zip files from S3 garcon_s3.remove_files_from_path.fill( path='bootstrap.s3_tmp_path', return_deleted_files=StaticParam(False)))) @property def notify_missing_files(self): """Notify missing files.""" return self.create( name='notify_missing_files', tasks=base.SyncRunner( tasks.notify_missing_files.fill( namespace='notify_missing_files', source_files_dict=( 'fetch_from_drop_location.source_files_dict')))) @property def reset_dynamo_db_status(self): """Activity to reset DynamoDB status.""" return self.create( name='reset_dynamo_db_status', tasks=base.SyncRunner( overall_status_tasks.delete_overall_status.fill( namespace='reset_dynamo_db_status', date='{}.date'.format(self.bootstrap_namespace), feed_name='{}.feed_name'.format( self.bootstrap_namespace)))) @property def mark_ingested_files(self): """Put list of injected files in DynamoDB.""" return self.create( name='mark_ingested_files', tasks=base.SyncRunner( feed_status_tasks.mark_ingested_files.fill( namespace='mark_ingested_files', feed_name='bootstrap.feed_name', date='bootstrap.date', source_files_dict=( 'fetch_from_drop_location.source_files_dict')))) @property def check_files_on_s3(self): """Check if there are some new files in s3_download_path.""" return self.create( name='check_files_on_s3', tasks=base.SyncRunner( feed_status_tasks.check_files_on_s3.fill( namespace='check_files_on_s3', feed_name='bootstrap.feed_name', date='bootstrap.date', file_pattern='bootstrap.file_pattern', s3_download_path='bootstrap.s3_tmp_path'))) @property def set_status_to_downloaded(self): """Set overall feed status to DOWNLOADED.""" return self.create( name='set_status_to_downloaded', tasks=base.SyncRunner( overall_status_tasks.set_overall_status.fill( namespace='set_status_to_downloaded', feed_name='bootstrap.feed_name', date='bootstrap.date', status=StaticParam( garcon_feed_status.STATUS_DOWNLOADED)))) @property def set_status_to_populated_raw_table(self): """Set overall feed status to POPULATED_RAW_TABLE.""" return self.create( name='set_status_to_downloaded', tasks=base.SyncRunner( overall_status_tasks.set_overall_status.fill( namespace='set_status_to_downloaded', feed_name='bootstrap.feed_name', date='bootstrap.date', status=StaticParam( garcon_feed_status.STATUS_POPULATED_RAW_TABLE)))) load_staging_raw_table = AmazonDSSL.load_activity( feed_name='bootstrap.feed_name', secrets_path=config.secrets_path, sql_loader=SQLLoader(__file__), requirements=dict( date='bootstrap.date', feed_name='bootstrap.feed_name', source_files_dict='fetch_from_drop_location.source_files_dict', s3_dir_path='bootstrap.s3_archive_path', staging_raw_table_name='bootstrap.staging_raw_table', licensor='bootstrap.licensor'))