"""YouTube Claim Ingestion Workflow.""" from garcon import param 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.youtube_claim import config from feed_ingestion.flows.youtube_claim import tasks from feed_ingestion.flows.youtube_claim.stage_loader import YouTubeClaimSL 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 from feed_ingestion.tasks import youtube_tasks class Flow( base.FlowBase, base.FlowConfigMixin, base.FlowLoadFactMixinSF, base.FlowYouTubeMixin): """Class representing the workflow.""" def __init__(self): """Initialize flow object.""" super(Flow, self).__init__(feed_name=config.feed_name, version='1.0') self.GRAB_DROP_FILES = { 'sme': self.sme_copy_from_athena_to_s3, 'theorchard': self.grab_reports_files } 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 licensor = bootstrap.result.get('bootstrap.licensor') check_assets_availability = schedule( 'check_assets_availability', self.check_assets_availability, requires=[bootstrap]) if check_assets_availability.result.get( 'check_assets_availability.stop'): return last_activity = check_assets_availability # This flag required to skip download reports step # (e.g. if reload for a particular date required). if not context.get('skip_download_reports'): grab_reports_files = schedule( 'grab_reports_files', self.GRAB_DROP_FILES[ bootstrap.result.get('bootstrap.licensor')], requires=[bootstrap]) if grab_reports_files.result.get('grab_reports_files.stop'): return last_activity = grab_reports_files # load data into if licensor == 'sme': create_temp_staging_raw_table = schedule( 'create_temp_staging_raw_table', self.create_temp_staging_raw_table, requires=[last_activity]) load_temp_staging_raw_table = schedule( 'load_temp_staging_raw_table', self.load_temp_staging_raw_table, requires=[create_temp_staging_raw_table]) load_staging_raw_table = schedule( 'load_staging_raw_table', self.load_staging_raw_table, requires=[load_temp_staging_raw_table]) update_channel_names_table = schedule( 'update_channel_names_table', self.update_channel_names_table, requires=[load_staging_raw_table]) schedule( 'set_overall_status_ingested', self.set_overall_status_ingested, requires=[update_channel_names_table]) return # licensor == 'theorchard' source_files = schedule( 'source_files', self.source_files, requires=[last_activity]) # Status updated to DOWNLOADED if files are present set_status_to_downloaded = schedule( 'set_status_to_downloaded', self.set_status_to_downloaded, requires=[source_files]) # clean out old data clean_staging_raw = schedule( 'clean_staging_raw', self.clean_staging_raw, requires=[set_status_to_downloaded]) # populate temporary staging tables populate_temp_staging_tables = schedule( 'populate_temp_staging_tables', self.populate_temp_staging_tables, requires=[clean_staging_raw]) # load staging raw table populate_staging_raw = schedule( 'populate_staging_raw', self.populate_staging_raw, requires=[populate_temp_staging_tables]) set_status_to_populated_raw_table = schedule( 'set_status_to_populated_raw_table', self.set_status_to_populated_raw_table, requires=[populate_staging_raw]) # drop temporary staging tables drop_temp_staging_tables = schedule( 'drop_temp_staging_tables', self.drop_temp_staging_tables, requires=[set_status_to_populated_raw_table]) update_dim_claim = schedule( 'update_dim_claim', self.update_dim_claim, requires=[drop_temp_staging_tables]) update_channel_names_table = schedule( 'update_channel_names_table', self.update_channel_names_table, requires=[update_dim_claim]) # Load from staging_raw to fact_analytics & fact_analytics_error load_staging_fact_table = schedule( 'load_staging_fact_table', self.load_staging_fact_table, requires=[update_channel_names_table]) schedule( 'load_fact_tables', self.load_fact_tables, requires=[load_staging_fact_table]) 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' licensor = context['licensor'] assert licensor in config.licensors, \ 'There is no such licensor in config file' return '_'.join([self.feed_name, licensor]) 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'] date = initial_context['context_date'] return '{flow_name}-{licensor}-{date}'.format( flow_name=self.name, licensor=licensor, date=date) @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 clean_staging_raw(self): """Delete any existing data from staging raw table.""" return self.create( name='clean_staging_raw', tasks=base.SyncRunner( tasks.clean_staging_raw_table.fill( namespace='clean_staging_raw_table', date='bootstrap.date', feed_name='bootstrap.feed_name', staging_raw_table='bootstrap.staging_raw_table_name'))) @property def populate_temp_staging_tables(self): """Populate the temporary staging tables with the raw files.""" return self.create( name='populate_temp_staging_tables', tasks=base.SyncRunner( tasks.create_temp_staging_raw_table.fill( namespace='populate_temp_staging_tables', date='bootstrap.date', temp_table_names='bootstrap.temp_table_names', feed_name='bootstrap.feed_name'), tasks.load_temp_staging_raw_table.fill( namespace='populate_temp_staging_tables', date='bootstrap.date', files='source_files.source_files_dict', temp_table_names='bootstrap.temp_table_names', s3_archive_path='bootstrap.s3_split_path', feed_name='bootstrap.feed_name'))) @property def populate_staging_raw(self): """Load staging raw table from temp tables.""" return self.create( name='populate_staging_raw', tasks=base.SyncRunner( tasks.load_staging_raw_table.fill( namespace='load_staging_raw_table', date='bootstrap.date', processed_datetime='bootstrap.processed_datetime', temp_table_names='bootstrap.temp_table_names', s3_archive_path='bootstrap.s3_archive_path', feed_name='bootstrap.feed_name', staging_raw_table='bootstrap.staging_raw_table_name'))) @property def drop_temp_staging_tables(self): """Drop the temporary staging tables.""" return self.create( name='drop_temp_staging_tables', tasks=base.SyncRunner( tasks.drop_temp_table.fill( namespace='drop_temp_table', temp_table_names='bootstrap.temp_table_names', feed_name='bootstrap.feed_name'))) @property def check_assets_availability(self): """Drop the temporary staging tables.""" return self.create( name='check_assets_availability', tasks=base.SyncRunner( tasks.check_assets_availability.fill( namespace='check_assets_availability', date='bootstrap.date', feed_name='bootstrap.feed_name'))) @property def update_dim_claim(self): """Update dim_claim table.""" return self.create( name='update_dim_claim', tasks=base.SyncRunner( tasks.update_dim_claim.fill( namespace='update_dim_claim', date='bootstrap.date', feed_name='bootstrap.feed_name'))) @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=StaticParam(config.s3_bucket), s3_path='bootstrap.s3_dir_path', file_pattern=StaticParam(config.source_file_pattern)))) @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_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 grab_reports_files(self): """Archive report files to archive location.""" return self.create( name='grab_reports_files', schedule_to_start=48000, tasks=base.SyncRunner( garcon_s3.remove_files_from_path.fill( namespace='clear_s3_split_path', path='bootstrap.s3_split_path', return_deleted_files=StaticParam(False)), youtube_tasks.grab_reports_files.fill( namespace='grab_reports_files', report_name=f'{self.bootstrap_namespace}.report_name', report_status_name=f'{self.bootstrap_namespace}.feed_name', date=f'{self.bootstrap_namespace}.report_date', archive_path=f'{self.bootstrap_namespace}.s3_archive_path', credentials_path=( f'{self.bootstrap_namespace}.credentials_path'), api_service_name=( f'{self.bootstrap_namespace}.api_service_name'), api_version=( f'{self.bootstrap_namespace}.api_version'), jobs_meta_path=( f'{self.bootstrap_namespace}.jobs_meta_path'), split_file=param.StaticParam(True), split_path=( f'{self.bootstrap_namespace}.s3_split_path'), cms_dict=( f'{self.bootstrap_namespace}.cms_dict'), gz=param.StaticParam(False)))) @property def sme_copy_from_athena_to_s3(self): """sme_copy_from_athena_to_s3.""" return self.create( name='sme_copy_from_athena_to_s3', tasks=base.SyncRunner( tasks.sme_copy_athena_to_s3_total_size.fill( namespace='grab_reports_files', date='bootstrap.date', report_status_name='bootstrap.feed_name', sme_athena_database=StaticParam( config.sme_athena_database), sme_athena_temp_database=StaticParam( config.sme_athena_temp_database), sme_athena_source_table=StaticParam( config.youtube_report_full_name), destination_s3_bucket=StaticParam(config.s3_bucket), destination_s3_path='bootstrap.s3_dir_path', athena_workgroup=StaticParam(config.athena_workgroup), aws_region=StaticParam(config.athena_aws_region), ))) @property def create_temp_staging_raw_table(self): """Create temp staging raw table.""" return self.create( name='create_temp_staging_raw_table', tasks=base.SyncRunner( load_raw_table_tasks_sf.create_temp_staging_raw_table.fill( namespace='create_temp_staging_raw_table', date='bootstrap.date', sfdb_params='bootstrap.sfdb_params', feed_name='bootstrap.feed_name', secrets_path='bootstrap.secrets_path', temp_staging_raw_table=( 'bootstrap.load_temp_staging_raw_table_sme')))) load_temp_staging_raw_table = YouTubeClaimSL.load_activity( feed_name=config.feed_name, secrets_path=config.secrets_path, sql_loader=SQLLoader(__file__, folder='/queries/sme_stage_queries'), requirements=dict( date='bootstrap.date', s3_dir_path='bootstrap.s3_archive_path', feed_name='bootstrap.feed_name', temp_staging_raw_table='bootstrap.load_temp_staging_raw_table_sme', licensor='bootstrap.licensor', file_size='grab_reports_files.total_file_size')) @property def load_staging_raw_table(self): """Load staging raw table.""" return self.create( name='load_staging_raw_table', 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='bootstrap.staging_raw_table_name', temp_staging_raw_table=( 'bootstrap.load_temp_staging_raw_table_sme'), sfdb_params='bootstrap.sfdb_params', clean=StaticParam('True'), set_complete=StaticParam('False'), secrets_path='bootstrap.secrets_path' ))) @property def set_overall_status_ingested(self): """Set overall feed status INGESTED if all reports are completed.""" return self.create( name='set_overall_status_ingested', schedule_to_start=48000, 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_INGESTED))))