Ejemplo n.º 1
0
    def merge(self, other: "SerdeFlags") -> "SerdeFlags":
        """Merge the values of another SerdeFlags instance into this one."""
        case = other.case or self.case
        signature_only = self.signature_only or other.signature_only
        if other.omit and self.omit:
            omit = (*self.omit, *(o for o in other.omit if o not in self.omit))
        else:
            omit = other.omit or self.omit  # type: ignore

        if other.fields and self.fields:
            if not isinstance(other.fields, Mapping):
                other.fields = freeze({x: x
                                       for x in other.fields})  # type: ignore
            if not isinstance(self.fields, Mapping):
                self.fields = freeze({x: x
                                      for x in self.fields})  # type: ignore
            fields = {**self.fields, **other.fields}  # type: ignore
        else:
            fields = other.fields or self.fields  # type: ignore

        if other.exclude and self.exclude:
            exclude = {*self.exclude, *other.exclude}
        else:
            exclude = other.exclude or self.exclude  # type: ignore
        return SerdeFlags(
            signature_only=signature_only,
            case=case,
            omit=omit,
            fields=fields,
            exclude=exclude,
        )
Ejemplo n.º 2
0
def _resolve_class(
    cls: Type[ObjectT],
    *,
    strict: StrictModeT = STRICT_MODE,
    always: bool = True,
    jsonschema: bool = True,
    serde: SerdeFlags = None,
) -> Type[WrappedObjectT]:
    # Build the namespace for the new class
    strict = cast(bool, strict)
    protos = protocols(cls, strict=strict)
    if hasattr(cls, SERDE_FLAGS_ATTR):
        pserde: SerdeFlags = getattr(cls, SERDE_FLAGS_ATTR)
        serde = pserde.merge(serde) if serde else pserde
    serde = serde or SerdeFlags()
    ns: Dict[str, Any] = {
        SERDE_FLAGS_ATTR: serde,
        TYPIC_ANNOS_NAME: protos,
    }
    if jsonschema:
        ns["schema"] = classmethod(schema)
        schema_builder.attach(cls)

    # Frozen dataclasses don't use the native setattr
    # So we wrap the init. This should be fine,
    # just slower :(
    if isfrozendataclass(cls):
        ns["__init__"] = wrap(cls.__init__, strict=strict)
    # The faster way - create a new setattr that applies the protocol for a given attr
    else:
        trans = freeze({x: y.transmute for x, y in protos.items()})

        def setattr_typed(setter):
            @functools.wraps(setter)
            def __setattr_typed__(self, name, item, *, __trans=trans, __setter=setter):
                __setter(
                    self,
                    name,
                    __trans[name](item) if name in __trans else item,
                )

            return __setattr_typed__

        ns.update(
            **{
                ORIG_SETTER_NAME: _get_setter(cls),
                "__setattr__": setattr_typed(cls.__setattr__),
            }
        )

    for name, attr in ns.items():
        setattr(cls, name, attr)
    # Get the protocol
    proto: SerdeProtocol = resolver.resolve(cls, is_strict=strict)
    # Bind it to the new class
    _bind_proto(cls, proto)
    # Track resolution state.
    setattr(cls, "__typic_resolved__", True)
    return cls
Ejemplo n.º 3
0
def make_typedclass(
    cls: Type,
    *,
    init: bool = True,
    repr: bool = True,
    eq: bool = True,
    order: bool = False,
    unsafe_hash: bool = False,
    frozen: bool = False,
    delay: bool = False,
    strict: bool = False,
    jsonschema: bool = False,
    slots: bool = False,
    serde: SerdeFlags = None,
):
    """A convenience function for generating a dataclass with type-coercion.

    Allows the user to create typed dataclasses on-demand from a base-class, i.e.::

        TypedClass = make_typedclass(UnTypedClass)

    The preferred method is via the `klass` decorator, however.

    See Also
    --------
    :py:func:`klass`
    :py:func:`dataclasses.dataclass`
    """
    # Make the base dataclass.
    dcls = dataclasses.dataclass(  # type: ignore
        cls,
        init=init,
        repr=repr,
        eq=eq,
        order=order,
        unsafe_hash=unsafe_hash,
        frozen=frozen,
    )
    if slots:
        dcls = slotted(dcls)

    fields = [
        f if isinstance(f, Field) else Field.from_field(f)
        for f in dataclasses.fields(dcls)
    ]
    field_names = freeze({f.name: f.external_name or f.name for f in fields})
    exclude = frozenset({f.name for f in fields if f.exclude})
    dcls.__typic_fields__ = (*fields, )
    serde = serde or SerdeFlags()
    serde = serde.merge(SerdeFlags(fields=field_names,
                                   exclude=exclude))  # type: ignore
    return wrap_cls(
        dcls,
        delay=delay,
        strict=strict,
        jsonschema=jsonschema,
        serde=serde,
    )
Ejemplo n.º 4
0
    If given a mapping, provide a mapping to the output field name.
    """
    exclude: Optional[Iterable[str]] = None
    """Provide a set of fields which will be excluded from the output."""
    def __init__(
        self,
        signature_only: bool = False,
        case: Case = None,
        omit: OmitSettingsT = None,
        fields: FieldSettingsT = None,
        exclude: Iterable[str] = None,
    ):
        self.signature_only = signature_only
        self.case = case
        self.omit = freeze(omit)  # type: ignore
        self.fields = cast(FieldSettingsT, freeze(fields))
        self.exclude = cast(Iterable[str], freeze(exclude))

    def merge(self, other: "SerdeFlags") -> "SerdeFlags":
        """Merge the values of another SerdeFlags instance into this one."""
        case = other.case or self.case
        signature_only = self.signature_only or other.signature_only
        if other.omit and self.omit:
            omit = (*self.omit, *(o for o in other.omit if o not in self.omit))
        else:
            omit = other.omit or self.omit  # type: ignore

        if other.fields and self.fields:
            if not isinstance(other.fields, Mapping):
                other.fields = freeze({x: x
Ejemplo n.º 5
0
def _resolve_class(
    cls: Type[ObjectT],
    *,
    strict: StrictModeT = STRICT_MODE,
    always: bool = None,
    jsonschema: bool = True,
    serde: SerdeFlags = None,
) -> Type[WrappedObjectT[ObjectT]]:
    # Build the namespace for the new class
    strict = cast(bool, strict)
    protos = protocols(cls, strict=strict)
    if hasattr(cls, SERDE_FLAGS_ATTR):
        pserde: SerdeFlags = getattr(cls, SERDE_FLAGS_ATTR)
        if isinstance(pserde, dict):
            pserde = SerdeFlags(**pserde)
        serde = pserde.merge(serde) if serde else pserde
    serde = serde or SerdeFlags()
    ns: Dict[str, Any] = {
        SERDE_FLAGS_ATTR: serde,
        TYPIC_ANNOS_NAME: protos,
    }
    frozen = isfrozendataclass(cls)
    always = False if frozen else always
    if always is None:
        warnings.warn(
            "Keyword `always` will default to `False` in a future version. "
            "You should update your code to either explicitly declare `always=True` "
            "or update your code to not assume values will be coerced when set.",
            category=UserWarning,
            stacklevel=5,
        )
        always = True
    if jsonschema:
        ns["schema"] = classmethod(schema)
        schema_builder.attach(cls)

    # Wrap the init if
    #   a) this is a "frozen" dataclass
    #   b) we only want to coerce on init.
    # N.B.: Frozen dataclasses don't use the native setattr and can't be updated.
    if always is False:
        ns["__init__"] = wrap(cls.__init__, strict=strict)
    # For 'always', create a new setattr that applies the protocol for a given attr
    else:
        trans = freeze({x: y.transmute for x, y in protos.items()})

        def setattr_typed(setter):
            @functools.wraps(setter)
            def __setattr_typed__(self, name, item, *, __trans=trans, __setter=setter):
                __setter(
                    self,
                    name,
                    __trans[name](item) if name in __trans else item,
                )

            return __setattr_typed__

        ns.update(
            **{
                ORIG_SETTER_NAME: _get_setter(cls),
                "__setattr__": setattr_typed(cls.__setattr__),
            }
        )

    for name, attr in ns.items():
        setattr(cls, name, attr)
    # Get the protocol
    proto: SerdeProtocol = resolver.resolve(cls, is_strict=strict)
    # Bind it to the new class
    _bind_proto(cls, proto)
    # Track resolution state.
    setattr(cls, "__typic_resolved__", True)
    return cast(Type[WrappedObjectT[ObjectT]], cls)