"""playlist_placements workflow tasks.""" from datetime import datetime from datetime import timedelta import json from ddtrace import tracer as ddtracer from garcon import task from snowflake_connector.snowflake_conn import get_session from snowflake_connector.snowflake_conn import SQLLoader from activity_detector.flows.playlist_placements import config from activity_detector.utils import dynamodb from activity_detector.utils import ows_notifications sql_loader = SQLLoader(__file__) DATE_CONSTANT = '*' @task.decorate(timeout=1000) def bootstrap(activity, dsp, run_id): """Startup flow with parameters from exec command.""" return dict( dsp=dsp.lower() ) @task.decorate(timeout=300) def check_dynamo_status(activity, dsp): """Check the activity status in DynamoDB. Args: activity (ActivityWorker): The activity worker dsp (str): Name of DSP which contains playlists """ res = dynamodb.get_status(f'{config.ACTIVITY_TYPE_NAME}:{dsp}', DATE_CONSTANT) last_timestamp = res and res['last_processed_timestamp'] if not last_timestamp: last_timestamp = datetime.utcnow() - timedelta(hours=24) return { 'last_timestamp': datetime.strftime(last_timestamp, '%Y-%m-%d %H:%M:%S') } @task.decorate(timeout=7200) def detect_placements(activity, dsp, last_timestamp): """Find playlist placements in date range and send to ows-notifications. Args: activity (ActivityWorker): The activity worker dsp (str): Name of DSP which contains playlists last_timestamp (str): Most recent processed placement Returns: dict: - failures (int): number of errors sending to ows-notifications - last_timestamp (str): most recent processed placement """ return _detect_placements(activity, dsp, last_timestamp) @ddtracer.wrap(resource='task.playlist_placements', name='detect_placements') def _detect_placements(activity, dsp, last_timestamp): """Find playlist placements to send them to ows-notifications. @ddtracer.wrap decorator is incompatible with garcon task runner and causes failures in the playlist_placements workflow execution. To enable full tracing without impacting the workflow, all related logic was moved here instead. """ insert_placements_sql = sql_loader.load_query('insert_placements').format( date_format=config.PLACEMENT_TIME_FORMAT, env=config.env ) get_placements_sql = sql_loader.load_query('get_placements').format( date_format=config.PLACEMENT_TIME_FORMAT, env=config.env ) # get playlist placements for date rows = [] with get_session() as session: session.execute( insert_placements_sql, { 'last_timestamp': last_timestamp, 'dsp': dsp }) session.execute('commit') rows = session.execute( get_placements_sql, { 'last_timestamp': last_timestamp, 'dsp': dsp } ).fetchall() activity.logger.info(f'Found {len(rows)} placements for {dsp}') # make calls to ows-notification to add events failures = 0 last_processed_timestamp = None for row in rows: success = ows_notifications.create_playlist_placement_notification( row['first_added'], row['playlist_name'], row['playlist_id'], row['storename'].lower(), row['storeid'], row['playlist_rank'], row['isrc'], json.loads(row['tracks_list']) ) if not success: failures += 1 last_processed_timestamp = row['first_added'] return dict( failures=failures, last_timestamp=last_processed_timestamp ) @task.decorate(timeout=300) def set_dynamo_status(activity, dsp, last_timestamp, failures): """Set status of workflow run according to params. Args: activity (ActivityWorker): The activity worker dsp (str): Name of DSP which contained playlists last_timestamp (str): Most recent processed placement failures (int): number of API request failures """ if last_timestamp: status = config.STATUS_PROCESSED_NOTIF_SENT if not failures \ else config.STATUS_PROCESSED last_timestamp = datetime.strptime(last_timestamp, '%Y-%m-%d %H:%M:%S') dynamodb.set_status( f'{config.ACTIVITY_TYPE_NAME}:{dsp}', DATE_CONSTANT, status, last_timestamp)