def __init__(self, name, definition, role, tags=[], execution_input=None, timeout_seconds=None, comment=None, version=None, state_machine_arn=None, format_json=True, client=None): """ Args: name (str): The name of the workflow. A name must not contain: * whitespace * brackets < > { } [ ] * wildcard characters ? * * special characters " # % \\ ^ | ~ ` $ & , ; : / * control characters (U+0000-001F , U+007F-009F ) definition (str): The `Amazon States Language <https://states-language.net/spec.html>`_ definition of the workflow. role (str): The Amazon Resource Name (ARN) of the IAM role to use for creating, managing, and running the workflow. tags (list): Tags to be added when creating a workflow. Tags are key-value pairs that can be associated with Step Functions workflows and activities. (default: []) execution_input (ExecutionInput, optional): Placeholder collection that defines the placeholder variables for the workflow execution. \ This is also used to validate inputs provided when executing the workflow. (default: None) timeout_seconds (int, optional): The maximum number of seconds an execution of the workflow can run. If it runs longer than the specified time, the workflow run fails with a `States.Timeout` Error Name. (default: None) comment (str, optional): A human-readable description of the workflow. (default: None) version (str, optional): The version of the Amazon States Language used in the workflow. (default: None) state_machine_arn (str, optional): The Amazon Resource Name (ARN) of the workflow. (default: None) format_json (bool, optional): Boolean flag set to `True` if workflow definition and execution inputs should be prettified for this workflow. `False`, otherwise. (default: True) client (SFN.Client, optional): boto3 client to use for creating, managing, and running the workflow on Step Functions. If not provided, a default boto3 client for Step Functions will be automatically created and used. (default: None) """ self.timeout_seconds = timeout_seconds self.comment = comment self.version = version if isinstance(definition, Graph): self.definition = definition else: self.definition = Graph(definition, timeout_seconds=self.timeout_seconds, comment=self.comment, version=self.version) self.name = name self.role = role self.tags = tags self.workflow_input = execution_input if client: self.client = client else: self.client = boto3.client('stepfunctions') append_user_agent_to_client(self.client) self.format_json = format_json self.state_machine_arn = state_machine_arn
def test_step_input_order_validation(): workflow_input = ExecutionInput() test_step_01 = Pass(state_id='StateOne', parameters={ 'ParamA': workflow_input['Key02']['Key03'], 'ParamD': workflow_input['Key01']['Key03'], }) test_step_02 = Pass(state_id='StateTwo', parameters={ 'ParamC': workflow_input["Key05"], "ParamB": "SampleValueB", "ParamE": test_step_01.output()["Response"]["Key04"] }) test_step_03 = Pass(state_id='StateThree', parameters={ 'ParamG': "SampleValueG", "ParamF": workflow_input["Key06"], "ParamH": "SampleValueH" }) workflow_definition = Chain([test_step_01, test_step_03, test_step_02]) with pytest.raises(ValueError): result = Graph(workflow_definition).to_dict()
def test_choice_state_with_placeholders(): first_state = Task( 'FirstState', resource='arn:aws:lambda:us-east-1:1234567890:function:FirstState') retry = Chain([Pass('Retry'), Pass('Cleanup'), first_state]) choice_state = Choice('Is Completed?') choice_state.add_choice( ChoiceRule.BooleanEquals(choice_state.output()["Completed"], True), Succeed('Complete')) choice_state.add_choice( ChoiceRule.BooleanEquals(choice_state.output()["Completed"], False), retry) first_state.next(choice_state) result = Graph(first_state).to_dict() expected_repr = { "StartAt": "FirstState", "States": { "FirstState": { "Resource": "arn:aws:lambda:us-east-1:1234567890:function:FirstState", "Type": "Task", "Next": "Is Completed?" }, "Is Completed?": { "Type": "Choice", "Choices": [{ "Variable": "$['Completed']", "BooleanEquals": True, "Next": "Complete" }, { "Variable": "$['Completed']", "BooleanEquals": False, "Next": "Retry" }] }, "Complete": { "Type": "Succeed" }, "Retry": { "Type": "Pass", "Next": "Cleanup" }, "Cleanup": { "Type": "Pass", "Next": "FirstState" } } } assert result == expected_repr
def test_wait_example(): chain = Chain() chain.append( Task('FirstState', resource='arn:aws:lambda:us-east-1:1234567890:function:StartState' )) chain.append(Wait('wait_using_seconds', seconds=10)) chain.append(Wait('wait_using_timestamp', timestamp='2015-09-04T01:59:00Z')) chain.append( Wait('wait_using_timestamp_path', timestamp_path='$.expirydate')) chain.append( Wait('wait_using_seconds_path', seconds_path='$.expiryseconds')) chain.append( Task( 'FinalState', resource='arn:aws:lambda:us-east-1:1234567890:function:EndLambda')) result = Graph(chain).to_dict() assert result == { 'StartAt': 'FirstState', 'States': { 'FirstState': { 'Type': 'Task', 'Resource': 'arn:aws:lambda:us-east-1:1234567890:function:StartState', 'Next': 'wait_using_seconds' }, 'wait_using_seconds': { 'Type': 'Wait', 'Seconds': 10, 'Next': 'wait_using_timestamp' }, 'wait_using_timestamp': { 'Type': 'Wait', 'Timestamp': '2015-09-04T01:59:00Z', 'Next': 'wait_using_timestamp_path' }, 'wait_using_timestamp_path': { 'Type': 'Wait', 'TimestampPath': '$.expirydate', 'Next': 'wait_using_seconds_path' }, 'wait_using_seconds_path': { 'Type': 'Wait', 'SecondsPath': '$.expiryseconds', 'Next': 'FinalState', }, 'FinalState': { 'Type': 'Task', 'Resource': 'arn:aws:lambda:us-east-1:1234567890:function:EndLambda', 'End': True } } }
def update(self, definition=None, role=None): """ Updates an existing state machine by modifying its definition and/or role. Executions started immediately after calling this method may use the previous definition and role. Args: definition (State or Chain, optional): The `Amazon States Language <https://states-language.net/spec.html>`_ definition to update the workflow with. (default: None) role (str, optional): The Amazon Resource Name (ARN) of the IAM role to use for creating, managing, and running the workflow. (default: None) Returns: str: The state machine definition and/or role updated. If the update fails, None will be returned. """ if definition is None and role is None: raise MissingRequiredParameter( "A new definition and/or role must be provided to update an existing workflow." ) if self.state_machine_arn is None: raise WorkflowNotFound( "Local workflow instance does not point to an existing workflow on AWS StepFunctions. Please consider using Workflow.create(...) to create a new workflow, or Workflow.attach(...) to attach the instance to an existing workflow on AWS Step Functions." ) if definition: if isinstance(definition, Graph): self.definition = definition else: self.definition = Graph(definition, timeout_seconds=self.timeout_seconds, comment=self.comment, version=self.version) if role: self.role = role response = self.client.update_state_machine( stateMachineArn=self.state_machine_arn, definition=self.definition.to_json(pretty=self.format_json), roleArn=self.role) logger.info( "Workflow updated successfully on AWS Step Functions. All execute() calls will use the updated definition and role within a few seconds. " ) return self.state_machine_arn
def test_map_state_with_placeholders(): workflow_input = ExecutionInput() step_result = StepResult() map_state = Map(state_id="MapState01", result_selector={ "foo": step_result["foo"], "bar": step_result["bar1"]["bar2"] }) iterator_state = Pass("TrainIterator", parameters={ "ParamA": map_state.output()["X"]["Y"], "ParamB": workflow_input["Key01"]["Key02"]["Key03"] }) map_state.attach_iterator(iterator_state) workflow_definition = Chain([map_state]) expected_repr = { "StartAt": "MapState01", "States": { "MapState01": { "Type": "Map", "ResultSelector": { "foo.$": "$['foo']", "bar.$": "$['bar1']['bar2']" }, "End": True, "Iterator": { "StartAt": "TrainIterator", "States": { "TrainIterator": { "Parameters": { "ParamA.$": "$['X']['Y']", "ParamB.$": "$$.Execution.Input['Key01']['Key02']['Key03']" }, "Type": "Pass", "End": True } } } } } } result = Graph(workflow_definition).to_dict() assert result == expected_repr
def test_wait_loop(): first_state = Task( 'FirstState', resource='arn:aws:lambda:us-east-1:1234567890:function:FirstState') retry = Chain([Pass('Retry'), Pass('Cleanup'), first_state]) choice_state = Choice('Is Completed?') choice_state.add_choice(ChoiceRule.BooleanEquals('$.Completed', True), Succeed('Complete')) choice_state.add_choice(ChoiceRule.BooleanEquals('$.Completed', False), retry) first_state.next(choice_state) result = Graph(first_state).to_dict() assert result == { 'StartAt': 'FirstState', 'States': { 'FirstState': { 'Type': 'Task', 'Resource': 'arn:aws:lambda:us-east-1:1234567890:function:FirstState', 'Next': 'Is Completed?' }, 'Is Completed?': { 'Type': 'Choice', 'Choices': [{ 'Variable': '$.Completed', 'BooleanEquals': True, 'Next': 'Complete' }, { 'Variable': '$.Completed', 'BooleanEquals': False, 'Next': 'Retry' }] }, 'Complete': { 'Type': 'Succeed' }, 'Retry': { 'Type': 'Pass', 'Next': 'Cleanup', }, 'Cleanup': { 'Type': 'Pass', 'Next': 'FirstState' } } }
def test_nested_parallel_example(): nested_level_1 = Parallel('NestedStateLevel1') nested_level_1.add_branch(Succeed('NestedStateLevel2')) first_state = Parallel('FirstState') first_state.add_branch(nested_level_1) result = Graph(first_state, comment='This is a test.', version='1.0', timeout_seconds=3600).to_dict() assert result == { 'StartAt': 'FirstState', 'Comment': 'This is a test.', 'Version': '1.0', 'TimeoutSeconds': 3600, 'States': { 'FirstState': { 'Type': 'Parallel', 'Branches': [{ 'StartAt': 'NestedStateLevel1', 'States': { 'NestedStateLevel1': { 'Type': 'Parallel', 'Branches': [{ 'StartAt': 'NestedStateLevel2', 'States': { 'NestedStateLevel2': { 'Type': 'Succeed' } } }], 'End': True } } }], 'End': True } } }
def test_map_state_with_placeholders(): workflow_input = ExecutionInput() map_state = Map('MapState01') iterator_state = Pass('TrainIterator', parameters={ 'ParamA': map_state.output()['X']["Y"], 'ParamB': workflow_input["Key01"]["Key02"]["Key03"] }) map_state.attach_iterator(iterator_state) workflow_definition = Chain([map_state]) expected_repr = { "StartAt": "MapState01", "States": { "MapState01": { "Type": "Map", "End": True, "Iterator": { "StartAt": "TrainIterator", "States": { "TrainIterator": { "Parameters": { "ParamA.$": "$['X']['Y']", "ParamB.$": "$$.Execution.Input['Key01']['Key02']['Key03']" }, "Type": "Pass", "End": True } } } } } } result = Graph(workflow_definition).to_dict() assert result == expected_repr
def test_workflow_with_placeholders(): workflow_input = ExecutionInput() test_step_01 = Pass(state_id='StateOne', parameters={ 'ParamA': workflow_input['Key02']['Key03'], 'ParamD': workflow_input['Key01']['Key03'], }) test_step_02 = Pass(state_id='StateTwo', parameters={ 'ParamC': workflow_input["Key05"], "ParamB": "SampleValueB", "ParamE": test_step_01.output()["Response"]["Key04"] }) test_step_03 = Pass(state_id='StateThree', parameters={ 'ParamG': "SampleValueG", "ParamF": workflow_input["Key06"], "ParamH": "SampleValueH" }) workflow_definition = Chain([test_step_01, test_step_02, test_step_03]) result = Graph(workflow_definition).to_dict() expected_workflow_repr = { "StartAt": "StateOne", "States": { "StateOne": { "Type": "Pass", "Parameters": { "ParamA.$": "$$.Execution.Input['Key02']['Key03']", "ParamD.$": "$$.Execution.Input['Key01']['Key03']" }, "Next": "StateTwo" }, "StateTwo": { "Type": "Pass", "Parameters": { "ParamC.$": "$$.Execution.Input['Key05']", "ParamB": "SampleValueB", "ParamE.$": "$['Response']['Key04']" }, "Next": "StateThree" }, "StateThree": { "Type": "Pass", "Parameters": { "ParamG": "SampleValueG", "ParamF.$": "$$.Execution.Input['Key06']", "ParamH": "SampleValueH" }, "End": True } } } assert result == expected_workflow_repr
def test_parallel_state_with_placeholders(): workflow_input = ExecutionInput() parallel_state = Parallel('ParallelState01') branch_A = Pass('Branch_A', parameters={ 'ParamA': parallel_state.output()['A']["B"], 'ParamB': workflow_input["Key01"] }) branch_B = Pass('Branch_B', parameters={ 'ParamA': "TestValue", 'ParamB': parallel_state.output()["Response"]["Key"]["State"] }) branch_C = Pass('Branch_C', parameters={ 'ParamA': parallel_state.output()['A']["B"].get("C", float), 'ParamB': "HelloWorld" }) parallel_state.add_branch(branch_A) parallel_state.add_branch(branch_B) parallel_state.add_branch(branch_C) workflow_definition = Chain([parallel_state]) result = Graph(workflow_definition).to_dict() expected_repr = { "StartAt": "ParallelState01", "States": { "ParallelState01": { "Type": "Parallel", "End": True, "Branches": [{ "StartAt": "Branch_A", "States": { "Branch_A": { "Parameters": { "ParamA.$": "$['A']['B']", "ParamB.$": "$$.Execution.Input['Key01']" }, "Type": "Pass", "End": True } } }, { "StartAt": "Branch_B", "States": { "Branch_B": { "Parameters": { "ParamA": "TestValue", "ParamB.$": "$['Response']['Key']['State']" }, "Type": "Pass", "End": True } } }, { "StartAt": "Branch_C", "States": { "Branch_C": { "Parameters": { "ParamA.$": "$['A']['B']['C']", "ParamB": "HelloWorld" }, "Type": "Pass", "End": True } } }] } } } assert result == expected_repr
class Workflow(object): """ Class for creating and managing a workflow. """ @classmethod def list_workflows(cls, max_items=100, client=None, html=False): """ Lists all the workflows in the account. Args: max_items (int, optional): The maximum number of items to be returned. (default: 100) client (SFN.Client, optional): boto3 client to use for the query. If not provided, a default boto3 client for Step Functions will be automatically created and used. (default: None) html (bool, optional): Renders the list as an HTML table (If running in an IPython environment). If the parameter is not provided, or set to False, a Python list is returned. (default: False) Returns: list: The list of workflows. Refer to :meth:`.SFN.Client.list_state_machines()` for the response structure. """ if client is None: logger.debug( "The argument 'client' is not provided. Creating a new boto3 client instance with default settings." ) client = boto3.client('stepfunctions') logger.debug("Retrieving list of workflows from AWS Step Functions.") paginator = client.get_paginator('list_state_machines') params = { 'PaginationConfig': { 'MaxItems': max_items, 'PageSize': 1000 } } response_iterator = paginator.paginate(**params) workflows = [] for page in response_iterator: for workflow in page['stateMachines']: workflows.append(workflow) workflows_list = WorkflowList(workflows) if html: return HTML(workflows_list.to_html()) else: return workflows_list @classmethod def attach(cls, state_machine_arn, client=None): """ Factory method to create an instance attached to an exisiting workflow in Step Functions. Arguments: state_machine_arn (str): The Amazon Resource Name (ARN) of the existing workflow. client (SFN.Client, optional): boto3 client to use for attaching the existing workflow in Step Functions to the Workflow object. If not provided, a default boto3 client for Step Functions will be automatically created and used. (default: None) Returns: Workflow: Workflow object attached to the existing workflow in Step Functions. """ if client is None: logger.debug( "The argument 'client' is not provided. Creating a new boto3 client instance with default settings." ) client = boto3.client('stepfunctions') response = client.describe_state_machine( stateMachineArn=state_machine_arn) return Workflow(name=response['name'], definition=FrozenGraph.from_json( response['definition']), role=response['roleArn'], state_machine_arn=response['stateMachineArn'], client=client) def __init__(self, name, definition, role, tags=[], execution_input=None, timeout_seconds=None, comment=None, version=None, state_machine_arn=None, format_json=True, client=None): """ Args: name (str): The name of the workflow. A name must not contain: * whitespace * brackets < > { } [ ] * wildcard characters ? * * special characters " # % \\ ^ | ~ ` $ & , ; : / * control characters (U+0000-001F , U+007F-009F ) definition (str): The `Amazon States Language <https://states-language.net/spec.html>`_ definition of the workflow. role (str): The Amazon Resource Name (ARN) of the IAM role to use for creating, managing, and running the workflow. tags (list): Tags to be added when creating a workflow. Tags are key-value pairs that can be associated with Step Functions workflows and activities. (default: []) execution_input (ExecutionInput, optional): Placeholder collection that defines the placeholder variables for the workflow execution. \ This is also used to validate inputs provided when executing the workflow. (default: None) timeout_seconds (int, optional): The maximum number of seconds an execution of the workflow can run. If it runs longer than the specified time, the workflow run fails with a `States.Timeout` Error Name. (default: None) comment (str, optional): A human-readable description of the workflow. (default: None) version (str, optional): The version of the Amazon States Language used in the workflow. (default: None) state_machine_arn (str, optional): The Amazon Resource Name (ARN) of the workflow. (default: None) format_json (bool, optional): Boolean flag set to `True` if workflow definition and execution inputs should be prettified for this workflow. `False`, otherwise. (default: True) client (SFN.Client, optional): boto3 client to use for creating, managing, and running the workflow on Step Functions. If not provided, a default boto3 client for Step Functions will be automatically created and used. (default: None) """ self.timeout_seconds = timeout_seconds self.comment = comment self.version = version if isinstance(definition, Graph): self.definition = definition else: self.definition = Graph(definition, timeout_seconds=self.timeout_seconds, comment=self.comment, version=self.version) self.name = name self.role = role self.tags = tags self.workflow_input = execution_input if client: self.client = client else: self.client = boto3.client('stepfunctions') append_user_agent_to_client(self.client) self.format_json = format_json self.state_machine_arn = state_machine_arn def create(self): """ Creates the workflow on Step Functions. Returns: str: The Amazon Resource Name (ARN) of the workflow created. If the workflow already existed, the ARN of the existing workflow is returned. """ if self.state_machine_arn is not None: logger.warning( "The workflow already exists on AWS Step Functions. No action will be performed." ) return self.state_machine_arn try: self.state_machine_arn = self._create() except self.client.exceptions.StateMachineAlreadyExists as e: self.state_machine_arn = self._extract_state_machine_arn(e) logger.error( "A workflow with the same name already exists on AWS Step Functions. To update a workflow, use Workflow.update()." ) return self.state_machine_arn def _create(self): response = self.client.create_state_machine( name=self.name, definition=self.definition.to_json(pretty=self.format_json), roleArn=self.role, tags=self.tags) logger.info("Workflow created successfully on AWS Step Functions.") return response['stateMachineArn'] def update(self, definition=None, role=None): """ Updates an existing state machine by modifying its definition and/or role. Executions started immediately after calling this method may use the previous definition and role. definition (str, optional): The `Amazon States Language <https://states-language.net/spec.html>`_ definition to update the workflow with. (default: None) role (str, optional): The Amazon Resource Name (ARN) of the IAM role to use for creating, managing, and running the workflow. (default: None) Returns: str: The state machine definition and/or role updated. If the update fails, None will be returned. """ if definition is None and role is None: raise MissingRequiredParameter( "A new definition and/or role must be provided to update an existing workflow." ) if self.state_machine_arn is None: raise WorkflowNotFound( "Local workflow instance does not point to an existing workflow on AWS StepFunctions. Please consider using Workflow.create(...) to create a new workflow, or Workflow.attach(...) to attach the instance to an existing workflow on AWS Step Functions." ) if definition: if isinstance(definition, Graph): self.definition = definition else: self.definition = Graph(definition, timeout_seconds=self.timeout_seconds, comment=self.comment, version=self.version) if role: self.role = role response = self.client.update_state_machine( stateMachineArn=self.state_machine_arn, definition=self.definition.to_json(pretty=self.format_json), roleArn=self.role) logger.info( "Workflow updated successfully on AWS Step Functions. All execute() calls will use the updated definition and role within a few seconds. " ) return self.state_machine_arn def _extract_state_machine_arn(self, exception): """ Message Example: { 'Error': { 'Message': "State Machine Already Exists: 'arn:aws:states:us-east-1:1234567890:stateMachine:test'" } } """ message = exception.response['Error']['Message'] return message.split("'")[1] def execute(self, name=None, inputs=None): """ Starts a single execution of the workflow. Args: name (str, optional): The name of the workflow execution. If one is not provided, a workflow execution name will be auto-generated. (default: None) inputs (str, list or dict, optional): Input data for the workflow execution. (default: None) Returns: stepfunctions.workflow.Execution: An execution instance of the workflow. """ if self.workflow_input: validation_result = self.workflow_input.validate(inputs) if validation_result.valid is False: raise ValueError( "Expected run input with the schema: {}".format( self.workflow_input.get_schema_as_json())) if self.state_machine_arn is None: raise WorkflowNotFound( "Local workflow instance does not point to an existing workflow on AWS StepFunctions. Before executing a workflow, call Workflow.create(...) or Workflow.attach(...)." ) params = {'stateMachineArn': self.state_machine_arn} if name is not None: params['name'] = name if inputs is not None: if self.format_json: params['input'] = json.dumps(inputs, indent=4) else: params['input'] = json.dumps(inputs) response = self.client.start_execution(**params) logger.info( "Workflow execution started successfully on AWS Step Functions.") # name is None because boto3 client.start_execution only returns startDate and executionArn return Execution(workflow=self, execution_arn=response['executionArn'], start_date=response['startDate'], status=ExecutionStatus.Running, client=self.client) def list_executions(self, max_items=100, status_filter=None, html=False): """ Lists the executions for the workflow. Args: max_items (int, optional): The maximum number of items to be returned. (default: 100) status_filter (ExecutionStatus, optional): If specified, only list the executions whose current status matches the given filter. (default: None) html (bool, optional): Renders the list as an HTML table (If running in an IPython environment). If the parameter is not provided, or set to False, a Python list is returned. (default: False) Returns: list(stepfunctions.workflow.Execution): List of workflow run instances. """ if self.state_machine_arn is None: return ExecutionsList() logger.debug("Retrieving list of executions from AWS Step Functions.") paginator = self.client.get_paginator('list_executions') params = { 'stateMachineArn': self.state_machine_arn, 'PaginationConfig': { 'MaxItems': max_items, 'PageSize': 1000 } } if status_filter is not None: params['statusFilter'] = status_filter.value response_iterator = paginator.paginate(**params) runs = [ Execution(name=execution['name'], workflow=self, execution_arn=execution['executionArn'], start_date=execution['startDate'], stop_date=execution.get('stopDate', None), status=execution['status'], client=self.client) for page in response_iterator for execution in page['executions'] ] executions_list = ExecutionsList(runs) if html: return HTML(executions_list.to_html()) else: return executions_list def delete(self): """ Deletes the workflow, if it exists. """ if self.state_machine_arn is not None: self.client.delete_state_machine( stateMachineArn=self.state_machine_arn) logger.info( "Workflow has been marked for deletion. If the workflow has running executions, it will be deleted when all executions are stopped." ) def render_graph(self, portrait=False): """ Renders a visualization of the workflow graph. Args: portrait (bool, optional): Boolean flag set to `True` if the workflow graph should be rendered in portrait orientation. Set to `False`, if the graph should be rendered in landscape orientation. (default: False) """ widget = WorkflowGraphWidget(self.definition.to_json()) return widget.show(portrait=portrait) def get_cloudformation_template(self): """ Returns a CloudFormation template that contains only the StateMachine resource. To reuse the CloudFormation template in a different region, please make sure to update the region specific AWS resources (e.g: Lambda ARN, Training Image) in the StateMachine definition. """ return build_cloudformation_template(self) def __repr__(self): return '{}(name={!r}, role={!r}, state_machine_arn={!r})'.format( self.__class__.__name__, self.name, self.role, self.state_machine_arn) def _repr_html_(self): if self.state_machine_arn: return 'Workflow: <a target="_blank" href="{}">{}</a>'.format( create_sfn_workflow_url(self.state_machine_arn), self.state_machine_arn) else: return 'Workflow: Does Not Exist.'
def test_graph_from_string(): g = Graph(Chain([Pass('HelloWorld')])) g1 = FrozenGraph.from_json(g.to_json()) assert isinstance(g1, Graph) assert g.to_dict() == g1.to_dict()
def test_choice_example(): next_state = Task( 'NextState', resource='arn:aws:lambda:us-east-1:1234567890:function:NextState') choice_state = Choice('ChoiceState') choice_state.default_choice( Fail('DefaultState', error='DefaultStateError', cause='No Matches!')) choice_state.add_choice( ChoiceRule.NumericEquals(variable='$.foo', value=1), Chain([ Task('FirstMatchState', resource= 'arn:aws:lambda:us-east-1:1234567890:function:FirstMatchState' ), next_state ])) choice_state.add_choice( ChoiceRule.NumericEquals(variable='$.foo', value=2), Chain([ Task( 'SecondMatchState', resource= 'arn:aws:lambda:us-east-1:1234567890:function:SecondMatchState' ), next_state ])) chain = Chain() chain.append( Task( 'FirstState', resource='arn:aws:lambda:us-east-1:1234567890:function:StartLambda' )) chain.append(choice_state) result = Graph(chain).to_dict() assert result == { 'StartAt': 'FirstState', 'States': { 'FirstState': { 'Type': 'Task', 'Resource': 'arn:aws:lambda:us-east-1:1234567890:function:StartLambda', 'Next': 'ChoiceState' }, 'ChoiceState': { 'Type': 'Choice', 'Choices': [{ 'Variable': '$.foo', 'NumericEquals': 1, 'Next': 'FirstMatchState' }, { 'Variable': '$.foo', 'NumericEquals': 2, 'Next': 'SecondMatchState' }], 'Default': 'DefaultState' }, 'FirstMatchState': { 'Type': 'Task', 'Resource': 'arn:aws:lambda:us-east-1:1234567890:function:FirstMatchState', 'Next': 'NextState' }, 'SecondMatchState': { 'Type': 'Task', 'Resource': 'arn:aws:lambda:us-east-1:1234567890:function:SecondMatchState', 'Next': 'NextState' }, 'DefaultState': { 'Type': 'Fail', 'Error': 'DefaultStateError', 'Cause': 'No Matches!' }, 'NextState': { 'Type': 'Task', 'Resource': 'arn:aws:lambda:us-east-1:1234567890:function:NextState', 'End': True } } }
def test_parallel_state_with_placeholders(): workflow_input = ExecutionInput() step_result = StepResult() parallel_state = Parallel(state_id="ParallelState01", result_selector={ "foo": step_result["foo"], "bar": step_result["bar1"]["bar2"] }) branch_A = Pass("Branch_A", parameters={ "ParamA": parallel_state.output()["A"]["B"], "ParamB": workflow_input["Key01"] }) branch_B = Pass("Branch_B", parameters={ "ParamA": "TestValue", "ParamB": parallel_state.output()["Response"]["Key"]["State"] }) branch_C = Pass("Branch_C", parameters={ "ParamA": parallel_state.output()["A"]["B"].get("C", float), "ParamB": "HelloWorld" }) parallel_state.add_branch(branch_A) parallel_state.add_branch(branch_B) parallel_state.add_branch(branch_C) workflow_definition = Chain([parallel_state]) result = Graph(workflow_definition).to_dict() expected_repr = { "StartAt": "ParallelState01", "States": { "ParallelState01": { "Type": "Parallel", "ResultSelector": { "foo.$": "$['foo']", "bar.$": "$['bar1']['bar2']" }, "End": True, "Branches": [{ "StartAt": "Branch_A", "States": { "Branch_A": { "Parameters": { "ParamA.$": "$['A']['B']", "ParamB.$": "$$.Execution.Input['Key01']" }, "Type": "Pass", "End": True } } }, { "StartAt": "Branch_B", "States": { "Branch_B": { "Parameters": { "ParamA": "TestValue", "ParamB.$": "$['Response']['Key']['State']" }, "Type": "Pass", "End": True } } }, { "StartAt": "Branch_C", "States": { "Branch_C": { "Parameters": { "ParamA.$": "$['A']['B']['C']", "ParamB": "HelloWorld" }, "Type": "Pass", "End": True } } }] } } } assert result == expected_repr