def encode(self, obj: Any, encoder: EncoderT[PrimitiveT], **kwargs) -> bytes: t = obj.__class__ if checks.isenumtype(t): obj = obj.value t = obj.__class__ proto: SerdeProtocol = self.resolve(t) return encoder(proto.primitive(obj), **kwargs) # type: ignore
def tojson(self, obj: Any, *, indent: int = 0, ensure_ascii: bool = False, **kwargs) -> str: """A method for dumping any object to a valid JSON string. Notes ----- If `ujson` is installed, we will default to that library for the final encoding, which can result in massive performance gains over the standard `json` library. Examples -------- >>> import typic >>> import datetime >>> import uuid >>> import ipaddress >>> import re >>> import dataclasses >>> import enum >>> typic.tojson("foo") '"foo"' >>> typic.tojson(("foo",)) '["foo"]' >>> typic.tojson(datetime.datetime(1970, 1, 1)) '"1970-01-01T00:00:00"' >>> typic.tojson(b"foo") '"foo"' >>> typic.tojson(ipaddress.IPv4Address("0.0.0.0")) '"0.0.0.0"' >>> typic.tojson(re.compile("[0-9]")) '"[0-9]"' >>> typic.tojson(uuid.UUID(int=0)) '"00000000-0000-0000-0000-000000000000"' >>> @dataclasses.dataclass ... class Foo: ... bar: str = 'bar' ... >>> typic.tojson(Foo()) '{"bar":"bar"}' >>> class Enum(enum.Enum): ... FOO = "foo" ... >>> typic.tojson(Enum.FOO) '"foo"' """ t = obj.__class__ if checks.isenumtype(t): obj = obj.value t = obj.__class__ proto: SerdeProtocol = self._get_serializer_proto(t) return proto.tojson(obj, indent=indent, ensure_ascii=ensure_ascii, **kwargs)
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
def primitive( self, obj: ObjectT, *, lazy: bool = False, name: util.ReprT = None, flags: SerdeFlags = None, ) -> PrimitiveT: """A method for converting an object to its primitive equivalent. Useful for encoding data to JSON. Examples -------- >>> import typic >>> import datetime >>> import uuid >>> import ipaddress >>> import re >>> import dataclasses >>> typic.primitive("foo") 'foo' >>> typic.primitive(("foo",)) # containers are converted to lists/dicts ['foo'] >>> typic.primitive(datetime.datetime(1970, 1, 1)) '1970-01-01T00:00:00' >>> typic.primitive(b"foo") 'foo' >>> typic.primitive(ipaddress.IPv4Address("0.0.0.0")) '0.0.0.0' >>> typic.primitive(re.compile("[0-9]")) '[0-9]' >>> typic.primitive(uuid.UUID(int=0)) '00000000-0000-0000-0000-000000000000' >>> @dataclasses.dataclass ... class Foo: ... bar: str = 'bar' ... >>> typic.primitive(Foo()) {'bar': 'bar'} """ t = obj.__class__ if checks.isenumtype(t): obj = obj.value # type: ignore t = obj.__class__ proto: SerdeProtocol = self.resolve(t, flags=flags) return proto.primitive(obj, lazy=lazy, name=name) # type: ignore
def iterate(self, obj, *, values: bool = False) -> Iterator[Union[Tuple[str, Any], Any]]: """Iterate over the fields of an object. Parameters ---------- obj The object to iterate over values Whether to only yield values of an object's fields. (defaults False) """ t = obj.__class__ # Extract the type of the enum value if this is an Enum. # Enums classes are iterable and will generate the wrong kind of iterator. if checks.isenumtype(t): obj = obj.value t = obj.__class__ iterator = self.translator.iterator(t, values=values) return iterator(obj)
def tojson( self, obj: ObjectT, *, indent: int = 0, ensure_ascii: bool = False, **kwargs ) -> str: """A method for dumping any object to a valid JSON string. Notes ----- If `ujson` is installed, we will default to that library for the final encoding, which can result in massive performance gains over the standard `json` library. Examples -------- >>> import typic >>> import datetime >>> import uuid >>> import ipaddress >>> import re >>> import dataclasses >>> import enum >>> typic.tojson("foo") b'"foo"' >>> typic.tojson(("foo",)) b'["foo"]' >>> typic.tojson(datetime.datetime(1970, 1, 1)) b'"1970-01-01T00:00:00"' >>> typic.tojson(b"foo") b'"foo"' >>> typic.tojson(ipaddress.IPv4Address("0.0.0.0")) b'"0.0.0.0"' >>> typic.tojson(uuid.UUID(int=0)) b'"00000000-0000-0000-0000-000000000000"' """ t = obj.__class__ if checks.isenumtype(t): obj = obj.value # type: ignore t = obj.__class__ proto: SerdeProtocol = self.resolve(t) return proto.tojson(obj, indent=indent, ensure_ascii=ensure_ascii, **kwargs)
def _compile_serializer( self, annotation: Annotation[Type[_T]]) -> SerializerT[_T]: # Check for an optional and extract the type if possible. func_name = self._get_name(annotation) # We've been here before... if func_name in self._serializer_cache: return self._serializer_cache[func_name] serializer: SerializerT origin = annotation.resolved_origin # Lazy shortcut for messy paths (Union, Any, ...) if (origin in self._DYNAMIC or not annotation.static or checks.isuniontype(origin)): serializer = cast(SerializerT, self.resolver.primitive) # Routines (functions or methods) can't be serialized... elif issubclass( origin, abc.Callable) or inspect.isroutine(origin): # type: ignore name = util.get_qualname(origin) with gen.Block() as main: with self._define(main, func_name) as func: func.l( f'raise TypeError("Routines are not serializable. ({name!r}).")' ) serializer = main.compile(name=func_name) self._serializer_cache[func_name] = serializer # Enums are special elif checks.isenumtype(annotation.resolved): serializer = self._compile_enum_serializer(annotation) # Primitives don't require further processing. # Just check for nullable and the correct type. elif origin in self._PRIMITIVES: ns: dict = {} with gen.Block(ns) as main: with self._define(main, func_name) as func: self._check_add_null_check(func, annotation) self._add_type_check(func, annotation) line = "o" if annotation.origin in (type(o) for o in self.resolver.OPTIONALS): line = "None" func.l(f"{gen.Keyword.RET} {line}") serializer = main.compile(name=func_name, ns=ns) self._serializer_cache[func_name] = serializer # Defined cases are pre-compiled, but we have to check for optionals. elif origin in self._DEFINED: serializer = self._compile_defined_serializer( annotation, self._DEFINED[origin]) elif issubclass(origin, (*self._DEFINED, )): serializer = self._compile_defined_subclass_serializer( origin, annotation) elif issubclass(origin, self._PRIMITIVES): serializer = self._compile_primitive_subclass_serializer( origin, annotation) else: # Build the function namespace anno_name = f"{func_name}_anno" ns = {anno_name: origin, **annotation.serde.asdict()} with gen.Block(ns) as main: with self._define(main, func_name) as func: # Mapping types need special nested processing as well istypeddict = checks.istypeddict(origin) istypedtuple = checks.istypedtuple(origin) istypicklass = checks.istypicklass(origin) if not istypeddict and issubclass(origin, self._DICTITER): self._build_dict_serializer(func, annotation) # Array types need nested processing. elif (not istypedtuple and not istypeddict and not istypicklass and issubclass(origin, self._LISTITER)): self._build_list_serializer(func, annotation) # Build a serializer for a structured class. else: self._build_class_serializer(func, annotation) serializer = main.compile(name=func_name, ns=ns) self._serializer_cache[func_name] = serializer return serializer
def _build_des( # noqa: C901 self, annotation: "Annotation", func_name: str, namespace: Type = None) -> Callable: args = annotation.args # Get the "origin" of the annotation. # For natives and their typing.* equivs, this will be a builtin type. # For SpecialForms (Union, mainly) this will be the un-subscripted type. # For custom types or classes, this will be the same as the annotation. origin = annotation.resolved_origin anno_name = get_unique_name(origin) ns = { anno_name: origin, "parent": getattr(origin, "__parent__", origin), "issubclass": cached_issubclass, **annotation.serde.asdict(), } if checks.isliteral(origin): return self._build_literal_des(annotation, func_name, namespace) with gen.Block(ns) as main: with main.f(func_name, main.param(f"{self.VNAME}")) as func: if origin not in self.UNRESOLVABLE: self._set_checks(func, anno_name, annotation) if origin is Union: self._build_union_des(func, annotation, namespace) elif checks.isdatetype(origin): self._build_date_des(func, anno_name, annotation) elif checks.istimetype(origin): self._build_time_des(func, anno_name, annotation) elif checks.istimedeltatype(origin): self._build_timedelta_des(func, anno_name, annotation) elif checks.isuuidtype(origin): self._build_uuid_des(func, anno_name, annotation) elif origin in {Pattern, re.Pattern}: # type: ignore self._build_pattern_des(func, anno_name) elif issubclass(origin, pathlib.Path): self._build_path_des(func, anno_name) elif not args and checks.isbuiltintype(origin): self._build_builtin_des(func, anno_name, annotation) elif checks.isfromdictclass(origin): self._build_fromdict_des(func, anno_name) elif checks.isenumtype(origin): self._build_builtin_des(func, anno_name, annotation) elif checks.istypeddict(origin): self._build_typeddict_des( func, anno_name, annotation, total=origin.__total__, # type: ignore namespace=namespace, ) elif checks.istypedtuple(origin) or checks.isnamedtuple( origin): self._build_typedtuple_des(func, anno_name, annotation, namespace=namespace) elif not args and checks.isbuiltinsubtype(origin): self._build_builtin_des(func, anno_name, annotation) elif checks.ismappingtype(origin): self._build_mapping_des(func, anno_name, annotation, namespace=namespace) elif checks.istupletype(origin): self._build_tuple_des(func, anno_name, annotation, namespace=namespace) elif checks.iscollectiontype(origin): self._build_collection_des(func, anno_name, annotation, namespace=namespace) else: self._build_generic_des(func, anno_name, annotation, namespace=namespace) func.l(f"{gen.Keyword.RET} {self.VNAME}") deserializer = main.compile(ns=ns, name=func_name) return deserializer
def _compile_serializer(self, annotation: "Annotation") -> SerializerT: # Check for an optional and extract the type if possible. func_name = self._get_name(annotation) # We've been here before... if func_name in self._serializer_cache: return self._serializer_cache[func_name] serializer: SerializerT origin = annotation.resolved_origin # Lazy shortcut for messy paths (Union, Any, ...) if origin in self._DYNAMIC or not annotation.static: serializer = self.resolver.primitive # Enums are special elif checks.isenumtype(annotation.resolved): serializer = self._compile_enum_serializer(annotation) # Primitives don't require further processing. # Just check for nullable and the correct type. elif origin in self._PRIMITIVES: ns: dict = {} with gen.Block(ns) as main: with main.f( func_name, main.param("o"), main.param("lazy", default=False), main.param("name", default=None), ) as func: self._check_add_null_check(func, annotation) self._add_type_check(func, annotation) line = "o" if annotation.origin in (type(o) for o in self.resolver.OPTIONALS): line = "None" func.l(f"{gen.Keyword.RET} {line}") serializer = main.compile(name=func_name, ns=ns) self._serializer_cache[func_name] = serializer # Defined cases are pre-compiled, but we have to check for optionals. elif origin in self._DEFINED: serializer = self._compile_defined_serializer( annotation, self._DEFINED[origin]) elif issubclass(origin, (*self._DEFINED, )): serializer = self._compile_defined_subclass_serializer( origin, annotation) elif issubclass(origin, self._PRIMITIVES): serializer = self._compile_primitive_subclass_serializer( origin, annotation) else: # Build the function namespace anno_name = f"{func_name}_anno" ns = {anno_name: origin, **annotation.serde.asdict()} with gen.Block(ns) as main: with main.f( func_name, main.param("o"), main.param("lazy", default=False), main.param("name", default=None), ) as func: # Mapping types need special nested processing as well if not checks.istypeddict(origin) and issubclass( origin, self._DICTITER): self._build_dict_serializer(func, annotation) # Array types need nested processing. elif not checks.istypedtuple(origin) and issubclass( origin, self._LISTITER): self._build_list_serializer(func, annotation) # Build a serializer for a structured class. else: self._build_class_serializer(func, annotation) serializer = main.compile(name=func_name, ns=ns) self._serializer_cache[func_name] = serializer return serializer