python lsp
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Any, Callable, TypeVar, get_args
|
||||
|
||||
from .._compat import is_subclass
|
||||
from ..converters import Converter, UnstructureHook
|
||||
from ..fns import identity
|
||||
|
||||
if sys.version_info[:2] < (3, 10):
|
||||
from typing_extensions import ParamSpec
|
||||
else:
|
||||
from typing import ParamSpec
|
||||
|
||||
|
||||
def validate_datetime(v, _):
|
||||
if not isinstance(v, datetime):
|
||||
raise Exception(f"Expected datetime, got {v}")
|
||||
return v
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
P = ParamSpec("P")
|
||||
|
||||
|
||||
def wrap(_: Callable[P, Any]) -> Callable[[Callable[..., T]], Callable[P, T]]:
|
||||
"""Wrap a `Converter` `__init__` in a type-safe way."""
|
||||
|
||||
def impl(x: Callable[..., T]) -> Callable[P, T]:
|
||||
return x
|
||||
|
||||
return impl
|
||||
|
||||
|
||||
def is_primitive_enum(type: Any, include_bare_enums: bool = False) -> bool:
|
||||
"""Is this a string or int enum that can be passed through?"""
|
||||
return is_subclass(type, Enum) and (
|
||||
is_subclass(type, (str, int))
|
||||
or (include_bare_enums and type.mro()[1:] == Enum.mro())
|
||||
)
|
||||
|
||||
|
||||
def literals_with_enums_unstructure_factory(
|
||||
typ: Any, converter: Converter
|
||||
) -> UnstructureHook:
|
||||
"""An unstructure hook factory for literals containing enums.
|
||||
|
||||
If all contained enums can be passed through (their unstructure hook is `identity`),
|
||||
the entire literal can also be passed through.
|
||||
"""
|
||||
if all(
|
||||
converter.get_unstructure_hook(type(arg)) == identity for arg in get_args(typ)
|
||||
):
|
||||
return identity
|
||||
return converter.unstructure
|
||||
@@ -0,0 +1,121 @@
|
||||
"""Preconfigured converters for bson."""
|
||||
|
||||
from base64 import b85decode, b85encode
|
||||
from collections.abc import Set
|
||||
from datetime import date, datetime
|
||||
from typing import Any, TypeVar, Union
|
||||
|
||||
from bson import DEFAULT_CODEC_OPTIONS, CodecOptions, Int64, ObjectId, decode, encode
|
||||
|
||||
from .._compat import is_mapping, is_subclass
|
||||
from ..cols import mapping_structure_factory
|
||||
from ..converters import BaseConverter, Converter
|
||||
from ..dispatch import StructureHook
|
||||
from ..fns import identity
|
||||
from ..literals import is_literal_containing_enums
|
||||
from ..strategies import configure_union_passthrough
|
||||
from . import (
|
||||
is_primitive_enum,
|
||||
literals_with_enums_unstructure_factory,
|
||||
validate_datetime,
|
||||
wrap,
|
||||
)
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class Base85Bytes(bytes):
|
||||
"""A subclass to help with binary key encoding/decoding."""
|
||||
|
||||
|
||||
class BsonConverter(Converter):
|
||||
def dumps(
|
||||
self,
|
||||
obj: Any,
|
||||
unstructure_as: Any = None,
|
||||
check_keys: bool = False,
|
||||
codec_options: CodecOptions = DEFAULT_CODEC_OPTIONS,
|
||||
) -> bytes:
|
||||
return encode(
|
||||
self.unstructure(obj, unstructure_as=unstructure_as),
|
||||
check_keys=check_keys,
|
||||
codec_options=codec_options,
|
||||
)
|
||||
|
||||
def loads(
|
||||
self,
|
||||
data: bytes,
|
||||
cl: type[T],
|
||||
codec_options: CodecOptions = DEFAULT_CODEC_OPTIONS,
|
||||
) -> T:
|
||||
return self.structure(decode(data, codec_options=codec_options), cl)
|
||||
|
||||
|
||||
def configure_converter(converter: BaseConverter):
|
||||
"""
|
||||
Configure the converter for use with the bson library.
|
||||
|
||||
* sets are serialized as lists
|
||||
* byte mapping keys are base85-encoded into strings when unstructuring, and reverse
|
||||
* non-string, non-byte mapping keys are coerced into strings when unstructuring
|
||||
* a deserialization hook is registered for bson.ObjectId by default
|
||||
* string and int enums are passed through when unstructuring
|
||||
|
||||
.. versionchanged:: 24.2.0
|
||||
Enums are left to the library to unstructure, speeding them up.
|
||||
"""
|
||||
|
||||
def gen_unstructure_mapping(cl: Any, unstructure_to=None):
|
||||
key_handler = str
|
||||
args = getattr(cl, "__args__", None)
|
||||
if args:
|
||||
if is_subclass(args[0], str):
|
||||
key_handler = None
|
||||
elif is_subclass(args[0], bytes):
|
||||
|
||||
def key_handler(k):
|
||||
return b85encode(k).decode("utf8")
|
||||
|
||||
return converter.gen_unstructure_mapping(
|
||||
cl, unstructure_to=unstructure_to, key_handler=key_handler
|
||||
)
|
||||
|
||||
def gen_structure_mapping(cl: Any) -> StructureHook:
|
||||
args = getattr(cl, "__args__", None)
|
||||
if args and is_subclass(args[0], bytes):
|
||||
h = mapping_structure_factory(cl, converter, key_type=Base85Bytes)
|
||||
else:
|
||||
h = mapping_structure_factory(cl, converter)
|
||||
return h
|
||||
|
||||
converter.register_structure_hook(Base85Bytes, lambda v, _: b85decode(v))
|
||||
converter.register_unstructure_hook_factory(is_mapping, gen_unstructure_mapping)
|
||||
converter.register_structure_hook_factory(is_mapping, gen_structure_mapping)
|
||||
|
||||
converter.register_structure_hook(ObjectId, lambda v, _: ObjectId(v))
|
||||
configure_union_passthrough(
|
||||
Union[str, bool, int, float, None, bytes, datetime, ObjectId, Int64], converter
|
||||
)
|
||||
|
||||
# datetime inherits from date, so identity unstructure hook used
|
||||
# here to prevent the date unstructure hook running.
|
||||
converter.register_unstructure_hook(datetime, lambda v: v)
|
||||
converter.register_structure_hook(datetime, validate_datetime)
|
||||
converter.register_unstructure_hook(date, lambda v: v.isoformat())
|
||||
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
||||
converter.register_unstructure_hook_func(is_primitive_enum, identity)
|
||||
converter.register_unstructure_hook_factory(
|
||||
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
||||
)
|
||||
|
||||
|
||||
@wrap(BsonConverter)
|
||||
def make_converter(*args: Any, **kwargs: Any) -> BsonConverter:
|
||||
kwargs["unstruct_collection_overrides"] = {
|
||||
Set: list,
|
||||
**kwargs.get("unstruct_collection_overrides", {}),
|
||||
}
|
||||
res = BsonConverter(*args, **kwargs)
|
||||
configure_converter(res)
|
||||
|
||||
return res
|
||||
@@ -0,0 +1,56 @@
|
||||
"""Preconfigured converters for cbor2."""
|
||||
|
||||
from collections.abc import Set
|
||||
from datetime import date, datetime, timezone
|
||||
from typing import Any, TypeVar, Union
|
||||
|
||||
from cbor2 import dumps, loads
|
||||
|
||||
from ..converters import BaseConverter, Converter
|
||||
from ..fns import identity
|
||||
from ..literals import is_literal_containing_enums
|
||||
from ..strategies import configure_union_passthrough
|
||||
from . import is_primitive_enum, literals_with_enums_unstructure_factory, wrap
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class Cbor2Converter(Converter):
|
||||
def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> bytes:
|
||||
return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs)
|
||||
|
||||
def loads(self, data: bytes, cl: type[T], **kwargs: Any) -> T:
|
||||
return self.structure(loads(data, **kwargs), cl)
|
||||
|
||||
|
||||
def configure_converter(converter: BaseConverter):
|
||||
"""
|
||||
Configure the converter for use with the cbor2 library.
|
||||
|
||||
* datetimes are serialized as timestamp floats
|
||||
* sets are serialized as lists
|
||||
* string and int enums are passed through when unstructuring
|
||||
"""
|
||||
converter.register_unstructure_hook(datetime, lambda v: v.timestamp())
|
||||
converter.register_structure_hook(
|
||||
datetime, lambda v, _: datetime.fromtimestamp(v, timezone.utc)
|
||||
)
|
||||
converter.register_unstructure_hook(date, lambda v: v.isoformat())
|
||||
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
||||
converter.register_unstructure_hook_func(is_primitive_enum, identity)
|
||||
converter.register_unstructure_hook_factory(
|
||||
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
||||
)
|
||||
configure_union_passthrough(Union[str, bool, int, float, None, bytes], converter)
|
||||
|
||||
|
||||
@wrap(Cbor2Converter)
|
||||
def make_converter(*args: Any, **kwargs: Any) -> Cbor2Converter:
|
||||
kwargs["unstruct_collection_overrides"] = {
|
||||
Set: list,
|
||||
**kwargs.get("unstruct_collection_overrides", {}),
|
||||
}
|
||||
res = Cbor2Converter(*args, **kwargs)
|
||||
configure_converter(res)
|
||||
|
||||
return res
|
||||
@@ -0,0 +1,69 @@
|
||||
"""Preconfigured converters for the stdlib json."""
|
||||
|
||||
from base64 import b85decode, b85encode
|
||||
from collections.abc import Set
|
||||
from datetime import date, datetime
|
||||
from json import dumps, loads
|
||||
from typing import Any, TypeVar, Union
|
||||
|
||||
from .._compat import Counter
|
||||
from ..converters import BaseConverter, Converter
|
||||
from ..fns import identity
|
||||
from ..literals import is_literal_containing_enums
|
||||
from ..strategies import configure_union_passthrough
|
||||
from . import is_primitive_enum, literals_with_enums_unstructure_factory, wrap
|
||||
|
||||
__all__ = ["JsonConverter", "configure_converter", "make_converter"]
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class JsonConverter(Converter):
|
||||
def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str:
|
||||
return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs)
|
||||
|
||||
def loads(self, data: Union[bytes, str], cl: type[T], **kwargs: Any) -> T:
|
||||
return self.structure(loads(data, **kwargs), cl)
|
||||
|
||||
|
||||
def configure_converter(converter: BaseConverter) -> None:
|
||||
"""
|
||||
Configure the converter for use with the stdlib json module.
|
||||
|
||||
* bytes are serialized as base85 strings
|
||||
* datetimes are serialized as ISO 8601
|
||||
* counters are serialized as dicts
|
||||
* sets are serialized as lists
|
||||
* string and int enums are passed through when unstructuring
|
||||
* union passthrough is configured for unions of strings, bools, ints,
|
||||
floats and None
|
||||
|
||||
.. versionchanged:: 24.2.0
|
||||
Enums are left to the library to unstructure, speeding them up.
|
||||
"""
|
||||
converter.register_unstructure_hook(
|
||||
bytes, lambda v: (b85encode(v) if v else b"").decode("utf8")
|
||||
)
|
||||
converter.register_structure_hook(bytes, lambda v, _: b85decode(v))
|
||||
converter.register_unstructure_hook(datetime, lambda v: v.isoformat())
|
||||
converter.register_structure_hook(datetime, lambda v, _: datetime.fromisoformat(v))
|
||||
converter.register_unstructure_hook(date, lambda v: v.isoformat())
|
||||
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
||||
converter.register_unstructure_hook_factory(
|
||||
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
||||
)
|
||||
converter.register_unstructure_hook_func(is_primitive_enum, identity)
|
||||
configure_union_passthrough(Union[str, bool, int, float, None], converter)
|
||||
|
||||
|
||||
@wrap(JsonConverter)
|
||||
def make_converter(*args: Any, **kwargs: Any) -> JsonConverter:
|
||||
kwargs["unstruct_collection_overrides"] = {
|
||||
Set: list,
|
||||
Counter: dict,
|
||||
**kwargs.get("unstruct_collection_overrides", {}),
|
||||
}
|
||||
res = JsonConverter(*args, **kwargs)
|
||||
configure_converter(res)
|
||||
|
||||
return res
|
||||
@@ -0,0 +1,65 @@
|
||||
"""Preconfigured converters for msgpack."""
|
||||
|
||||
from collections.abc import Set
|
||||
from datetime import date, datetime, time, timezone
|
||||
from typing import Any, TypeVar, Union
|
||||
|
||||
from msgpack import dumps, loads
|
||||
|
||||
from ..converters import BaseConverter, Converter
|
||||
from ..fns import identity
|
||||
from ..literals import is_literal_containing_enums
|
||||
from ..strategies import configure_union_passthrough
|
||||
from . import is_primitive_enum, literals_with_enums_unstructure_factory, wrap
|
||||
|
||||
__all__ = ["MsgpackConverter", "configure_converter", "make_converter"]
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class MsgpackConverter(Converter):
|
||||
def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> bytes:
|
||||
return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs)
|
||||
|
||||
def loads(self, data: bytes, cl: type[T], **kwargs: Any) -> T:
|
||||
return self.structure(loads(data, **kwargs), cl)
|
||||
|
||||
|
||||
def configure_converter(converter: BaseConverter) -> None:
|
||||
"""
|
||||
Configure the converter for use with the msgpack library.
|
||||
|
||||
* datetimes are serialized as timestamp floats
|
||||
* sets are serialized as lists
|
||||
* string and int enums are passed through when unstructuring
|
||||
|
||||
.. versionchanged:: 24.2.0
|
||||
Enums are left to the library to unstructure, speeding them up.
|
||||
"""
|
||||
converter.register_unstructure_hook(datetime, lambda v: v.timestamp())
|
||||
converter.register_structure_hook(
|
||||
datetime, lambda v, _: datetime.fromtimestamp(v, timezone.utc)
|
||||
)
|
||||
converter.register_unstructure_hook(
|
||||
date, lambda v: datetime.combine(v, time(tzinfo=timezone.utc)).timestamp()
|
||||
)
|
||||
converter.register_structure_hook(
|
||||
date, lambda v, _: datetime.fromtimestamp(v, timezone.utc).date()
|
||||
)
|
||||
converter.register_unstructure_hook_func(is_primitive_enum, identity)
|
||||
converter.register_unstructure_hook_factory(
|
||||
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
||||
)
|
||||
configure_union_passthrough(Union[str, bool, int, float, None, bytes], converter)
|
||||
|
||||
|
||||
@wrap(MsgpackConverter)
|
||||
def make_converter(*args: Any, **kwargs: Any) -> MsgpackConverter:
|
||||
kwargs["unstruct_collection_overrides"] = {
|
||||
Set: list,
|
||||
**kwargs.get("unstruct_collection_overrides", {}),
|
||||
}
|
||||
res = MsgpackConverter(*args, **kwargs)
|
||||
configure_converter(res)
|
||||
|
||||
return res
|
||||
@@ -0,0 +1,202 @@
|
||||
"""Preconfigured converters for msgspec."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from base64 import b64decode
|
||||
from dataclasses import is_dataclass
|
||||
from datetime import date, datetime
|
||||
from enum import Enum
|
||||
from functools import partial
|
||||
from typing import Any, Callable, TypeVar, Union, get_type_hints
|
||||
|
||||
from attrs import has as attrs_has
|
||||
from attrs import resolve_types
|
||||
from msgspec import Struct, convert, to_builtins
|
||||
from msgspec.json import Encoder, decode
|
||||
|
||||
from .._compat import fields, get_args, get_origin, is_bare, is_mapping, is_sequence
|
||||
from ..cols import is_namedtuple
|
||||
from ..converters import BaseConverter, Converter
|
||||
from ..dispatch import UnstructureHook
|
||||
from ..fns import identity
|
||||
from ..gen import make_hetero_tuple_unstructure_fn
|
||||
from ..literals import is_literal_containing_enums
|
||||
from ..strategies import configure_union_passthrough
|
||||
from . import literals_with_enums_unstructure_factory, wrap
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
__all__ = ["MsgspecJsonConverter", "configure_converter", "make_converter"]
|
||||
|
||||
|
||||
class MsgspecJsonConverter(Converter):
|
||||
"""A converter specialized for the _msgspec_ library."""
|
||||
|
||||
#: The msgspec encoder for dumping.
|
||||
encoder: Encoder = Encoder()
|
||||
|
||||
def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> bytes:
|
||||
"""Unstructure and encode `obj` into JSON bytes."""
|
||||
return self.encoder.encode(
|
||||
self.unstructure(obj, unstructure_as=unstructure_as), **kwargs
|
||||
)
|
||||
|
||||
def get_dumps_hook(
|
||||
self, unstructure_as: Any, **kwargs: Any
|
||||
) -> Callable[[Any], bytes]:
|
||||
"""Produce a `dumps` hook for the given type."""
|
||||
unstruct_hook = self.get_unstructure_hook(unstructure_as)
|
||||
if unstruct_hook in (identity, to_builtins):
|
||||
return self.encoder.encode
|
||||
return self.dumps
|
||||
|
||||
def loads(self, data: bytes, cl: type[T], **kwargs: Any) -> T:
|
||||
"""Decode and structure `cl` from the provided JSON bytes."""
|
||||
return self.structure(decode(data, **kwargs), cl)
|
||||
|
||||
def get_loads_hook(self, cl: type[T]) -> Callable[[bytes], T]:
|
||||
"""Produce a `loads` hook for the given type."""
|
||||
return partial(self.loads, cl=cl)
|
||||
|
||||
|
||||
def configure_converter(converter: Converter) -> None:
|
||||
"""Configure the converter for the msgspec library.
|
||||
|
||||
* bytes are serialized as base64 strings, directly by msgspec
|
||||
* datetimes and dates are passed through to be serialized as RFC 3339 directly
|
||||
* enums are passed through to msgspec directly
|
||||
* union passthrough configured for str, bool, int, float and None
|
||||
* bare, string and int enums are passed through when unstructuring
|
||||
|
||||
.. versionchanged:: 24.2.0
|
||||
Enums are left to the library to unstructure, speeding them up.
|
||||
"""
|
||||
configure_passthroughs(converter)
|
||||
|
||||
converter.register_unstructure_hook(Struct, to_builtins)
|
||||
converter.register_unstructure_hook(Enum, identity)
|
||||
|
||||
converter.register_structure_hook(Struct, convert)
|
||||
converter.register_structure_hook(bytes, lambda v, _: b64decode(v))
|
||||
converter.register_structure_hook(datetime, lambda v, _: convert(v, datetime))
|
||||
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
||||
converter.register_unstructure_hook_factory(
|
||||
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
||||
)
|
||||
configure_union_passthrough(Union[str, bool, int, float, None], converter)
|
||||
|
||||
|
||||
@wrap(MsgspecJsonConverter)
|
||||
def make_converter(*args: Any, **kwargs: Any) -> MsgspecJsonConverter:
|
||||
res = MsgspecJsonConverter(*args, **kwargs)
|
||||
configure_converter(res)
|
||||
return res
|
||||
|
||||
|
||||
def configure_passthroughs(converter: Converter) -> None:
|
||||
"""Configure optimizing passthroughs.
|
||||
|
||||
A passthrough is when we let msgspec handle something automatically.
|
||||
|
||||
.. versionchanged:: 25.1.0
|
||||
Dataclasses with private attributes are now passed through.
|
||||
"""
|
||||
converter.register_unstructure_hook(bytes, to_builtins)
|
||||
converter.register_unstructure_hook_factory(is_mapping, mapping_unstructure_factory)
|
||||
converter.register_unstructure_hook_factory(is_sequence, seq_unstructure_factory)
|
||||
converter.register_unstructure_hook_factory(
|
||||
attrs_has, msgspec_attrs_unstructure_factory
|
||||
)
|
||||
converter.register_unstructure_hook_factory(
|
||||
is_dataclass,
|
||||
partial(msgspec_attrs_unstructure_factory, msgspec_skips_private=False),
|
||||
)
|
||||
converter.register_unstructure_hook_factory(
|
||||
is_namedtuple, namedtuple_unstructure_factory
|
||||
)
|
||||
|
||||
|
||||
def seq_unstructure_factory(type, converter: Converter) -> UnstructureHook:
|
||||
"""The msgspec unstructure hook factory for sequences."""
|
||||
if is_bare(type):
|
||||
type_arg = Any
|
||||
else:
|
||||
args = get_args(type)
|
||||
type_arg = args[0]
|
||||
handler = converter.get_unstructure_hook(type_arg, cache_result=False)
|
||||
|
||||
if handler in (identity, to_builtins):
|
||||
return handler
|
||||
return converter.gen_unstructure_iterable(type)
|
||||
|
||||
|
||||
def mapping_unstructure_factory(type, converter: BaseConverter) -> UnstructureHook:
|
||||
"""The msgspec unstructure hook factory for mappings."""
|
||||
if is_bare(type):
|
||||
key_arg = Any
|
||||
val_arg = Any
|
||||
key_handler = converter.get_unstructure_hook(key_arg, cache_result=False)
|
||||
value_handler = converter.get_unstructure_hook(val_arg, cache_result=False)
|
||||
else:
|
||||
args = get_args(type)
|
||||
if len(args) == 2:
|
||||
key_arg, val_arg = args
|
||||
else:
|
||||
# Probably a Counter
|
||||
key_arg, val_arg = args, Any
|
||||
key_handler = converter.get_unstructure_hook(key_arg, cache_result=False)
|
||||
value_handler = converter.get_unstructure_hook(val_arg, cache_result=False)
|
||||
|
||||
if key_handler in (identity, to_builtins) and value_handler in (
|
||||
identity,
|
||||
to_builtins,
|
||||
):
|
||||
return to_builtins
|
||||
return converter.gen_unstructure_mapping(type)
|
||||
|
||||
|
||||
def msgspec_attrs_unstructure_factory(
|
||||
type: Any, converter: Converter, msgspec_skips_private: bool = True
|
||||
) -> UnstructureHook:
|
||||
"""Choose whether to use msgspec handling or our own.
|
||||
|
||||
Args:
|
||||
msgspec_skips_private: Whether the msgspec library skips unstructuring
|
||||
private attributes, making us do the work.
|
||||
"""
|
||||
origin = get_origin(type)
|
||||
attribs = fields(origin or type)
|
||||
if attrs_has(type) and any(isinstance(a.type, str) for a in attribs):
|
||||
resolve_types(type)
|
||||
attribs = fields(origin or type)
|
||||
|
||||
if msgspec_skips_private and any(
|
||||
attr.name.startswith("_")
|
||||
or (
|
||||
converter.get_unstructure_hook(attr.type, cache_result=False)
|
||||
not in (identity, to_builtins)
|
||||
)
|
||||
for attr in attribs
|
||||
):
|
||||
return converter.gen_unstructure_attrs_fromdict(type)
|
||||
|
||||
return to_builtins
|
||||
|
||||
|
||||
def namedtuple_unstructure_factory(
|
||||
type: type[tuple], converter: BaseConverter
|
||||
) -> UnstructureHook:
|
||||
"""A hook factory for unstructuring namedtuples, modified for msgspec."""
|
||||
|
||||
if all(
|
||||
converter.get_unstructure_hook(t) in (identity, to_builtins)
|
||||
for t in get_type_hints(type).values()
|
||||
):
|
||||
return identity
|
||||
|
||||
return make_hetero_tuple_unstructure_fn(
|
||||
type,
|
||||
converter,
|
||||
unstructure_to=tuple,
|
||||
type_args=tuple(get_type_hints(type).values()),
|
||||
)
|
||||
@@ -0,0 +1,108 @@
|
||||
"""Preconfigured converters for orjson."""
|
||||
|
||||
from base64 import b85decode, b85encode
|
||||
from collections.abc import Set
|
||||
from datetime import date, datetime
|
||||
from enum import Enum
|
||||
from functools import partial
|
||||
from typing import Any, TypeVar, Union
|
||||
|
||||
from orjson import dumps, loads
|
||||
|
||||
from .._compat import is_subclass
|
||||
from ..cols import is_mapping, is_namedtuple, namedtuple_unstructure_factory
|
||||
from ..converters import Converter
|
||||
from ..fns import identity
|
||||
from ..literals import is_literal_containing_enums
|
||||
from ..strategies import configure_union_passthrough
|
||||
from . import is_primitive_enum, literals_with_enums_unstructure_factory, wrap
|
||||
|
||||
__all__ = ["OrjsonConverter", "configure_converter", "make_converter"]
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class OrjsonConverter(Converter):
|
||||
def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> bytes:
|
||||
return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs)
|
||||
|
||||
def loads(self, data: Union[bytes, bytearray, memoryview, str], cl: type[T]) -> T:
|
||||
return self.structure(loads(data), cl)
|
||||
|
||||
|
||||
def configure_converter(converter: Converter) -> None:
|
||||
"""
|
||||
Configure the converter for use with the orjson library.
|
||||
|
||||
* bytes are serialized as base85 strings
|
||||
* datetimes and dates are passed through to be serialized as RFC 3339 by orjson
|
||||
* typed namedtuples are serialized as lists
|
||||
* sets are serialized as lists
|
||||
* string enum mapping keys have special handling
|
||||
* mapping keys are coerced into strings when unstructuring
|
||||
* bare, string and int enums are passed through when unstructuring
|
||||
|
||||
.. versionchanged:: 24.1.0
|
||||
Add support for typed namedtuples.
|
||||
.. versionchanged:: 24.2.0
|
||||
Enums are left to the library to unstructure, speeding them up.
|
||||
"""
|
||||
converter.register_unstructure_hook(
|
||||
bytes, lambda v: (b85encode(v) if v else b"").decode("utf8")
|
||||
)
|
||||
converter.register_structure_hook(bytes, lambda v, _: b85decode(v))
|
||||
|
||||
converter.register_structure_hook(datetime, lambda v, _: datetime.fromisoformat(v))
|
||||
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
||||
|
||||
def unstructure_mapping_factory(cl: Any, unstructure_to=None):
|
||||
key_handler = str
|
||||
args = getattr(cl, "__args__", None)
|
||||
if args:
|
||||
if is_subclass(args[0], str) and is_subclass(args[0], Enum):
|
||||
|
||||
def key_handler(v):
|
||||
return v.value
|
||||
|
||||
else:
|
||||
# It's possible the handler for the key type has been overridden.
|
||||
# (For example base85 encoding for bytes.)
|
||||
# In that case, we want to use the override.
|
||||
|
||||
kh = converter.get_unstructure_hook(args[0])
|
||||
if kh != identity:
|
||||
key_handler = kh
|
||||
|
||||
return converter.gen_unstructure_mapping(
|
||||
cl, unstructure_to=unstructure_to, key_handler=key_handler
|
||||
)
|
||||
|
||||
converter._unstructure_func.register_func_list(
|
||||
[
|
||||
(is_mapping, unstructure_mapping_factory, True),
|
||||
(
|
||||
is_namedtuple,
|
||||
partial(namedtuple_unstructure_factory, unstructure_to=tuple),
|
||||
"extended",
|
||||
),
|
||||
]
|
||||
)
|
||||
converter.register_unstructure_hook_func(
|
||||
partial(is_primitive_enum, include_bare_enums=True), identity
|
||||
)
|
||||
converter.register_unstructure_hook_factory(
|
||||
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
||||
)
|
||||
configure_union_passthrough(Union[str, bool, int, float, None], converter)
|
||||
|
||||
|
||||
@wrap(OrjsonConverter)
|
||||
def make_converter(*args: Any, **kwargs: Any) -> OrjsonConverter:
|
||||
kwargs["unstruct_collection_overrides"] = {
|
||||
Set: list,
|
||||
**kwargs.get("unstruct_collection_overrides", {}),
|
||||
}
|
||||
res = OrjsonConverter(*args, **kwargs)
|
||||
configure_converter(res)
|
||||
|
||||
return res
|
||||
@@ -0,0 +1,74 @@
|
||||
"""Preconfigured converters for pyyaml."""
|
||||
|
||||
from datetime import date, datetime
|
||||
from functools import partial
|
||||
from typing import Any, TypeVar, Union
|
||||
|
||||
from yaml import safe_dump, safe_load
|
||||
|
||||
from .._compat import FrozenSetSubscriptable
|
||||
from ..cols import is_namedtuple, namedtuple_unstructure_factory
|
||||
from ..converters import BaseConverter, Converter
|
||||
from ..strategies import configure_union_passthrough
|
||||
from . import validate_datetime, wrap
|
||||
|
||||
__all__ = ["PyyamlConverter", "configure_converter", "make_converter"]
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def validate_date(v: Any, _):
|
||||
if not isinstance(v, date):
|
||||
raise ValueError(f"Expected date, got {v}")
|
||||
return v
|
||||
|
||||
|
||||
class PyyamlConverter(Converter):
|
||||
def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str:
|
||||
return safe_dump(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs)
|
||||
|
||||
def loads(self, data: str, cl: type[T]) -> T:
|
||||
return self.structure(safe_load(data), cl)
|
||||
|
||||
|
||||
def configure_converter(converter: BaseConverter) -> None:
|
||||
"""
|
||||
Configure the converter for use with the pyyaml library.
|
||||
|
||||
* frozensets are serialized as lists
|
||||
* string enums are converted into strings explicitly
|
||||
* datetimes and dates are validated
|
||||
* typed namedtuples are serialized as lists
|
||||
|
||||
.. versionchanged:: 24.1.0
|
||||
Add support for typed namedtuples.
|
||||
"""
|
||||
converter.register_unstructure_hook(
|
||||
str, lambda v: v if v.__class__ is str else v.value
|
||||
)
|
||||
|
||||
# datetime inherits from date, so identity unstructure hook used
|
||||
# here to prevent the date unstructure hook running.
|
||||
converter.register_unstructure_hook(datetime, lambda v: v)
|
||||
converter.register_structure_hook(datetime, validate_datetime)
|
||||
converter.register_structure_hook(date, validate_date)
|
||||
|
||||
converter.register_unstructure_hook_factory(is_namedtuple)(
|
||||
partial(namedtuple_unstructure_factory, unstructure_to=tuple)
|
||||
)
|
||||
|
||||
configure_union_passthrough(
|
||||
Union[str, bool, int, float, None, bytes, datetime, date], converter
|
||||
)
|
||||
|
||||
|
||||
@wrap(PyyamlConverter)
|
||||
def make_converter(*args: Any, **kwargs: Any) -> PyyamlConverter:
|
||||
kwargs["unstruct_collection_overrides"] = {
|
||||
FrozenSetSubscriptable: list,
|
||||
**kwargs.get("unstruct_collection_overrides", {}),
|
||||
}
|
||||
res = PyyamlConverter(*args, **kwargs)
|
||||
configure_converter(res)
|
||||
|
||||
return res
|
||||
@@ -0,0 +1,89 @@
|
||||
"""Preconfigured converters for tomlkit."""
|
||||
|
||||
from base64 import b85decode, b85encode
|
||||
from collections.abc import Set
|
||||
from datetime import date, datetime
|
||||
from enum import Enum
|
||||
from operator import attrgetter
|
||||
from typing import Any, TypeVar, Union
|
||||
|
||||
from tomlkit import dumps, loads
|
||||
from tomlkit.items import Float, Integer, String
|
||||
|
||||
from .._compat import is_mapping, is_subclass
|
||||
from ..converters import BaseConverter, Converter
|
||||
from ..strategies import configure_union_passthrough
|
||||
from . import validate_datetime, wrap
|
||||
|
||||
__all__ = ["TomlkitConverter", "configure_converter", "make_converter"]
|
||||
|
||||
T = TypeVar("T")
|
||||
_enum_value_getter = attrgetter("_value_")
|
||||
|
||||
|
||||
class TomlkitConverter(Converter):
|
||||
def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str:
|
||||
return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs)
|
||||
|
||||
def loads(self, data: str, cl: type[T]) -> T:
|
||||
return self.structure(loads(data), cl)
|
||||
|
||||
|
||||
def configure_converter(converter: BaseConverter):
|
||||
"""
|
||||
Configure the converter for use with the tomlkit library.
|
||||
|
||||
* bytes are serialized as base85 strings
|
||||
* sets are serialized as lists
|
||||
* tuples are serializas as lists
|
||||
* mapping keys are coerced into strings when unstructuring
|
||||
"""
|
||||
converter.register_structure_hook(bytes, lambda v, _: b85decode(v))
|
||||
converter.register_unstructure_hook(
|
||||
bytes, lambda v: (b85encode(v) if v else b"").decode("utf8")
|
||||
)
|
||||
|
||||
def gen_unstructure_mapping(cl: Any, unstructure_to=None):
|
||||
key_handler = str
|
||||
args = getattr(cl, "__args__", None)
|
||||
if args:
|
||||
# Currently, tomlkit has inconsistent behavior on 3.11
|
||||
# so we paper over it here.
|
||||
# https://github.com/sdispater/tomlkit/issues/237
|
||||
if is_subclass(args[0], str):
|
||||
key_handler = _enum_value_getter if is_subclass(args[0], Enum) else None
|
||||
elif is_subclass(args[0], bytes):
|
||||
|
||||
def key_handler(k: bytes):
|
||||
return b85encode(k).decode("utf8")
|
||||
|
||||
return converter.gen_unstructure_mapping(
|
||||
cl, unstructure_to=unstructure_to, key_handler=key_handler
|
||||
)
|
||||
|
||||
converter._unstructure_func.register_func_list(
|
||||
[(is_mapping, gen_unstructure_mapping, True)]
|
||||
)
|
||||
|
||||
# datetime inherits from date, so identity unstructure hook used
|
||||
# here to prevent the date unstructure hook running.
|
||||
converter.register_unstructure_hook(datetime, lambda v: v)
|
||||
converter.register_structure_hook(datetime, validate_datetime)
|
||||
converter.register_unstructure_hook(date, lambda v: v.isoformat())
|
||||
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
||||
configure_union_passthrough(
|
||||
Union[str, String, bool, int, Integer, float, Float], converter
|
||||
)
|
||||
|
||||
|
||||
@wrap(TomlkitConverter)
|
||||
def make_converter(*args: Any, **kwargs: Any) -> TomlkitConverter:
|
||||
kwargs["unstruct_collection_overrides"] = {
|
||||
Set: list,
|
||||
tuple: list,
|
||||
**kwargs.get("unstruct_collection_overrides", {}),
|
||||
}
|
||||
res = TomlkitConverter(*args, **kwargs)
|
||||
configure_converter(res)
|
||||
|
||||
return res
|
||||
@@ -0,0 +1,66 @@
|
||||
"""Preconfigured converters for ujson."""
|
||||
|
||||
from base64 import b85decode, b85encode
|
||||
from collections.abc import Set
|
||||
from datetime import date, datetime
|
||||
from typing import Any, AnyStr, TypeVar, Union
|
||||
|
||||
from ujson import dumps, loads
|
||||
|
||||
from ..converters import BaseConverter, Converter
|
||||
from ..fns import identity
|
||||
from ..literals import is_literal_containing_enums
|
||||
from ..strategies import configure_union_passthrough
|
||||
from . import is_primitive_enum, literals_with_enums_unstructure_factory, wrap
|
||||
|
||||
__all__ = ["UjsonConverter", "configure_converter", "make_converter"]
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class UjsonConverter(Converter):
|
||||
def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str:
|
||||
return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs)
|
||||
|
||||
def loads(self, data: AnyStr, cl: type[T], **kwargs: Any) -> T:
|
||||
return self.structure(loads(data, **kwargs), cl)
|
||||
|
||||
|
||||
def configure_converter(converter: BaseConverter):
|
||||
"""
|
||||
Configure the converter for use with the ujson library.
|
||||
|
||||
* bytes are serialized as base64 strings
|
||||
* datetimes are serialized as ISO 8601
|
||||
* sets are serialized as lists
|
||||
* string and int enums are passed through when unstructuring
|
||||
|
||||
.. versionchanged:: 24.2.0
|
||||
Enums are left to the library to unstructure, speeding them up.
|
||||
"""
|
||||
converter.register_unstructure_hook(
|
||||
bytes, lambda v: (b85encode(v) if v else b"").decode("utf8")
|
||||
)
|
||||
converter.register_structure_hook(bytes, lambda v, _: b85decode(v))
|
||||
|
||||
converter.register_unstructure_hook(datetime, lambda v: v.isoformat())
|
||||
converter.register_structure_hook(datetime, lambda v, _: datetime.fromisoformat(v))
|
||||
converter.register_unstructure_hook(date, lambda v: v.isoformat())
|
||||
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
||||
converter.register_unstructure_hook_func(is_primitive_enum, identity)
|
||||
converter.register_unstructure_hook_factory(
|
||||
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
||||
)
|
||||
configure_union_passthrough(Union[str, bool, int, float, None], converter)
|
||||
|
||||
|
||||
@wrap(UjsonConverter)
|
||||
def make_converter(*args: Any, **kwargs: Any) -> UjsonConverter:
|
||||
kwargs["unstruct_collection_overrides"] = {
|
||||
Set: list,
|
||||
**kwargs.get("unstruct_collection_overrides", {}),
|
||||
}
|
||||
res = UjsonConverter(*args, **kwargs)
|
||||
configure_converter(res)
|
||||
|
||||
return res
|
||||
Reference in New Issue
Block a user