""" Base Work Flow. Base Work Flow class that new activity_detector flows should extend. Standardizes generated workflow_id and adds default on_exception method. Flows must override the decider method. """ import datetime import logging import os import boto3 from botocore.config import Config from ddtrace import tracer as ddtracer from garcon import activity import sentry_sdk logger = logging.getLogger('garcon') class BaseFlow: """Base Garcon Workflow that other Flows can extend.""" @ddtracer.wrap(resource='flow.init', name='flow_init') def __init__(self, *, flow_name, version, swf_domain=None): """Initialize Flow. Args: flow_name (str): version (str): swf_domain (str): """ if swf_domain: self.domain = swf_domain else: self.domain = get_domain() self.name = flow_name self.version = version self.client = boto3.client('swf', config=Config( connect_timeout=60, region_name=os.getenv('AWS_REGION'), read_timeout=180, retries={'max_attempts': 2})) self.create = activity.create( self.client, self.domain, self.name, version=self.version, on_exception=self.on_exception) # Sentry logging sentry_dsn = os.getenv('SENTRY_DSN') if sentry_dsn: sentry_sdk.init(sentry_dsn) self.sentry_client = sentry_sdk else: self.sentry_client = None if ddtracer.enabled: with ddtracer.trace('flow_init') as dd_span: dd_span.set_tag('flow_name', flow_name) dd_span.set_tag('swf_domain', self.domain) def on_exception(self, actor, exception): """Capture an exception that has occurred in the application. Args: actor (ActivityWorker, DeciderWorker): The actor that has received the exception. exception (Exception): The exception to capture. """ if self.sentry_client: self.sentry_client.capture_exception() if isinstance(actor, activity.Activity): actor.logger.error(exception, exc_info=True) else: logger.error(exception, exc_info=True) def workflow_id(self, initial_context): """Generate workflow id. Assumes a unique workflow is defined by it's context date and defaults to today's date. If that is not the case this method should be overridden. Args: initial_context (dict): The initial context for the flow. Returns: str: A unique identifier for a workflow being executed In the forms of '-YYYY-MM-DD', where YYYY-MM-DD is the context date or, if none passed the current date. """ 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=self.name, date=date) def get_domain(): """Get workflow domain. Returns: str: SWF domain the workflow should use. """ env = os.getenv('Environment') if not env or env == 'dev': domain = os.getenv('DEV_SWF_DOMAIN', 'dev') else: domain = '{}_{}'.format(env, 'activity_detector') return domain