"""Tasks of the Chartmetric Participants Ingestion Workflow.""" from datetime import datetime from datetime import timedelta from garcon import task from feed_ingestion.flows.chartmetric_participants import config from feed_ingestion.flows.chartmetric_participants.snowflake_executor \ import SnowflakeExecutor from feed_ingestion.flows.helpers import get_neo4j_config from feed_ingestion.flows.helpers import get_sf_config from feed_ingestion.util.neo4j.neo4j_executor \ import Neo4jExecutor @task.decorate(timeout=600) def bootstrap(activity, date, reload): """Bootstrap workflow by getting the correct configurations. Args: activity (ActivityWorker): The activity worker. date (str): Reporting date (YYYY-MM-DD). reload (str): A flag which indicates if we have to clear out the log table, and perform a force refresh. Returns: dict: Context. """ # date is the date passed in or yesterday's date if date: date_obj = datetime.strptime(date, '%Y-%m-%d') else: date_obj = datetime.utcnow().date() - timedelta(days=1) activity.logger.info('Bootstrap flow: {}'.format(date_obj)) return dict( feed_name=config.feed_name, reload=True if reload else False, date=date_obj.strftime('%Y-%m-%d'), ingestion_started_at=datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') ) @task.decorate(timeout=3600) def clear_log_table(activity, reload): """Create the table that will hold the spotify data to ingest. Args: activity (ActivityWorker): The activity worker. reload (str): A flag which indicates if we have to clear out the log table, and perform a force refresh. Returns: dict: Context. """ activity.logger.info('Starting clear_log_table') if reload: with SnowflakeExecutor( get_sf_config(config.secrets_path)) as sf_executor: sf_executor.execute_query('clear_log_table', **{}) activity.logger.info('clear_log_table finished') else: activity.logger.info('clear_log_table not required, skip') @task.decorate(timeout=600) def create_temp_participants_table(activity): """Create the table that will hold the temp data. Args: activity (ActivityWorker): The activity worker. Returns: dict: Context. """ activity.logger.info('Starting create_temp_participants_table') with SnowflakeExecutor(get_sf_config(config.secrets_path)) as sf_executor: query_name = 'create_temp_participants_table' sf_executor.execute_query(query_name, **{}) activity.logger.info('Finished create_temp_participants_table') @task.decorate(timeout=600) def create_main_participants_table(activity): """Create the table that will hold the data. Args: activity (ActivityWorker): The activity worker. Returns: dict: Context. """ activity.logger.info('Starting create_main_participants_table') with SnowflakeExecutor(get_sf_config(config.secrets_path)) as sf_executor: query_name = 'create_main_participants_table' sf_executor.execute_query(query_name, **{}) # this is useful for investigating issues in production sf_executor.grant_select_to_facts_db_prod_schema_read( 'chartmetric_participants') activity.logger.info('Finished create_main_participants_table') @task.decorate(timeout=60 * 60 * 24) def ingest_participants(activity, feed_name): """Ingest participants from Snowflake into Neo4j. Args: activity (ActivityWorker): The activity worker. feed_name (str): The name of the feed. Returns: dict: Context. """ activity.logger.info('Starting ingest_participants') sf_config = get_sf_config(config.secrets_path) neo4j_config = get_neo4j_config(feed_name) with Neo4jExecutor(feed_name, neo4j_config) as neo4j_executor: offset = 0 limit = config.row_limit_per_batch while True: with SnowflakeExecutor(sf_config) as sf_executor: sf_params = { 'table_name': 'chartmetric_participants', 'offset': offset, 'limit': limit } rows = sf_executor.fetchall_dict_query( 'get_participants', **sf_params) offset += limit if not rows: break neo4j_params = {'rows': rows} neo4j_executor.execute_write_query( 'ingest_participants', neo4j_params) activity.logger.info('Finished ingest_participants') @task.decorate(timeout=7200) def delete_defunct_relationships(activity, feed_name): """Delete defunct relationships in Neo4j. Args: activity (ActivityWorker): The activity worker. feed_name (str): The name of the feed. Returns: dict: Context. """ activity.logger.info('Starting delete_defunct_relationships') sf_config = get_sf_config(config.secrets_path) neo4j_config = get_neo4j_config(feed_name) with Neo4jExecutor(feed_name, neo4j_config) as neo4j_executor: offset = 0 limit = config.delete_row_limit_per_batch while True: with SnowflakeExecutor(sf_config) as sf_executor: sf_params = { 'offset': offset, 'limit': limit } rows = sf_executor.fetchall_dict_query( 'get_defunct_relationships', **sf_params) offset += limit if not rows: break neo4j_params = {'rows': rows} neo4j_executor.execute_write_query( 'delete_defunct_relationships', neo4j_params) activity.logger.info('Finished delete_defunct_relationships') @task.decorate(timeout=7200) def insert_into_log_table(activity, ingestion_started_at): """Insert unique keys of ingested rows to the log table. Args: activity (ActivityWorker): The activity worker. feed_name (str): The name of the feed. ingestion_started_at (str): Timestamp '%Y-%m-%d %H:%M:%S' when ETL has started. """ activity.logger.info('Insert into log table') with SnowflakeExecutor(get_sf_config(config.secrets_path)) as sf_executor: params = { 'ingestion_started_at': ingestion_started_at } sf_executor.execute_query('insert_into_log_table', **params) activity.logger.info('All done')