class WorkflowType(BaseModel): """Simple Workflow Type wrapper :param domain: Domain the workflow type should be registered in :type domain: swf.models.Domain :param name: name of the workflow type :type name: str :param version: workflow type version :type version: str :param status: workflow type status :type status: swf.core.ConnectedSWFObject.{REGISTERED, DEPRECATED} :param creation_date: creation date of the current WorkflowType (timestamp) :type creation_date: float :param deprecation_date: deprecation date of WorkflowType (timestamp) :type deprecation_date: float :param task_list: task list to use for scheduling decision tasks for executions of this workflow type :type task_list: str :param child_policy: policy to use for the child workflow executions when a workflow execution of this type is terminated :type child_policy: CHILD_POLICIES.{TERMINATE | REQUEST_CANCEL | ABANDON} :param execution_timeout: maximum duration for executions of this workflow type :type execution_timeout: str :param decision_tasks_timeout: maximum duration of decision tasks for this workflow type :type decision_tasks_timeout: str :param description: Textual description of the workflow type :type description: str """ __slots__ = [ 'domain', 'name', 'version', 'status', 'creation_date', 'deprecation_date', 'task_list', 'child_policy', 'execution_timeout', 'decision_tasks_timeout', 'description', ] def __init__(self, domain, name, version, status=REGISTERED, creation_date=0.0, deprecation_date=0.0, task_list=None, child_policy=CHILD_POLICIES.TERMINATE, execution_timeout='300', decision_tasks_timeout='300', description=None, *args, **kwargs): self.domain = domain self.name = name self.version = version self.status = status self.creation_date = creation_date self.deprecation_date = deprecation_date self.task_list = task_list self.execution_timeout = execution_timeout self.decision_tasks_timeout = decision_tasks_timeout self.description = description # Explicitly call child_policy setter # to validate input value self.set_child_policy(child_policy) # immutable decorator rebinds class name, # so have to use generice self.__class__ super(self.__class__, self).__init__(*args, **kwargs) def set_child_policy(self, policy): if policy not in CHILD_POLICIES: raise ValueError("invalid child policy value: {}".format(policy)) self.child_policy = policy def _diff(self): """Checks for differences between WorkflowType instance and upstream version :returns: A swf.models.base.ModelDiff describing differences :rtype: ModelDiff """ try: description = self.connection.describe_workflow_type( self.domain.name, self.name, self.version ) except SWFResponseError as e: if e.error_code == 'UnknownResourceFault': raise DoesNotExistError("Remote Domain does not exist") raise ResponseError(e.body['message']) workflow_info = description['typeInfo'] workflow_config = description['configuration'] return ModelDiff( ('name', self.name, workflow_info['workflowType']['name']), ('version', self.version, workflow_info['workflowType']['version']), ('status', self.status, workflow_info['status']), ('creation_date', self.creation_date, workflow_info['creationDate']), ('deprecation_date', self.deprecation_date, workflow_info['deprecationDate']), ('task_list', self.task_list, workflow_config['defaultTaskList']['name']), ('child_policy', self.child_policy, workflow_config['defaultChildPolicy']), ('execution_timeout', self.execution_timeout, workflow_config['defaultExecutionStartToCloseTimeout']), ('decision_tasks_timout', self.decision_tasks_timeout, workflow_config['defaultTaskStartToCloseTimeout']), ('description', self.description, workflow_info['description']), ) @property @exceptions.translate(SWFResponseError, to=ResponseError) @exceptions.is_not(WorkflowTypeDoesNotExist) @exceptions.catch(SWFResponseError, raises(WorkflowTypeDoesNotExist, when=exceptions.is_unknown('WorkflowType'), extract=exceptions.extract_resource)) def exists(self): """Checks if the WorkflowType exists amazon-side :rtype: bool """ self.connection.describe_workflow_type( self.domain.name, self.name, self.version ) return True def save(self): """Creates the workflow type amazon side""" try: self.connection.register_workflow_type( self.domain.name, self.name, self.version, task_list=str(self.task_list), default_child_policy=str(self.child_policy), default_execution_start_to_close_timeout=str(self.execution_timeout), default_task_start_to_close_timeout=str(self.decision_tasks_timeout), description=self.description ) except SWFTypeAlreadyExistsError: raise AlreadyExistsError("Workflow type %s already exists amazon-side" % self.name) except SWFResponseError as e: if e.error_code == 'UnknownResourceFault': raise DoesNotExistError(e.body['message']) def delete(self): """Deprecates the workflow type amazon-side""" try: self.connection.deprecate_workflow_type(self.domain.name, self.name, self.version) except SWFResponseError as e: if e.error_code in ['UnknownResourceFault', 'TypeDeprecatedFault']: raise DoesNotExistError(e.body['message']) def upstream(self): from swf.querysets.workflow import WorkflowTypeQuerySet qs = WorkflowTypeQuerySet(self.domain) return qs.get(self.name, self.version) def start_execution(self, workflow_id=None, task_list=None, child_policy=None, execution_timeout=None, input=None, tag_list=None, decision_tasks_timeout=None): """Starts a Workflow execution of current workflow type :param workflow_id: The user defined identifier associated with the workflow execution :type workflow_id: String :param task_list: task list to use for scheduling decision tasks for execution of this workflow :type task_list: String :param child_policy: policy to use for the child workflow executions of this workflow execution. :type child_policy: CHILD_POLICIES.{TERMINATE | REQUEST_CANCEL | ABANDON} :param execution_timeout: maximum duration for the workflow execution :type execution_timeout: String :param input: Input of the workflow execution :type input: dict :param tag_list: Tags associated with the workflow execution :type tag_list: String or list of strings or None :param decision_tasks_timeout: maximum duration of decision tasks for this workflow execution :type decision_tasks_timeout: String """ workflow_id = workflow_id or '%s-%s-%i' % (self.name, self.version, time.time()) task_list = task_list or self.task_list child_policy = child_policy or self.child_policy if child_policy not in CHILD_POLICIES: raise ValueError("invalid child policy value: {}".format(child_policy)) if input is None: input = {} input = json_dumps(input) if tag_list is not None and not isinstance(tag_list, list): tag_list = [tag_list] # checks if tag_list and len(tag_list) > 5: raise ValueError("You cannot have more than 5 tags in StartWorkflowExecution.") run_id = self.connection.start_workflow_execution( self.domain.name, workflow_id, self.name, self.version, task_list=task_list, child_policy=child_policy, execution_start_to_close_timeout=execution_timeout, input=input, tag_list=tag_list, task_start_to_close_timeout=decision_tasks_timeout, )['runId'] return WorkflowExecution(self.domain, workflow_id, run_id=run_id) def __repr__(self): return '<{} domain={} name={} version={} status={}>'.format( self.__class__.__name__, self.domain.name, self.name, self.version, self.status)
class WorkflowExecution(BaseModel): """Simple Workflow execution wrapper :param domain: Domain the workflow execution should be registered in :type domain: swf.models.domain.Domain :param workflow_type: The WorkflowType associated with the workflow execution is associated with :type workflow_type: String :param workflow_id: The user defined identifier associated with the workflow execution :type workflow_id: String :param run_id: The Amazon defined identifier associated with the workflow execution :type run_id: String :param status: Whether the WorkflowExecution instance represents an opened or closed execution :type status: String constant :param task_list: The task list to use for the decision tasks generated for this workflow execution. :type task_list: string :param input: input data of the execution, which will be passed around using serialized json :type input: dict """ STATUS_OPEN = "OPEN" STATUS_CLOSED = "CLOSED" CLOSE_STATUS_COMPLETED = "COMPLETED" CLOSE_STATUS_FAILED = "FAILED" CLOSE_STATUS_CANCELED = "CANCELED" CLOSE_STATUS_TERMINATED = "TERMINATED" CLOSE_STATUS_CONTINUED_AS_NEW = "CLOSE_STATUS_CONTINUED_AS_NEW" CLOSE_TIMED_OUT = "TIMED_OUT" kind = 'execution' __slots__ = [ 'domain', 'workflow_id', 'run_id', 'status', 'workflow_type', 'task_list', 'child_policy', 'close_status', 'execution_timeout', 'input', 'tag_list', 'decision_tasks_timeout', 'close_timestamp', 'start_timestamp', 'cancel_requested', 'latest_execution_context', 'latest_activity_task_timestamp', 'open_counts', 'parent', ] def __init__(self, domain, workflow_id, run_id=None, status=STATUS_OPEN, workflow_type=None, task_list=None, child_policy=None, close_status=None, execution_timeout=None, input=None, tag_list=None, decision_tasks_timeout=None, close_timestamp=None, start_timestamp=None, cancel_requested=None, latest_execution_context=None, latest_activity_task_timestamp=None, open_counts=None, parent=None, *args, **kwargs): Domain.check(domain) self.domain = domain self.workflow_id = workflow_id self.run_id = run_id self.status = status self.workflow_type = workflow_type self.task_list = task_list self.child_policy = child_policy self.close_status = close_status self.execution_timeout = execution_timeout self.input = input self.tag_list = tag_list or [] self.decision_tasks_timeout = decision_tasks_timeout self.close_timestamp = close_timestamp self.start_timestamp = start_timestamp self.cancel_requested = cancel_requested self.latest_execution_context = latest_execution_context self.latest_activity_task_timestamp = latest_activity_task_timestamp self.open_counts = open_counts or {} # so we can query keys in any case self.parent = parent or {} # so we can query keys in any case # immutable decorator rebinds class name, # so have to use generice self.__class__ super(self.__class__, self).__init__(*args, **kwargs) def _diff(self): """Checks for differences between WorkflowExecution instance and upstream version :returns: A swf.models.base.ModelDiff describing differences :rtype: ModelDiff """ try: description = self.connection.describe_workflow_execution( self.domain.name, self.run_id, self.workflow_id ) except SWFResponseError as e: if e.error_code == 'UnknownResourceFault': raise DoesNotExistError("Remote Domain does not exist") raise ResponseError(e.body['message']) execution_info = description['executionInfo'] execution_config = description['executionConfiguration'] return ModelDiff( ('workflow_id', self.workflow_id, execution_info['execution']['workflowId']), ('run_id', self.run_id, execution_info['execution']['runId']), ('status', self.status, execution_info['executionStatus']), ('task_list', self.task_list, execution_config['taskList']['name']), ('child_policy', self.child_policy, execution_config['childPolicy']), ('execution_timeout', self.execution_timeout, execution_config['executionStartToCloseTimeout']), ('tag_list', self.tag_list, execution_info.get('tagList')), ('decision_tasks_timeout', self.decision_tasks_timeout, execution_config['taskStartToCloseTimeout']), ) @property @exceptions.translate(SWFResponseError, to=ResponseError) @exceptions.is_not(WorkflowExecutionDoesNotExist) @exceptions.catch(SWFResponseError, raises(WorkflowExecutionDoesNotExist, when=exceptions.is_unknown('WorkflowExecution'), extract=exceptions.extract_resource)) def exists(self): """Checks if the WorkflowExecution exists amazon-side :rtype: bool """ self.connection.describe_workflow_execution( self.domain.name, self.run_id, self.workflow_id ) return True def upstream(self): from swf.querysets.workflow import WorkflowExecutionQuerySet qs = WorkflowExecutionQuerySet(self.domain) return qs.get(self.workflow_id, self.run_id) def history(self, *args, **kwargs): """Returns workflow execution history report :returns: The workflow execution complete events history :rtype: swf.models.event.History """ domain = kwargs.pop('domain', self.domain) if not isinstance(domain, compat.basestring): domain = domain.name response = self.connection.get_workflow_execution_history( domain, self.run_id, self.workflow_id, **kwargs ) events = response['events'] next_page = response.get('nextPageToken') while next_page is not None: response = self.connection.get_workflow_execution_history( domain, self.run_id, self.workflow_id, next_page_token=next_page, **kwargs ) events.extend(response['events']) next_page = response.get('nextPageToken') return History.from_event_list(events) @exceptions.translate(SWFResponseError, to=ResponseError) @exceptions.catch(SWFResponseError, raises(WorkflowExecutionDoesNotExist, when=exceptions.is_unknown('WorkflowExecution'), extract=exceptions.extract_resource)) def signal(self, signal_name, input=None, *args, **kwargs): """Records a signal event in the workflow execution history and creates a decision task. The signal event is recorded with the specified user defined ``signal_name`` and ``input`` (if provided). :param signal_name: The name of the signal. This name must be meaningful to the target workflow. :type signal_name: str :param input: Data to attach to the WorkflowExecutionSignaled event in the target workflow execution’s history. :type input: dict """ if input is None: input = {} self.connection.signal_workflow_execution( self.domain.name, signal_name, self.workflow_id, input=json_dumps(input), run_id=self.run_id) @exceptions.translate(SWFResponseError, to=ResponseError) @exceptions.catch(SWFResponseError, raises(WorkflowExecutionDoesNotExist, when=exceptions.is_unknown('domain'), extract=exceptions.extract_resource)) def request_cancel(self, *args, **kwargs): """Requests the workflow execution cancel""" self.connection.request_cancel_workflow_execution( self.domain.name, self.workflow_id, run_id=self.run_id) @exceptions.translate(SWFResponseError, to=ResponseError) @exceptions.catch(SWFResponseError, raises(WorkflowExecutionDoesNotExist, when=exceptions.is_unknown('domain'), extract=exceptions.extract_resource)) def terminate(self, *args, **kwargs): """Terminates the workflow execution""" self.connection.terminate_workflow_execution( self.domain.name, self.workflow_id, run_id=self.run_id )
class ActivityType(BaseModel): """ActivityType wrapper :param domain: Domain the workflow type should be registered in :type domain: swf.models.Domain :param name: name of the ActivityType :type name: str :param version: version of the ActivityType :type version: str :param status: ActivityType status :type status: swf.constants.{REGISTERED, DEPRECATED} :param description: ActivityType description :type description: str | None :param creation_date: creation date of the current ActivityType :type creation_date: float (timestamp) :param deprecation_date: deprecation date of ActivityType :type deprecation_date: float (timestamp) :param task_list: specifies the default task list to use for scheduling tasks of this activity type. :type task_list: str :param task_heartbeat_timeout: default maximum time before which a worker processing a task of this type must report progress by calling RecordActivityTaskHeartbeat. :type task_heartbeat_timeout: int :param task_schedule_to_close_timeout: default maximum duration for a task of this activity type. :type task_schedule_to_close_timeout: int :param task_schedule_to_start_timeout: default maximum duration that a task of this activity type can wait before being assigned to a worker. :type task_schedule_to_start_timeout: int :param task_start_to_close_timeout: default maximum duration that a worker can take to process tasks of this activity type. :type task_start_to_close_timeout: int """ kind = 'type' __slots__ = [ 'domain', 'name', 'version', 'status', 'description', 'creation_date', 'deprecation_date', 'task_list', 'task_heartbeat_timeout', 'task_schedule_to_close_timeout', 'task_schedule_to_start_timeout', 'task_start_to_close_timeout', ] def __init__(self, domain, name, version, status=REGISTERED, description=None, creation_date=0.0, deprecation_date=0.0, task_list=None, task_heartbeat_timeout=0, task_schedule_to_close_timeout=0, task_schedule_to_start_timeout=0, task_start_to_close_timeout=0, *args, **kwargs): self.domain = domain self.name = name self.version = version self.status = status self.description = description self.creation_date = creation_date self.deprecation_date = deprecation_date self.task_list = task_list self.task_heartbeat_timeout = task_heartbeat_timeout self.task_schedule_to_close_timeout = task_schedule_to_close_timeout self.task_schedule_to_start_timeout = task_schedule_to_start_timeout self.task_start_to_close_timeout = task_start_to_close_timeout # immutable decorator rebinds class name, # so have to use generic self.__class__ super(self.__class__, self).__init__(*args, **kwargs) def _diff(self): """Checks for differences between ActivityType instance and upstream version :returns: A list (swf.models.base.ModelDiff) namedtuple describing differences :rtype: ModelDiff """ try: description = self.connection.describe_activity_type( self.domain.name, self.name, self.version) except SWFResponseError as err: if err.error_code == 'UnknownResourceFault': raise DoesNotExistError("Remote ActivityType does not exist") raise ResponseError(err.body['message']) info = description['typeInfo'] config = description['configuration'] return ModelDiff( ('name', self.name, info['activityType']['name']), ('version', self.version, info['activityType']['version']), ('status', self.status, info['status']), ('description', self.description, info['description']), ('creation_date', self.creation_date, info['creationDate']), ('deprecation_date', self.deprecation_date, info['deprecationDate']), ('task_list', self.task_list, config['defaultTaskList']['name']), ('task_heartbeat_timeout', self.task_heartbeat_timeout, config['defaultTaskHeartbeatTimeout']), ('task_schedule_to_close_timeout', self.task_schedule_to_close_timeout, config['defaultTaskScheduleToCloseTimeout']), ('task_schedule_to_start_timeout', self.task_schedule_to_start_timeout, config['defaultTaskScheduleToStartTimeout']), ('task_start_to_close_timeout', self.task_start_to_close_timeout, config['defaultTaskStartToCloseTimeout']), ) @property @exceptions.is_not(ActivityTypeDoesNotExist) @exceptions.catch(SWFResponseError, raises(ActivityTypeDoesNotExist, when=exceptions.is_unknown('ActivityType'), extract=exceptions.extract_resource)) def exists(self): """Checks if the ActivityType exists amazon-side :rtype: bool """ self.connection.describe_activity_type(self.domain.name, self.name, self.version) return True def save(self): """Creates the activity type amazon side""" try: self.connection.register_activity_type( self.domain.name, self.name, self.version, task_list=str(self.task_list), default_task_heartbeat_timeout=str( self.task_heartbeat_timeout), default_task_schedule_to_close_timeout=str( self.task_schedule_to_close_timeout), default_task_schedule_to_start_timeout=str( self.task_schedule_to_start_timeout), default_task_start_to_close_timeout=str( self.task_start_to_close_timeout), description=self.description) except SWFTypeAlreadyExistsError as err: raise AlreadyExistsError('{} already exists'.format(self)) except SWFResponseError as err: if err.error_code in [ 'UnknownResourceFault', 'TypeDeprecatedFault' ]: raise DoesNotExistError(err.body['message']) raise @exceptions.catch(SWFResponseError, raises(ActivityTypeDoesNotExist, when=exceptions.is_unknown('ActivityType'), extract=exceptions.extract_resource)) def delete(self): """Deprecates the domain amazon side""" self.connection.deprecate_activity_type(self.domain.name, self.name, self.version) def upstream(self): from swf.querysets.activity import ActivityTypeQuerySet qs = ActivityTypeQuerySet(self.domain) return qs.get(self.name, self.version) def __repr__(self): return '<{} domain={} name={} version={} status={}>'.format( self.__class__.__name__, self.domain.name, self.name, self.version, self.status)
class Domain(BaseModel): """Simple Workflow Domain wrapper :param name: Name of the domain to register (unique) :type name: string :param retention_period: Domain's workflow executions records retention in days :type retention_period: Integer :param status: Specifies the registration status of the workflow types to list. Valid values are: * swf.constants.REGISTERED * swf.constants.DEPRECATED :type status: string :param description: Textual description of the domain :type description: string """ __slots__ = [ 'name', 'status', 'description', 'retention_period', ] def __init__(self, name, status=REGISTERED, description=None, retention_period=30, *args, **kwargs): self.name = name self.status = status self.description = description self.retention_period = retention_period # immutable decorator rebinds class name, # so have to use generice self.__class__ super(self.__class__, self).__init__(*args, **kwargs) @classmethod def check(cls, domain): """ Ensures *domain* is a :class:`Domain` object and has a *name* attribute as a string. """ if (not isinstance(domain, cls) or not hasattr(domain, 'name') or not isinstance(domain.name, basestring)): raise TypeError('invalid type {} for domain'.format(type(domain))) def _diff(self): """Checks for differences between Domain instance and upstream version :returns: A list of swf.models.base.ModelDiff namedtuple describing differences :rtype: list """ try: description = self.connection.describe_domain(self.name) except SWFResponseError as e: if e.error_code == 'UnknownResourceFault': raise DoesNotExistError("Remote Domain does not exist") raise ResponseError(e.body['message']) domain_info = description['domainInfo'] domain_config = description['configuration'] return ModelDiff( ('name', self.name, domain_info['name']), ('status', self.status, domain_info['status']), ('description', self.description, domain_info.get('description')), ('retention_period', self.retention_period, domain_config['workflowExecutionRetentionPeriodInDays']), ) @property @exceptions.translate(SWFResponseError, to=ResponseError) @exceptions.is_not(DomainDoesNotExist) @exceptions.catch(SWFResponseError, raises(DomainDoesNotExist, when=exceptions.is_unknown('domain'), extract=exceptions.extract_resource)) def exists(self): """Checks if the Domain exists amazon-side :rtype: bool """ self.connection.describe_domain(self.name) return True def save(self): """Creates the domain amazon side""" try: self.connection.register_domain(self.name, str(self.retention_period), self.description) except SWFDomainAlreadyExistsError: raise AlreadyExistsError("Domain %s already exists amazon-side" % self.name) @exceptions.translate(SWFResponseError, to=ResponseError) @exceptions.catch(SWFResponseError, raises(DomainDoesNotExist, when=exceptions.is_unknown('domain'), extract=exceptions.extract_resource)) def delete(self): """Deprecates the domain amazon side""" self.connection.deprecate_domain(self.name) def upstream(self): from swf.querysets.domain import DomainQuerySet qs = DomainQuerySet() return qs.get(self.name) def workflows(self, status=REGISTERED): """Lists the current domain's workflow types :param status: Specifies the registration status of the workflow types to list. Valid values are: * swf.constants.REGISTERED * swf.constants.DEPRECATED :type status: string """ from swf.querysets.workflow import WorkflowTypeQuerySet qs = WorkflowTypeQuerySet(self) return qs.all(registration_status=status) def activities(self, status=REGISTERED): """Lists the current domain's activity types :param status: Specifies the registration status of the workflow types to list. Valid values are: * swf.constants.REGISTERED * swf.constants.DEPRECATED :type status: string """ from swf.querysets.activity import ActivityTypeQuerySet qs = ActivityTypeQuerySet(self) return qs.all(registration_status=status) @property def executions(self): pass def __repr__(self): return '<{} name={} status={}>'.format(self.__class__.__name__, self.name, self.status)
task_list=str(self.task_list), default_task_heartbeat_timeout=str(self.task_heartbeat_timeout), default_task_schedule_to_close_timeout=str(self.task_schedule_to_close_timeout), default_task_schedule_to_start_timeout=str(self.task_schedule_to_start_timeout), default_task_start_to_close_timeout=str(self.task_start_to_close_timeout), description=self.description) except SWFTypeAlreadyExistsError, err: raise AlreadyExistsError('{} already exists'.format(self)) except SWFResponseError as err: if err.error_code in ['UnknownResourceFault', 'TypeDeprecatedFault']: raise DoesNotExistError(err.body['message']) raise @exceptions.when(SWFResponseError, raises(ActivityTypeDoesNotExist, when=exceptions.is_unknown('ActivityType'), extract=exceptions.extract_resource)) def delete(self): """Deprecates the domain amazon side""" self.connection.deprecate_activity_type( self.domain.name, self.name, self.version ) def upstream(self): from swf.querysets.activity import ActivityTypeQuerySet qs = ActivityTypeQuerySet(self.domain) return qs.get(self.name, self.version) def __repr__(self):