def coerce_value(self, value: Any, annotation: Any) -> Any: """Coerce the given value to the given annotation, if possible. Checks for: - :class:`datetime.date` - :class:`datetime.datetime` - builtin types - extended type annotations as described in the ``typing`` module. - Classes with a ``from_dict`` method Parameters ---------- value : The value to be coerced annotation : The provided annotation for determining the coercion """ # Resolve NewTypes into their annotation. Recursive. annotation = checks.resolve_supertype(annotation) # Get the "origin" of the annotation. This will be a builtin for native types. # For custom types or classes, this will be the same as the annotation. origin = self.get_origin(annotation) optional = checks.isoptionaltype(annotation) args = set(self.get_args(annotation)) # Short-circuit checks if this is an optional type and the value is None # Or if the type of the value is the annotation. if ((checks.isinstance(value, origin) and not args) or (optional and value is None) or (origin is Union and any(checks.isinstance(value, x) for x in args))): return value coerced = self.registry.coerce(value, origin, annotation) return coerced
def _evaluate_contraints(self): type = self.t if checks.isoptionaltype(type): args = util.get_args(type)[:-1] type = args[0] if len(args) == 1 else Union[args] self.nullable = True self.t = type c = self.factory(type, nullable=self.nullable, name=self.name) return c
def _coerce_class(self, value: Any, origin: Type, annotation: Any) -> Any: value = self._pre_process_class(value, origin) coerced = value if isinstance(value, (Mapping, dict)): coerced = origin(**value) elif value is not None and not checks.isoptionaltype(annotation): coerced = origin(value) return coerced
def get_constraints( t: Type[VT], *, nullable: bool = False, name: str = None, cls: Optional[Type] = ..., # type: ignore ) -> ConstraintsProtocolT[VT]: while should_unwrap(t): nullable = nullable or isoptionaltype(t) t = get_args(t)[0] if t is cls or t in __stack: dc = DelayedConstraints( t, nullable=nullable, name=name, factory=get_constraints ) return cast(ConstraintsProtocolT, dc) if isforwardref(t): if cls is ...: # pragma: nocover raise TypeError( f"Cannot build constraints for {t} without an enclosing class." ) fdc = ForwardDelayedConstraints( t, # type: ignore cls.__module__, localns=getattr(cls, "__dict__", {}).copy(), nullable=nullable, name=name, factory=get_constraints, ) return cast(ConstraintsProtocolT, fdc) if isconstrained(t): c: ConstraintsProtocolT = t.__constraints__ # type: ignore if (c.name, c.nullable) != (name, nullable): return dataclasses.replace(c, name=name, nullable=nullable) return c if isenumtype(t): ec = _from_enum_type(t, nullable=nullable, name=name) # type: ignore return cast(ConstraintsProtocolT, ec) if isabstract(t): return cast( ConstraintsProtocolT, _from_strict_type(t, nullable=nullable, name=name) ) if isnamedtuple(t) or istypeddict(t): handler = _from_class else: ot = origin(t) if ot in {type, abc.Callable}: handler = _from_strict_type # type: ignore t = ot else: handler = _CONSTRAINT_BUILDER_HANDLERS.get_by_parent(ot, _from_class) # type: ignore __stack.add(t) c = handler(t, nullable=nullable, name=name, cls=cls) __stack.clear() return c
def _from_union( t: Type[VT], *, nullable: bool = False, name: str = None, cls: Type = None ) -> ConstraintsProtocolT: _nullable: bool = isoptionaltype(t) nullable = nullable or _nullable _args = get_args(t)[:-1] if _nullable else get_args(t) if len(_args) == 1: return get_constraints(_args[0], nullable=nullable, name=name, cls=cls) c = MultiConstraints( (*(get_constraints(a, nullable=nullable, cls=cls) for a in _args),), name=name, tag=get_tag_for_types(_args), ) return cast(ConstraintsProtocolT, c)
def _evaluate_contraints(self): globalns = sys.modules[self.module].__dict__.copy() try: type = evaluate_forwardref(self.ref, globalns or {}, self.localns or {}) except NameError as e: # pragma: nocover warnings.warn( f"Counldn't resolve forward reference: {e}. " f"Make sure this type is available in {self.module}.") type = object # make it a no-op if checks.isoptionaltype(type): args = util.get_args(type)[:-1] type = args[0] if len(args) == 1 else Union[args] self.nullable = True c = self.factory(type, nullable=self.nullable, name=self.name) return c
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 _resolve_params( cls: Type, **param: inspect.Parameter, ) -> Mapping[str, ConstraintsProtocolT]: items: Dict[str, ConstraintsProtocolT] = {} while param: name, p = param.popitem() anno = p.annotation nullable = p.default in (None, Ellipsis) or isoptionaltype(anno) if anno in {Any, Ellipsis, p.empty}: continue if isuniontype(anno) and not isforwardref(anno): items[name] = _from_union(anno, nullable=nullable, name=name, cls=cls) continue else: items[name] = get_constraints(anno, nullable=nullable, name=name, cls=cls) return items
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 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