def test_generic_forvard_ref(self): LLT = List[List['CC']] class CC: pass self.assertEqual(typing._eval_type(LLT, globals(), locals()), List[List[CC]]) T = TypeVar('T') TTE = Tuple[T, ...] self.assertIs(typing._eval_type(TTE, globals(), locals()), Tuple[T, ...])
def resolve_annotations(raw_annotations: Dict[str, Type[Any]], module_name: Optional[str]) -> Dict[str, Type[Any]]: """ Partially taken from typing.get_type_hints. Resolve string or ForwardRef annotations into type objects if possible. """ if module_name: base_globals: Optional[Dict[str, Any]] = sys.modules[module_name].__dict__ else: base_globals = None annotations = {} for name, value in raw_annotations.items(): if isinstance(value, str): if sys.version_info >= (3, 7): value = ForwardRef(value, is_argument=False) else: value = ForwardRef(value) try: value = _eval_type(value, base_globals, None) except NameError: # this is ok, it can be fixed with update_forward_refs pass annotations[name] = value return annotations
def get_canonical_type_hints(cls: Type) -> Dict[str, Union[Type, ForwardRef]]: """Extract class type annotations returning forward references for partially undefined types. The canonicalization consists in returning either a fully-specified type annotation or a :class:`typing.ForwarRef` instance with the type annotation for not fully-specified types. Based on :func:`typing.get_type_hints` implementation. """ hints: Dict[str, Union[Type, ForwardRef]] = {} for base in reversed(cls.__mro__): base_globals = sys.modules[base.__module__].__dict__ ann = base.__dict__.get("__annotations__", {}) for name, value in ann.items(): if value is None: value = type(None) elif isinstance(value, str): value = ForwardRef(value, is_argument=False) try: value = typing._eval_type( value, base_globals, None ) # type: ignore[attr-defined] # typing._eval_type is not public except NameError as e: if "ForwardRef(" not in repr(value): raise e if not isinstance(value, ForwardRef) and "ForwardRef(" in repr(value): value = canonicalize_forward_ref(value) hints[name] = value return hints
def get_class_type_hints_strict( cls: type, globalns: Mapping[str, Any] = None, localns: Mapping[str, Any] = None) -> Dict[str, type]: if not inspect.isclass(cls): raise TypeError( "cls needs to be a class. Use typing.get_type_hints for other types" ) if globalns is None: globalns = sys.modules[cls.__module__].__dict__ ann = cls.__dict__.get("__annotations__", {}) hints = {} for name, value in ann.items(): if value is None: value = type(None) if isinstance(value, str): value = ForwardRef(value, is_argument=False) value = _eval_type(value, globalns, localns) hints[name] = value return hints
def eval_type(obj: Annotation, globals_: dict, locals_: dict, recursive_guard: frozenset = frozenset()) -> Annotation: """ Evaluate all forward references in the given type t. For use of globals_ and locals_ see the docstring for get_type_hints(). recursive_guard is used to prevent infinite recursion with recursive ForwardRef. """ return typing._eval_type(obj, globals_, locals_, recursive_guard)
def get_class_type_hints(klass: Type, localns=None) -> Dict[str, Any]: """Return type hints for a class. Adapted from `typing.get_type_hints`, adds support for PEP 585 & PEP 604""" hints = {} for base in reversed(klass.__mro__): base_globals = sys.modules[base.__module__].__dict__ base_globals['_Union'] = Union if sys.version_info < (3, 9): base_globals['_List'] = List base_globals['_Set'] = Set base_globals['_Type'] = Type base_globals['_Tuple'] = Tuple base_globals['_Dict'] = Dict ann = base.__dict__.get('__annotations__', {}) for name, value in ann.items(): if value is None: value = type(None) if isinstance(value, str): t = ast.parse(value, '<unknown>', 'eval') union_transformer = RewriteUnionTypes() t = union_transformer.visit(t) builtin_generics_transformer = RewriteBuiltinGenerics() if sys.version_info < (3, 9): t = builtin_generics_transformer.visit(t) if builtin_generics_transformer.rewritten or union_transformer.rewritten: # Note: ForwardRef raises a TypeError when given anything that isn't a string, so we need # to compile & eval the ast here code = compile(t, '<unknown>', 'eval') hints[name] = eval(code, base_globals, localns) continue else: value = ForwardRef(value, is_argument=False) value = _eval_type(value, base_globals, localns) hints[name] = value return hints
def resolve_annotations(raw_annotations: Dict[str, Type[Any]], module_name: Optional[str]) -> Dict[str, Type[Any]]: """ Partially taken from typing.get_type_hints. Resolve string or ForwardRef annotations into type objects if possible. """ base_globals: Optional[Dict[str, Any]] = None if module_name: try: module = sys.modules[module_name] except KeyError: # happens occasionally, see https://github.com/samuelcolvin/pydantic/issues/2363 pass else: base_globals = module.__dict__ annotations = {} for name, value in raw_annotations.items(): if isinstance(value, str): if sys.version_info >= (3, 7): value = ForwardRef(value, is_argument=False) else: value = ForwardRef(value) try: value = _eval_type(value, base_globals, None) except NameError: # this is ok, it can be fixed with update_forward_refs pass annotations[name] = value return annotations
def _eval_type(self, globalns, localns): new_tp = typing._eval_type(self.__type__, globalns, localns) if new_tp == self.__type__: return self return type(self)(self.__name__, self.__bases__, dict(self.__dict__), tp=self.__type__, _root=True)
def annodize(name="__main__", annotations=None): globals, locals = map(vars, map(importlib.import_module, [name] * 2)) __annotations__ = globals.get("__annotations__", {}) annotations = toolz.keymap(Forward, (annotations or {})) annotations = toolz.keymap(lambda x: typing._eval_type(x, globals, locals), annotations) for key, value in __annotations__.items(): if getattr(value, "__forward_coerce__", False) is True: value.__forward_coerce__ = key if value in annotations: new = copy.copy(annotations[value]) if getattr(new, "__forward_coerce__", False) is True: new.__forward_coerce__ = key __annotations__[key] = typing.Union[value, new] typing._eval_type(__annotations__[key], globals, locals)
def _get_typed_annotation(self, param: inspect.Parameter, globalns: Dict[str, Any]) -> Any: try: if isinstance(param.annotation, str): return _eval_type(ForwardRef(param.annotation), globalns, globalns) else: return param.annotation except Exception: return param.annotation
def _evaluate_type(typ: t.Type[t.Any], field: Field[t.Any]) -> t.Type[t.Any]: field_type = field.type if not isinstance(field_type, str): return field_type m = sys.modules[typ.__module__] # TODO: the types that defined in closure is not supported yet field_type = field.type = t._eval_type(t.ForwardRef(field_type), m.__dict__, typ.__dict__) return field_type
def evaluate_forward_reference(forward_reference, module_name=None): """ Evaluate a forward reference using the module name's dict """ if module_name: try: globalns = sys.modules[module_name].__dict__ except (KeyError, AttributeError): globalns = None else: globalns = None return _eval_type(forward_reference, globalns, None)
def evaluate(tp: t.Union[str, t.TypeVar, t.Type, t.ForwardRef], *, frame=None): if isinstance(tp, str): tp = t.ForwardRef(tp) if ti.is_typevar(tp): tp = ti.get_bound(tp) # TODO python versions return t._eval_type( tp, frame.f_globals if frame else None, frame.f_locals if frame else None, )
def _class_annotations(cls, globalns, localns): hints = {} if globalns is None: base_globals = sys.modules[cls.__module__].__dict__ else: base_globals = globalns for name, value in cls.__dict__.get("__annotations__", {}).items(): if value is None: value = type(None) if isinstance(value, str): value = ForwardRef(value, is_argument=False) hints[name] = _eval_type(value, base_globals, localns) return hints
def resolve_type( type_hint: Any, global_ns: Optional[Dict[str, Any]] = None, local_ns: Optional[Dict[str, Any]] = None, *, allow_partial: bool = False, ) -> Union[Type, ForwardRef]: """Resolve forward references in type annotations. Arguments: global_ns: globals dict used in the evaluation of the annotations. local_ns: locals dict used in the evaluation of the annotations. Keyword Arguments: allow_partial: if ``True``, the resolution is allowed to fail and a :class:`typing.ForwardRef` will be returned. Examples: >>> import typing >>> resolve_type( ... typing.Dict[typing.ForwardRef('str'), 'typing.Tuple["int", typing.ForwardRef("float")]'] ... ) typing.Dict[str, typing.Tuple[int, float]] """ actual_type = ForwardRef(type_hint) if isinstance(type_hint, str) else type_hint while "ForwardRef(" in repr(actual_type): try: if local_ns: safe_local_ns = {**local_ns} safe_local_ns.setdefault("typing", sys.modules["typing"]) safe_local_ns.setdefault("NoneType", type(None)) else: safe_local_ns = { "typing": sys.modules["typing"], "NoneType": type(None) } actual_type = typing._eval_type( # type: ignore[attr-defined] # typing._eval_type is not visible for mypy actual_type, global_ns, safe_local_ns, ) except Exception as e: if allow_partial: actual_type = canonicalize_forward_ref(actual_type) break else: raise e return actual_type
def _eval_type(self, globals, locals, **kwargs): for arg in (self.__forward_arg__ if toolz.isiterable( self.__forward_arg__) else (self.__forward_arg__, )): if isinstance(arg, str): arg = Forward(arg) if isinstance(arg, Forward): arg.__forward_coerce__ = (arg.__forward_coerce__ or self.__forward_coerce__) object = typing._eval_type(arg, globals, locals) if (object is not None) and (not isinstance(object, BaseException)): return object
def eval_type(type_, globalns=None, localns=None): """Evaluate the type. If the type is string, evaluate it with ForwardRef. Args: type_: The type to evaluate. globalns: The currently known global namespace. localns: The currently known local namespace. Returns: The evaluated type. """ globalns, localns = _get_namespace(type_, globalns, localns) if isinstance(type_, six.string_types): type_ = ForwardRef(type_) return _eval_type(type_, globalns, localns)
def get_type(self, forwarded_type): actual_type = None for base in self._obj.__mro__: base_globals = sys.modules[base.__module__].__dict__ ref = forwarded_type if isinstance(forwarded_type, str): ref = ForwardRef(forwarded_type, is_argument=False) try: actual_type = _eval_type(ref, base_globals, None) break except NameError: continue if not actual_type: raise ValidationError(f'Can not find type {forwarded_type}') return actual_type
def build_choices( self, clazz: Type, parent_name: str, parent_namespace: Optional[str], choices: List[Dict], ): existing_types = set() globalns = sys.modules[clazz.__module__].__dict__ for choice in choices: xml_type = XmlType.WILDCARD if choice.get( "wildcard") else XmlType.ELEMENT namespace = choice.get("namespace") tokens = choice.get("tokens", False) nillable = choice.get("nillable", False) format_str = choice.get("format", None) default_value = choice.get("default_factory", choice.get("default")) types = self.real_types(_eval_type(choice["type"], globalns, None)) is_class = any(is_dataclass(clazz) for clazz in types) any_type = xml_type == XmlType.ELEMENT and object in types derived = any(True for tp in types if tp in existing_types) or any_type namespaces = self.resolve_namespaces(xml_type, namespace, parent_namespace) default_namespace = self.default_namespace(namespaces) qname = build_qname(default_namespace, choice.get("name", "any")) existing_types.update(types) yield XmlVar( xml_type=xml_type, name=parent_name, qname=qname, tokens=tokens, format=format_str, derived=derived, any_type=any_type, nillable=nillable, dataclass=is_class, default=default_value, types=types, namespaces=namespaces, )
def _try_import_forward_ref(thing, bound): # pragma: no cover """ Tries to import a real bound type from ``TypeVar`` bound to a ``ForwardRef``. This function is very "magical" to say the least, please don't use it. This function fully covered, but is excluded from coverage because we can only cover each path in a separate python version. """ try: return typing._eval_type(bound, vars(sys.modules[thing.__module__]), None) except (KeyError, AttributeError, NameError): # We fallback to `ForwardRef` instance, you can register it as a type as well: # >>> from typing import ForwardRef # >>> from hypothesis import strategies as st # >>> st.register_type_strategy(ForwardRef('YourType'), your_strategy) return bound
def resolve(self) -> Union[StrawberryType, type]: annotation: object if isinstance(self.annotation, str): annotation = ForwardRef(self.annotation) else: annotation = self.annotation evaled_type = _eval_type(annotation, self.namespace, None) if evaled_type is None: raise ValueError("Annotation cannot be plain None type") if self._is_async_generator(evaled_type): evaled_type = self._strip_async_generator(evaled_type) if self._is_lazy_type(evaled_type): return evaled_type if self._is_generic(evaled_type): if any(is_type_var(type_) for type_ in evaled_type.__args__): return evaled_type return self.create_concrete_type(evaled_type) # Simply return objects that are already StrawberryTypes if self._is_strawberry_type(evaled_type): return evaled_type # Everything remaining should be a raw annotation that needs to be turned into # a StrawberryType if self._is_enum(evaled_type): return self.create_enum(evaled_type) if self._is_list(evaled_type): return self.create_list(evaled_type) elif self._is_optional(evaled_type): return self.create_optional(evaled_type) elif self._is_scalar(evaled_type): return evaled_type elif self._is_union(evaled_type): return self.create_union(evaled_type) elif is_type_var(evaled_type): return self.create_type_var(evaled_type) # TODO: Raise exception now, or later? # ... raise NotImplementedError(f"Unknown type {evaled_type}") return evaled_type
def _dp_init_subclass(sub_cls, *args, **kwargs): # Add function for datapipe instance to reinforce the type sub_cls.reinforce_type = reinforce_type # TODO: # - add global switch for type checking at compile-time # Ignore internal type class if getattr(sub_cls, '__type_class__', False): return # Check if the string type is valid if isinstance(sub_cls.type.param, ForwardRef): base_globals = sys.modules[sub_cls.__module__].__dict__ try: param = _eval_type(sub_cls.type.param, base_globals, locals()) sub_cls.type.param = param except TypeError as e: raise TypeError("{} is not supported by Python typing".format( sub_cls.type.param.__forward_arg__)) from e if '__iter__' in sub_cls.__dict__: iter_fn = sub_cls.__dict__['__iter__'] hints = get_type_hints(iter_fn) if 'return' in hints: return_hint = hints['return'] # Plain Return Hint for Python 3.6 if return_hint == Iterator: return if not (hasattr(return_hint, '__origin__') and (return_hint.__origin__ == Iterator or return_hint.__origin__ == collections.abc.Iterator)): raise TypeError( "Expected 'Iterator' as the return annotation for `__iter__` of {}" ", but found {}".format(sub_cls.__name__, _type_repr(hints['return']))) data_type = return_hint.__args__[0] if not issubtype(data_type, sub_cls.type.param): raise TypeError( "Expected return type of '__iter__' as a subtype of {}, but found {}" " for {}".format(sub_cls.type, _type_repr(data_type), sub_cls.__name__))
def eval_type(typ: Any, globalns: Dict[str, Any] = None, localns: Dict[str, Any] = None, invalid_types: Set = None, alias_types: Mapping = None) -> Type: """Convert (possible) string annotation to actual type. Examples: >>> eval_type('List[int]') == typing.List[int] """ invalid_types = invalid_types or set() alias_types = alias_types or {} if isinstance(typ, str): typ = ForwardRef(typ) if isinstance(typ, ForwardRef): # On 3.6/3.7 _eval_type crashes if str references ClassVar typ = _ForwardRef_safe_eval(typ, globalns, localns) typ = _eval_type(typ, globalns, localns) if typ in invalid_types: raise InvalidAnnotation(typ) return alias_types.get(typ, typ)
def _safe_get_type_hints( annotation: Union[Type, Callable]) -> Dict[str, Type[Any]]: raw_annotations: Dict[str, Any] = {} base_globals: Dict[str, Any] = {"typing": typing} if isinstance(annotation, type): for base in reversed(annotation.__mro__): base_globals.update(sys.modules[base.__module__].__dict__) raw_annotations.update( getattr(base, "__annotations__", None) or {}) else: raw_annotations = getattr(annotation, "__annotations__", None) or {} module_name = getattr(annotation, "__module__", None) if module_name: base_globals.update(sys.modules[module_name].__dict__) annotations = {} for name, value in raw_annotations.items(): if isinstance(value, str): value = transform_annotation(value) if not isinstance(value, ForwardRef): if sys.version_info >= (3, 9, 8) and sys.version_info[:3] != ( 3, 10, 0): value = ForwardRef( # type: ignore value, is_argument=False, is_class=inspect.isclass(annotation), ) elif sys.version_info >= (3, 7): value = ForwardRef(value, is_argument=False) else: value = ForwardRef(value) try: value = _eval_type(value, base_globals or None, None) except NameError: # this is ok, we deal with it later. pass except TypeError as e: warnings.warn(f"Couldn't evaluate type {value!r}: {e}") value = Any annotations[name] = value return annotations
def get_type_hints( obj, # type: Any globalns=None, # type: Optional[Dict[str, Any]] localns=None # type: Optional[Dict[str, Any]] ): # type: (...) -> Dict[str, Any] """Return all type hints for the function. This attempts to use typing.get_type_hints first, but if that returns None then it will attempt to reuse much of the logic from the Python 3 version of typing.get_type_hints; the Python 2 version does nothing. In addition to this logic, if no code annotations exist, it will attempt to extract comment type hints for Python 2/3 compatibility. Args: obj: The object to search for type hints. globalns: The currently known global namespace. localns: The currently known local namespace. Returns: A mapping of value names to type hints. """ hints = {} try: if not isinstance(obj, type): hints = _get_type_hints(obj, globalns, localns) or {} except TypeError: if not isinstance(obj, _STRING_TYPES): raise if not hints and not getattr(obj, '__no_type_check__', None): globalns, localns = _get_namespace(obj, globalns, localns) hints = _get_comment_type_hints(obj, globalns, localns) for name, value in six.iteritems(hints): if value is None: value = type(None) elif isinstance(value, _STRING_TYPES): value = ForwardRef(value) hints[name] = _eval_type(value, globalns, localns) return hints
def _try_import_forward_ref(thing, bound): # pragma: no cover """ Tries to import a real bound type from ``TypeVar`` bound to a ``ForwardRef``. This function is very "magical" to say the least, please don't use it. This function fully covered, but is excluded from coverage because we can only cover each path in a separate python version. """ try: return typing._eval_type(bound, vars(sys.modules[thing.__module__]), None) except (KeyError, AttributeError, NameError): if (isinstance(thing, typing.TypeVar) and getattr(thing, "__module__", None) == "typing"): raise ResolutionFailed( "It looks like you're using a TypeVar bound to a ForwardRef on Python " "3.6, which is not supported - try ugrading to Python 3.7 or later." ) from None # We fallback to `ForwardRef` instance, you can register it as a type as well: # >>> from typing import ForwardRef # >>> from hypothesis import strategies as st # >>> st.register_type_strategy(ForwardRef('YourType'), your_strategy) return bound
def _eval_type(self, globalns, localns): new_tp = typing._eval_type(self.__type__, globalns, localns) if new_tp == self.__type__: return self return type(self)(new_tp, _root=True)
def proc_forward(etype, namespace: Dict[str, Any]): """ Resolve etype to an actual type if it is a forward reference """ return _eval_type(etype, namespace, namespace) if type(etype) is ForwardRef else etype
def proc_forwards(cls, glob_ns) -> None: for f in fields(cls): f.type = _eval_type(f.type, glob_ns, glob_ns)
def _eval_type(self, globalns, localns): new_tp = typing._eval_type(self.__type__, globalns, localns) if new_tp == self.__type__: return self return type(self)(new_tp, _root=True)
def update_event(self, inp=-1): self.set_output_val( 0, typing._eval_type(self.input(0), self.input(1), self.input(2), self.input(3)))
def _eval_type(self, globals, locals, **kwargs): eval = self.__forward_evaluated__ kwargs.update(self.__forward_kwargs__) try: try: if not isinstance(self.__forward_arg__, str): self.__forward_evaluated__ = True self.__forward_value__ = self.__forward_arg__ object = typing._ForwardRef._eval_type(self, globals, locals) except TypeError as Exception: module, name = _module_and_name(self.__forward_arg__) return getattr(importlib.import_module(module), name, Exception) except BaseException as Exception: return Exception if kwargs: for key, item in kwargs.items(): if isinstance(item, str): item = Forward(item) try: kwargs[key] = typing._eval_type(item, globals, locals) except BaseException as Exception: return Exception object = functools.partial(object, **kwargs) if self.__forward_observe__ and self.__forward_coerce__: if not eval and isinstance(self.__forward_coerce__, str): module, name = _module_and_name(self.__forward_coerce__) self.__forward_display__ = object( **{ self.__forward_observe__: getattr(importlib.import_module(module), name, None) }) def change(change): nonlocal globals, locals, self if not isinstance(change, dict): change = dict(new=change) setattr(importlib.import_module(module), name, change["new"]) if hasattr(self.__forward_display__, "param"): self.__forward_display__.param.watch( change, self.__forward_observe__) elif hasattr(self.__forward_display__, "observe"): self.__forward_display__.observe(change, self.__forward_observe__) if hasattr(self.__forward_display__, "description"): self.__forward_display__.description = self.__forward_coerce__ change(getattr(self.__forward_display__, "value")) IPython.display.display(self.__forward_display__) elif self.__forward_coerce__ and self.__forward_coerce__ is not True: module, name = _module_and_name(self.__forward_coerce__) value = globals.get(self.__forward_coerce__, None) if (not isinstance(object, type)) or (not isinstance( value, object)): setattr(importlib.import_module(module), name, object(value)) return object