""" SoundCloud Ingestion Workflow. Ingest data from SoundCloud feed. Files downloaded from theorchard s3. """ from garcon.param import StaticParam from garcon_contrib.dynamo_feed_status import garcon_feed_status from feed_ingestion.flows import base from feed_ingestion.flows.soundcloud import config from feed_ingestion.flows.soundcloud import tasks from feed_ingestion.tasks import load_fact_tables_tasks_sf from feed_ingestion.tasks import load_raw_table_tasks_sf from feed_ingestion.tasks import overall_status_tasks from feed_ingestion.tasks import validate_raw_data_tasks_sf class Flow(base.FlowLicensor, base.FlowConfigMixin): """Class representing the workflow.""" def __init__(self): """Initialize flow object.""" super(Flow, self).__init__(config.feed_name, version='1.0') self.GRAB_DROP_FILES = { 'sme': self.grab_drop_files_sme, 'theorchard': self.grab_drop_files, } def decider(self, schedule): """Activity decider. Args: schedule (callable): The scheduler method. """ bootstrap = schedule('bootstrap', self.bootstrap_activity) licensor = bootstrap.result.get('bootstrap.licensor') # stop flow if already ingested if bootstrap.result.get('bootstrap.stop') is True: return # grab files grab_drop_files = schedule( 'grab_drop_files', self.GRAB_DROP_FILES[licensor], requires=[bootstrap] ) # stop flow if data is not available if grab_drop_files.result.get('grab_drop_files.stop') is True: return # create temp staging raw tables create_temp_staging_raw_tables = schedule( 'create_temp_staging_raw_tables', self.create_temp_staging_raw_tables, requires=[grab_drop_files]) # load temp staging raw tables load_temp_staging_raw_tables = schedule( 'load_temp_staging_raw_tables', self.load_temp_staging_raw_tables, requires=[create_temp_staging_raw_tables]) # load staging raw table load_staging_raw_table = schedule( 'load_staging_raw_table', self.load_staging_raw_table, requires=[load_temp_staging_raw_tables]) # mark staging raw table tasks complete mark_staging_raw_table_tasks_complete = schedule( 'mark_staging_raw_table_tasks_complete', self.mark_staging_raw_table_tasks_complete, requires=[load_staging_raw_table]) # set status populated raw table set_status_populated_raw_table = schedule( 'set_status_populated_raw_table', self.set_status_populated_raw_table, requires=[mark_staging_raw_table_tasks_complete]) # drop temp staging raw table drop_temp_staging_raw_table = schedule( 'drop_temp_staging_raw_table', self.drop_temp_staging_raw_table, requires=[set_status_populated_raw_table]) # update dimension tables update_dim_tables = schedule( 'update_dim_tables', self.update_dim_tables, requires=[drop_temp_staging_raw_table]) # load staging fact table load_staging_fact_table = schedule( 'load_staging_fact_table', self.load_staging_fact_table, requires=[update_dim_tables]) # load aggregated skips and saves load_aggregated_skips_and_saves = schedule( 'load_aggregated_skips_and_saves', self.load_aggregated_skips_and_saves, requires=[load_staging_fact_table]) # load fact table load_fact_table = schedule( 'load_fact_table', self.load_fact_table, requires=[ load_aggregated_skips_and_saves, load_staging_fact_table]) if update_dim_tables.result.get( 'update_dim_tables.sns_report_subject', False): schedule( 'send_dimension_tables_update_report_sns_notification', self.send_dimension_tables_update_report_sns_notification, requires=[load_fact_table]) @property def bootstrap_activity(self): """First activity for the workflow.""" return self.create( name='bootstrap', tasks=base.SyncRunner( tasks.bootstrap.fill( namespace='bootstrap', date='context_date', licensor='licensor', reload='reload'))) @property def grab_drop_files(self): """Grab files and upload to archive bucket on S3.""" return self.create( name='grab_drop_files', tasks=base.SyncRunner( tasks.grab_drop_files.fill( namespace='grab_drop_files', feed_name='bootstrap.feed_name', date='bootstrap.date', drop_path='bootstrap.drop_path', archive_path='bootstrap.archive_path', source_files_dict='bootstrap.source_files_dict'))) @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', tasks=base.AsyncRunner( tasks.grab_drop_files_sme.fill( namespace='grab_drop_files', feed_name='bootstrap.feed_name', date='bootstrap.date', drop_path='bootstrap.drop_path', archive_path='bootstrap.archive_path', source_files_dict='bootstrap.source_files_dict'))) @property def create_temp_staging_raw_tables(self): """Create temp staging raw table.""" return self.create( name='create_temp_staging_raw_tables', generators=[self.report_name_generator], tasks=base.AsyncRunner( load_raw_table_tasks_sf.create_temp_staging_raw_table.fill( namespace='create_temp_staging_raw_tables', feed_name='bootstrap.feed_name', date='bootstrap.date', sfdb_params='sfdb_params', temp_staging_raw_table='temp_staging_raw_table', secrets_path='bootstrap.secrets_path', kwargs='kwargs'), max_workers=4)) @property def load_temp_staging_raw_tables(self): """Load temp staging raw table.""" return self.create( name='load_temp_staging_raw_tables', generators=[self.report_name_generator], tasks=base.SyncRunner( validate_raw_data_tasks_sf.load_temp_staging_raw_table.fill( namespace='load_temp_staging_raw_tables', feed_name='bootstrap.feed_name', sfdb_params='sfdb_params', date='bootstrap.date', key_dir='key_dir', temp_staging_raw_table='temp_staging_raw_table', secrets_path='bootstrap.secrets_path', error_limit=( StaticParam(config.snowflake_error_limit)), kwargs='kwargs'))) @property def load_staging_raw_table(self): """Load staging raw table.""" return self.create( name='load_staging_raw_table', generators=[self.load_staging_raw_table_generator], tasks=base.SyncRunner( load_raw_table_tasks_sf.load_staging_raw_table.fill( namespace='load_staging_raw_table', feed_name='bootstrap.feed_name', date='bootstrap.date', staging_raw_table='staging_raw_table_name', temp_staging_raw_table='temp_staging_raw_table_name', sfdb_params='bootstrap.sfdb_params', clean=StaticParam('True'), set_complete=StaticParam('False'), secrets_path='bootstrap.secrets_path', kwargs='kwargs'))) @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_populated_raw_table(self): """Set the overall feed status to populated_raw_table.""" return self.create( name='set_status_populated_raw_table', tasks=base.SyncRunner( overall_status_tasks.set_overall_status.fill( namespace='set_status_populated_raw_table', feed_name='bootstrap.feed_name', date='bootstrap.date', status=StaticParam( garcon_feed_status.STATUS_POPULATED_RAW_TABLE)))) @property def drop_temp_staging_raw_table(self): """Drop the temporary staging tables.""" return self.create( name='drop_temp_staging_raw_table', generators=[self.report_name_generator], schedule_to_start=48000, tasks=base.SyncRunner( load_raw_table_tasks_sf.drop_temp_staging_raw_table.fill( namespace='drop_temp_staging_table', temp_staging_raw_table='temp_staging_raw_table', feed_name='bootstrap.feed_name', secrets_path='bootstrap.secrets_path', sfdb_params='bootstrap.sfdb_params'))) @property def update_dim_tables(self): """Update dimension tables.""" return self.create( name='update_dim_tables', schedule_to_start=48000, tasks=base.SyncRunner( load_fact_tables_tasks_sf.update_dim_tables.fill( namespace='update_dim_tables', feed_name='bootstrap.feed_name', secrets_path=StaticParam(config.secrets_path), date='bootstrap.date', sfdb_params='bootstrap.sfdb_params', kwargs=StaticParam(config.dimension_tables)))) @property def send_dimension_tables_update_report_sns_notification(self): """Send SNS message with dimension tables update report.""" return self.create( name='send_dimension_tables_update_report_sns_notification', tasks=base.SyncRunner( tasks.sns_publish_message.fill( topic=StaticParam( config.dimension_tables['sns_topic']), feed_name='bootstrap.feed_name', date='bootstrap.date', message='update_dim_tables.sns_report_message', subject='update_dim_tables.sns_report_subject'))) @property def load_staging_fact_table(self): """Activity to load staging_fact_analytics_{feed_name}_{datestamp}.""" return self.create( name='unload_fact_data', tasks=base.SyncRunner( load_fact_tables_tasks_sf.create_staging_fact.fill( namespace='create_staging_fact', feed_name='bootstrap.feed_name', date='bootstrap.date', secrets_path='bootstrap.secrets_path', sfdb_params='bootstrap.sfdb_params'), load_fact_tables_tasks_sf.load_staging_fact.fill( namespace='load_staging_fact', feed_name='bootstrap.feed_name', date='bootstrap.date', secrets_path='bootstrap.secrets_path', sfdb_params='bootstrap.sfdb_params'))) @property def load_aggregated_skips_and_saves(self): """Activity to load load_aggregated_skips_and_saves.""" return self.create( name='load_aggregated_skips_and_saves', tasks=base.SyncRunner( tasks.load_aggregated_skips_and_saves.fill( namespace='load_aggregated_skips_and_saves', feed_name='bootstrap.feed_name', date='bootstrap.date', sfdb_params='bootstrap.sfdb_params', secrets_path='bootstrap.secrets_path', ))) @property def load_fact_table(self): """Activity to load fact_analytics & fact_analytics_error. Deletes any existing data for this feed and date in fact_analytics & fact_analytics_error. """ return self.create( name='load_fact_table', tasks=base.SyncRunner( load_fact_tables_tasks_sf.load_fact_data.fill( namespace='load_fact_data', feed_name='bootstrap.feed_name', date='bootstrap.date', sfdb_params='bootstrap.sfdb_params', secrets_path='bootstrap.secrets_path', kwargs='kwargs'), overall_status_tasks.set_overall_status.fill( feed_name='bootstrap.feed_name', date='bootstrap.date', set_status_once=StaticParam(True), status=StaticParam( garcon_feed_status.STATUS_INGESTED)))) def report_name_generator(self, context): """Generate parameters for ingestion activities. Args: context (dict): The current context. Yields: dict: Dictionary with a report name. """ date = context['bootstrap.date'].replace('-', '_') for report in context['bootstrap.reports']: table_name = ( config.snowflake_table_names .get('temp_staging_raw_table_template') .format(context['bootstrap.licensor'], report.lower(), date) ) key_dir = '{}{}'.format( context['bootstrap.s3_archive_path'], context['bootstrap.source_files_dict'][report]) yield dict( temp_staging_raw_table=table_name, key_dir=key_dir, kwargs=dict(report_type=report, licensor=context['bootstrap.licensor'])) def load_staging_raw_table_generator(self, context): """Generate parameters for ingestion activities. Args: context (dict): The current context. Yields: dict: Dictionary with a report name. """ date = context['bootstrap.date'].replace('-', '_') for report in context['bootstrap.reports']: yield { 'temp_staging_raw_table_name': ( config.snowflake_table_names .get('temp_staging_raw_table_template') .format(context['bootstrap.licensor'], report.lower(), date) ), 'staging_raw_table_name': ( config.snowflake_table_names .get('staging_raw_table_template') .format(report.lower()) ), 'kwargs': {'report_name': report.lower(), 'licensor': context['bootstrap.licensor']} }