Skip to content

Commit

Permalink
Fix preconf mapping optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
Tinche committed Nov 12, 2024
1 parent 8fe5373 commit be25733
Show file tree
Hide file tree
Showing 11 changed files with 81 additions and 54 deletions.
2 changes: 2 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Our backwards-compatibility policy can be found [here](https://github.com/python
([#598](https://github.com/python-attrs/cattrs/pull/598))
- Literals containing enums are now unstructured properly, and their unstructuring is greatly optimized in the _bson_, stdlib JSON, _cbor2_, _msgpack_, _msgspec_, _orjson_ and _ujson_ preconf converters.
([#598](https://github.com/python-attrs/cattrs/pull/598))
- Preconf converters now handle dictionaries with literal keys properly.
([#599](https://github.com/python-attrs/cattrs/pull/599))
- Replace `cattrs.gen.MappingStructureFn` with `cattrs.SimpleStructureHook[In, T]`.
- Python 3.13 is now supported.
([#543](https://github.com/python-attrs/cattrs/pull/543) [#547](https://github.com/python-attrs/cattrs/issues/547))
Expand Down
1 change: 0 additions & 1 deletion src/cattrs/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,6 @@ def is_literal(_) -> bool:


Set = AbcSet
AbstractSet = AbcSet
MutableSet = AbcMutableSet
Sequence = AbcSequence
MutableSequence = AbcMutableSequence
Expand Down
18 changes: 9 additions & 9 deletions src/cattrs/preconf/bson.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""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 cattrs._compat import AbstractSet, is_mapping
from cattrs.gen import make_mapping_structure_fn

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
Expand Down Expand Up @@ -69,9 +69,9 @@ def gen_unstructure_mapping(cl: Any, unstructure_to=None):
key_handler = str
args = getattr(cl, "__args__", None)
if args:
if issubclass(args[0], str):
if is_subclass(args[0], str):
key_handler = None
elif issubclass(args[0], bytes):
elif is_subclass(args[0], bytes):

def key_handler(k):
return b85encode(k).decode("utf8")
Expand All @@ -82,10 +82,10 @@ def key_handler(k):

def gen_structure_mapping(cl: Any) -> StructureHook:
args = getattr(cl, "__args__", None)
if args and issubclass(args[0], bytes):
h = make_mapping_structure_fn(cl, converter, key_type=Base85Bytes)
if args and is_subclass(args[0], bytes):
h = mapping_structure_factory(cl, converter, key_type=Base85Bytes)
else:
h = make_mapping_structure_fn(cl, converter)
h = mapping_structure_factory(cl, converter)
return h

converter.register_structure_hook(Base85Bytes, lambda v, _: b85decode(v))
Expand All @@ -112,7 +112,7 @@ def gen_structure_mapping(cl: Any) -> StructureHook:
@wrap(BsonConverter)
def make_converter(*args: Any, **kwargs: Any) -> BsonConverter:
kwargs["unstruct_collection_overrides"] = {
AbstractSet: list,
Set: list,
**kwargs.get("unstruct_collection_overrides", {}),
}
res = BsonConverter(*args, **kwargs)
Expand Down
5 changes: 2 additions & 3 deletions src/cattrs/preconf/cbor2.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
"""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 cattrs._compat import AbstractSet

from ..converters import BaseConverter, Converter
from ..fns import identity
from ..literals import is_literal_containing_enums
Expand Down Expand Up @@ -48,7 +47,7 @@ def configure_converter(converter: BaseConverter):
@wrap(Cbor2Converter)
def make_converter(*args: Any, **kwargs: Any) -> Cbor2Converter:
kwargs["unstruct_collection_overrides"] = {
AbstractSet: list,
Set: list,
**kwargs.get("unstruct_collection_overrides", {}),
}
res = Cbor2Converter(*args, **kwargs)
Expand Down
5 changes: 3 additions & 2 deletions src/cattrs/preconf/json.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"""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 AbstractSet, Counter
from .._compat import Counter
from ..converters import BaseConverter, Converter
from ..fns import identity
from ..literals import is_literal_containing_enums
Expand Down Expand Up @@ -56,7 +57,7 @@ def configure_converter(converter: BaseConverter):
@wrap(JsonConverter)
def make_converter(*args: Any, **kwargs: Any) -> JsonConverter:
kwargs["unstruct_collection_overrides"] = {
AbstractSet: list,
Set: list,
Counter: dict,
**kwargs.get("unstruct_collection_overrides", {}),
}
Expand Down
5 changes: 2 additions & 3 deletions src/cattrs/preconf/msgpack.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
"""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 cattrs._compat import AbstractSet

from ..converters import BaseConverter, Converter
from ..fns import identity
from ..literals import is_literal_containing_enums
Expand Down Expand Up @@ -55,7 +54,7 @@ def configure_converter(converter: BaseConverter):
@wrap(MsgpackConverter)
def make_converter(*args: Any, **kwargs: Any) -> MsgpackConverter:
kwargs["unstruct_collection_overrides"] = {
AbstractSet: list,
Set: list,
**kwargs.get("unstruct_collection_overrides", {}),
}
res = MsgpackConverter(*args, **kwargs)
Expand Down
9 changes: 5 additions & 4 deletions src/cattrs/preconf/orjson.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
"""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 AbstractSet, is_mapping
from ..cols import is_namedtuple, namedtuple_unstructure_factory
from .._compat import is_subclass
from ..cols import is_mapping, is_namedtuple, namedtuple_unstructure_factory
from ..converters import BaseConverter, Converter
from ..fns import identity
from ..literals import is_literal_containing_enums
Expand Down Expand Up @@ -56,7 +57,7 @@ def gen_unstructure_mapping(cl: Any, unstructure_to=None):
key_handler = str
args = getattr(cl, "__args__", None)
if args:
if issubclass(args[0], str) and issubclass(args[0], Enum):
if is_subclass(args[0], str) and is_subclass(args[0], Enum):

def key_handler(v):
return v.value
Expand Down Expand Up @@ -96,7 +97,7 @@ def key_handler(v):
@wrap(OrjsonConverter)
def make_converter(*args: Any, **kwargs: Any) -> OrjsonConverter:
kwargs["unstruct_collection_overrides"] = {
AbstractSet: list,
Set: list,
**kwargs.get("unstruct_collection_overrides", {}),
}
res = OrjsonConverter(*args, **kwargs)
Expand Down
12 changes: 6 additions & 6 deletions src/cattrs/preconf/tomlkit.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""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
Expand All @@ -9,8 +10,7 @@
from tomlkit import dumps, loads
from tomlkit.items import Float, Integer, String

from cattrs._compat import AbstractSet, is_mapping

from .._compat import is_mapping, is_subclass
from ..converters import BaseConverter, Converter
from ..strategies import configure_union_passthrough
from . import validate_datetime, wrap
Expand Down Expand Up @@ -48,9 +48,9 @@ def gen_unstructure_mapping(cl: Any, unstructure_to=None):
# Currently, tomlkit has inconsistent behavior on 3.11
# so we paper over it here.
# https://github.com/sdispater/tomlkit/issues/237
if issubclass(args[0], str):
key_handler = _enum_value_getter if issubclass(args[0], Enum) else None
elif issubclass(args[0], bytes):
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")
Expand All @@ -77,7 +77,7 @@ def key_handler(k: bytes):
@wrap(TomlkitConverter)
def make_converter(*args: Any, **kwargs: Any) -> TomlkitConverter:
kwargs["unstruct_collection_overrides"] = {
AbstractSet: list,
Set: list,
tuple: list,
**kwargs.get("unstruct_collection_overrides", {}),
}
Expand Down
4 changes: 2 additions & 2 deletions src/cattrs/preconf/ujson.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""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 .._compat import AbstractSet
from ..converters import BaseConverter, Converter
from ..fns import identity
from ..literals import is_literal_containing_enums
Expand Down Expand Up @@ -55,7 +55,7 @@ def configure_converter(converter: BaseConverter):
@wrap(UjsonConverter)
def make_converter(*args: Any, **kwargs: Any) -> UjsonConverter:
kwargs["unstruct_collection_overrides"] = {
AbstractSet: list,
Set: list,
**kwargs.get("unstruct_collection_overrides", {}),
}
res = UjsonConverter(*args, **kwargs)
Expand Down
5 changes: 3 additions & 2 deletions tests/test_cols.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"""Tests for the `cattrs.cols` module."""

from collections.abc import Set
from typing import Dict

from immutables import Map

from cattrs import BaseConverter, Converter
from cattrs._compat import AbstractSet, FrozenSet
from cattrs._compat import FrozenSet
from cattrs.cols import (
is_any_set,
iterable_unstructure_factory,
Expand All @@ -23,7 +24,7 @@ def test_set_overriding(converter: BaseConverter):
lambda t, c: iterable_unstructure_factory(t, c, unstructure_to=sorted),
)

assert converter.unstructure({"c", "b", "a"}, AbstractSet[str]) == ["a", "b", "c"]
assert converter.unstructure({"c", "b", "a"}, Set[str]) == ["a", "b", "c"]
assert converter.unstructure(frozenset(["c", "b", "a"]), FrozenSet[str]) == [
"a",
"b",
Expand Down
Loading

0 comments on commit be25733

Please sign in to comment.