Example #1
0
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)
Example #2
0
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())
Example #3
0
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
Example #4
0
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)
Example #5
0
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
Example #8
0
def isstdlibsubtype(t: Type[ObjectT]) -> TypeGuard[Type[STDLibTypeT]]:
    return issubclass(util.resolve_supertype(t), STDLIB_TYPES_TUPLE)
Example #9
0
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)