Beispiel #1
0
    def _get_configuration(self, origin: Type, flags: SerdeFlags) -> SerdeConfig:
        if hasattr(origin, SERDE_FLAGS_ATTR):
            flags = getattr(origin, SERDE_FLAGS_ATTR)
        # Get all the annotated fields
        params = util.safe_get_params(origin)
        # This is probably a builtin and has no signature
        fields: Dict[str, Annotation] = {}
        hints = util.cached_type_hints(origin)
        for name, t in hints.items():
            fields[name] = self.annotation(
                t,
                flags=dataclasses.replace(flags, fields={}),
                default=getattr(origin, name, EMPTY),
                namespace=origin,
            )

        # Filter out any annotations which aren't part of the object's signature.
        if flags.signature_only:
            fields = {x: fields[x] for x in fields.keys() & params.keys()}
        # Create a field-to-field mapping
        fields_out = {x: x for x in fields}
        # Make sure to include any fields explicitly listed
        include = flags.fields
        if include:
            if isinstance(include, Mapping):
                fields_out.update(include)
            else:
                fields_out.update({x: x for x in include})
        # Transform the output fields to the correct case.
        if flags.case:
            case = Case(flags.case)
            fields_out = {x: case.transformer(y) for x, y in fields_out.items()}
    def get_fields(
            self,
            type: Type,
            as_source: bool = False
    ) -> Optional[Mapping[str, inspect.Parameter]]:
        """Get the fields for the given type.

        Notes
        -----
        We want this to be the type's signature, we really do. But if for some reason we
        can't make that happen, we fallback to a few known, semi-reliable methods for
        making this happen.
        """
        # Try first with the signature of the target if this is the target type
        params = safe_get_params(type)
        undefined = self.sig_is_undef(params)
        if not as_source and not undefined:
            return params
        # Now we start building a fake signature
        k: ParameterKind = inspect.Parameter.POSITIONAL_OR_KEYWORD
        # **kwargs
        if self.kw_only(params):
            k = inspect.Parameter.KEYWORD_ONLY
        # *args
        elif self.pos_only(params):
            k = inspect.Parameter.POSITIONAL_ONLY
        # Fetch any type hints and try to use those.
        hints = cached_type_hints(type)
        if hints:
            return self._fields_from_hints(k, hints)
        # Fallback to the target object's defined attributes
        # This will basically work for ORM models, Pydantic models...
        # Anything that defines the instance using the class body.
        attrs = cached_simple_attributes(type)
        if attrs:
            return self._fields_from_attrs(k, attrs)
        # Can't be done.
        return None if undefined else params
Beispiel #3
0
def _resolve_from_env(
    cls: Type[ObjectT],
    prefix: str,
    case_sensitive: bool,
    aliases: Mapping[str, str],
) -> Type[ObjectT]:
    fields = cached_type_hints(cls)
    vars = {
        (f"{prefix}{x}".lower() if not case_sensitive else f"{prefix}{x}"): (x, y)
        for x, y in fields.items()
    }
    attr_to_aliases = collections.defaultdict(set)
    for alias, attr in aliases.items():
        attr_to_aliases[attr].add(alias)

    sentinel = object()
    for name in vars:
        attr, typ = vars[name]
        names = attr_to_aliases[name]
        field = getattr(cls, attr, sentinel)
        if field is sentinel:
            field = dataclasses.field()
        elif not isinstance(field, dataclasses.Field):
            field = dataclasses.field(default=field)
        if field.default_factory != dataclasses.MISSING:
            continue

        kwargs = dict(var=name, ci=not case_sensitive)
        if field.default != dataclasses.MISSING:
            kwargs["default"] = field.default
            field.default = dataclasses.MISSING

        factory = environ.register(typ, *names, name=name)
        field.default_factory = functools.partial(factory, **kwargs)
        setattr(cls, attr, field)

    return cls
    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
Beispiel #5
0
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)