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, )
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
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, )
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
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)