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
Esempio n. 2
0
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()
Esempio n. 3
0
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
Esempio n. 4
0
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
            }
        }
    }
Esempio n. 5
0
    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
Esempio n. 7
0
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'
            }
        }
    }
Esempio n. 8
0
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
            }
        }
    }
Esempio n. 9
0
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
Esempio n. 10
0
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
Esempio n. 11
0
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.'
Esempio n. 13
0
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()
Esempio n. 14
0
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