python lsp

This commit is contained in:
Christoph Brandau
2025-07-16 17:14:09 +02:00
parent 9febcebddb
commit 99718eb856
194 changed files with 52506 additions and 116 deletions
+55
View File
@@ -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
+121
View File
@@ -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
+56
View File
@@ -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
+69
View File
@@ -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
+65
View File
@@ -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
+202
View File
@@ -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()),
)
+108
View File
@@ -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
+74
View File
@@ -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
+89
View File
@@ -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
+66
View File
@@ -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