Esempio n. 1
0
    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
Esempio n. 2
0
 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
Esempio n. 3
0
    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
Esempio n. 4
0
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
Esempio n. 5
0
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)
Esempio n. 6
0
 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
Esempio n. 7
0
    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
Esempio n. 8
0
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
Esempio n. 9
0
    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]
Esempio n. 10
0
    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