"""Tasks for YouTube claim Ingestion Workflow.""" from datetime import datetime from garcon import task from garcon_contrib.dynamo_feed_status import garcon_feed_status from feed_ingestion.flows import registered_executors from feed_ingestion.flows.helpers import get_sf_config from feed_ingestion.flows.youtube_claim import config from feed_ingestion.tasks import check_status, youtube_tasks from feed_ingestion.util import task_status from feed_ingestion.util.aws import s3 STOP_RESPONSE = {'stop': True} @task.decorate(timeout=1000) def bootstrap(activity, date, licensor=None, reload=None, dw_config=None): """Bootstrap workflow by getting the correct configurations. Args: activity (ActivityWorker): The activity worker. date (str): Reporting date (YYYY-MM-DD). Returns: dict: Context. """ if not licensor: licensor = 'theorchard' assert licensor in config.licensors, f'unsupported licensor "{licensor}"' # date is the date passed in or yesterday's date date_obj = datetime.strptime( date, '%Y-%m-%d') if date else datetime.today() processed_datetime = datetime.now().strftime('%Y-%m-%dT%H:%M:%S') feed_name = '_'.join([config.feed_name, licensor]) report_status_name = feed_name activity.logger.info('Bootstrap flow: {}'.format(date_obj)) if reload == 'True': activity.logger.info( 'Delete status for feed: {} {} '.format(report_status_name, date)) garcon_feed_status.delete_status(report_status_name, date) else: overall_status = garcon_feed_status.get_overall_status( report_status_name, date) if overall_status == garcon_feed_status.STATUS_INGESTED: return STOP_RESPONSE s3_archive_path = config.s3_archive_path_template.format( date=date_obj, licensor=licensor) s3_split_path = config.s3_split_path_template.format( date=date_obj, licensor=licensor) s3_dir_path = config.s3_dir_path.format(date=date_obj, licensor=licensor) credentials_path = config.credentials_paths.get(licensor) temp_table_names = [] cms_dict = None staging_raw_table = config.snowflake_table_names[f'staging_raw_{licensor}'] if licensor == 'theorchard': for account in config.cms_dict.values(): temp_table_name = config.temp_table_name_template.format( staging_raw_table=staging_raw_table, date=date_obj, account=account ) temp_table_names.append(temp_table_name) cms_dict = config.cms_dict load_temp_staging_raw_table_sme = config.temp_table_name_template.format( staging_raw_table=staging_raw_table, date=date_obj, account='sme' ) return dict( feed_name=feed_name, licensor=licensor, report_name=config.youtube_report_full_name, secrets_path=config.secrets_path, date=date_obj.strftime('%Y-%m-%d'), report_date=date_obj.strftime('%Y-%m-%d'), processed_datetime=processed_datetime, s3_dir_path=s3_dir_path, s3_archive_path=s3_archive_path, s3_split_path=s3_split_path, temp_table_names=temp_table_names, load_temp_staging_raw_table_sme=load_temp_staging_raw_table_sme, staging_raw_table_name=staging_raw_table, credentials_path=credentials_path, api_service_name=config.youtube_reporting_api_service_name, api_version=config.youtube_reporting_api_version, jobs_meta_path=config.jobs_meta_path, cms_dict=cms_dict ) @check_status(task_id='load_staging_raw_table_all') @task.decorate(timeout=1800) def clean_staging_raw_table(activity, date, staging_raw_table, feed_name): """Delete records from staging_raw_youtube_claim for the given date. Args: activity (ActivityWorker): The Garcon activity worker. date (str): Date in YYYY-MM-DD format to delete from table. staging_raw_table (str): Staging raw table name. feed_name (str): The name of the feed. """ sf_config = get_sf_config(config.secrets_path) ExecutorFA = registered_executors.get(feed_name) with ExecutorFA(sf_config) as executor: executor.clean_staging_raw_table(staging_raw_table, date) activity.logger.info( '{table} was cleaned from rows for {date}'.format( table=staging_raw_table, date=date)) @task.decorate(timeout=600) def create_temp_staging_raw_table(activity, date, temp_table_names, feed_name): """Create temp staging raw table. Args: activity (ActivityWorker): The Garcon activity worker. date (str): Date in YYYY-MM-DD format to delete from table. temp_table_names (list): List of temp tables per account """ # check task status for each temp_table_name for temp_table_name in temp_table_names: if task_status.is_completed_task( feed_name, date, 'create_temp_staging_raw_table {}'.format(temp_table_name)): return sf_config = get_sf_config(config.secrets_path) ExecutorFA = registered_executors.get(feed_name) with ExecutorFA(sf_config) as executor: for temp_table_name in temp_table_names: executor.create_temp_staging_raw_table(temp_table_name) activity.logger.info('{} was created'.format(temp_table_names)) # update task status for temp_table_name in temp_table_names: task_status.mark_completed_task( feed_name, date, 'create_temp_staging_raw_table {}'.format(temp_table_name)) # TODO: 4H is a temporary solution. We need to split large files (DS-6840) @task.decorate(timeout=4 * 3600) def load_temp_staging_raw_table( activity, date, s3_archive_path, temp_table_names, feed_name): """Load one of the temp staging raw tables from S3. Args: activity (ActivityWorker): The Garcon activity worker. date (str): Date in YYYY-MM-DD format to delete from table. temp_table_names (list): A table name to load data for each account s3_archive_path (str): S3 path do load table from. """ # check task status for each temp_table_name for temp_table_name in temp_table_names: if task_status.is_completed_task( feed_name, date, 'load_temp_staging_raw_table {}'.format(temp_table_name)): return sf_config = get_sf_config(config.secrets_path) ExecutorFA = registered_executors.get(feed_name) with ExecutorFA(sf_config) as executor: for temp_table_name in temp_table_names: s3_file_path = s3_archive_path + config.source_filename_template if 'IODA' in temp_table_name: s3_file_path = s3_file_path.format( account='IODA') elif 'ENT' in temp_table_name: s3_file_path = s3_file_path.format( account='ENT') elif 'ORCH' in temp_table_name: s3_file_path = s3_file_path.format( account='ORCH') else: continue executor.load_temp_staging_raw_table( temp_table_name, s3_file_path ) activity.logger.info( '{} was loaded with the data'.format(temp_table_names)) # update task status for each temp table name for temp_table_name in temp_table_names: task_status.mark_completed_task( feed_name, date, 'load_temp_staging_raw_table {}'.format(temp_table_name)) @task.decorate(timeout=7200) def load_staging_raw_table( activity, date, processed_datetime, temp_table_names, staging_raw_table, s3_archive_path, feed_name): """Load staging raw table from temp tables. Args: activity (ActivityWorker): The Garcon activity worker. date (str): Date in YYYY-MM-DD format to delete from table. processed_datetime (str): A single processeddaytime to use through all the tables during the workflow run. temp_table_names (list): Name of the temp tables. staging_raw_table (str): Staging raw table name s3_archive_path (str): S3 path to files location """ # check task status if task_status.is_completed_task( feed_name, date, 'load_staging_raw_table_all'): return sf_config = get_sf_config(config.secrets_path) ExecutorFA = registered_executors.get(feed_name) with ExecutorFA(sf_config) as executor: for temp_table_name in temp_table_names: filename = config.source_filename_template if 'IODA' in temp_table_name: filename = filename.format(account='IODA') elif 'ENT' in temp_table_name: filename = filename.format(account='ENT') elif 'ORCH' in temp_table_name: filename = filename.format(account='ORCH') else: continue try: filesize = s3.get_key_size( '{}{}'.format(s3_archive_path, filename), config.expected_bucket_owner ) except AttributeError: # theorchard publishing is not always in scope of files # we should just pass this filename if it's not exist continue executor.load_staging_raw_table( date=date, processed_datetime=processed_datetime, filename=filename, filesize=filesize, staging_raw_table=staging_raw_table, temp_staging_raw_table=temp_table_name ) activity.logger.info( '{staging_raw} was loaded with the data from {table_names}'.format( staging_raw=staging_raw_table, table_names=temp_table_names)) # update task status task_status.mark_completed_task( feed_name, date, 'load_staging_raw_table {temp_table_names}'.format( temp_table_names=temp_table_names)) # update task status for load_staging_raw_table task_status.mark_completed_task( feed_name, date, 'load_staging_raw_table_all') @task.decorate(timeout=600) def drop_temp_table(activity, temp_table_names, feed_name): """Drop temp staging raw tables. Args: activity (ActivityWorker): The Garcon activity worker. temp_table_names (str): Table name to drop. """ sf_config = get_sf_config(config.secrets_path) ExecutorFA = registered_executors.get(feed_name) with ExecutorFA(sf_config) as executor: for temp_table_name in temp_table_names: executor.drop_table(temp_table_name) activity.logger.info('{} was dropped'.format(temp_table_name)) @task.decorate(timeout=600) def check_assets_availability(activity, date, feed_name): """Check if Asset Report data is available. Stop otherwise. Args: activity (ActivityWorker): The Garcon activity worker. date (str): Reporting date (YYYY-MM-DD). """ sf_config = get_sf_config(config.secrets_path) ExecutorFA = registered_executors.get(feed_name) with ExecutorFA(sf_config) as executor: if not executor.is_assets_available(): activity.logger.error('Asset Report table is empty.') garcon_feed_status.set_overall_status( feed_name, date, garcon_feed_status.STATUS_NOT_AVAILABLE) return {'stop': True} @task.decorate(timeout=7200) def update_dim_claim(activity, date, feed_name): """Update dim_claim table. Args: activity (ActivityWorker): The Garcon activity worker. date (str): Reporting date (YYYY-MM-DD). """ # check task status if task_status.is_completed_task( feed_name, date, 'update_dim_table'): return sf_config = get_sf_config(config.secrets_path) ExecutorFA = registered_executors.get(feed_name) with ExecutorFA(sf_config) as executor: executor.update_dim_claim(date) activity.logger.info('Updated dim_claim table.') task_status.mark_completed_task( feed_name, date, 'update_dim_table') @task.decorate(timeout=7200) def sme_copy_athena_to_s3_total_size( activity, date, report_status_name, sme_athena_database, sme_athena_temp_database, sme_athena_source_table, destination_s3_bucket, destination_s3_path, athena_workgroup, aws_region='us-east-1'): """Wrap YT task to add file size into result.""" source_files_dict = youtube_tasks.sme_copy_from_athena_to_s3( activity, date, report_status_name, sme_athena_database, sme_athena_temp_database, sme_athena_source_table, destination_s3_bucket, destination_s3_path, athena_workgroup, aws_region ) total_file_size = 0 if source_files_dict is None: source_files_dict = {'total_file_size': total_file_size} else: if 'stop' not in source_files_dict: files_list = source_files_dict['source_files_dict']['files'] if len(files_list) > 0: total_file_size = sum( [file['file_size'] for file in files_list]) source_files_dict['source_files_dict'] = { 'files': 'file names were removed due to size violation' } source_files_dict['total_file_size'] = total_file_size return source_files_dict