interp-engine 0.0.24__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,85 @@
1
+ # Utilities for dataclasses that are very fast to serialize and deserialize, with limited data
2
+ # validation. Fields must not be tuples, since they get serialized and then deserialized as lists.
3
+ #
4
+ # The unit tests for this library show how to use it.
5
+
6
+ import json
7
+ from dataclasses import dataclass, field, fields, is_dataclass
8
+ from functools import partial
9
+ from typing import Any, Union
10
+
11
+ import orjson
12
+
13
+ dataclasses_by_name = {}
14
+ dataclasses_by_fieldnames = {}
15
+
16
+
17
+ @dataclass
18
+ class FastDataclass:
19
+ dataclass_name: str = field(init=False)
20
+
21
+ def __post_init__(self) -> None:
22
+ self.dataclass_name = self.__class__.__name__
23
+
24
+
25
+ def register_dataclass(cls): # type: ignore
26
+ assert is_dataclass(cls), "Only dataclasses can be registered."
27
+ dataclasses_by_name[cls.__name__] = cls
28
+ name_set = frozenset(f.name for f in fields(cls) if f.name != "dataclass_name")
29
+ dataclasses_by_fieldnames[name_set] = cls
30
+ return cls
31
+
32
+
33
+ def dumps(obj: Any) -> bytes:
34
+ return orjson.dumps(obj, option=orjson.OPT_SERIALIZE_NUMPY)
35
+
36
+
37
+ def _object_hook(d: Any, backwards_compatible: bool = True) -> Any:
38
+ # If d is a list, recurse.
39
+ if isinstance(d, list):
40
+ return [_object_hook(x, backwards_compatible=backwards_compatible) for x in d]
41
+ # If d is not a dict, return it as is.
42
+ if not isinstance(d, dict):
43
+ return d
44
+ cls = None
45
+ if "dataclass_name" in d:
46
+ if d["dataclass_name"] in dataclasses_by_name:
47
+ cls = dataclasses_by_name[d["dataclass_name"]]
48
+ else:
49
+ assert backwards_compatible, (
50
+ f"Dataclass {d['dataclass_name']} not found, set backwards_compatible=True if you "
51
+ f"are okay with that."
52
+ )
53
+ # Load objects created without dataclass_name set.
54
+ else:
55
+ # Try our best to find a dataclass if backwards_compatible is True.
56
+ if backwards_compatible:
57
+ d_fields = frozenset(d.keys())
58
+ if d_fields in dataclasses_by_fieldnames:
59
+ cls = dataclasses_by_fieldnames[d_fields]
60
+ elif len(d_fields) > 0:
61
+ # Check if the fields are a subset of a dataclass (if the dataclass had extra fields
62
+ # added since the data was created). Note that this will fail if fields were removed
63
+ # from the dataclass.
64
+ for key, possible_cls in dataclasses_by_fieldnames.items():
65
+ if d_fields.issubset(key):
66
+ cls = possible_cls
67
+ break
68
+ else:
69
+ print(f"Could not find dataclass for {d_fields} {cls}")
70
+ new_d = {
71
+ k: _object_hook(v, backwards_compatible=backwards_compatible)
72
+ for k, v in d.items()
73
+ if k != "dataclass_name"
74
+ }
75
+ if cls is not None:
76
+ return cls(**new_d)
77
+ else:
78
+ return new_d
79
+
80
+
81
+ def loads(s: Union[str, bytes], backwards_compatible: bool = True) -> Any:
82
+ return json.loads(
83
+ s,
84
+ object_hook=partial(_object_hook, backwards_compatible=backwards_compatible),
85
+ )
@@ -0,0 +1,83 @@
1
+ from dataclasses import dataclass
2
+
3
+ import pytest
4
+
5
+ from .fast_dataclasses import FastDataclass, dumps, loads, register_dataclass
6
+
7
+
8
+ # Inheritance is a bit tricky with our setup. dataclass_name must be set for instances of these
9
+ # classes to serialize and deserialize correctly, but if it's given a default value, then subclasses
10
+ # can't have any fields that don't have default values, because of how constructors are generated
11
+ # for dataclasses (fields with no default value can't follow those with default values). To work
12
+ # around this, we set dataclass_name in __post_init__ on the base class, which is called after the
13
+ # constructor. The implementation does the right thing for both the base class and the subclass.
14
+ @register_dataclass
15
+ @dataclass
16
+ class DataclassC(FastDataclass):
17
+ ints: list[int]
18
+
19
+
20
+ @register_dataclass
21
+ @dataclass
22
+ class DataclassC_ext(DataclassC):
23
+ s: str
24
+
25
+
26
+ @register_dataclass
27
+ @dataclass
28
+ class DataclassB(FastDataclass):
29
+ str_to_c: dict[str, DataclassC]
30
+ cs: list[DataclassC]
31
+
32
+
33
+ @register_dataclass
34
+ @dataclass
35
+ class DataclassA(FastDataclass):
36
+ floats: list[float]
37
+ strings: list[str]
38
+ bs: list[DataclassB]
39
+
40
+
41
+ @register_dataclass
42
+ @dataclass
43
+ class DataclassD(FastDataclass):
44
+ s1: str
45
+ s2: str = "default"
46
+
47
+
48
+ def test_dataclasses() -> None:
49
+ a = DataclassA(
50
+ floats=[1.0, 2.0],
51
+ strings=["a", "b"],
52
+ bs=[
53
+ DataclassB(
54
+ str_to_c={"a": DataclassC(ints=[1, 2]), "b": DataclassC(ints=[3, 4])},
55
+ cs=[DataclassC(ints=[5, 6]), DataclassC_ext(ints=[7, 8], s="s")],
56
+ ),
57
+ DataclassB(
58
+ str_to_c={"c": DataclassC_ext(ints=[9, 10], s="t"), "d": DataclassC(ints=[11, 12])},
59
+ cs=[DataclassC(ints=[13, 14]), DataclassC(ints=[15, 16])],
60
+ ),
61
+ ],
62
+ )
63
+ assert loads(dumps(a)) == a
64
+
65
+
66
+ def test_c_and_c_ext() -> None:
67
+ c_ext = DataclassC_ext(ints=[3, 4], s="s")
68
+ assert loads(dumps(c_ext)) == c_ext
69
+
70
+ c = DataclassC(ints=[1, 2])
71
+ assert loads(dumps(c)) == c
72
+
73
+
74
+ def test_bad_serialized_data() -> None:
75
+ assert type(loads(dumps(DataclassC(ints=[3, 4])))) == DataclassC
76
+ assert type(loads('{"ints": [3, 4]}', backwards_compatible=False)) == dict
77
+ assert type(loads('{"ints": [3, 4], "dataclass_name": "DataclassC"}')) == DataclassC
78
+ with pytest.raises(TypeError):
79
+ loads('{"ints": [3, 4], "bogus_extra_field": "foo", "dataclass_name": "DataclassC"}')
80
+ with pytest.raises(TypeError):
81
+ loads('{"ints_field_is_missing": [3, 4], "dataclass_name": "DataclassC"}')
82
+ assert type(loads('{"s1": "test"}', backwards_compatible=False)) == dict
83
+ assert type(loads('{"s1": "test"}', backwards_compatible=True)) == DataclassD