"""WYNK Data Ingestion Workflow.""" from datetime import datetime import os import shutil import boto3 from botocore.exceptions import ClientError from garcon import task from garcon_contrib.dynamo_feed_status import garcon_feed_status from snowflake_connector.etl_connector import SQLLoader from feed_ingestion.flows.wynk import config from feed_ingestion.tasks import bootstrap as reload, check_status from feed_ingestion.tasks import s3_tasks # Load SQL templates sql_loader = SQLLoader(__file__) @task.decorate(timeout=1000) @reload.reset_dynamodb_status_on_reload(config.feed_name) def bootstrap(activity, date, 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. """ date_obj = (datetime.strptime( date, '%Y-%m-%d') if date else datetime.today()) activity.logger.info('Bootstrap flow: {}'.format(date_obj)) drop_path = config.s3['drop'].format(date=date_obj) archive_path = config.s3['archive'].format(date=date_obj) source_file_name = config.filename.format(date=date_obj) temp_staging_raw_table = \ config.temp_staging_raw_table.format(date.replace('-', '_')) s3_archive_path = f's3://{config.data_bucket}/{archive_path}' key_dir = '{}{}'.format(s3_archive_path, source_file_name) return dict( feed_name=config.feed_name, date=date_obj.strftime('%Y-%m-%d'), drop_path=drop_path, archive_path=archive_path, source_file_name=source_file_name, s3_archive_path=s3_archive_path, key_dir=key_dir, secrets_path=config.secrets_path, staging_raw_table=config.staging_raw_table, temp_staging_raw_table=temp_staging_raw_table ) @task.decorate(timeout=3600) @check_status() def grab_drop_files( activity, feed_name, date, source_file_name, drop_path, archive_path): """Copy a feed files from the drop location into the archive folder. Args: activity (ActivityWorker): The activity worker. feed_name (str): The name of the feed. date (str): Reporting date (YYYY-MM-DD). source_file_name (str): The name of the raw file. drop_path (str): s3 path to raw files. archive_path (str): s3 path to drop raw files. """ source_key_name = '{}{}'.format(drop_path, source_file_name) archive_key_name = '{}{}'.format(archive_path, source_file_name) result = s3_tasks.copy_file( activity=activity, source_bucket_name=config.drop_bucket, source_key_name=source_key_name, destination_bucket_name=config.data_bucket, destination_key_name=archive_key_name, replace=True) if not result.get(source_file_name): garcon_feed_status.set_overall_status( feed_name, date, garcon_feed_status.STATUS_NOT_AVAILABLE) garcon_feed_status.set_missing_files( feed_name, date, [source_file_name]) return {'stop': True} garcon_feed_status.set_overall_status( feed_name, date, garcon_feed_status.STATUS_DOWNLOADED) return result @task.decorate(timeout=3600) @check_status() def upload_file( activity, feed_name, date, archive_path, source_file_name): """Upload the file on s3. Args: activity (ActivityWorker): The activity worker. feed_name (str): The name of the feed. date (str): Reporting date (YYYY-MM-DD). archive_path (str): Source S3 path of the archive location. source_file_name (str): Name of zipped archive. Returns: (dict): {'stop': True} if number of files in a zip archive is less than expected. Raises: ClientError: If copy failed. """ local_folder = os.path.join(os.path.curdir, f'file_stage_{date}') os.makedirs(local_folder, exist_ok=True) local_file = f'{local_folder}/{source_file_name}' s3_source_filename = '{}{}'.format(archive_path, source_file_name) activity.logger.info('Downloading file - {}'.format(s3_source_filename)) s3 = boto3.client('s3') s3.download_file(config.data_bucket, s3_source_filename, local_file) activity.logger.info('Start uploading file - {}'.format( local_file)) try: s3.upload_file( local_file, config.data_bucket, f'{archive_path}{source_file_name}' ) except ClientError as e: activity.logger.error(f'Uploading on s3 failed {date} on {local_file}') raise e finally: # delete status to try upload file again in the next run garcon_feed_status.delete_status(feed_name, date) garcon_feed_status.set_overall_status( feed_name, date, garcon_feed_status.STATUS_NOT_INGESTED) shutil.rmtree(local_folder)