""" Kuwo Ingestion Workflow. Ingests Kuwo data and loads into fact analytics. """ import datetime from garcon.param import StaticParam from garcon_contrib.aws.utils 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.base import FlowBase from feed_ingestion.flows.base import FlowConfigMixin from feed_ingestion.flows.base import FlowLoadFactMixinSF from feed_ingestion.flows.base import FlowLoadRawMixinSF from feed_ingestion.flows.kuwo import config from feed_ingestion.flows.kuwo import tasks from feed_ingestion.flows.kuwo.stage_loader import KuwoSL from feed_ingestion.tasks import feed_status_tasks from feed_ingestion.tasks import load_raw_table_tasks_sf from feed_ingestion.tasks import overall_status_tasks from feed_ingestion.tasks import s3_tasks class Flow(FlowBase, FlowConfigMixin, FlowLoadFactMixinSF, FlowLoadRawMixinSF): """Kuwo Flow class. This flow downloads the Kuwo data and loads it into staging_raw_kuwo and fact_analytics/fact_analytics_error tables. """ def __init__(self): """Initialize a Kuwo flow.""" super(Flow, self).__init__(config.feed_name, version='1.0') def decider(self, schedule): """Activity decider. Args: schedule (callable): The scheduler method. """ # get execution parameters bootstrap = schedule( 'bootstrap', self.bootstrap) # stop flow if data already ingested if bootstrap.result.get('bootstrap.stop') is True: return grab_drop_files = schedule( 'grab_drop_files', self.grab_drop_files_sme, requires=[bootstrap]) # update file statuses update_feed_s3_file_status = schedule( 'update_feed_s3_file_status', self.update_feed_s3_file_status, requires=[grab_drop_files]) # stop flow if any file is unavailable file_status = update_feed_s3_file_status.result.get( 'update_feed_s3_file_status.file_status') if file_status == garcon_feed_status.STATUS_NOT_AVAILABLE: return # set overall feed status to DOWNLOADED set_status_to_downloaded = schedule( 'set_feed_status_downloaded', self.set_status_to_downloaded, requires=[update_feed_s3_file_status]) source_files = schedule( 'source_files', self.source_files, requires=[set_status_to_downloaded]) load_staging_raw = schedule( 'load_staging_raw', self.load_staging_raw, requires=[source_files]) mark_staging_raw_table_tasks_complete = schedule( 'mark_staging_raw_table_tasks_complete', self.mark_staging_raw_table_tasks_complete, requires=[load_staging_raw]) # set overall feed status to POPULATED_RAW_TABLE set_status_to_populated_raw_table = schedule( 'set_status_to_populated_raw_table', self.set_status_to_populated_raw_table, requires=[mark_staging_raw_table_tasks_complete]) load_staging_fact_table = schedule( 'load_staging_fact_table', self.load_staging_fact_table, requires=[set_status_to_populated_raw_table]) collect_kwargs_activity = schedule( 'collect_kwargs_activity', self.collect_kwargs_activity, requires=[load_staging_fact_table]) schedule( 'load_fact_table', self.load_fact_tables, requires=[collect_kwargs_activity]) 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 = '_'.join([self.name, licensor]) if 'context_date' not in initial_context: date = datetime.datetime.today().strftime('%Y-%m-%d') else: date = initial_context['context_date'] return '{flow_name}-{date}'.format( flow_name=flow_name, 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' ))) # This is temporary (for testing purposes) @property def grab_drop_files_sme(self): """Grab files and upload to archive bucket on S3 for sme.""" return self.create( name='grab_drop_files_sme', generators=[self.drop_files_generator], tasks=base.AsyncRunner( s3_tasks.copy_file_from_sme_s3_to_theocrhard.fill( namespace='grab_drop_files_sme', secrets_path=StaticParam(config.secrets_path), source_bucket_name=StaticParam(config.sme_drop_bucket), source_key_name='source_key_name', destination_bucket_name=StaticParam(config.data_bucket), destination_key_name='destination_key_name', replace='bootstrap.replace_archive_files'), max_workers=4)) @property def update_feed_s3_file_status(self): """Update status of downloaded files.""" return self.create( name='update_feed_s3_file_status', tasks=base.SyncRunner( feed_status_tasks.update_feed_s3_file_status.fill( namespace='update_feed_s3_file_status', s3_path='bootstrap.s3_full_path', file_names='bootstrap.expected_files', feed_name='bootstrap.feed_name', date='bootstrap.date'))) @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 source_files(self): """Get list of source files.""" return self.create( name='source_files', tasks=base.SyncRunner( s3_tasks.source_files.fill( namespace='source_files', s3_bucket='bootstrap.s3_bucket', s3_path='bootstrap.archive_path', file_pattern='bootstrap.source_file_pattern'))) load_staging_raw = KuwoSL.load_activity( feed_name=config.feed_name, secrets_path=config.secrets_path, sql_loader=SQLLoader(__file__), requirements=dict( date='bootstrap.date', feed_name='bootstrap.feed_name', source_files_dict='source_files.source_files_dict', s3_dir_path='bootstrap.s3_full_path', staging_raw_table_name='bootstrap.staging_raw_table', licensor='bootstrap.licensor', skip_corrupted_rows='bootstrap.skip_corrupted_rows')) @property def mark_staging_raw_table_tasks_complete(self): """Set the complete staging_raw_table_tasks to feed.""" return self.create( name='mark_staging_raw_table_tasks_complete', tasks=base.SyncRunner( (load_raw_table_tasks_sf.mark_staging_raw_table_tasks_complete. fill(namespace='mark_staging_raw_table_tasks_complete', date='bootstrap.date', feed_name='bootstrap.feed_name')))) @property def set_status_to_populated_raw_table(self): """Set overall feed status to POPULATED_RAW_TABLE.""" return self.create( name='set_status_to_populated_raw_table', tasks=base.SyncRunner( overall_status_tasks.set_overall_status.fill( namespace='set_status_to_populated_raw_table', feed_name='bootstrap.feed_name', date='bootstrap.date', status=StaticParam( garcon_feed_status.STATUS_POPULATED_RAW_TABLE)))) @property def collect_kwargs_activity(self): """Collect kwargs for load_fact_tables.""" return self.create_kwargs_collect_activity_for( activity_name='load_fact_tables', mapping=dict( licensor='bootstrap.licensor', storeid='bootstrap.store_id' ) ) def drop_files_generator(self, context): """Generate raw files to archive. Used by the grab_drop_files activity. Args: context (dict): The current context. Yields: dict: Dictionary of file to archive and archive destination. """ for file in context['bootstrap.expected_files']: drop_file = '{dir}{file}'.format( dir=context['bootstrap.drop_path'], file=file) archive_file = '{dir}{file}'.format( dir=context['bootstrap.s3_full_path'], file=file) file_status = garcon_feed_status.get_status( self.feed_name, context['bootstrap.date'], file) if file_status != garcon_feed_status.STATUS_DOWNLOADED: source_key_name = ( garcon_s3.extract_bucket_path(drop_file)[1]) destination_key_name = ( garcon_s3.extract_bucket_path(archive_file)[1]) yield dict( source_key_name=source_key_name, destination_key_name=destination_key_name)