Пример #1
0
    def __init__(
        self, *args: Any, input_path: str = "$", output_path: str = "$", **kwargs: Any
    ):
        """Initialize subclasses.

        Args:
            args: Args to pass to parent classes.
            input_path: Used to select a portion of the state input. Default is
                $ (pass everything).
            output_path: Used to select a portion of the state output. Default
                is $ (pass everything).
            kwargs: Kwargs to pass to parent classes.
        """
        super().__init__(*args, **kwargs)
        self.input_path = ReferencePath(input_path)
        self.output_path = ReferencePath(output_path)
Пример #2
0
    def _execute(self, state_input: Any,
                 resource_to_mock_fn: ResourceToMockFn) -> Any:
        """Execute the Map State.

        Args:
            state_input: The input state data.
            resource_to_mock_fn: A mapping of resource URIs to mock functions to
                use if the state performs a task.

        Raises:
            StateSimulationError: Raised when items_path does not evaluate to a
                list.

        Returns:
            The output of the state by running the iterator state machine for
            all items.
        """
        items = ReferencePath(self.items_path).apply(state_input)
        self.print(
            f"Items after applying items_path of {self.items_path}: {items}",
            style=Style.DIM,
        )
        if not isinstance(items, list):
            raise StateSimulationError("items_path must yield a list")

        state_output = []
        for item in items:
            state_output.append(
                self.iterator.simulate(
                    item, resource_to_mock_fn=resource_to_mock_fn))
        return state_output
Пример #3
0
    def __init__(self, type: str, expression: Any):  # noqa: A002
        """Initialize a data-test expression.

        Args:
            type: The type of data-test expression, such as string_equals.
            expression: The expression to use when evaluating based on the type.
        """
        # NOTE: The enum is just used for validation
        self.type = DataTestExpressionType(type).value
        self.expression = ReferencePath(expression) if "path" in type else expression
Пример #4
0
    def __init__(
        self,
        *args: Any,
        seconds: Optional[int] = None,
        timestamp: Optional[datetime] = None,
        seconds_path: Optional[str] = None,
        timestamp_path: Optional[str] = None,
        **kwargs: Any,
    ):
        """Initialize a Wait State.

        Args:
            args: Args to pass to parent classes.
            seconds: The number of seconds to wait.
            timestamp: Wait until the specified time.
            seconds_path: A Reference Path to the number of seconds to wait.
            timestamp_path: A Reference Path to the timestamp to wait until.
            kwargs: Kwargs to pass to parent classes.

        Raises:
            AWSStepFuncsValueError: Raised when not exactly one is defined: seconds,
                timestamp, seconds_path, timestamp_path.
        """
        super().__init__(*args, **kwargs)

        if (sum(
                bool(variable) for variable in
            [seconds, timestamp, seconds_path, timestamp_path]) != 1):
            raise AWSStepFuncsValueError(
                "Exactly one must be defined: seconds, timestamp, seconds_path, timestamp_path"
            )

        if seconds and not (seconds > 0):
            raise AWSStepFuncsValueError("seconds must be greater than zero")

        self.seconds = seconds
        self.timestamp = timestamp
        self.seconds_path = ReferencePath(
            seconds_path) if seconds_path else None
        self.timestamp_path = ReferencePath(
            timestamp_path) if timestamp_path else None
Пример #5
0
    def __init__(self, variable: str, **data_test_expression: Any):
        """Initialize a Choice Rule.

        Args:
            variable: The Reference Path to a variable in the state input.
            data_test_expression: The data-test expression to use.

        Raises:
            AWSStepFuncsValueError: Raised when there is not exactly one data-test
                expression defined.
        """
        self.variable = ReferencePath(variable)

        if len(data_test_expression) != 1:
            raise AWSStepFuncsValueError(
                "Exactly one data-test expression must be defined"
            )

        self.data_test_expression = DataTestExpression(
            *list(data_test_expression.items())[0]
        )
Пример #6
0
    def __init__(self, *args: Any, result_path: Optional[str] = "$", **kwargs: Any):
        """Initialize subclasses.

        Args:
            args: Args to pass to parent classes.
            result_path: Specifies where (in the input) to place the "output" of
                the virtual task specified in Result. The input is further filtered
                as specified by the OutputPath field (if present) before being used
                as the state's output. Default is $ (pass only the output state).
            kwargs: Kwargs to pass to parent classes.
        """
        super().__init__(*args, **kwargs)
        self.result_path = ReferencePath(result_path) if result_path else None
Пример #7
0
    def _apply_result_selector(self, state_output: Any) -> Dict[str, Any]:
        """Apply the ResultSelector to select a portion of the state output.

        Args:
            state_output: The state output to filter.

        Returns:
            The filtered state output.
        """
        new_state_output = {}
        for key, reference_path in self.result_selector.items():  # type: ignore
            key = key[:-2]  # Strip ".$"
            if extracted := ReferencePath(reference_path).apply(state_output):
                new_state_output[key] = extracted
Пример #8
0
    def _validate_result_selector(result_selector: Dict[str, str]) -> None:
        """Validate result selector.

        Args:
            result_selector: The result selector to validate.

        Raises:
            AWSStepFuncsValueError: Raised when a key doesn't end with ".$".
            AWSStepFuncsValueError: Raised when a ReferencePath is invalid.
        """
        for key, reference_path in result_selector.items():
            if not key[-2:] == ".$":
                raise AWSStepFuncsValueError(
                    "All resource selector keys must end with .$"
                )

            # TODO: Check if ReferencePath(...) is being called twice
            ReferencePath(reference_path)
Пример #9
0
class AbstractInputPathOutputPathState(AbstractState):
    """An Amazon States Language state including InputPath and OutputPath.

    `input_path` and `output_path` let you control what is input and output from
    a state by using Reference Paths.
    """

    def __init__(
        self, *args: Any, input_path: str = "$", output_path: str = "$", **kwargs: Any
    ):
        """Initialize subclasses.

        Args:
            args: Args to pass to parent classes.
            input_path: Used to select a portion of the state input. Default is
                $ (pass everything).
            output_path: Used to select a portion of the state output. Default
                is $ (pass everything).
            kwargs: Kwargs to pass to parent classes.
        """
        super().__init__(*args, **kwargs)
        self.input_path = ReferencePath(input_path)
        self.output_path = ReferencePath(output_path)

    def simulate(self, state_input: Any, resource_to_mock_fn: ResourceToMockFn) -> Any:
        """Simulate the state including input and output processing.

        Args:
            state_input: The input to the state.
            resource_to_mock_fn: A mapping of resource URIs to mock functions to
                use if the state performs a task.

        Returns:
            The output of the state after applying any output processing.
        """
        state_input = self._apply_input_path(state_input)
        state_output = self._execute(state_input, resource_to_mock_fn) or {}
        return self._apply_output_path(state_output)

    def _apply_input_path(self, state_input: Any) -> Any:
        """Apply input path to some state input."""
        state_input = self.input_path.apply(state_input)
        self.print(
            f"State input after applying input path of {self.input_path}:",
            state_input,
            style=Style.DIM,
        )
        return state_input

    def _apply_output_path(self, state_output: Any) -> Any:
        """Apply output path to some state output."""
        state_output = self.output_path.apply(state_output)
        self.print(
            f"State output after applying output path of {self.output_path}:",
            state_output,
            style=Style.DIM,
        )
        return state_output

    def compile(self) -> Dict[str, Any]:  # noqa: A003
        """Compile the state to Amazon States Language.

        Returns:
            A dictionary representing the compiled state in Amazon States
            Language.
        """
        compiled = super().compile()
        if input_path := self.input_path:
            compiled["InputPath"] = str(input_path)
        if output_path := self.output_path:
            compiled["OutputPath"] = str(output_path)
Пример #10
0
class ChoiceRule:
    """Choice Rules are used in Choices.

    When initializing a Choice Rule, a data test expression must be provided. A
    Choice Rule evalulates to `True` or `False` based on the data-test
    expression on some data.
    """

    def __init__(self, variable: str, **data_test_expression: Any):
        """Initialize a Choice Rule.

        Args:
            variable: The Reference Path to a variable in the state input.
            data_test_expression: The data-test expression to use.

        Raises:
            AWSStepFuncsValueError: Raised when there is not exactly one data-test
                expression defined.
        """
        self.variable = ReferencePath(variable)

        if len(data_test_expression) != 1:
            raise AWSStepFuncsValueError(
                "Exactly one data-test expression must be defined"
            )

        self.data_test_expression = DataTestExpression(
            *list(data_test_expression.items())[0]
        )

    def __repr__(self) -> str:
        """Return a string representation of the Choice Rule.

        Returns:
            A string representing the Choice Rule.
        """
        return f"{self.__class__.__name__}({self.variable!r}, {self.data_test_expression.type}={self.data_test_expression.expression!r})"

    def evaluate(self, data: Any) -> bool:
        """Evaulate the Choice Rule with a data-test expression on some data.

        Args:
            data: Input data to evaluate.

        Returns:
            True or false based on the data and the Choice Rule.
        """
        variable_value = self.variable.apply(data)

        if variable_value is None:
            return False

        if "path" in self.data_test_expression.type:
            return eval(f"self._{self.data_test_expression.type}(data, variable_value)")
        else:
            return eval(f"self._{self.data_test_expression.type}(variable_value)")

    def _is_present(self, variable_value: Any) -> bool:
        return variable_value is not None

    def _string_equals(self, variable_value: str) -> bool:
        return variable_value == self.data_test_expression.expression

    def _string_equals_path(self, data: Any, variable_value: str) -> bool:
        string_equals = self.data_test_expression.expression.apply(data)  # type: ignore
        if not (isinstance(string_equals, str)):
            raise AWSStepFuncsValueError(
                "string_equals_path must evaluate to a string value"
            )
        return variable_value == string_equals

    def _string_greater_than(self, variable_value: str) -> bool:
        return variable_value > self.data_test_expression.expression  # type: ignore

    def _string_greater_than_path(self, data: Any, variable_value: str) -> bool:
        string_greater_than = self.data_test_expression.expression.apply(data)  # type: ignore
        if not (isinstance(string_greater_than, str)):  # pragma: no cover
            raise AWSStepFuncsValueError(
                "string_greater_than_path must evaluate to a string value"
            )
        return variable_value > string_greater_than

    def _string_less_than(self, variable_value: str) -> bool:
        return variable_value < self.data_test_expression.expression  # type: ignore

    def _string_less_than_path(self, data: Any, variable_value: str) -> bool:
        string_less_than = self.data_test_expression.expression.apply(data)  # type: ignore
        if not (isinstance(string_less_than, str)):  # pragma: no cover
            raise AWSStepFuncsValueError(
                "string_less_than_path must evaluate to a string value"
            )
        return variable_value < string_less_than

    def _string_greater_than_equals(self, variable_value: str) -> bool:
        return variable_value >= self.data_test_expression.expression  # type: ignore

    def _string_greater_than_equals_path(self, data: Any, variable_value: str) -> bool:
        string_greater_than_equals = self.data_test_expression.expression.apply(data)  # type: ignore
        if not (isinstance(string_greater_than_equals, str)):  # pragma: no cover
            raise AWSStepFuncsValueError(
                "string_greater_than_equals_path must evaluate to a string value"
            )
        return variable_value >= string_greater_than_equals

    def _string_less_than_equals(self, variable_value: str) -> bool:
        return variable_value <= self.data_test_expression.expression  # type: ignore

    def _string_less_than_equals_path(self, data: Any, variable_value: str) -> bool:
        string_less_than_equals = self.data_test_expression.expression.apply(data)  # type: ignore
        if not (isinstance(string_less_than_equals, str)):  # pragma: no cover
            raise AWSStepFuncsValueError(
                "string_less_than_equals_path must evaluate to a string value"
            )
        return variable_value <= string_less_than_equals

    def _numeric_greater_than_equals(self, variable_value: Union[float, int]) -> bool:
        return variable_value >= self.data_test_expression.expression  # type: ignore

    def _numeric_greater_than_path(
        self, data: Any, variable_value: Union[float, int]
    ) -> bool:
        numeric_greater_than = self.data_test_expression.expression.apply(data)  # type: ignore
        if not (
            isinstance(numeric_greater_than, int)
            or isinstance(numeric_greater_than, float)
        ):
            raise AWSStepFuncsValueError(
                "numeric_greater_than_path must evaluate to a numeric value"
            )
        return variable_value > numeric_greater_than

    def _numeric_less_than(self, variable_value: Union[float, int]) -> bool:
        return variable_value < self.data_test_expression.expression  # type: ignore
Пример #11
0
def test_valid_reference_path(reference_path):
    # Should not raise any ValueError
    ReferencePath(reference_path)
Пример #12
0
def test_apply_reference_path_no_match(sample_data):
    assert ReferencePath("$.notfound").apply(sample_data) is None
Пример #13
0
def test_reference_path_must_begin_with_dollar():
    with pytest.raises(AWSStepFuncsValueError,
                       match=re.escape('Reference Path must begin with "$"')):
        ReferencePath("foo[*].baz")
Пример #14
0
def test_reference_path_unsupported_operator():
    with pytest.raises(AWSStepFuncsValueError,
                       match='Unsupported Reference Path operator: "*"'):
        ReferencePath("$foo[*].baz")
Пример #15
0
def test_apply_reference_path(reference_path, match, sample_data):
    assert ReferencePath(reference_path).apply(sample_data) == match