""" Base Accounting Work Flow Class. Base Work Flow class that accounting flows should extend. Standardizes how flow domain, name, and workflow_id are generated and adds default on_exception method. Flows should override the decider method. """ import datetime import logging import os from garcon import activity import raven logger = logging.getLogger('garcon') class FlowBase(object): """Base Garcon Workflow that other Flows can extend.""" def __init__(self, flow_name, version): """Initialize the flow using proper attributes. Initializes Flow: domain - '{env_name}_{flow_name}_swf_accounting' flow_name - '{flow_name}-accounting' Args: flow_name (str): name of the work flow. version (str): Version of the workflow type to filter on. should be a 'x.x' float (ex. 1.0, 1.1, etc.) """ self.domain = get_domain() self.name = generate_workflow_name(flow_name) self.version = version self.create = activity.create( self.domain, self.name, version=self.version, on_exception=self.on_exception) 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. """ # client grabs sentry dns from SENTRY_DSN environment variable if os.environ.get('SENTRY_DSN'): client = raven.Client() client.captureException() if isinstance(actor, activity.Activity): actor.logger.error(exception) else: logger.error(exception) def workflow_id(self, initial_context=None): """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 decider(self, schedule, context): """Activity decider. Args: schedule (callable): the scheduler method. context (dict): Initial context of workflow. """ raise NotImplementedError() def generate_workflow_name(workflow_name): """Generate workflow name. To play nice with non-class based Flows. Args: workflow_name (str): name of the work flow. Returns: str: SWF name in the format of '{workflow_name}-accounting' """ assert workflow_name.islower() and ' ' not in workflow_name, ( 'feed name must be lower case with no spaces') return '{workflow_name}-accounting'.format( workflow_name=workflow_name) def get_domain(): """Get workflow domain. To play nice with non-class based Flows. Returns: str: SWF domain the workflow should use """ domain_namespace = os.getenv('Environment') if domain_namespace == 'dev': domain_namespace = os.getenv('DEV_DOMAIN', 'dev') return '{}_swf_accounting'.format(domain_namespace)