""" Pandora workflow tasks. Tasks to ingest and process Pandora data. """ from datetime import date as date_module 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.pandora import config from feed_ingestion.flows.pandora.snowflake_executor import Pandora from feed_ingestion.util import task_status STOP_RESPONSE = {'stop': True} # task_id for staging_raw tasks TASK_ID = 'staging_raw_table_tasks' @task.decorate(timeout=1000) def bootstrap(activity, date, reload, licensor, snowflake_error_limit=None, snowflake_error_on_column_count_mismatch=None): """Bootstrap workflow by injecting initial context from config. Args: activity (ActivityWorker): The Garcon activity worker. date (str): Reporting date (YYYY-MM-DD). reload (str or None): If 'True' delete all feed statuses in DynamoDB. licensor (str): The licensor to ingest. snowflake_error_limit (int or None): Snowflake error limit. snowflake_error_on_column_count_mismatch (str or None): Snowflake error on column count mismatch. Returns: dict: Initial context for the workflow. """ feed_name = '_'.join([config.feed_name, licensor]) activity.logger.info( 'Bootstrapping workflow {feed_name} for {date}...'.format( feed_name=feed_name, date=date)) # date is the date passed in or today's date date = date or date_module.today().strftime('%Y-%m-%d') if reload == 'True': activity.logger.info( 'Delete status for feed: {} {} '.format(feed_name, date)) garcon_feed_status.delete_status(feed_name, date) else: overall_status = garcon_feed_status.get_overall_status( feed_name, date) if overall_status == garcon_feed_status.STATUS_INGESTED: return STOP_RESPONSE # a single processeddaytime to use across all the tables processed_datetime = datetime.now().strftime('%Y-%m-%dT%H:%M:%S') replace_archive_files = True # if archive files have already been downloaded to archives, don't reingest if task_status.is_completed_task( feed_name, date, 'set_overall_status_DOWNLOADED'): replace_archive_files = False # get year, month, day year, month, day = date.split('-') # array to store files to download expected_files = [] # hydrate directory names drop_bucket = config.s3['drop'][licensor].format( YYYY=year, MM=month, DD=day) archive_bucket = config.s3['archive'][licensor].format(date=date) # add metadata file to list of files metadata_file = config.pandora_metadata_file.format( licensor=config.licensors[licensor], YYYY=year, MM=month, DD=day) expected_files.append(metadata_file) # dictionary to store information about temporary staging raw tables temp_staging_raw_tables = {} # add metadata to temp_staging_raw_tables dict temp_metadata_table = {} temp_metadata_table['temp_table_name'] = config.snowflake_table_names[ 'metadata_temp_raw_template'].format( date=date.replace('-', ''), licensor=licensor) temp_metadata_table['temp_table_s3_full_path'] = '{dir}{file}'.format( dir=archive_bucket, file=metadata_file) temp_staging_raw_tables['metadata'] = temp_metadata_table # add files for each country streams_file = config.pandora_streams_file.format( YYYY=year, MM=month, DD=day, licensor=config.licensors[licensor]) for country in config.countries: # add streams file for each country to list of files streams_country_file = streams_file.format(country=country) expected_files.append(streams_country_file) # add country to temporary staging tables temp_country_table = {} temp_country_table['temp_table_name'] = config.snowflake_table_names[ 'streams_temp_raw_template'].format( country=country, date=date.replace('-', ''), licensor=licensor) temp_country_table['temp_table_s3_full_path'] = '{dir}{file}'.format( dir=archive_bucket, file=streams_country_file) temp_staging_raw_tables[country] = temp_country_table if snowflake_error_limit is None: error_limit = config.default_snowflake_error_limit elif isinstance(snowflake_error_limit, int): error_limit = snowflake_error_limit else: raise ValueError('snowflake_error_limit should be int') mismatch_error = \ snowflake_error_on_column_count_mismatch or 'true' return dict( feed_name=feed_name, secrets_path=config.secrets_path, date=date, processed_datetime=processed_datetime, date_as_in_uuid=date, drop_bucket=drop_bucket, archive_bucket=archive_bucket, expected_files=expected_files, replace_archive_files=replace_archive_files, temp_staging_raw_tables=temp_staging_raw_tables, licensor=licensor, snowflake_error_limit=error_limit, snowflake_error_on_column_count_mismatch=mismatch_error, jenkins_config=config.jenkins_config ) @task.decorate(timeout=1800) def clean_staging_raw_table(activity, date, feed_name): """Delete records from staging_raw_pandora for the given date. Args: activity (ActivityWorker): The Garcon activity worker. date (str): Date in YYYY-MM-DD format to delete from table. feed_name (str): The name of a feed. """ if task_status.is_completed_task(feed_name, date, TASK_ID): return staging_raw_table = config.snowflake_table_names['staging_raw'] sf_config = get_sf_config(config.secrets_path) pandora_executor = registered_executors.get(feed_name) with pandora_executor(sf_config) as executor: executor.clean_staging_raw_table(staging_raw_table, date) activity.logger.info( '{table} was cleaned from rows for {date} for {feed_name}'.format( table=staging_raw_table, date=date, feed_name=feed_name)) @task.decorate(timeout=600) def create_temp_staging_raw_table(activity, date, temp_table_name, 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_name (str): A table name to create. feed_name (str): The name of a feed. """ # check task status if task_status.is_completed_task(feed_name, date, TASK_ID): return sf_config = get_sf_config(config.secrets_path) pandora_executor = registered_executors.get(feed_name) with pandora_executor(sf_config) as executor: kwargs_for_executor = dict( date_for_sqlloader=date ) if 'streams' in temp_table_name: executor.create_streams_temp_staging_raw_table( temp_table_name, **kwargs_for_executor) else: executor.create_metadata_temp_staging_raw_table( temp_table_name, **kwargs_for_executor) activity.logger.info('{} was created'.format(temp_table_name)) @task.decorate(timeout=3600) def load_staging_raw_table( activity, date, processed_datetime, filename, temp_streams_table, temp_metadata_table, 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. filename (str): Filename of the streams data from which a particular temp streams table was loaded. temp_streams_table (str): Name of the temp streams table. temp_metadata_table (str): Name of the temp metadata table. feed_name (str): The name of a feed. """ # check task status if task_status.is_completed_task(feed_name, date, TASK_ID): return staging_raw_table = config.snowflake_table_names['staging_raw'] sf_config = get_sf_config(config.secrets_path) pandora_executor = registered_executors.get(feed_name) with pandora_executor(sf_config) as executor: kwargs_for_executor = dict( date_for_sqlloader=date ) executor.load_staging_raw_table( date, processed_datetime, filename, staging_raw_table, temp_streams_table, temp_metadata_table, **kwargs_for_executor) activity.logger.info( '{staging_raw} was loaded with the data from {streams} and ' '{metadata}'.format( staging_raw=staging_raw_table, streams=temp_streams_table, metadata=temp_metadata_table)) # update task status task_status.mark_completed_task( feed_name, date, 'load_staging_raw_table {streams} {metadata}'.format( streams=temp_streams_table, metadata=temp_metadata_table)) # update task status for clean_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_name): """Drop temp staging raw tables. Args: activity (ActivityWorker): The Garcon activity worker. temp_table_name (str): Table name to drop. """ sf_config = get_sf_config(config.secrets_path) with Pandora(sf_config) as executor: executor.drop_table(temp_table_name) activity.logger.info('{} was dropped'.format(temp_table_name))