def _next_and_end(cls: "StateMirror") -> "StateMirror": """Add "Next" and "End" parameters to the class. Also adds the "then()" and "end()" helper methods. """ def _validate_next(instance, attribute: attr.Attribute, value: Any): if value is not None and instance.End is not None: raise ValueError("Only one of 'Next' and 'End' is allowed") cls.Next = RHODES_ATTRIB(validator=(optional(instance_of(str)), _validate_next)) cls.__doc__ = docstring_with_param( cls, "Next", description="The state that will follow this state") def _validate_end(instance, attribute: attr.Attribute, value: Any): if value is not None and instance.Next is not None: raise ValueError("Only one of 'Next' and 'End' is allowed") if value is not None and value is not True: raise ValueError("If 'End' is set, value must be True") cls.End = RHODES_ATTRIB(validator=(optional(instance_of(bool)), _validate_end)) cls.__doc__ = docstring_with_param( cls, "End", bool, description="This state is a terminal state") def _then(instance, next_state): """Set the next state in this state machine.""" if instance.End is not None: raise InvalidDefinitionError( f"Cannot set state transition. State {instance.title!r} already has an end condition." ) if instance.Next is not None: raise InvalidDefinitionError( f"Cannot set state transition. State {instance.title!r} already has a state transition." ) instance.member_of.add_state(next_state) # TODO: set reference rather than extracting name instance.Next = next_state.title return next_state cls.then = _then def _end(instance): """Make this state a terminal state.""" if instance.Next is not None: raise InvalidDefinitionError( "Cannot set end condition." f"State {instance.title!r} already has a state transition.") instance.End = True return instance cls.end = _end return cls
def task_type(cls: "StateMirror") -> "StateMirror": """Add common parameters used by all "Task" types.""" cls = state(cls) cls = _next_and_end(cls) cls = _input_output(cls) cls = _result_path(cls) cls = _catch_retry(cls) def _validate_positive_value(instance, attribute: attr.Attribute, value: int): if value is not None and not value > 0: raise ValueError( f"{instance.__class__.__name__} parameter '{attribute.name}' value must be positive" ) # default=99999999 cls.TimeoutSeconds = RHODES_ATTRIB(validator=(optional(instance_of(int)), _validate_positive_value)) cls.__doc__ = docstring_with_param( cls, "TimeoutSeconds", int, description="Maximum time that this state is allowed to run") cls.HeartbeatSeconds = RHODES_ATTRIB(validator=(optional(instance_of(int)), _validate_positive_value)) cls.__doc__ = docstring_with_param( cls, "HeartbeatSeconds", int, description= "Maximum time allowed between heartbeat responses from state") return cls
def _input_output(cls: "StateMirror") -> "StateMirror": """Add the "InputPath" and "OutputPath" parameters to the class.""" cls.InputPath = RHODES_ATTRIB(default=JsonPath("$"), validator=optional(instance_of(JsonPath)), converter=convert_to_json_path) cls.__doc__ = docstring_with_param( cls, "InputPath", JsonPath, description= "The portion of the state input data to be used as input for the state", default=JsonPath("$"), ) cls.OutputPath = RHODES_ATTRIB(default=JsonPath("$"), validator=optional(instance_of(JsonPath)), converter=convert_to_json_path) cls.__doc__ = docstring_with_param( cls, "OutputPath", JsonPath, description= "The portion of the state input data to be passed to the next state", default=JsonPath("$"), ) return cls
def _catch_retry(cls: "StateMirror") -> "StateMirror": """Add the "Catch" and "Retry" parameters to the class.""" cls.Retry = RHODES_ATTRIB() cls.__doc__ = docstring_with_param(cls, "Retry") cls.Catch = RHODES_ATTRIB() cls.__doc__ = docstring_with_param(cls, "Catch") return cls
def state(cls: "StateMirror") -> "StateMirror": """Add common parameters used by all states.""" cls.__doc__ = docstring_with_param( cls, "title", str, description="Name of state in state machine") cls.__doc__ = docstring_with_param( cls, "Comment", str, description="Human-readable description of the state", default="") return cls
def _number(cls): cls = _single(cls) def _numeric_converter(value) -> Decimal: if isinstance(value, Decimal): return value return Decimal(str(value)) def _value_serializer(instance) -> float: return float(instance.Value) # TODO: Note that for interoperability, # numeric comparisons should not be assumed to work # with values outside the magnitude or precision # representable using the IEEE 754-2008 “binary64” data type. # In particular, # integers outside of the range [-(253)+1, (253)-1] # might fail to compare in the expected way. cls.Value = RHODES_ATTRIB(validator=instance_of(Decimal), converter=_numeric_converter) cls.__doc__ = docstring_with_param( cls, "Value", description="The value to which to compare ``Variable``") cls._serialized_value = _value_serializer return cls
def _endpoint_name(cls: StateMirror) -> StateMirror: cls.EndpointName = RHODES_ATTRIB() cls.__doc__ = docstring_with_param(cls, "EndpointName", description="The name of the endpoint.") return cls
def _endpoint_config_name(cls: StateMirror) -> StateMirror: cls.EndpointConfigName = RHODES_ATTRIB() cls.__doc__ = docstring_with_param( cls, "EndpointConfigName", description="The name of an endpoint configuration.") return cls
def _multi(cls): cls.Rules = RHODES_ATTRIB(validator=_validate_multi_subrules) cls.__doc__ = docstring_with_param( cls, "Rules", description="One or more :class:`ChoiceRule` to evaluate for this rule" ) cls.Next = RHODES_ATTRIB(validator=optional(instance_of(str))) cls.__doc__ = docstring_with_param( cls, "Next", description= "The state to which to continue if this rule evaluates as true") cls.to_dict = _multi_to_dict return cls
def _single(cls): cls.Variable = RHODES_ATTRIB(validator=instance_of(VariablePath), converter=_convert_to_variable_path) cls.__doc__ = docstring_with_param( cls, "Variable", VariablePath, description="Path to value in state input that will be evaluated") cls.Next = RHODES_ATTRIB(validator=optional(instance_of(str))) cls.__doc__ = docstring_with_param( cls, "Next", description= "The state to which to continue if this rule evaluates as true") cls.to_dict = _single_to_dict return cls
def _ddb_key(cls: StateMirror) -> StateMirror: cls.Key = RHODES_ATTRIB() cls.__doc__ = docstring_with_param( cls, "Key", description= ("A map of attribute names to AttributeValue objects, representing the primary key of the item to retrieve." ), ) return cls
def _bool(cls): cls = _single(cls) cls.Value = RHODES_ATTRIB(validator=instance_of(bool)) cls.__doc__ = docstring_with_param( cls, "Value", bool, description="The value to which to compare ``Variable``") return cls
def _parameters(cls: "StateMirror") -> "StateMirror": """Add the "Parameters" parameter to the class.""" cls.Parameters = RHODES_ATTRIB(validator=optional(instance_of(Parameters))) cls.__doc__ = docstring_with_param( cls, "Parameters", Parameters, description= "Additional parameters for Step Functions to provide to connected resource", ) return cls
def _tags(cls: StateMirror) -> StateMirror: cls.Tags = RHODES_ATTRIB() cls.__doc__ = docstring_with_param( cls, "Tags", description= ("An array of key-value pairs. " "For more information, see Using Cost Allocation Tagsin the AWS Billing and Cost Management User Guide." ), ) return cls
def _decorate(cls: StateMirror) -> StateMirror: cls = task_type(cls) cls.Pattern = RHODES_ATTRIB(default=options[0], validator=in_(options)) cls.__doc__ = docstring_with_param( cls, "Pattern", IntegrationPattern, description="Step Functions integration pattern", default=options[0]) def to_dict(instance) -> Dict: """Serialize state as a dictionary.""" for required in instance._required_fields: require_field(instance=instance, required_value=required) task = instance._build_task() return task.to_dict() cls.to_dict = to_dict def _build_task(instance) -> Task: task_fields = [field.name for field in attr.fields(Task)] field_name_blacklist = ("Pattern", ) resource_name = instance._resource_name.value + instance.Pattern.value task_kwargs = {} parameters_kwargs = {} for field in attr.fields(type(instance)): if field.name in field_name_blacklist or field.name.startswith( "_"): continue value = getattr(instance, field.name) if value is None: continue if field.name in task_fields and field.name != "Parameters": task_kwargs[field.name] = value else: parameters_kwargs[field.name] = value params = Parameters(**parameters_kwargs) return Task(Parameters=params, Resource=resource_name, **task_kwargs) cls._build_task = _build_task return cls
def _result_path(cls: "StateMirror") -> "StateMirror": """Add the "ResultPath" parameter to the class.""" cls.ResultPath = RHODES_ATTRIB(default=JsonPath("$"), validator=optional(instance_of(JsonPath)), converter=convert_to_json_path) cls.__doc__ = docstring_with_param( cls, "ResultPath", JsonPath, description= "Where in the state input data to place the results of this state", default=JsonPath("$"), ) return cls
def _timestamp(cls): cls = _single(cls) def _datetime_validator(instance, attribute, value): if value.tzinfo is None: raise ValueError( f"'{attribute.name}' must have a 'tzinfo' value set.") def _value_serializer(instance): return instance.Value.isoformat() cls.Value = RHODES_ATTRIB( validator=[instance_of(datetime), _datetime_validator]) cls.__doc__ = docstring_with_param( cls, "Value", datetime, description="The value to which to compare ``Variable``") cls._serialized_value = _value_serializer return cls
def _ddb_table_name(cls: StateMirror) -> StateMirror: cls.TableName = RHODES_ATTRIB() cls.__doc__ = docstring_with_param( cls, "TableName", description="The table to interact with") return cls
def _ddb_write_attributes(cls: StateMirror) -> StateMirror: cls.ConditionalOperator = RHODES_ATTRIB() cls.__doc__ = docstring_with_param( cls, "ConditionalOperator", description= ("This is a legacy parameter. " "Use ConditionExpression instead. " "For more information, see ConditionalOperator in the Amazon DynamoDB Developer Guide." ), ) cls.ConditionExpression = RHODES_ATTRIB() cls.__doc__ = docstring_with_param( cls, "ConditionExpression", description= "A condition that must be satisfied in order for a conditional operation to succeed.", ) cls.Expected = RHODES_ATTRIB() cls.__doc__ = docstring_with_param( cls, "Expected", description= ("This is a legacy parameter. " "Use ConditionExpression instead. " "For more information, see Expected in the Amazon DynamoDB Developer Guide." ), ) cls.ExpressionAttributeNames = RHODES_ATTRIB() cls.__doc__ = docstring_with_param( cls, "ExpressionAttributeNames", description= "One or more substitution tokens for attribute names in an expression.", ) cls.ExpressionAttributeValues = RHODES_ATTRIB() cls.__doc__ = docstring_with_param( cls, "ExpressionAttributeValues", description= "One or more values that can be substituted in an expression.") cls.ReturnConsumedCapacity = RHODES_ATTRIB() cls.__doc__ = docstring_with_param( cls, "ReturnConsumedCapacity", description= ("Determines the level of detail about provisioned throughput consumption that is returned in the response" ), ) cls.ReturnItemCollectionMetrics = RHODES_ATTRIB() cls.__doc__ = docstring_with_param( cls, "ReturnItemCollectionMetrics", description="Determines whether item collection metrics are returned.") cls.ReturnValues = RHODES_ATTRIB() cls.__doc__ = docstring_with_param( cls, "ReturnValues", description=( "Use ReturnValues if you want to get the item attributes " "as they appeared before they were updated with the request."), ) return cls