"""snowflake_views_etl workflow tasks.""" from datetime import datetime import itertools import boto3 from garcon import task from snowflake_connector.etl_connector import SnowflakeSQLExecutor from snowflake_connector.metadata_connector import SnowflakeMetadataConnector from snowflake_views import config from snowflake_views.materialized_view import load_view_from_config DATE_TIME_FORMAT = '%Y-%m-%d %H:%M:%S:%f' @task.decorate(timeout=1000) def check_dynamo_status(activity, view_name): """Check status in DynamoDB. Args: activity (ActivityWorker): garcon activity worker. view_name (str): Name of view. Returns: dict: rebuild_view (bool): outcome of timestamp comparison current_latest_timestamp (str, optional): latest timestamp """ view = load_view_from_config(view_name) with SnowflakeSQLExecutor(config.SF_PARAMS) as executor: hydrated_sql, _ = executor.validator.format_identifiers( view.state_sql, dict(config.SF_PARAMS)) current_timestamp = executor.fetchone(hydrated_sql)[0] saved_timestamp = _get_saved_last_processed_timestamp(view_name) if not saved_timestamp or current_timestamp > saved_timestamp: timestamp_string = current_timestamp.strftime(DATE_TIME_FORMAT) return { 'rebuild_view': True, 'current_last_processed_timestamp': timestamp_string} return {'rebuild_view': False} @task.decorate(timeout=21600) def create_view(activity, view_name): """Create view by executing SQL to create table and prepare for querying. Args: activity (ActivityWorker): garcon activity worker. view_name (str): Name of view. Returns: None. """ view = load_view_from_config(view_name) with SnowflakeSQLExecutor(config.SF_PARAMS) as executor: for sql in [view.create_sql, view.grant_sql, view.cluster_sql]: hydrated_sql, _ = executor.validator.format_identifiers( sql, dict(config.SF_PARAMS)) executor.execute(hydrated_sql) @task.decorate(timeout=10800) def cache_view(activity, view_name, attempt_limit): """Cache view by executing SQL to load data onto warehouse. Args: activity (ActivityWorker): garcon activity worker. view_name (str): Name of view. attempt_limit (int): Max number of warmup cache attempts. Returns: None. """ view = load_view_from_config(view_name) api_connector = SnowflakeMetadataConnector(config.SF_MICROSERVICE_PARAMS) api_connector.authenticate() with SnowflakeSQLExecutor(config.SF_MICROSERVICE_PARAMS) as executor: hydrated_sql, _ = executor.validator.format_identifiers( view.cache_sql, dict(config.SF_MICROSERVICE_PARAMS)) _run_cache_sql( activity, executor, api_connector, hydrated_sql, attempt_limit) @task.decorate(timeout=10800) def save_last_processed_timestamp(activity, view_name, latest_timestamp): """Save the 'latest_timestamp' value in dynamoDB for the view. Args: activity (ActivityWorker): garcon activity worker. view_name (str): Name of view. latest_timestamp (str): date and time of most recently processed data Returns: None. """ table = _connect_to_dynamo_table(config.INGESTION_STATUS_TABLE) table.update_item( Key={'view_name': view_name}, UpdateExpression='SET last_processed_timestamp = :d', ExpressionAttributeValues={':d': latest_timestamp}) def _get_saved_last_processed_timestamp(view_name): """Get the last processed timestamp saved in dynamoDB for a view.""" table = _connect_to_dynamo_table(config.INGESTION_STATUS_TABLE) response = table.get_item( Key={'view_name': view_name}, ProjectionExpression='last_processed_timestamp') if 'Item' not in response: return None response = response['Item'] return datetime.strptime( response['last_processed_timestamp'], DATE_TIME_FORMAT) def _run_cache_sql(activity, sf_executor, api_connector, sql, attempt_limit): """Run cache SQL until the cache is warm.""" for i in itertools.count(start=1): if i > attempt_limit: activity.logger.warning('Cache warmup attempt limit exceeded') return result = sf_executor.execute(sql) if api_connector.get_query_scan_bytes_number(result) == 0: activity.logger.info('View was successfully cached') break def _connect_to_dynamo_table(tablename): """Connect to a dynamoDB table.""" dynamodb = boto3.resource('dynamodb', region_name=config.AWS_REGION) return dynamodb.Table(tablename)