""" Spotify Charts Ingestion Workflow. Ingest data from Spotify Charts feed. Files downloaded from Spotify API. """ import datetime from garcon.param import StaticParam from feed_ingestion.flows import base from feed_ingestion.flows.spotify_charts import config from feed_ingestion.flows.spotify_charts import tasks from feed_ingestion.tasks import load_raw_table_tasks_sf from feed_ingestion.tasks import validate_raw_data_tasks_sf class Flow( base.FlowBase, base.FlowConfigMixin, base.FlowLoadRawMixinSF): """Class representing the workflow.""" def __init__(self): """Initialize flow object.""" super(Flow, self).__init__(config.feed_name, version='1.0') def decider(self, schedule): """Activity decider. Args: schedule (callable): The scheduler method. """ bootstrap = schedule('bootstrap', self.bootstrap_activity) if bootstrap.result.get( 'bootstrap.stop') is True: return grab_drop_files = schedule( 'grab_drop_files', self.grab_drop_files, requires=[bootstrap] ) # if files unavailable, let's stop here if grab_drop_files.result.get( 'grab_drop_files.stop') is True: return create_temp_staging_raw_table = schedule( 'create_temp_staging_raw_table', self.create_temp_staging_raw_table, requires=[grab_drop_files]) load_temp_staging_raw_table = schedule( 'load_temp_staging_raw_table', self.load_temp_staging_raw_table, requires=[create_temp_staging_raw_table]) clean_staging_raw_table = schedule( 'clean_staging_raw_table', self.clean_staging_raw_table, requires=[load_temp_staging_raw_table]) load_staging_raw_table = schedule( 'load_staging_raw_table', self.load_staging_raw_table, requires=[clean_staging_raw_table]) load_staging_charts_data = schedule( 'load_staging_charts_data', self.load_staging_charts_data, requires=[load_staging_raw_table]) build_jenkins_charts = schedule( 'build_jenkins_charts', self.build_jenkins_charts, requires=[load_staging_charts_data]) schedule( 'set_status_to_ingested', self.set_status_to_ingested, requires=[build_jenkins_charts]) 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. """ chart = initial_context['chart'] flow_name = '_'.join([self.name, chart]) if 'context_date' not in initial_context: date = datetime.datetime.today().strftime('%Y-%m-%d') else: date = initial_context['context_date'] return '{flow_name}-{date}'.format( flow_name=flow_name, date=date) 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 'chart' in context, 'There is no chart in context' return '_'.join([self.feed_name, context['chart']]) @property def bootstrap_activity(self): """First activity for the workflow.""" return self.create( name='bootstrap', tasks=base.SyncRunner( tasks.bootstrap.fill( namespace='bootstrap', date='context_date', reload='reload', chart='chart'))) @property def grab_drop_files(self): """Grab files and upload to archive bucket on S3.""" return self.create( name='grab_drop_files', tasks=base.SyncRunner( tasks.grab_drop_files.fill( namespace='grab_drop_files', feed_name='bootstrap.feed_name', date='bootstrap.date', chart='bootstrap.chart', file_pattern='bootstrap.file_pattern', archive_path='bootstrap.archive_path'))) @property def create_temp_staging_raw_table(self): """Create temp staging raw table.""" return self.create( name='create_temp_staging_raw_table', generators=[self.temp_staging_tables_generator], schedule_to_start=48000, 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='sfdb_params', feed_name='bootstrap.feed_name', secrets_path=StaticParam(config.secrets_path), temp_staging_raw_table='temp_staging_raw_table'))) @property def load_temp_staging_raw_table(self): """Load temp staging raw table.""" return self.create( name='load_temp_staging_raw_table', generators=[self.temp_staging_tables_generator], schedule_to_start=48000, tasks=base.SyncRunner( validate_raw_data_tasks_sf.load_temp_staging_raw_table.fill( namespace='load_temp_staging_raw_table', date='bootstrap.date', sfdb_params='sfdb_params', feed_name='bootstrap.feed_name', secrets_path=StaticParam(config.secrets_path), key_dir='key_dir', temp_staging_raw_table='temp_staging_raw_table', error_limit=StaticParam(config.snowflake_error_limit)))) @property def clean_staging_raw_table(self): """Clean staging raw table for date.""" return self.create( name='clean_staging_raw_table', generators=[self.clean_staging_raw_table_generator], tasks=base.SyncRunner( load_raw_table_tasks_sf.clean_staging_raw_table.fill( namespace='clean_staging_raw_table', date='bootstrap.date', feed_name='bootstrap.feed_name', secrets_path=StaticParam(config.secrets_path), staging_raw_table='bootstrap.staging_raw_table', kwargs='kwargs'))) @property def load_staging_raw_table(self): """Load permanent staging raw table.""" activity_name = 'load_staging_raw_table' return self.create( name=activity_name, generators=[self.temp_staging_tables_generator], schedule_to_start=48000, tasks=base.SyncRunner( load_raw_table_tasks_sf.load_staging_raw_table.fill( namespace=activity_name, date='bootstrap.date', feed_name='bootstrap.feed_name', secrets_path=StaticParam(config.secrets_path), temp_staging_raw_table='temp_staging_raw_table', sfdb_params='sfdb_params', staging_raw_table=StaticParam(config.staging_raw_table), clean=StaticParam('False'), set_complete=StaticParam('False'), kwargs='kwargs'))) @property def load_staging_charts_data(self): """Load load_staging_charts_data table.""" activity_name = 'load_staging_charts_data' return self.create( name=activity_name, schedule_to_start=48000, tasks=base.SyncRunner( tasks.load_staging_charts_data.fill( namespace=activity_name, date='bootstrap.date', feed_name='bootstrap.feed_name', chart='bootstrap.chart', countries='grab_drop_files.countries'))) @property def build_jenkins_charts(self): """Build Jenkins Chartmetric Charts job.""" return self.create( name='build_jenkins_charts', tasks=base.SyncRunner( tasks.build_jenkins_charts.fill( namespace='build_jenkins_charts', feed_name='bootstrap.feed_name', date='bootstrap.date', licensor='licensor', build_charts='build_charts'))) @property def set_status_to_ingested(self): """Set overall feed status to INGESTED.""" return self.create( name='set_status_to_ingested', tasks=base.SyncRunner( tasks.set_status_to_ingested.fill( namespace='set_status_to_ingested', feed_name='bootstrap.feed_name', countries='grab_drop_files.countries', chart='bootstrap.chart', date='bootstrap.date'))) def temp_staging_tables_generator(self, context): """Generate parameters for staging_raw_table activities. Args: context (dict): The current context. Yields: dict: Dictionary with report_name and feed name, which will be used for writing statuses in DynamoDB. """ temp_staging_raw_table = context['bootstrap.temp_staging_raw_table'] file_pattern = context['bootstrap.file_pattern'] s3_archive_path = context['bootstrap.s3_archive_path'] common_kwargs = context['bootstrap.common_kwargs'] for country in context['grab_drop_files.countries']: file_name = file_pattern.format(country=country) yield dict( temp_staging_raw_table=temp_staging_raw_table.format( country=country), key_dir=f'{s3_archive_path}{file_name}', kwargs=dict( country=country, chart_type=common_kwargs['chart_type'], frequency=common_kwargs['frequency'], ) ) def clean_staging_raw_table_generator(self, context): """Generate parameters for clean_staging_raw_table activities. Args: context (dict): The current context. Yields: dict: Dictionary with report_name and feed name, which will be used for writing statuses in DynamoDB. """ kwargs = dict( countries=context['grab_drop_files.countries'], **context['bootstrap.common_kwargs']) yield dict(kwargs=kwargs)