def to_dict(self) -> Dict: """Return a serialized representation of the Wait state.""" data = {"Type": "Wait"} for keyword in self.ast_node.value.keywords: if keyword.arg == "seconds": if isinstance(keyword.value, ast.Num): data["Seconds"] = keyword.value.n else: data["SecondsPath"] = convert_input_data_ref(keyword.value) elif keyword.arg == "timestamp": if isinstance(keyword.value, ast.Str): data["Timestamp"] = keyword.value.s else: data["TimestampPath"] = convert_input_data_ref( keyword.value) else: raise UnsupportedOperation( f"Valid keyword arguments include `seconds` and `timestamp` but `{keyword.arg}` was provided", self.ast_node, ) self._set_end_or_next(data) return data
def _get_result_path(self) -> Optional[str]: """Get the ResultPath value for the map state If the result path was not explicitly provided, return None to indicate that the the result should be discarded. Returns: result path string or None """ if isinstance(self.ast_node, ast.Assign) and len(self.ast_node.targets) > 0: return convert_input_data_ref(self.ast_node.targets[0]) return None
def visit_Dict(self, node: Any) -> None: """Visit a dict""" key_nodes = [] value_nodes = [] for key_node, value_node in zip(node.keys, node.values): if isinstance(value_node, ast.Subscript): key_nodes.append(ast.Str(s=f"{key_node.s}.$", ctx=ast.Load())) value_nodes.append( ast.Str(s=convert_input_data_ref(value_node))) else: key_nodes.append(self.generic_visit(key_node)) value_nodes.append(self.generic_visit(value_node)) return ast.copy_location( ast.Dict(keys=key_nodes, values=value_nodes, ctx=ast.Load()), node)
def _get_input_path(self) -> str: """Get the InputPath value for the map state The input path should point to an object with keys: * **key** -- key pointing to the collection of state data items stored in DynamoDB. This key would have been generated in an earlier task. * **items** -- list of items that will be fanned-out. The length of the list is more important than the contents of each item because we'll use the ``key`` to fetch the actual item value from DynamoDB. Returns: input path string """ if len(self.ast_node.value.args) > 0: return convert_input_data_ref(self.ast_node.value.args[0]) return "$"
def _parse_result_path(self) -> str: """Parse the result path from the AST node. The result path is where the result value will be inserted into the data object. Returns: result path string """ if isinstance(self.ast_node, ast.Assign): result_path = convert_input_data_ref(self.ast_node.targets[0]) assert_supported_operation( re.search(INVALID_RESULT_PATH_PATTERN, result_path) is None, "Task result path is invalid. Check that it does not contain reserved" f" keys: {', '.join(RESERVED_INPUT_DATA_KEYS)}", self.ast_node, ) return result_path return "$"
def _get_result_path(self) -> Optional[str]: """Get the ResultPath value for the task state If the result path was not explicitly provided, return None to indicate that the the result should be discarded. Returns: result path string or None """ if isinstance(self.ast_node, ast.Assign) and len(self.ast_node.targets) > 0: result_path = convert_input_data_ref(self.ast_node.targets[0]) assert_supported_operation( re.search(INVALID_RESULT_PATH_PATTERN, result_path) is None, "Task result path is invalid. Check that it does not contain reserved" f" keys: {', '.join(RESERVED_INPUT_DATA_KEYS)}", self.ast_node, ) return result_path return None
def visit_Subscript(self, node: Any) -> None: """Visit a subscript""" return ast.copy_location( ast.Str(s=convert_input_data_ref(node), ctx=ast.Load()), node)
def _serialize(self, node: Any) -> Any: """Recursive function to serialize a part of the choice branch AST node. This looks at each part of the conditional statement's AST to determine the correct ASL representation. """ if isinstance(node, ast.BoolOp): # e.g. ``___ and ___`` return { OP_TO_KEY[node.op.__class__]: [ self._serialize(value) for value in node.values ] } elif isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not): # e.g. ``not bool(data["foo"])`` if isinstance(node.operand, ast.Call) and node.operand.func.id == "bool": value = { "Variable": self._serialize(node.operand), OP_TO_KEY[(ast.Eq, bool)]: True, } else: value = self._serialize(node.operand) return {"Not": value} elif isinstance(node, ast.Compare): # e.g. ``data["foo"] > 0`` assert_supported_operation( len(node.ops) == 1, "Only 1 comparison operator at a time is allowed", node, ) assert_supported_operation( len(node.comparators) == 1, "Only 1 comparator at a time is allowed", node, ) op = node.ops[0] comparator = node.comparators[0] # Determine the data type of the choice type_ = None if isinstance(node.left, ast.Call): type_ = node.left.func.id if isinstance(comparator, ast.Call): assert_supported_operation( type_ is None or (type_ is not None and comparator.func.id == type_), f"Value types must match. Found: {type_} {op.__class__}" f" {comparator.func.id}", node, ) if type_ is None: type_ = comparator.func.id elif isinstance(comparator, ast.Str): type_ = "str" elif isinstance(comparator, ast.Num): type_ = "float" elif isinstance(comparator, ast.NameConstant) and comparator.value in ( True, False, ): type_ = "bool" elif isinstance(comparator, ast.Subscript): raise UnsupportedOperation( "Input data cannot be used as the comparator (right side of operation)", comparator, ) else: raise UnsupportedOperation( "Could not determine data type for choice variable", comparator ) type_class = TYPE_TO_CLASS[type_] if isinstance(op, ast.NotEq): return { "Not": { "Variable": self._serialize(node.left), OP_TO_KEY[(ast.Eq, type_class)]: self._serialize(comparator), } } return { "Variable": self._serialize(node.left), OP_TO_KEY[(op.__class__, type_class)]: self._serialize(comparator), } elif isinstance(node, ast.Subscript): # e.g. ``data["foo"]`` return convert_input_data_ref(node) elif isinstance(node, ast.Call): # e.g. ``int(data["foo"])`` assert_supported_operation( node.func.id in TYPE_TO_CLASS, f"Function {node.func.id} is not supported. Allowed built-ins: " ", ".join(TYPE_TO_CLASS.keys()), node, ) assert_supported_operation( len(node.args) == 1, "Data type casting functions only accept 1 positional argument", node, ) if node.func.id == "bool": return { "Variable": self._serialize(node.args[0]), OP_TO_KEY[(ast.Eq, bool)]: True, } else: return self._serialize(node.args[0]) elif isinstance(node, ast.NameConstant): assert_supported_operation( node.value is not None, "The value `None` is not allowed in Choice states", node, ) return node.value elif isinstance(node, ast.Str): return node.s elif isinstance(node, ast.Num): return node.n raise UnsupportedOperation("Unsupported choice branch logic", node)