def is_optional_type(t: Any, test_type: Optional[type] = None) -> bool: """Check if `t` is optional type (Union[None, ...]). And optionally check if T is of `test_type` >>> is_optional_type(Optional[int]) True >>> is_optional_type(Union[None, int]) True >>> is_optional_type(Union[int, str, None]) True >>> is_optional_type(Optional[int], int) True >>> is_optional_type(Optional[int], str) False >>> is_optional_type(Optional[State], int) False >>> is_optional_type(Optional[State], State) True """ if get_origin(t) in union_types and None.__class__ in get_args(t): for arg in get_args(t): if arg is None.__class__: continue return not test_type or is_of_type(arg, test_type) return False
def is_of_type(t: Any, test_type: Any) -> bool: """Check if annotation type is valid for type. >>> is_of_type(list, list) True >>> is_of_type(List[int], List[int]) True >>> is_of_type(strEnum, str) True >>> is_of_type(strEnum, Enum) True >>> is_of_type(int, str) False """ if is_union_type(test_type): return any(get_origin(t) is get_origin(arg) for arg in get_args(test_type)) if ( get_origin(t) and get_origin(test_type) and get_origin(t) is get_origin(test_type) and get_args(t) == get_args(test_type) ): return True if test_type is t: # Test type is a typing type instance and matches return True # Workaround for the fact that you can't call issubclass on typing types try: return issubclass(t, test_type) except TypeError: return False
def is_list_type(t: Any, test_type: Optional[type] = None) -> bool: """Check if `t` is list type. And optionally check if the list items are of `test_type` >>> is_list_type(List[int]) True >>> is_list_type(Optional[List[int]]) True >>> is_list_type(Optional[List[int]], int) True >>> is_list_type(Optional[List[int]], str) False >>> is_list_type(Optional[int]) False >>> is_list_type(List[Tuple[int, int]]) True >>> is_list_type(List[Tuple[int, int]], int) False >>> is_list_type(List[Tuple[int, int]], Tuple[int, int]) True >>> is_list_type(List[strEnum], Enum) True >>> is_list_type(int) False >>> is_list_type(Literal[1,2,3]) False >>> is_list_type(List[Union[str, int]]) True >>> is_list_type(List[Union[str, int]], Union[str, int]) False >>> is_list_type(List[Union[str, int]], str) True >>> is_list_type(List[Union[str, int]], int) False >>> is_list_type(List[Union[str, int]], Union[int, int]) False """ if get_origin(t): if is_optional_type(t) or is_union_type(t): for arg in get_args(t): if is_list_type(arg, test_type): return True elif get_origin(t) == Literal: # type:ignore return False # Literal cannot contain lists see pep 586 elif issubclass(get_origin(t), list): # type: ignore if test_type and get_args(t): first_arg = get_args(t)[0] # To support a list with union of multiple product blocks. if is_union_type(first_arg) and get_args(first_arg) and not is_union_type(test_type): first_arg = get_args(first_arg)[0] return is_of_type(first_arg, test_type) else: return True return False
def is_union_type(t: Any, test_type: Optional[type] = None) -> bool: """Check if `t` is union type (Union[Type, AnotherType]). Optionally check if T is of `test_type` We cannot check for literal Nones. >>> is_union_type(Union[int, str]) True >>> is_union_type(Union[int, str], str) True >>> is_union_type(Union[int, str], Union[int, str]) True >>> is_union_type(Union[int, None]) True >>> is_union_type(int) False """ if get_origin(t) not in union_types: return False if not test_type: return True if is_of_type(t, test_type): return True for arg in get_args(t): result = is_of_type(arg, test_type) if result: return result return False
def get_possible_product_block_types(list_field_type: Any) -> dict: possible_product_block_types = {} if is_union_type(list_field_type): for list_item_field_type in get_args(list_field_type): if ( not isinstance(None, list_item_field_type) and list_item_field_type.name not in possible_product_block_types ): possible_product_block_types[list_item_field_type.name] = list_item_field_type else: possible_product_block_types = {list_field_type.name: list_field_type} return possible_product_block_types
def test_get_args(input_value, output_value): if input_value is None: pytest.skip('Skipping undefined hint for this python version') assert get_args(input_value) == output_value
def _analyze_arg_type(self, arg_type: Optional[type]): if arg_type is None: return origin = get_origin(arg_type) if origin: shape = None dtype = None if origin is Union: allow_types = [] for arg in get_args(arg_type): if arg is not None: allow_types.append(arg) self.shape = ArgTypeShape.UNION self.outer_type = [self._get_arg_type(t) for t in allow_types] return if inspect.isclass(origin): if issubclass(origin, List): shape = ArgTypeShape.LIST args = get_args(arg_type) if args and len(args) == 1: vt = args[0] if not inspect.isclass(vt): vt = None dtype = self._get_arg_type(vt) elif issubclass(origin, Mapping): shape = ArgTypeShape.DICT args = get_args(arg_type) if args and len(args) == 2: kt = args[0] vt = args[1] dtype = (self._get_arg_type(kt), self._get_arg_type(vt)) else: dtype = (None, None) elif issubclass(origin, Set): shape = ArgTypeShape.SET args = get_args(arg_type) if args and len(args) == 1: vt = args[0] if not inspect.isclass(vt): vt = None dtype = self._get_arg_type(vt) if shape is None: raise AssertionError( f'Unsupported generic argument type: {arg_type}') self.shape = shape self.outer_type = dtype return if isinstance(arg_type, str): if hasattr(builtins, arg_type): arg_type = getattr(builtins, arg_type) self.outer_type = arg_type if not inspect.isclass(arg_type): raise AssertionError( f'Non-generic argument type must be a class, but got: {arg_type}' ) if issubclass(arg_type, List): self.shape = ArgTypeShape.LIST self.outer_type = None elif issubclass(arg_type, Mapping): self.shape = ArgTypeShape.DICT self.outer_type = (None, None) elif issubclass(arg_type, Set): self.shape = ArgTypeShape.SET self.outer_type = None
def test_get_args(input_value, output_value): assert get_args(input_value) == output_value
def _build_arguments(func: Union[StepFunc, InputStepFunc], state: State) -> List: """Build actual arguments based on step function signature and state. What the step function requests in its function signature it what this function retrieves from the state or DB. Domain models are retrieved from the DB (after `subscription_id` lookup in the state). Everything else is retrieved from the state. For domain models only ``Optional`` and ``List`` are supported as container types. Union, Dict and others are not supported Args: func: step function to inspect for requested arguments state: workflow state Returns: List of actual positional arguments. Raises: KeyError: if requested argument is not in the state, or cannot be reconstructed as an initial domain model. """ sig = inspect.signature(func) arguments: List[Any] = [] if sig.parameters: for name, param in sig.parameters.items(): # Ignore dynamic arguments. Mostly need to deal with `const` if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD): logger.warning( "*args and **kwargs are not supported as step params") continue # If we find an argument named "state" we use the whole state as argument to # This is mainly to be backward compatible with code that needs the whole state... # TODO: Remove this construction if name == "state": arguments.append(state) continue # Workaround for the fact that you can't call issubclass on typing types try: is_subscription_model_type = issubclass( param.annotation, SubscriptionModel) except Exception: is_subscription_model_type = False if is_subscription_model_type: subscription_id = _get_sub_id(state.get(name)) if subscription_id: sub_mod = param.annotation.from_subscription( subscription_id) arguments.append(sub_mod) else: logger.error("Could not find key in state.", key=name, state=state) raise KeyError(f"Could not find key '{name}' in state.") elif is_list_type(param.annotation, SubscriptionModel): subscription_ids = map(_get_sub_id, state.get(name, [])) subscriptions = [ # Actual type is first argument from list type get_args(param.annotation) [0].from_subscription(subscription_id) for subscription_id in subscription_ids ] arguments.append(subscriptions) elif is_optional_type(param.annotation, SubscriptionModel): subscription_id = _get_sub_id(state.get(name)) if subscription_id: # Actual type is first argument from optional type sub_mod = get_args( param.annotation)[0].from_subscription(subscription_id) arguments.append(sub_mod) else: arguments.append(None) elif param.default is not inspect.Parameter.empty: arguments.append(state.get(name, param.default)) else: try: arguments.append(state[name]) except KeyError as key_error: logger.error("Could not find key in state.", key=name, state=state) raise KeyError( f"Could not find key '{name}' in state. for function {func.__module__}.{func.__qualname__}" ) from key_error return arguments