def __post_init__(self): self.has_default = self.parameter.default is not self.EMPTY self.args = util.get_args(self.resolved) self.resolved_origin = util.origin(self.resolved) self.generic = getattr(self.resolved, "__origin__", self.resolved_origin) self.is_class_var = isclassvartype(self.un_resolved)
def get_origin(cls, annotation: Any) -> Any: """Get origins for subclasses of typing._SpecialForm, recursive""" # Resolve custom NewTypes, recursively. actual = checks.resolve_supertype(annotation) # Extract the origin of the annotation, recursively. actual = getattr(actual, "__origin__", actual) if checks.isoptionaltype(annotation) or checks.isclassvartype( annotation): args = cls.get_args(annotation) return cls.get_origin(args[0]) if args else actual # provide defaults for generics if not checks.isbuiltintype(actual): actual = cls._check_generics(actual) return actual
def origin(annotation: Any) -> Any: """Get the highest-order 'origin'-type for subclasses of typing._SpecialForm. For the purposes of this library, if we can resolve to a builtin type, we will. Examples -------- >>> import typic >>> from typing import Dict, Mapping, NewType, Optional >>> typic.origin(Dict) <class 'dict'> >>> typic.origin(Mapping) <class 'dict'> >>> Registry = NewType('Registry', Dict) >>> typic.origin(Registry) <class 'dict'> >>> class Foo: ... ... >>> typic.origin(Foo) <class 'typic.util.Foo'> """ # Resolve custom NewTypes. actual = resolve_supertype(annotation) # Unwrap optional/classvar if checks.isclassvartype(actual): args = get_args(actual) actual = args[0] if args else actual actual = get_origin(actual) or actual # provide defaults for generics if not checks.isbuiltintype(actual): actual = _check_generics(actual) if inspect.isroutine(actual): actual = collections.abc.Callable return actual
def check( self, origin: Type, annotation: Any, *, name: str = None, default: Any = _Empty, param_kind: inspect._ParameterKind = _Empty, ) -> ResolvedAnnotation: """Locate the coercer for this annotation from either registry.""" key = self.key(annotation, default=default, param_kind=param_kind) if key not in self.__annotation_registry: use = annotation is_optional = checks.isoptionaltype(annotation) if is_optional or (checks.isclassvartype(annotation) and getattr(annotation, "__args__", ())): use = annotation.__args__[0] coercer = self._check( reg=self.__user_registry + self.__registry, origin=origin, annotation=use, ) anno = (ResolvedAnnotation( annotation=use, origin=origin, un_resolved=annotation, coercer=coercer, name=name, default=default, param_kind=param_kind, is_optional=is_optional, ) if coercer else None) self.__annotation_registry[key] = anno return self.__annotation_registry[key]
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 _from_class( t: Type[VT], *, nullable: bool = False, name: str = None, cls: Type = None ) -> ConstraintsProtocolT[VT]: if not istypeddict(t) and not isnamedtuple(t) and isbuiltinsubtype(t): return cast( ConstraintsProtocolT, _from_strict_type(t, nullable=nullable, name=name) ) try: params: Dict[str, inspect.Parameter] = {**cached_signature(t).parameters} hints = cached_type_hints(t) for x in hints.keys() & params.keys(): p = params[x] params[x] = inspect.Parameter( p.name, p.kind, default=p.default, annotation=hints[x] ) for x in hints.keys() - params.keys(): hint = hints[x] if not isclassvartype(hint): continue # Hack in the classvars as "parameters" to allow for validation. default = getattr(t, x, empty) args = get_args(hint) if not args: hint = ClassVar[default.__class__] # type: ignore params[x] = inspect.Parameter( x, inspect.Parameter.KEYWORD_ONLY, default=default, annotation=hint ) except (ValueError, TypeError): return cast( ConstraintsProtocolT, _from_strict_type(t, nullable=nullable, name=name) ) name = name or get_name(t) items: Optional[frozendict.FrozenDict[Hashable, ConstraintsT]] = ( frozendict.FrozenDict(_resolve_params(t, **params)) or None ) required = frozenset( ( pname for pname, p in params.items() if ( p.kind not in {p.VAR_POSITIONAL, p.VAR_KEYWORD} and p.default is p.empty ) ) ) has_varargs = any( p.kind in {p.VAR_KEYWORD, p.VAR_POSITIONAL} for p in params.values() ) kwargs = { "type": t, "nullable": nullable, "name": name, "required_keys": required, "items": items, "total": not has_varargs, } cls = ObjectConstraints if istypeddict(t): cls = TypedDictConstraints kwargs.update(type=dict, ttype=t, total=getattr(t, "__total__", bool(required))) c = cls(**kwargs) # type: ignore return cast(ConstraintsProtocolT, c)