def isbuiltinsubtype(t: Type[ObjectT]) -> TypeGuard[Type[BuiltInTypeT]]: """Check whether the provided type is a subclass of a builtin-type. Parameters ---------- t Examples -------- >>> import typic >>> from typing import NewType, Mapping >>> class SuperStr(str): ... ... >>> typic.isbuiltinsubtype(SuperStr) True >>> typic.isbuiltinsubtype(NewType("MyStr", SuperStr)) True >>> class Foo: ... ... >>> typic.isbuiltintype(Foo) False >>> typic.isbuiltintype(Mapping) False """ return issubclass(util.resolve_supertype(t), BUILTIN_TYPES_TUPLE)
def test_transmute_mapping_subscripted(annotation, value): annotation = resolve_supertype(annotation) key_arg, value_arg = annotation.__args__ transmuted = transmute(annotation, value) assert isinstance(transmuted, annotation.__origin__) assert all(isinstance(x, get_origin(key_arg)) for x in transmuted.keys()) assert all(isinstance(x, get_origin(value_arg)) for x in transmuted.values())
def isclassvartype(obj: Type[ObjectT]) -> TypeGuard[ClassVar]: """Test whether an annotation is a ClassVar annotation. Examples -------- >>> import typic >>> from typing import ClassVar, NewType >>> typic.isclassvartype(ClassVar[str]) True >>> typic.isclassvartype(NewType("Foo", ClassVar[str])) True """ obj = util.resolve_supertype(obj) return getattr(obj, "__origin__", obj) is ClassVar
def isbuiltintype(obj: Type[ObjectT]) -> TypeGuard[Type[BuiltInTypeT]]: """Check whether the provided object is a builtin-type. Python stdlib and Python documentation have no "definitive list" of builtin-**types**, despite the fact that they are well-known. The closest we have is https://docs.python.org/3.7/library/functions.html, which clumps the builtin-types with builtin-functions. Despite clumping these types with functions in the documentation, these types eval as False when compared to :py:class:`types.BuiltinFunctionType`, which is meant to be an alias for the builtin-functions listed in the documentation. All this to say, here we are with a custom check to determine whether a type is a builtin. Parameters ---------- obj Examples -------- >>> import typic >>> from typing import NewType, Mapping >>> typic.isbuiltintype(str) True >>> typic.isbuiltintype(NewType("MyStr", str)) True >>> class Foo: ... ... >>> typic.isbuiltintype(Foo) False >>> typic.isbuiltintype(Mapping) False """ return (util.resolve_supertype(obj) in BUILTIN_TYPES or util.resolve_supertype(type(obj)) in BUILTIN_TYPES)
def isconstrained(obj: Type[ObjectT]) -> TypeGuard[_Constrained]: """Test whether a type is restricted. Parameters ---------- obj Examples -------- >>> import typic >>> from typing import NewType >>> >>> @typic.constrained(ge=0, le=1) ... class TinyInt(int): ... ... >>> typic.isconstrained(TinyInt) True >>> Switch = NewType("Switch", TinyInt) >>> typic.isconstrained(Switch) True """ return hasattr(util.resolve_supertype(obj), "__constraints__")
def protocols(self, obj, *, strict: bool = False) -> SerdeProtocolsT: """Get a mapping of param/attr name -> :py:class:`SerdeProtocol` Parameters ---------- obj The class or callable object you wish to extract resolved annotations from. strict Whether to validate instead of coerce. Examples -------- >>> import typic >>> >>> @typic.klass ... class Foo: ... bar: str ... >>> protocols = typic.protocols(Foo) See Also -------- :py:class:`SerdeProtocol` """ if not any((inspect.ismethod(obj), inspect.isfunction(obj), inspect.isclass(obj))): obj = obj.__class__ hints = util.cached_type_hints(obj) params = util.safe_get_params(obj) fields: Mapping[str, dataclasses.Field] = {} if dataclasses.is_dataclass(obj): fields = {f.name: f for f in dataclasses.fields(obj)} ann = {} for name in params.keys() | hints.keys(): param = params.get(name) hint = hints.get(name) field = fields.get(name) annotation = hint or param.annotation # type: ignore annotation = util.resolve_supertype(annotation) param = param or inspect.Parameter( name, inspect.Parameter.POSITIONAL_OR_KEYWORD, default=EMPTY, annotation=hint or annotation, ) if repr(param.default) == "<factory>": param = param.replace(default=EMPTY) if checks.isclassvartype(annotation): val = getattr(obj, name) if annotation is ClassVar: annotation = annotation[type(val)] default = val param = param.replace(default=default) if (field and field.default is not dataclasses.MISSING and param.default is EMPTY): if field.init is False and util.origin( annotation) is not ReadOnly: annotation = ReadOnly[annotation] # type: ignore param = param.replace(default=field.default) if not checks.ishashable(param.default): param = param.replace(default=...) resolved = self.resolve( annotation, parameter=param, name=name, is_strict=strict, namespace=obj, ) ann[name] = resolved try: setattr(obj, TYPIC_ANNOS_NAME, ann) # We wrapped a bound method, or # are wrapping a static-/classmethod # after they were wrapped with @static/class except (AttributeError, TypeError): pass return ann
def annotation( self, annotation: Type[ObjectT], name: str = None, parameter: Optional[inspect.Parameter] = None, is_optional: bool = None, is_strict: StrictModeT = None, flags: "SerdeFlags" = None, default: Any = EMPTY, namespace: Type = None, ) -> AnnotationT: """Get a :py:class:`Annotation` for this type. Unlike a :py:class:`ResolvedAnnotation`, this does not provide access to a serializer/deserializer/validator protocol. """ flags = cast( "SerdeFlags", getattr(annotation, SERDE_FLAGS_ATTR, flags or SerdeFlags())) if parameter is None: parameter = inspect.Parameter( name or "_", inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=annotation, default=default if checks.ishashable(default) else ..., ) # Check for the super-type non_super = util.resolve_supertype(annotation) # Note, this may be a generic, like Union. orig = util.origin(annotation) use = non_super # Get the unfiltered args args = getattr(non_super, "__args__", None) # Set whether this is optional/strict is_optional = (is_optional or checks.isoptionaltype(non_super) or parameter.default in self.OPTIONALS) is_strict = is_strict or checks.isstrict(non_super) or self.STRICT is_static = util.origin(use) not in self._DYNAMIC is_literal = checks.isliteral(use) # Determine whether we should use the first arg of the annotation while checks.should_unwrap(use) and args: is_optional = is_optional or checks.isoptionaltype(use) is_strict = is_strict or checks.isstrict(use) if is_optional and len(args) > 2: # We can't resolve this annotation. is_static = False use = Union[args[:-1]] break # Note that we don't re-assign `orig`. # This is intentional. # Special forms are needed for building the downstream validator. # Callers should be aware of this and perhaps use `util.origin` elsewhere. non_super = util.resolve_supertype(args[0]) use = non_super args = util.get_args(use) is_static = util.origin(use) not in self._DYNAMIC is_literal = is_literal or checks.isliteral(use) # Only allow legal parameters at runtime, this has implementation implications. if is_literal: args = util.get_args(use) if any(not isinstance(a, self.LITERALS) for a in args): raise TypeError( f"PEP 586: Unsupported parameters for 'Literal' type: {args}. " "See https://www.python.org/dev/peps/pep-0586/" "#legal-parameters-for-literal-at-type-check-time " "for more information.") # The type definition doesn't exist yet. if use.__class__ is ForwardRef: module, localns = self.__module__, {} # Ideally we have a namespace from a parent class/function to the field if namespace: module = namespace.__module__ localns = getattr(namespace, "__dict__", {}) return ForwardDelayedAnnotation( ref=use, resolver=self, _name=name, parameter=parameter, is_optional=is_optional, is_strict=is_strict, flags=flags, default=default, module=module, localns=localns, ) # The type definition is recursive or within a recursive loop. elif use is namespace or use in self.__stack: # If detected via stack, we can remove it now. # Otherwise we'll cause another recursive loop. if use in self.__stack: self.__stack.remove(use) return DelayedAnnotation( type=use, resolver=self, _name=name, parameter=parameter, is_optional=is_optional, is_strict=is_strict, flags=flags, default=default, ) # Otherwise, add this type to the stack to prevent a recursive loop from elsewhere. if not checks.isstdlibtype(use): self.__stack.add(use) serde = (self._get_configuration(util.origin(use), flags) if is_static and not is_literal else SerdeConfig(flags)) anno = Annotation( resolved=use, origin=orig, un_resolved=annotation, parameter=parameter, optional=is_optional, strict=is_strict, static=is_static, serde=serde, ) anno.translator = functools.partial(self.translator.factory, anno) # type: ignore return anno
def isstdlibsubtype(t: Type[ObjectT]) -> TypeGuard[Type[STDLibTypeT]]: return issubclass(util.resolve_supertype(t), STDLIB_TYPES_TUPLE)
def isstdlibtype(obj: Type[ObjectT]) -> TypeGuard[Type[STDLibTypeT]]: return (util.resolve_supertype(obj) in STDLIB_TYPES or util.resolve_supertype(type(obj)) in STDLIB_TYPES)