Skip to content

Commit af5b177

Browse files
committed
Fix preconf mapping optimizations
1 parent 8fe5373 commit af5b177

File tree

9 files changed

+76
-52
lines changed

9 files changed

+76
-52
lines changed

src/cattrs/_compat.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,6 @@ def is_literal(_) -> bool:
252252

253253

254254
Set = AbcSet
255-
AbstractSet = AbcSet
256255
MutableSet = AbcMutableSet
257256
Sequence = AbcSequence
258257
MutableSequence = AbcMutableSequence

src/cattrs/preconf/bson.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
"""Preconfigured converters for bson."""
22

33
from base64 import b85decode, b85encode
4+
from collections.abc import Set
45
from datetime import date, datetime
56
from typing import Any, TypeVar, Union
67

78
from bson import DEFAULT_CODEC_OPTIONS, CodecOptions, Int64, ObjectId, decode, encode
89

9-
from cattrs._compat import AbstractSet, is_mapping
10-
from cattrs.gen import make_mapping_structure_fn
11-
10+
from .._compat import is_mapping, is_subclass
11+
from ..cols import mapping_structure_factory
1212
from ..converters import BaseConverter, Converter
1313
from ..dispatch import StructureHook
1414
from ..fns import identity
@@ -69,9 +69,9 @@ def gen_unstructure_mapping(cl: Any, unstructure_to=None):
6969
key_handler = str
7070
args = getattr(cl, "__args__", None)
7171
if args:
72-
if issubclass(args[0], str):
72+
if is_subclass(args[0], str):
7373
key_handler = None
74-
elif issubclass(args[0], bytes):
74+
elif is_subclass(args[0], bytes):
7575

7676
def key_handler(k):
7777
return b85encode(k).decode("utf8")
@@ -82,10 +82,10 @@ def key_handler(k):
8282

8383
def gen_structure_mapping(cl: Any) -> StructureHook:
8484
args = getattr(cl, "__args__", None)
85-
if args and issubclass(args[0], bytes):
86-
h = make_mapping_structure_fn(cl, converter, key_type=Base85Bytes)
85+
if args and is_subclass(args[0], bytes):
86+
h = mapping_structure_factory(cl, converter, key_type=Base85Bytes)
8787
else:
88-
h = make_mapping_structure_fn(cl, converter)
88+
h = mapping_structure_factory(cl, converter)
8989
return h
9090

9191
converter.register_structure_hook(Base85Bytes, lambda v, _: b85decode(v))
@@ -112,7 +112,7 @@ def gen_structure_mapping(cl: Any) -> StructureHook:
112112
@wrap(BsonConverter)
113113
def make_converter(*args: Any, **kwargs: Any) -> BsonConverter:
114114
kwargs["unstruct_collection_overrides"] = {
115-
AbstractSet: list,
115+
Set: list,
116116
**kwargs.get("unstruct_collection_overrides", {}),
117117
}
118118
res = BsonConverter(*args, **kwargs)

src/cattrs/preconf/cbor2.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
"""Preconfigured converters for cbor2."""
22

3+
from collections.abc import Set
34
from datetime import date, datetime, timezone
45
from typing import Any, TypeVar, Union
56

67
from cbor2 import dumps, loads
78

8-
from cattrs._compat import AbstractSet
9-
109
from ..converters import BaseConverter, Converter
1110
from ..fns import identity
1211
from ..literals import is_literal_containing_enums
@@ -48,7 +47,7 @@ def configure_converter(converter: BaseConverter):
4847
@wrap(Cbor2Converter)
4948
def make_converter(*args: Any, **kwargs: Any) -> Cbor2Converter:
5049
kwargs["unstruct_collection_overrides"] = {
51-
AbstractSet: list,
50+
Set: list,
5251
**kwargs.get("unstruct_collection_overrides", {}),
5352
}
5453
res = Cbor2Converter(*args, **kwargs)

src/cattrs/preconf/json.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
"""Preconfigured converters for the stdlib json."""
22

33
from base64 import b85decode, b85encode
4+
from collections.abc import Set
45
from datetime import date, datetime
56
from json import dumps, loads
67
from typing import Any, TypeVar, Union
78

8-
from .._compat import AbstractSet, Counter
9+
from .._compat import Counter
910
from ..converters import BaseConverter, Converter
1011
from ..fns import identity
1112
from ..literals import is_literal_containing_enums
@@ -56,7 +57,7 @@ def configure_converter(converter: BaseConverter):
5657
@wrap(JsonConverter)
5758
def make_converter(*args: Any, **kwargs: Any) -> JsonConverter:
5859
kwargs["unstruct_collection_overrides"] = {
59-
AbstractSet: list,
60+
Set: list,
6061
Counter: dict,
6162
**kwargs.get("unstruct_collection_overrides", {}),
6263
}

src/cattrs/preconf/msgpack.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
"""Preconfigured converters for msgpack."""
22

3+
from collections.abc import Set
34
from datetime import date, datetime, time, timezone
45
from typing import Any, TypeVar, Union
56

67
from msgpack import dumps, loads
78

8-
from cattrs._compat import AbstractSet
9-
109
from ..converters import BaseConverter, Converter
1110
from ..fns import identity
1211
from ..literals import is_literal_containing_enums
@@ -55,7 +54,7 @@ def configure_converter(converter: BaseConverter):
5554
@wrap(MsgpackConverter)
5655
def make_converter(*args: Any, **kwargs: Any) -> MsgpackConverter:
5756
kwargs["unstruct_collection_overrides"] = {
58-
AbstractSet: list,
57+
Set: list,
5958
**kwargs.get("unstruct_collection_overrides", {}),
6059
}
6160
res = MsgpackConverter(*args, **kwargs)

src/cattrs/preconf/orjson.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
"""Preconfigured converters for orjson."""
22

33
from base64 import b85decode, b85encode
4+
from collections.abc import Set
45
from datetime import date, datetime
56
from enum import Enum
67
from functools import partial
78
from typing import Any, TypeVar, Union
89

910
from orjson import dumps, loads
1011

11-
from .._compat import AbstractSet, is_mapping
12-
from ..cols import is_namedtuple, namedtuple_unstructure_factory
12+
from .._compat import is_subclass
13+
from ..cols import is_mapping, is_namedtuple, namedtuple_unstructure_factory
1314
from ..converters import BaseConverter, Converter
1415
from ..fns import identity
1516
from ..literals import is_literal_containing_enums
@@ -56,7 +57,7 @@ def gen_unstructure_mapping(cl: Any, unstructure_to=None):
5657
key_handler = str
5758
args = getattr(cl, "__args__", None)
5859
if args:
59-
if issubclass(args[0], str) and issubclass(args[0], Enum):
60+
if is_subclass(args[0], str) and is_subclass(args[0], Enum):
6061

6162
def key_handler(v):
6263
return v.value
@@ -96,7 +97,7 @@ def key_handler(v):
9697
@wrap(OrjsonConverter)
9798
def make_converter(*args: Any, **kwargs: Any) -> OrjsonConverter:
9899
kwargs["unstruct_collection_overrides"] = {
99-
AbstractSet: list,
100+
Set: list,
100101
**kwargs.get("unstruct_collection_overrides", {}),
101102
}
102103
res = OrjsonConverter(*args, **kwargs)

src/cattrs/preconf/tomlkit.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Preconfigured converters for tomlkit."""
22

33
from base64 import b85decode, b85encode
4+
from collections.abc import Set
45
from datetime import date, datetime
56
from enum import Enum
67
from operator import attrgetter
@@ -9,8 +10,7 @@
910
from tomlkit import dumps, loads
1011
from tomlkit.items import Float, Integer, String
1112

12-
from cattrs._compat import AbstractSet, is_mapping
13-
13+
from .._compat import is_mapping, is_subclass
1414
from ..converters import BaseConverter, Converter
1515
from ..strategies import configure_union_passthrough
1616
from . import validate_datetime, wrap
@@ -48,9 +48,9 @@ def gen_unstructure_mapping(cl: Any, unstructure_to=None):
4848
# Currently, tomlkit has inconsistent behavior on 3.11
4949
# so we paper over it here.
5050
# https://github.com/sdispater/tomlkit/issues/237
51-
if issubclass(args[0], str):
52-
key_handler = _enum_value_getter if issubclass(args[0], Enum) else None
53-
elif issubclass(args[0], bytes):
51+
if is_subclass(args[0], str):
52+
key_handler = _enum_value_getter if is_subclass(args[0], Enum) else None
53+
elif is_subclass(args[0], bytes):
5454

5555
def key_handler(k: bytes):
5656
return b85encode(k).decode("utf8")
@@ -77,7 +77,7 @@ def key_handler(k: bytes):
7777
@wrap(TomlkitConverter)
7878
def make_converter(*args: Any, **kwargs: Any) -> TomlkitConverter:
7979
kwargs["unstruct_collection_overrides"] = {
80-
AbstractSet: list,
80+
Set: list,
8181
tuple: list,
8282
**kwargs.get("unstruct_collection_overrides", {}),
8383
}

src/cattrs/preconf/ujson.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
"""Preconfigured converters for ujson."""
22

33
from base64 import b85decode, b85encode
4+
from collections.abc import Set
45
from datetime import date, datetime
56
from typing import Any, AnyStr, TypeVar, Union
67

78
from ujson import dumps, loads
89

9-
from .._compat import AbstractSet
1010
from ..converters import BaseConverter, Converter
1111
from ..fns import identity
1212
from ..literals import is_literal_containing_enums
@@ -55,7 +55,7 @@ def configure_converter(converter: BaseConverter):
5555
@wrap(UjsonConverter)
5656
def make_converter(*args: Any, **kwargs: Any) -> UjsonConverter:
5757
kwargs["unstruct_collection_overrides"] = {
58-
AbstractSet: list,
58+
Set: list,
5959
**kwargs.get("unstruct_collection_overrides", {}),
6060
}
6161
res = UjsonConverter(*args, **kwargs)

tests/test_preconf.py

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import sys
2+
from collections.abc import Callable, Set
23
from datetime import date, datetime, timezone
34
from enum import Enum, IntEnum, unique
45
from json import dumps as json_dumps
@@ -31,23 +32,23 @@
3132
text,
3233
)
3334

35+
from cattrs import Converter
3436
from cattrs._compat import (
35-
AbstractSet,
3637
Counter,
3738
FrozenSet,
3839
Mapping,
3940
MutableMapping,
4041
MutableSequence,
4142
MutableSet,
4243
Sequence,
43-
Set,
4444
TupleSubscriptable,
4545
)
4646
from cattrs.fns import identity
4747
from cattrs.preconf.bson import make_converter as bson_make_converter
4848
from cattrs.preconf.cbor2 import make_converter as cbor2_make_converter
4949
from cattrs.preconf.json import make_converter as json_make_converter
5050
from cattrs.preconf.msgpack import make_converter as msgpack_make_converter
51+
from cattrs.preconf.pyyaml import make_converter as pyyaml_make_converter
5152
from cattrs.preconf.tomlkit import make_converter as tomlkit_make_converter
5253
from cattrs.preconf.ujson import make_converter as ujson_make_converter
5354

@@ -301,7 +302,7 @@ def test_stdlib_json_converter(everything: Everything):
301302

302303
@given(everythings())
303304
def test_stdlib_json_converter_unstruct_collection_overrides(everything: Everything):
304-
converter = json_make_converter(unstruct_collection_overrides={AbstractSet: sorted})
305+
converter = json_make_converter(unstruct_collection_overrides={Set: sorted})
305306
raw = converter.unstructure(everything)
306307
assert raw["a_set"] == sorted(raw["a_set"])
307308
assert raw["a_mutable_set"] == sorted(raw["a_mutable_set"])
@@ -395,9 +396,7 @@ def test_ujson_converter(everything: Everything):
395396
)
396397
)
397398
def test_ujson_converter_unstruct_collection_overrides(everything: Everything):
398-
converter = ujson_make_converter(
399-
unstruct_collection_overrides={AbstractSet: sorted}
400-
)
399+
converter = ujson_make_converter(unstruct_collection_overrides={Set: sorted})
401400
raw = converter.unstructure(everything)
402401
assert raw["a_set"] == sorted(raw["a_set"])
403402
assert raw["a_mutable_set"] == sorted(raw["a_mutable_set"])
@@ -484,9 +483,7 @@ def test_orjson_converter(everything: Everything, detailed_validation: bool):
484483
def test_orjson_converter_unstruct_collection_overrides(everything: Everything):
485484
from cattrs.preconf.orjson import make_converter as orjson_make_converter
486485

487-
converter = orjson_make_converter(
488-
unstruct_collection_overrides={AbstractSet: sorted}
489-
)
486+
converter = orjson_make_converter(unstruct_collection_overrides={Set: sorted})
490487
raw = converter.unstructure(everything)
491488
assert raw["a_set"] == sorted(raw["a_set"])
492489
assert raw["a_mutable_set"] == sorted(raw["a_mutable_set"])
@@ -568,9 +565,7 @@ def test_msgpack_converter(everything: Everything):
568565

569566
@given(everythings(min_int=-9223372036854775808, max_int=18446744073709551615))
570567
def test_msgpack_converter_unstruct_collection_overrides(everything: Everything):
571-
converter = msgpack_make_converter(
572-
unstruct_collection_overrides={AbstractSet: sorted}
573-
)
568+
converter = msgpack_make_converter(unstruct_collection_overrides={Set: sorted})
574569
raw = converter.unstructure(everything)
575570
assert raw["a_set"] == sorted(raw["a_set"])
576571
assert raw["a_mutable_set"] == sorted(raw["a_mutable_set"])
@@ -663,7 +658,7 @@ def test_bson_converter(everything: Everything, detailed_validation: bool):
663658
)
664659
)
665660
def test_bson_converter_unstruct_collection_overrides(everything: Everything):
666-
converter = bson_make_converter(unstruct_collection_overrides={AbstractSet: sorted})
661+
converter = bson_make_converter(unstruct_collection_overrides={Set: sorted})
667662
raw = converter.unstructure(everything)
668663
assert raw["a_set"] == sorted(raw["a_set"])
669664
assert raw["a_mutable_set"] == sorted(raw["a_mutable_set"])
@@ -755,9 +750,7 @@ def test_tomlkit_converter(everything: Everything, detailed_validation: bool):
755750
)
756751
)
757752
def test_tomlkit_converter_unstruct_collection_overrides(everything: Everything):
758-
converter = tomlkit_make_converter(
759-
unstruct_collection_overrides={AbstractSet: sorted}
760-
)
753+
converter = tomlkit_make_converter(unstruct_collection_overrides={Set: sorted})
761754
raw = converter.unstructure(everything)
762755
assert raw["a_set"] == sorted(raw["a_set"])
763756
assert raw["a_mutable_set"] == sorted(raw["a_mutable_set"])
@@ -797,9 +790,7 @@ def test_cbor2_converter(everything: Everything):
797790

798791
@given(everythings(min_int=-9223372036854775808, max_int=18446744073709551615))
799792
def test_cbor2_converter_unstruct_collection_overrides(everything: Everything):
800-
converter = cbor2_make_converter(
801-
unstruct_collection_overrides={AbstractSet: sorted}
802-
)
793+
converter = cbor2_make_converter(unstruct_collection_overrides={Set: sorted})
803794
raw = converter.unstructure(everything)
804795
assert raw["a_set"] == sorted(raw["a_set"])
805796
assert raw["a_mutable_set"] == sorted(raw["a_mutable_set"])
@@ -856,9 +847,7 @@ def test_msgspec_json_unstruct_collection_overrides(everything: Everything):
856847
"""Ensure collection overrides work."""
857848
from cattrs.preconf.msgspec import make_converter as msgspec_make_converter
858849

859-
converter = msgspec_make_converter(
860-
unstruct_collection_overrides={AbstractSet: sorted}
861-
)
850+
converter = msgspec_make_converter(unstruct_collection_overrides={Set: sorted})
862851
raw = converter.unstructure(everything)
863852
assert raw["a_set"] == sorted(raw["a_set"])
864853
assert raw["a_mutable_set"] == sorted(raw["a_mutable_set"])
@@ -913,3 +902,39 @@ def test_msgspec_efficient_enum():
913902
converter.get_unstructure_hook(fields(Everything).a_literal_with_bare.type)
914903
== identity
915904
)
905+
906+
907+
@pytest.mark.parametrize(
908+
"converter_factory",
909+
[
910+
bson_make_converter,
911+
cbor2_make_converter,
912+
json_make_converter,
913+
msgpack_make_converter,
914+
tomlkit_make_converter,
915+
ujson_make_converter,
916+
pyyaml_make_converter,
917+
],
918+
)
919+
def test_literal_dicts(converter_factory: Callable[[], Converter]):
920+
"""Dicts with keys that aren't subclasses of `type` work."""
921+
converter = converter_factory()
922+
923+
assert converter.structure({"a": 1}, Dict[Literal["a"], int]) == {"a": 1}
924+
assert converter.unstructure({"a": 1}, Dict[Literal["a"], int]) == {"a": 1}
925+
926+
927+
@pytest.mark.skipif(NO_ORJSON, reason="orjson not available")
928+
def test_literal_dicts_orjson():
929+
"""Dicts with keys that aren't subclasses of `type` work."""
930+
from cattrs.preconf.orjson import make_converter as orjson_make_converter
931+
932+
test_literal_dicts(orjson_make_converter)
933+
934+
935+
@pytest.mark.skipif(NO_MSGSPEC, reason="msgspec not available")
936+
def test_literal_dicts_msgspec():
937+
"""Dicts with keys that aren't subclasses of `type` work."""
938+
from cattrs.preconf.msgspec import make_converter as msgspec_make_converter
939+
940+
test_literal_dicts(msgspec_make_converter)

0 commit comments

Comments
 (0)