tmsgpack 0.3.24__tar.gz → 0.3.26__tar.gz

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.
Files changed (33) hide show
  1. {tmsgpack-0.3.24/tmsgpack.egg-info → tmsgpack-0.3.26}/PKG-INFO +1 -1
  2. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/pyproject.toml +1 -1
  3. tmsgpack-0.3.26/tests/test_codec.py +135 -0
  4. tmsgpack-0.3.26/tests/test_codec_custom.py +212 -0
  5. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack/codec.py +4 -8
  6. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack/core.c +9 -9
  7. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack/core.pyx +1 -1
  8. {tmsgpack-0.3.24 → tmsgpack-0.3.26/tmsgpack.egg-info}/PKG-INFO +1 -1
  9. tmsgpack-0.3.24/tests/test_codec.py +0 -197
  10. tmsgpack-0.3.24/tests/test_codec_custom.py +0 -240
  11. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/MANIFEST.in +0 -0
  12. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/README.md +0 -0
  13. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/build_pyx.py +0 -0
  14. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/setup.cfg +0 -0
  15. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/setup.py +0 -0
  16. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tests/test_build_pyx.py +0 -0
  17. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tests/test_round_trip.py +0 -0
  18. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack/__init__.py +0 -0
  19. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack/api.py +0 -0
  20. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack/codec_custom.py +0 -0
  21. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack/src-parts/01-imports +0 -0
  22. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack/src-parts/02-constants +0 -0
  23. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack/src-parts/03-encode-fn +0 -0
  24. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack/src-parts/04-decode-fn +0 -0
  25. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack/src-parts/05-encode-buffer +0 -0
  26. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack/src-parts/06-decode-buffer +0 -0
  27. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack/src-parts/07-encode-buffer-slow +0 -0
  28. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack/src-parts/08-decode-buffer-slow +0 -0
  29. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack/src-parts/09-exceptions +0 -0
  30. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack.egg-info/SOURCES.txt +0 -0
  31. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack.egg-info/dependency_links.txt +0 -0
  32. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack.egg-info/requires.txt +0 -0
  33. {tmsgpack-0.3.24 → tmsgpack-0.3.26}/tmsgpack.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tmsgpack
3
- Version: 0.3.24
3
+ Version: 0.3.26
4
4
  Summary: Typed MessagePack serializer (inspired by but different from msgpack)
5
5
  Author-email: Yaakov Belch <yaakov.belch@gmail.com>
6
6
  License: ISC
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tmsgpack"
3
- version = "0.3.24"
3
+ version = "0.3.26"
4
4
  description = "Typed MessagePack serializer (inspired by but different from msgpack)"
5
5
  requires-python = ">=3.14"
6
6
  license = {text = "ISC"}
@@ -0,0 +1,135 @@
1
+ """Integration tests for TmsgpackCodec with DI and the dispatch system."""
2
+
3
+ import textwrap
4
+ import pytest
5
+ from pathlib import Path
6
+
7
+ from tmsgpack.codec import NoDependencyInjector, TmsgpackCodec
8
+ from semifun.dispatch.Inject import Inject, injected_type
9
+ from semifun.dispatch.SemifunApp import SemifunApp
10
+ from semifun.dispatch.load_lookup_tables import LoadedFn
11
+
12
+
13
+ # --- Inline test types (no file scanning needed) ---
14
+
15
+ import enum
16
+ from dataclasses import dataclass
17
+
18
+ #::testing_tmsgpack_codec:FailureSeverity
19
+ class FailureSeverity(enum.IntEnum):
20
+ OK = 0
21
+ PROBLEM = 1
22
+ NOT_AUTHORIZED = 2
23
+ NOT_AUTHENTICATED = 3
24
+ UNEXPECTED = 4
25
+
26
+ @dataclass(frozen=True)
27
+ class Dbh:
28
+ pass
29
+
30
+ #::testing_tmsgpack_codec:Foo
31
+ @dataclass(frozen=True)
32
+ class Foo:
33
+ x: str
34
+ y: int
35
+
36
+ #::testing_tmsgpack_codec:Bar
37
+ @dataclass(frozen=True)
38
+ class Bar:
39
+ dbh: Inject[Dbh]
40
+ x: str
41
+
42
+
43
+ # --- DI adapter for tests ---
44
+
45
+ @dataclass(frozen=True)
46
+ class _TestDiAdapter:
47
+ """Test DI adapter satisfying DependencyInjectorProtocol."""
48
+ lookup_table: dict # {type_name: class}
49
+ seed_data: dict
50
+
51
+ @staticmethod
52
+ def injected_type(annotation):
53
+ return injected_type(annotation)
54
+
55
+ def lookup_type(self, type_name):
56
+ if type_name in self.lookup_table:
57
+ return self.lookup_table[type_name]
58
+ raise LookupError(f"No codec for {type_name}")
59
+
60
+ def with_seed_data(self, seed_data):
61
+ return _TestDiAdapter(lookup_table=self.lookup_table, seed_data={**self.seed_data, **seed_data})
62
+
63
+ def sync_call_with_args(self, *, fn, args, kwargs):
64
+ app = SemifunApp(entry_points_group={})
65
+ with app.open_sync_di_ctx(parent_ctx=None, seed_data=self.seed_data, ftype='test') as ctx:
66
+ return ctx.fn_call(fn=fn, args=args, kwargs=kwargs)
67
+
68
+
69
+ LOOKUP_TABLE = {
70
+ 'FailureSeverity': FailureSeverity,
71
+ 'Foo': Foo,
72
+ 'Bar': Bar,
73
+ }
74
+
75
+
76
+ @pytest.fixture
77
+ def codec():
78
+ di = _TestDiAdapter(lookup_table=LOOKUP_TABLE, seed_data={Dbh: Dbh()})
79
+ return TmsgpackCodec(sort_keys=False, di=di)
80
+
81
+
82
+ @pytest.fixture
83
+ def codec_no_di():
84
+ return TmsgpackCodec(sort_keys=False, di=NoDependencyInjector())
85
+
86
+
87
+ # --- Round-trip: plain dataclass ---
88
+
89
+ def test_round_trip_dataclass(codec):
90
+ foo = Foo(x='hello', y=123)
91
+ assert codec.decode(codec.encode(foo)) == foo
92
+
93
+
94
+ # --- Round-trip: enum ---
95
+
96
+ def test_round_trip_enum(codec):
97
+ for severity in FailureSeverity:
98
+ assert codec.decode(codec.encode(severity)) == severity
99
+
100
+
101
+ # --- Round-trip: dataclass with Inject[T] fields ---
102
+
103
+ def test_round_trip_dataclass_with_inject(codec):
104
+ bar = Bar(dbh=Dbh(), x='world')
105
+ data = codec.encode(bar)
106
+ decoded = codec.decode(data)
107
+ assert decoded.x == 'world'
108
+ assert isinstance(decoded.dbh, Dbh)
109
+
110
+
111
+ # --- NoDependencyInjector: plain types work without DI ---
112
+
113
+ def test_no_di_raises_on_lookup(codec_no_di):
114
+ foo = Foo(x='abc', y=42)
115
+ with pytest.raises(TypeError, match='Type lookup'):
116
+ codec_no_di.encode(foo)
117
+
118
+
119
+ # --- with_seed_data ---
120
+
121
+ def test_with_seed_data_round_trip(codec):
122
+ other_dbh = Dbh()
123
+ codec2 = codec.with_seed_data({Dbh: other_dbh})
124
+ bar = Bar(dbh=Dbh(), x='test')
125
+ decoded = codec2.decode(codec2.encode(bar))
126
+ assert decoded.x == 'test'
127
+ assert decoded.dbh is other_dbh
128
+
129
+
130
+ # --- with_seed_data produces independent caches ---
131
+
132
+ def test_with_seed_data_has_independent_caches(codec):
133
+ codec2 = codec.with_seed_data({Dbh: Dbh()})
134
+ assert codec2.encoder_cache is not codec.encoder_cache
135
+ assert codec2.decoder_cache is not codec.decoder_cache
@@ -0,0 +1,212 @@
1
+ """Tests for TmsgpackCustom: custom encode/decode for non-dataclass types."""
2
+
3
+ import struct
4
+ import pytest
5
+ from dataclasses import dataclass
6
+
7
+ from tmsgpack.codec import NoDependencyInjector, TmsgpackCodec
8
+ from tmsgpack.codec_custom import TmsgpackCustom
9
+ from semifun.dispatch.Inject import Inject, injected_type
10
+ from semifun.dispatch.SemifunApp import SemifunApp
11
+ from semifun.dispatch.load_lookup_tables import LoadedFn
12
+
13
+
14
+ # --- Inline test types ---
15
+
16
+ class Pair:
17
+ def __init__(self, x, y):
18
+ self.x = x
19
+ self.y = y
20
+ def __eq__(self, other):
21
+ return type(other) is Pair and self.x == other.x and self.y == other.y
22
+
23
+ class Point:
24
+ def __init__(self, x, y):
25
+ self.x = x
26
+ self.y = y
27
+ def __eq__(self, other):
28
+ return type(other) is Point and self.x == other.x and self.y == other.y
29
+
30
+ class FloatPair:
31
+ def __init__(self, x, y):
32
+ self.x = x
33
+ self.y = y
34
+ def __eq__(self, other):
35
+ return (type(other) is FloatPair
36
+ and self.x == other.x and self.y == other.y)
37
+
38
+
39
+ # --- Custom codecs ---
40
+
41
+ class CodecPairDict(TmsgpackCustom):
42
+ def encode_dict(obj):
43
+ return {'x': obj.x, 'y': obj.y}
44
+ def decode_dict(*, x, y):
45
+ return Pair(x=x, y=y)
46
+
47
+ class CodecPointList(TmsgpackCustom):
48
+ def encode_list(obj):
49
+ return [obj.x, obj.y]
50
+ def decode_list(x, y):
51
+ return Point(x, y)
52
+
53
+ class CodecFloatPairBytes(TmsgpackCustom):
54
+ def encode_bytes(obj):
55
+ return struct.pack('<ff', obj.x, obj.y)
56
+ def decode_bytes(data):
57
+ x, y = struct.unpack('<ff', data)
58
+ return FloatPair(x, y)
59
+
60
+
61
+ # --- DI adapter for tests ---
62
+
63
+ @dataclass(frozen=True)
64
+ class _TestDiAdapter:
65
+ lookup_table: dict
66
+ seed_data: dict
67
+
68
+ @staticmethod
69
+ def injected_type(annotation):
70
+ return injected_type(annotation)
71
+
72
+ def lookup_type(self, type_name):
73
+ if type_name in self.lookup_table:
74
+ return self.lookup_table[type_name]
75
+ raise LookupError(f"No codec for {type_name}")
76
+
77
+ def with_seed_data(self, seed_data):
78
+ return _TestDiAdapter(lookup_table=self.lookup_table, seed_data={**self.seed_data, **seed_data})
79
+
80
+ def sync_call_with_args(self, *, fn, args, kwargs):
81
+ app = SemifunApp(entry_points_group={})
82
+ with app.open_sync_di_ctx(parent_ctx=None, seed_data=self.seed_data, ftype='test') as ctx:
83
+ return ctx.fn_call(fn=fn, args=args, kwargs=kwargs)
84
+
85
+
86
+ LOOKUP_TABLE = {
87
+ 'Pair': CodecPairDict,
88
+ 'Point': CodecPointList,
89
+ 'FloatPair': CodecFloatPairBytes,
90
+ }
91
+
92
+
93
+ @pytest.fixture
94
+ def codec():
95
+ di = _TestDiAdapter(lookup_table=LOOKUP_TABLE, seed_data={})
96
+ return TmsgpackCodec(sort_keys=False, di=di)
97
+
98
+
99
+ # --- Dict mode round-trip ---
100
+
101
+ def test_dict_mode_round_trip(codec):
102
+ pair = Pair(x='hello', y=42)
103
+ data = codec.encode(pair)
104
+ restored = codec.decode(data)
105
+ assert restored == pair
106
+
107
+
108
+ # --- List mode round-trip ---
109
+
110
+ def test_list_mode_round_trip(codec):
111
+ point = Point(x=10, y=20)
112
+ data = codec.encode(point)
113
+ restored = codec.decode(data)
114
+ assert restored == point
115
+
116
+
117
+ # --- Bytes mode round-trip ---
118
+
119
+ def test_bytes_mode_round_trip(codec):
120
+ fp = FloatPair(x=1.5, y=2.5)
121
+ data = codec.encode(fp)
122
+ restored = codec.decode(data)
123
+ assert restored == fp
124
+
125
+
126
+ # --- Content hashing works with custom types ---
127
+
128
+ def test_content_hash_dict_mode(codec):
129
+ di = _TestDiAdapter(lookup_table=LOOKUP_TABLE, seed_data={})
130
+ codec_sorted = TmsgpackCodec(sort_keys=True, di=di)
131
+ pair = Pair(x='a', y=1)
132
+ h = codec_sorted.hash_to_str(pair)
133
+ assert isinstance(h, str)
134
+ assert len(h) == 22
135
+ # Same value, same hash
136
+ assert codec_sorted.hash_to_str(Pair(x='a', y=1)) == h
137
+
138
+
139
+ # --- Nested values in custom types ---
140
+
141
+ def test_dict_mode_nested_values(codec):
142
+ pair = Pair(x=[1, 2, 3], y={'a': True})
143
+ data = codec.encode(pair)
144
+ restored = codec.decode(data)
145
+ assert restored == pair
146
+
147
+
148
+ # --- Validation: mismatched modes ---
149
+
150
+ def test_mismatched_modes_raises():
151
+ with pytest.raises(TypeError, match="mode mismatch"):
152
+ class Bad(TmsgpackCustom):
153
+ def encode_dict(obj): return {}
154
+ def decode_list(x): return None
155
+
156
+
157
+ def test_no_encode_raises():
158
+ with pytest.raises(TypeError, match="encode_dict, encode_list, encode_bytes"):
159
+ class Bad(TmsgpackCustom):
160
+ def decode_dict(*, x): return None
161
+
162
+
163
+ def test_no_decode_raises():
164
+ with pytest.raises(TypeError, match="decode_dict, decode_list, decode_bytes"):
165
+ class Bad(TmsgpackCustom):
166
+ def encode_dict(obj): return {}
167
+
168
+
169
+ # --- DI on encode and decode ---
170
+
171
+ class Cache:
172
+ def __init__(self):
173
+ self.store = {}
174
+ def get(self, key):
175
+ return self.store[key]
176
+ def put(self, key, value):
177
+ self.store[key] = value
178
+
179
+ class Widget:
180
+ def __init__(self, name, data):
181
+ self.name = name
182
+ self.data = data
183
+ def __eq__(self, other):
184
+ return (type(other) is Widget
185
+ and self.name == other.name
186
+ and self.data == other.data)
187
+
188
+ class CodecWidget(TmsgpackCustom):
189
+ def encode_dict(obj, cache: Inject[Cache]):
190
+ cache.put(obj.name, obj.data)
191
+ return {'name': obj.name}
192
+ def decode_dict(*, name, cache: Inject[Cache]):
193
+ data = cache.get(name)
194
+ return Widget(name=name, data=data)
195
+
196
+
197
+ def test_di_on_encode_and_decode():
198
+ cache = Cache()
199
+ di = _TestDiAdapter(
200
+ lookup_table={'Widget': CodecWidget},
201
+ seed_data={Cache: cache},
202
+ )
203
+ codec = TmsgpackCodec(sort_keys=False, di=di)
204
+
205
+ widget = Widget(name='gizmo', data=[1, 2, 3])
206
+ data = codec.encode(widget)
207
+
208
+ # The cache was populated during encode
209
+ assert cache.store == {'gizmo': [1, 2, 3]}
210
+
211
+ restored = codec.decode(data)
212
+ assert restored == widget
@@ -1,7 +1,6 @@
1
1
  from base64 import urlsafe_b64encode
2
2
  from dataclasses import dataclass, field, fields, is_dataclass, replace
3
3
  from typing import Any, Protocol
4
- from semifun.caching.cached_property import cached_property
5
4
 
6
5
  import enum
7
6
  from .api import EncodeDecode
@@ -11,6 +10,7 @@ from .codec_custom import TmsgpackCustom, make_codec_handler_custom
11
10
  class DependencyInjectorProtocol(Protocol):
12
11
  @staticmethod
13
12
  def injected_type(annotation: Any) -> type | None: ...
13
+ def lookup_type(self, type_name: str) -> Any: ...
14
14
  def with_seed_data(self, seed_data: dict) -> "DependencyInjectorProtocol": ...
15
15
  def sync_call_with_args(self, *, fn: Any, args: tuple, kwargs: dict[str, Any]) -> Any: ...
16
16
 
@@ -20,6 +20,8 @@ class NoDependencyInjector:
20
20
  @staticmethod
21
21
  def injected_type(annotation):
22
22
  return None
23
+ def lookup_type(self, type_name):
24
+ raise TypeError("Type lookup is not supported without a DependencyInjector")
23
25
  def with_seed_data(self, seed_data):
24
26
  raise TypeError("Dependency injection is not supported without a DependencyInjector")
25
27
  def sync_call_with_args(self, *, fn, args, kwargs):
@@ -34,7 +36,6 @@ class TmsgpackCodec(EncodeDecode):
34
36
  # `replace()` re-runs default_factory, giving each derived codec fresh caches.
35
37
  encoder_cache: dict = field(default_factory=dict, init=False, repr=False)
36
38
  decoder_cache: dict = field(default_factory=dict, init=False, repr=False)
37
- plugin_feature_type: str
38
39
 
39
40
  def with_seed_data(self, seed_data):
40
41
  return replace(self, di=self.di.with_seed_data(seed_data))
@@ -73,16 +74,11 @@ class TmsgpackCodec(EncodeDecode):
73
74
  codec_handler = self.decoder_cache[type_name]
74
75
  return codec_handler.decode_dctx(dctx=dctx)
75
76
 
76
- @cached_property
77
- def feature_map(self):
78
- from semifun.plugins.registry import get_cached_feature_map
79
- return get_cached_feature_map(feature_type=self.plugin_feature_type)
80
-
81
77
  def type_to_type_name(self, _type):
82
78
  return _type.__name__
83
79
 
84
80
  def type_name_to_codec_spec(self, type_name):
85
- return self.feature_map(feature=type_name)
81
+ return self.di.lookup_type(type_name)
86
82
 
87
83
  def codec_spec_to_codec_handler(self, codec_spec, type_name):
88
84
  if isinstance(codec_spec, enum.EnumType):
@@ -3515,7 +3515,7 @@ static __pyx_mstatetype * const __pyx_mstate_global = &__pyx_mstate_global_stati
3515
3515
  #endif
3516
3516
  /* #### Code section: constant_name_defines ### */
3517
3517
  #define __pyx_kp_u_tree_fragment __pyx_string_tab[0]
3518
- #define __pyx_kp_u_0_3_24 __pyx_string_tab[1]
3518
+ #define __pyx_kp_u_0_3_26 __pyx_string_tab[1]
3519
3519
  #define __pyx_kp_u_B __pyx_string_tab[2]
3520
3520
  #define __pyx_kp_u_H __pyx_string_tab[3]
3521
3521
  #define __pyx_kp_u_I __pyx_string_tab[4]
@@ -27280,11 +27280,11 @@ __Pyx_RefNannySetupContext("PyInit_core", 0);
27280
27280
  /* "tmsgpack/core.pyx":4
27281
27281
  * # DON'T EDIT THIS FILE. EDIT THE SOURCES, INSTEAD: tmsgpack/src-parts/[inserted by cython to avoid comment start]*
27282
27282
  *
27283
- * __version__ = "0.3.24" # <<<<<<<<<<<<<<
27283
+ * __version__ = "0.3.26" # <<<<<<<<<<<<<<
27284
27284
  *
27285
27285
  * from libc.stdint cimport int8_t, int16_t, int32_t, int64_t
27286
27286
  */
27287
- if (PyDict_SetItem(__pyx_mstate_global->__pyx_d, __pyx_mstate_global->__pyx_n_u_version, __pyx_mstate_global->__pyx_kp_u_0_3_24) < (0)) __PYX_ERR(0, 4, __pyx_L1_error)
27287
+ if (PyDict_SetItem(__pyx_mstate_global->__pyx_d, __pyx_mstate_global->__pyx_n_u_version, __pyx_mstate_global->__pyx_kp_u_0_3_26) < (0)) __PYX_ERR(0, 4, __pyx_L1_error)
27288
27288
 
27289
27289
  /* "tmsgpack/core.pyx":13
27290
27290
  * ctypedef double float64_t
@@ -28889,7 +28889,7 @@ static int __Pyx_InitConstants(__pyx_mstatetype *__pyx_mstate) {
28889
28889
  #define CYTHON_COMPRESS_STRINGS 90
28890
28890
  #endif
28891
28891
  #if (CYTHON_COMPRESS_STRINGS) == 3 && __PYX_LIMITED_VERSION_HEX >= 0x030e0000 /* compression: zstd (1723 bytes) */
28892
- static const char cstring[] = "(\265/\375`.\021\2155\000zH\374\016E\360\222\250\r\314\300\354\004[\0237;6]\326\235B\243\301t\246H\232\270y*\232\265\315-\211D\226\337\306F\222b\247\375J\005\004`\200\023\240)\212\002\257%\235\254\022\376D\013\330\253\0024O\020\340\232\255\033\014\315\000\307\000\347\000\236\210e\312\343\223\246p\216\252\237\232\277\255\231\246\311\310b\243\346\225n\256U\230\310\242\242\222\202\222!z\251\312\373\331F\327\354\354Xx\034s\262\204\243\350\245Z<)U\232UK_\276\255U\236\304w\034\253\307<\226]\264\0375\367\032\263\375\342\346\022=\330\273\357c\245\317\321L\212\233\215\3301\226\334$e\034\361O\267\3417jUF\320\n\010\245EK\213X\257\377\244\367q\3748\177y\235D9\327\"\246\261\332\230\377F\304\2271\037]\276\324\342\347\256\322\030S\312\266z|\273\"\262x\346$*V\351\234\265\244\310\311\222s\234K\010/DC1!-D\n\215B.\241\023!\021!P\212G\203\047\203\205\257~*\311\273E\367\373\025\002\377\216U\316\311\354\221\344\243\2343M\332\325\334}\377\\\306~O\031\233\246\307L\277\262\363\333\245\334O\262:\275_=\263\227\313\232\225\357\225\312\023~/\325\274M\233e\266V\231e\233\273x\225\257\310~R\022\252\257E\226]{o\213\212\227M\276\360\356{\361jW\334\367Y={\216?w\\\227_\365#n\n\313\332\002J\311\240\224\317W\272\362\322r\233v\377\035g\331\364\\\337\266,\302I8\226\"T_\347\361k\047\006\323\224]\t\237\356\343\237T{L\271x\355\306qr\306\314M\362#\013\366\377HY\366\264\322\311I\212\242\367Q\252\255Y\213Y\3463k~\263,>\227e\264U\303t\226\252\273;G\222\230j\001 \036/\221\007r@\025,|\001Q\033\264ahtpt\324h\225X#\026\t\353#\342\022\361xy\034\300\225\3753\236V\240\021l\222\335\242c\254P\235\220\333\003\266\2479\372\200\307k\244I4\326\340\243\303\342\020\232\000?\240\205\215m\020\253\020\001\000<vhr|^0/\256*SB\203\253\303J\361xu6\007\215#\007g\363\022\205\237p\005\2240\342\032\331\274\\\214\\&\031\017-\020V\206\350\3626\004Q\030\003\024\201\035vg\207X\201lfX\250P\206\236\221\276\260\372\330\27449\260\343\254u9{\360\207\177\346+\037\343\317M\331\265\370\035o\234cPg\273Z\342\376)\207O\376\364U\256\271\373\311\336-""\313\266L\322\246iJ\222Oy\237\233\327\265k\261\307\322Y?,\275\322\317\366\332\231aJ\361u\024\366\330\210\007z\303\2130%D\020\232\260p\003\350\373A+\304\3065j\261\260>\224aI\204\215\353\003\2645;\005f2g\\^3\035\223\027\246\343\206F\307\327\222\255\031,\026\027\035A\206\226\200\240\300\002\360e\227\364\004\r\263* \332#\373\206\tM\"#eLZ\232\326\207\020\006X\342ho\206B\247e\207`\240\006\204\302\376\360J6\201\024q\024\242\014l\0053\030x\2625=\322%\030\244\361\t\003\204_\210\205\047 \3146\021m\007\237\335@\024\346\316f\260Q\tF,\021k\204\245a} (\206Fa\373E\213t\226aJn\017a\202P\003\303\003%\230\332.\266H\344\023\372Nid\274\226\010\047T\361(hLc\032\021} \344%\002G\300\r\373Kt\313\314\351\223\340\004\020\003\t\210\300\r\350m\316V\321R\243X\205x\2744,/D\001K\354\016\372W\240U\201x\250\241!rhDfDR\220\024\2440\034\341\014a\026:u\345\001B\221d\010q\2341D\031C \221#\222\nR\217Bs\264\026,\3104\356\356@\303&\010\030\267m\335\246I\326N\263\213\224\364\312 \243\345\301U:p\372y\023\033\216\241*9\262Q\200\034}\252\367;z8\254\203O\034#\217L\265B\314\034G+$\330\322}\235\327\001U\334L\254\244I\360\316\333\210\321iX\227\375\006\002\202\372h\016\000\204\234\246l@\332\374\014R\276r\304a\020\321r\302\004\366\023\"\207\300)\246\341`2\211|\354\373\335+t\311\010\205\307\352\310c\377p\235\201B\031\312\204\020\030\370\030L\357\025f\375\026\032\343{\264\321\200\367\237\227\333\t\344\324K\335h\347\366\322L\343\256K&\325b+n\367\211\245\211e\245;\335\0060H\203\326\341\215EK\232^\034,\347\013M\020\3664@\017;\310\365\023_`\234\3121TJQ\00759\212)dF\017\303bi\240\001\207\247$.l\240\317\021\304`AY\032\324\225\026j\260\345\336[\374\304\251U}\322\365~\250\n\200\222\245(E\224R\363\224\024\014U\306\005\253\030F\315E\270\325\324\227\032\005\360p-\334\033\212\177|\047\271X\304?\234(\323Jf\276\244\027>\2726\204\275\244\214rJa\250\\4\225\020}\360\313\326<F\207\314$\340f\250\335D\323\326\230$H<\262\341\356)\324\344-\243\324\250\313\227=\313~\0344\252\r4T\317\253\246\260\0000\307n\366\206\314\332\263>p\241\036pH""\002\346Ih\237\376o\026e\242%\030\345j\013\252\367L\371B\320\206A\304\267\226\323\330.\267%\213\273_\323\201\357\270\nG\213\370\235\036,i\304\202\331cD\r\223\271\251\036\017\326j\326\010N,*v\372\367\271\257F\345\366\260&|b\";\323o\214\336bs\322\214\277 *en\222\314\253w\263\263)\372(\224\031\305\016\230\006\2049\2361\367\332\r\245%X\370;\027O%D8p\215>A\2370\223\264\201\016@\352\274\272.2\226\316\257\037\000Su%\260\007#\306\354Q\245\250mY\002\320-RZ\210\235\261\226\352\216\"[\t\010\331\035\220\177\310\317\n\270\304}\\\251\330!Gk\212\017&U\324\322\00645\035\366\261jM\200\257\320\216UF\202\2479\2510\235\275\232\020\264\351\003hR/\276`\217T\013\tK\243\247Qy_^\336[\271\265\276<\342X\250\266\257\013\200\r\270\251YR2\251\356\344\"\237\330\317\204\352jg!W\243\341\275\017\344G\351I\225a?\335N\257\217z\247\261J\013\350\031_\"\203\3173 \047\324\023h\241\314\263\005\345\242\223]\350:\003\355\005]+#\344\247P\221T\001";
28892
+ static const char cstring[] = "(\265/\375`.\021\2155\000zH\374\016E\000\221\250\r\374?\337\273\377=\337\253\357=\225\240_\372U\307\327Tr\023\212\260\277\271%\221\310\362\\\335HRl\010\016/\000J;\2504\303\214\376\261\022\372!xpf\234\223\025f\034Ch\036S\001\250\305\321F\315\000\307\000\347\000\237\210\245\352\343\223\252p\2162?5\177[3M\223\221\305F\315+\335\\\2530\221%%\005\345$CtS\225\367\263\215\256\331\331\255\3708\346$\tO\321K\265hb\2524\253\226\276|[\2534\211\3578V\217y,\273h?j\3565f\373\305\315%~\260w\337\307J\237\243\271\0247\033\261c,\271Y\3128\342\237^\303o\324\252\214\241\225\320\t\213\026\026\261^\377I\357\343\370q\376\362z\211r\256ELc\2651\377\211\210/c>\272|\251\305\317]\2451\306\224m\365\370VEd\361\314\311T\254\3229kK\221\223&\3478\027\020\236\210\212bD\232\250$\"\211\\D&D!D*(\037\r\237\014\026\276:\312$\357\026\335\357W\010\377\216e\234\223\331+\311O9w\232\264\253\271\373\376\271\214\375\2422>M\217\231~e\347\267S\271\243duz\277zf/\2275+\337+\225(\374n\252y\2336\313l\2552\3136o\3612\256\"\373IG\310|,\262\354\332{[R\274l\322\205w\337\213W\253\342\276\317\352\331s\374\271\343\272\3742/\342\252\260\254-!\224\034B\001}\245+/-\267i\367\337q\226\315\317\365m\313!|\204c\031B\346\353>~\355\304p\232\2623a\324}\374\223j\217)\027\257\3358N\316\230\371I~d\301\376_)\313\236V:9IS\364\376I\2655k1\313|f\315o\226\305\347\262\214\266j\230NSuw\347J\022S-\037\035\257\221\007v\300\024,|\001S;\264_lh:44Z&\026\2115\302\002\205\270B:^\336\006pe\177\215\247\025h\004{d\267h\030\253\224\047\344\366\200\355\351\216>\320\361\"\351\021\2155\360\360\2606\204%@\020hac\373\303\nD\000\200\216\034\033\034\240\027\314\213\253\316\220\320\340\362\260P:^\236\275A\337\300\321\341\274F!(\\\001%\210\270D8\257\026$\327i\246C\353\203\225!\272\274\ra\024\302\000G \207\355\331 V\037\234\032V*\224\341\207\244/\254<8\257\r\016\3548k]\316\037\374\341\237\371\312\307\370\363Sv-~\307\033\3478\324\331\256\232\270\177\312a\224?}\031\327\334\035e\357\226e[.i\3634e\311\247\274\317\315\353\332\265\370c\351\254\037\226^\351g{\355\3140\245""\370z\n{\230\210\007Z\305E\210\022\"\010OX\250\002\372~\320\002\301q\221Z,\254\017eH\022\201\343\362\010\355\315F\201\225\314\032\227W\215\247\344\205\321\330\330\320|-\331\222\301bq\321\021bh\t\010\n,\000_\266IO\3200\253\002\243-\262m\224\320#3r\246\244\265iy\010_\200\246\216\366j(xZ6\010\006n>(l\017\217d\023@\021Ia\312\300VP\203\201&[\323$M\202\301\031\2400@\370\205Xh\002\302\354\222\321v\000\332\rFa\366l\006;\225\200\304\032\261DX\033\326\007\206`\350\024\266_\364Hg\031\252\344\356\020&\0107/<P\202\252\335b\217B@\241o\325f\306k\205t\302\024\217\202\3064\266\t\321\007@^#P\004T\331O\242[jP\237\004\047\200\030H`\004r@owv\212\226\372\304\n\244\343\265ay\341\tHbs\320\277\n\255\002\201x\250\241!rhDfDR\220\024\2440\034\341\014a\026:u\345\001B\221d\010q\2341D\031C \221#\222\nR\217Bs\264\026,\3104\356\356@\303&\010\030\267m\335\246I\326N\263\213\224\364\312 \243\345\301U:p\372y\023\033\216\241*9\262Q\200\034}\252\367;z8\254\203O\034#\217L\265B\314\034G+$\330\322}\235\327\001U\334L\254\244I\360\316\333\210\321iX\227\375\006\002\202\372h\016\000\204\234\246l@\332\374\014R\276r\304a\020\321r\302\004\366\023\"\207\300)\246\341`2\211|\354\373\335+t\311\010\205\307\352\310c\377p\235\201B\031\312\204\020\030\370\030L\357\025f\375\026\032\343{\264\321\200\367\237\227\333\t\344\324K\335h\347\366\322L\343\256K&\325b+n\367\211\245\211e\245;\335\0060H\203\326\341\215EK\232^\034,\347\013M\020\3664@\017;\310\365\023_`\234\3121TJQ\00759\212)dF\017\303bi\240\001\207\247$.l\240\317\021\304`AY\032\324\225\026j\260\345\336[\374\304\251U}\322\365~\250\n\200\222\245(E\224R\363\224\024\014U\306\005\253\030F\315E\270\325\324\227\032\005\360p-\334\033\212\177|\047\271X\304?\234(\323Jf\276\244\027>\2726\204\275\244\214rJa\250\\4\225\020}\360\313\326<F\207\314$\340f\250\335D\323\326\230$H<\262\341\356)\324\344-\243\324\250\313\227=\313~\0344\252\r4T\317\253\246\260\0000\307n\366\206\314\332\263>p\241\036pH\002\346Ih\237\376o\026e\242%\030\345j\013\252\367L\371B\320\206A\304\267\226\323\330.\267%""\213\273_\323\201\357\270\nG\213\370\235\036,i\304\202\331cD\r\223\271\251\036\017\326j\326\010N,*v\372\367\271\257F\345\366\260&|b\";\323o\214\336bs\322\214\277 *en\222\314\253w\263\263)\372(\224\031\305\016\230\006\2049\2361\367\332\r\245%X\370;\027O%D8p\215>A\2370\223\264\201\016@\352\274\272.2\226\316\257\037\000Su%\260\007#\306\354Q\245\250mY\002\320-RZ\210\235\261\226\352\216\"[\t\010\331\035\220\177\310\317\n\270\304}\\\251\330!Gk\212\017&U\324\322\00645\035\366\261jM\200\257\320\216UF\202\2479\2510\235\275\232\020\264\351\003hR/\276`\217T\013\tK\243\247Qy_^\336[\271\265\276<\342X\250\266\257\013\200\r\270\251YR2\251\356\344\"\237\330\317\204\352jg!W\243\341\275\017\344G\351I\225a?\335N\257\217z\247\261J\013\350\031_\"\203\3173 \047\324\023h\241\314\263\005\345\242\223]\350:\003\355\005]+#\344\247P\221T\001";
28893
28893
  PyObject *data = __Pyx_DecompressString(cstring, 1723, 3);
28894
28894
  #define __Pyx_DecompressString_LZSS_UNUSED
28895
28895
  if (unlikely(!data)) __PYX_ERR(0, 1, __pyx_L1_error)
@@ -28897,9 +28897,9 @@ static const char cstring[] = "(\265/\375`.\021\2155\000zH\374\016E\360\222\250\
28897
28897
  #if !CYTHON_ASSUME_SAFE_MACROS
28898
28898
  if (likely(bytes)); else { Py_DECREF(data); __PYX_ERR(0, 1, __pyx_L1_error) }
28899
28899
  #endif
28900
- #elif (CYTHON_COMPRESS_STRINGS) == 1 /* compression: zlib (1738 bytes) */
28901
- static const char cstring[] = "x\332\245W\315s\323F\024\257\247i\223\222\264\301$\224\351\007t\003e(\323\211K\202\003i\352\2261\020h:-%\020\240\037\007\315ZZ;j\224\225\255\335ub\246\007\216>\352\270G\035u\364\321\307\0349\346\350c\376\204\376\t}\273\262\024\331\226Z:\315\214\254\335\337{zo\367\367\336\276}\371\202{\204\240\272\207\033\373\204\362\3537J7K\253\345\312\335\312\367\225\255\312v\245V\261*\273\025\273\322\272\263\3458\244\201\035Ts]\207`\212\332\330\021d\003=r9\"\324\025\215]d\323\246\340\310\302\034\003H\020\337\305\034\335\353\360]\227\"\233!\2138v\215x\230\023\247\203\030\367l\223\023O)Q\364x\363\361ry\275\2140\265\220G\376 &g\210\211\232\351`\306\010Cn\035\325\204\355p\233\"\336i\022VB[u\324q\005\242\204X\210\273\250\tz\351\017\370.\241\210\021\256\006\350\032\246\324\345\230\333.5\340s\2336\256!\313\366\300\211\335&\352\353\007\330a\244\364s\323t-\202\\\330\001\370\3630m\020tcyumm\003=\243\026\251\333\024|\271Zi\003a\3132\300(\261L~\210\004S\2538\260MR\322\363\003\314\020\0105^\262l\206k\016!c\212dB\221P\245\3270m\026\215,\352\002eu,\034\216\014\303#\2260\211a K\3505S\227.\003\205m\033\002b\030\246Mmn\250\025\241Z\207\023\266\2018\336#\206\036#\023C\340,Tw=\344\330\214k\211\005\344\247\005ZSK\224J\266\004B6ak\2375\232\330\334\373\312t=Rjv\016\357bF\356\023\305\321]Q\257\023o|^Jvb\352\2740\214\014\r\010\034x\344\377\240\343Y\321\336\262\004u\307\305|=KbS\276\222\203\257\346\340\345\034<\323>\020\224\005\213<\277\"\317\261\310\363,b\327\233t\224\343\364<\233\3431\215L\216Gt0;\345xDp\340\345\013N\311\037\227\304$d\340\2539x9\007\317\264?$\177\034\026y~E\236c\221\347Y\223\237\216H4\276\307\017\223\301$\365i\321\004\347\247\302\323\343:\206\251\203:\006\251\2037\006\301\346\307\020.\232\316\310\016\2421\310\223\301\344b\323\242\211\305\236\n\241\310Gk\035\205\324RG\021FZ\202P\223\214\241\334\033\005\364E\362\310\245d\007\212\373S\\\037I\373\361\371\344\25234&\026?\241\223\024\212,\211\312\231\034|5\007/\347\340\231\366E\236\003\221\347A\344\271\020\261\217t\254\307\347\331\234\375[E""\230\320I\316w\226$\336R\326\371\316\301\3139x\246}\221\347@\344y\020y.4g;?EW\327\246\347\271\236a<\356\034\302s\037R\330xD\016\371\023R7\214a\337\000\267\256\241\223[\277]S\275\352\202\352w#&\r\376\366\261M\365\233p\254\033\021=q-\341Db\3175\240\277\362l\242\005\024\357k\230\222\003\365jz\244\211=\215\3005j\230\273\304\334cb?\232y\204A\027\020\215\207\356\324P\365B\321H\320\246m\356\201\237\361K#C\234fcL<\236`\031\342\311\257\333\\u+j\341-\201\235x[q\2772\221y\t@\016\325\004\262.\241\202\245\310\234\310F\303P}\210z\267\211\307l\r\351\240\330\314\200\356\003\3326h\3204%\361\305\205Y\207\232\266[J\244L\301\256g\021\317t\2242\204\213{\330$5H\003\265+\323\252\211\272z\014]BuY\2624\035F\335s\367#\253i@Ub\002\372\3521\222JF4E\361\330\202l\202_\233\223}\346\330\234;\360nP\225yM\267\231T\322\270~\246\253\346\260V&v\343\246\047)]\303J5,L\303:4,;Q;\022\327\232\270\264\304\225$.\034\2148u`z\330f2\327\323\217\261G:\014\330\367\242\020\200\035\001\ry\207\235^R\311\325\224\\H\3615tz\371\304\275\241\342\237\010\275c\321\204\377\r\010t\273Ix\365\306\364\017\213\333\212\244\312\014\213\312\260\206\014K\306\260BD\027~\\\026\342*\020\037\372\370\214\277*\014\246\316t\277\364\267\325\240\024\342\260\335\203\341\311\324l\367\216\\\222_\007\325\340EXU\300g\262u\214n\364f\373\337\034\355\035o?\031L-\034/\\\t\326\303\225\260:\230\232~\365\247\177[.\rfV{K\3077\037\034\321\343\235g\003\260\261\362\252\360\327\314[\357\314u\277\223Ey=X\t\036\206\267{K\032<\363\211\322/\372\213\376\366`\346\254?\355\267\344\273\322\t\227\224\261v\367\205\277)\027%H\316\371\240u\301o\235\300\017\227\353\301\355H\2435\230\231?\236_\351-\366\266{\215\376\357\257\247_\203\306\010R\030\363\362\\\226e;\330\t\213o\356m-\230\016Zo\352\255:\230\231\355\226\273\334\377Z>\010\226\303V\2570\230\231\353\376\340c_}\r\370\232\234\226\255`*\250\276\211\352{A1\370<\300\261j\007V\273\257\251\216\025O`\027\372\263M\377<\314\271\274\025,\004\325\301\334Y\177\036\274\024R\203\242_\364\257F\226[\377\301\336z\260\026N\303\332\346""\373\255\243B\362q\244} \211\nD\270\242\240y\275\221\252|\"\017\002\034\264\322\320s\260Q\010?\014\023\360\033\211\265\326A\210#H\370U\177G\026\243I\333\177\241?*\007\235^\241\267\320\253B*\202\340C\360\330\226z\370\261\212f\332\220\0105\235\237\310\225L\374SY\315\304/\312g\301\265\260\030\246\254u\302\267\025!\221\364\267\200\251\355\245\3309\357o\373\226\274\026\024\223\365\234h\262.\310\017\300l;\324\313C\020\336\037a\351:\307\336\223\347\301lk\002?#\257\252\300L\340\263\220o\205\2408\201\317\311MH\206%\205_U\371{r\232+*\224-H\346\003\331\010\236\207\337\366\013\375\213G\370\010V\273\350?\214\030y)\027\200RX\353\373\335g\376\347~C\356\004\037\351\235\275\177\356\177M\257\370\277\274\371\364\262\2775\234n\364~\355\343~\347u!\013~\371\2728\270\2704\n\234\304l\253PlA\014 oNR\0215U\020\025t\021L\025\225\322\217\262 \213QX\313p\312\317\250ct<u\031\302}\036\252\033\357m\364\267\217\247\356\364\201\275K\220\305\267T=\270$\367\302s\341j\3704\242|\266{\313\277\244B\224b:;\374\367T\376\016\346 +\341\370\025\223\301\337\020\225\0349";
28902
- PyObject *data = __Pyx_DecompressString(cstring, 1738, 1);
28900
+ #elif (CYTHON_COMPRESS_STRINGS) == 1 /* compression: zlib (1739 bytes) */
28901
+ static const char cstring[] = "x\332\245W\315s\323F\024\257\247i\223\222\264\301$\224\351\007t\003e(\323\211K\202\003i\352\2261\020h:-%\020\240\037\007\315ZZ;j\224\225\255\335ub\246\007\216>\352\270G\035u\364\321\307\0349\346\350c\376\204\376\t}\273\262\024\331\226Z:\315\214\254\335\337{zo\367\367\336\276}\371\202{\204\240\272\207\033\373\204\362\3537J7K\253\267*w+\337W\266*\333\225Z\305\252\354V\354J\353\316\226\343\220\006vP\315u\035\202)jcG\220\r\364\310\345\210PW4v\221M\233\202#\013s\014 A|\027st\257\303w]\212l\206,\342\3305\342aN\234\016b\334\263MN<\245D\321\343\315\307\313\345\3652\302\324B\036\371\203\230\234!&j\246\203\031#\014\271uT\023\266\303m\212x\247IX\tm\325Q\307\025\210\022b!\356\242&\350\245?\340\273\204\"F\270\032\240k\230R\227cn\273\324\200\317m\332\270\206,\333\003\047v\233\250\257\037`\207\221\322\317M\323\265\010ra\007\340\317\303\264A\320\215\345\325\265\265\r\364\214Z\244nS\360\345j\245\r\204-\313\000\243\3042\371!\022L\255\342\3006II\317\0170C \324x\311\262\031\2569\204\214)\222\tEB\225^\303\264Y4\262\250\013\224\325\261p82\014\217X\302$\206\201,\241\327L]\272\014\024\266m\010\210a\2306\265\271\241V\204j\035N\330\006\342x\217\030z\214L\014\201\263P\335\365\220c3\256%\026\220\237\026hM-Q*\331\022\010\331\204\255}\326hbs\357+\323\365H\251\3319\274\213\031\271O\024GwE\275N\274\361y)\331\211\251\363\302024 p\340\221\377\203\216gE{\313\022\324\035\027\363\365,\211M\371J\016\276\232\203\227s\360L\373@P\026,\362\374\212<\307\"\317\263\210]o\322Q\216\323\363l\216\307429\036\321\301\354\224\343\021\301\201\227/8%\177\\\022\223\220\201\257\346\340\345\034<\323\376\220\374qX\344\371\025y\216E\236gM~:\"\321\370\036?L\006\223\324\247E\023\234\237\nO\217\353\030\246\016\352\030\244\016\336\030\004\233\037C\270h:#;\210\306 O\006\223\213M\213&\026{*\204\"\037\255u\024RK\035E\030i\tBM2\206ro\024\320\027\311#\227\222\035(\356Oq}$\355\307\347\223\253\316\320\230X\374\204NR(\262$*gr\360\325\034\274\234\203g\332\027y\016D\236\007\221\347B\304>\322\261\036\237gs\366o\025aB\0479\337Y\222xKY\347;""\007/\347\340\231\366E\236\003\221\347A\344\271\320\234\355\374\024]]\233\236\347z\206\361\270s\010\317}Ha\343\0219\344OH\3350\206}\003\334\272\206Nn\375vM\365\252\013\252\337\215\2304\370\333\3076\325o\302\261nD\364\304\265\204\023\211=\327\200\376\312\263\211\026P\274\257aJ\016\324\253\351\221&\3664\002\327\250a\356\022s\217\211\375h\346\021\006]@4\036\272SC\325\013E#A\233\266\271\007~\306/\215\014q\232\2151\361x\202e\210\047\277ns\325\255\250\205\267\004v\342m\305\375\312D\346%\0009T\023\310\272\204\n\226\"s\"\033\rC\365!\352\335&\036\2635\244\203b3\003\272\017h\333\240A\323\224\304\027\027f\035j\332n)\2212\005\273\236E<\323Q\312\020.\356a\223\324 \r\324\256L\253&\352\3521t\t\325e\311\322t\030u\317\335\217\254\246\001U\211\t\350\253\307H*\031\321\024\305c\013\262\t~mN\366\231cs\356\300\273AU\3465\335fRI\343\372\231\256\232\303Z\231\330\215\233\236\244t\r+\325\2600\r\353\320\260\354D\355H\\k\342\322\022W\222\270p0\342\324\201\351a\233\311\\O?\306\036\3510`\337\213B\000v\0044\344\035vzI%WSr!\305\327\320\351\345\023\367\206\212\177\"\364\216E\023\3767 \320\355&\341\325\033\323?,n+\222*3,*\303\0322,\031\303\n\021]\370qY\210\253@|\350\3433\376\2520\230:\323\375\322\337V\203R\210\303v\017\206\047S\263\335;rI~\035T\203\027aU\001\237\311\3261\272\321\233\355\177s\264w\274\375d0\265p\274p%X\017W\302\352`j\372\325\237\376m\2714\230Y\355-\035\337|pD\217w\236\r\300\306\312\253\302_3o\2753\327\375N\026\345\365`%x\030\336\356-i\360\314\047J\277\350/\372\333\203\231\263\376\264\337\222\357J\047\\R\306\332\335\027\376\246\\\224 9\347\203\326\005\277u\002?\\\256\007\267#\215\326`f\376x~\245\267\330\333\3565\372\277\277\236~\r\032#Ha\314\313sY\226\355`\047,\276\271\267\265`:h\275\251\267\352`f\266[\356r\377k\371 X\016[\275\302`f\256\373\203\217}\3655\340krZ\266\202\251\240\372&\252\357\005\305\340\363\000\307\252\035X\355\276\246:V<\201]\350\3176\375\3630\347\362V\260\020T\007sg\375y\360RH\r\212~\321\277\032Yn\375\007{\353\301Z8\rk\233\357\267\216\n\311\307\221\366\201$*\020\341""\212\202\346\365F\252\362\211<\010p\320JC\317\301F!\3740L\300o$\326Z\007!\216 \341W\375\035Y\214&m\377\205\376\250\034tz\205\336B\257\n\251\010\202\017\301c[\352\341\307*\232iC\"\324t~\"W2\361Oe5\023\277(\237\005\327\302b\230\262\326\t\337V\204D\322\337\002\246\266\227b\347\274\277\355[\362ZPL\326s\242\311\272 ?\000\263\355P/\017Ax\177\204\245\353\034{O\236\007\263\255\t\374\214\274\252\0023\201\317B\276\025\202\342\004>\0477!\031\226\024~U\345\357\311i\256\250P\266 \231\017d#x\036~\333/\364/\036\341#X\355\242\3770b\344\245\\\000Ja\255\357w\237\371\237\373\r\271\023|\244w\366\376\271\3775\275\342\377\362\346\323\313\376\326p\272\321\373\265\217\373\235\327\205,\370\345\353\342\340\342\322(p\022\263\255B\261\0051\200\2749IE\324TAT\320E0UTJ?\312\202,Fa-\303)?\243\216\321\361\324e\010\367y\250n\274\267\321\337>\236\272\323\007\366.A\026\337R\365\340\222\334\013\317\205\253\341\323\210\362\331\356-\377\222\nQ\212\351\354\360\337S\371;\230\203\254\204\343WL\006\177\0034\311\034;";
28902
+ PyObject *data = __Pyx_DecompressString(cstring, 1739, 1);
28903
28903
  #define __Pyx_DecompressString_LZSS_UNUSED
28904
28904
  if (unlikely(!data)) __PYX_ERR(0, 1, __pyx_L1_error)
28905
28905
  const char* const bytes = __Pyx_PyBytes_AsString(data);
@@ -28907,7 +28907,7 @@ static const char cstring[] = "x\332\245W\315s\323F\024\257\247i\223\222\264\301
28907
28907
  if (likely(bytes)); else { Py_DECREF(data); __PYX_ERR(0, 1, __pyx_L1_error) }
28908
28908
  #endif
28909
28909
  #elif (CYTHON_COMPRESS_STRINGS) > 0 && (CYTHON_COMPRESS_STRINGS) <= 90 /* compression: lzss (2299 bytes) */
28910
- static const char cstring[] = "\377(tree fr\377agment)0\377.3.24<B<\377H<I<Q<b<\377d<h<i<q?\377Illegal \377boolean \377value: N\377ot enoug\377h input \357data\022\000e t\377hat Cyth\377on is de\377liberate\377ly stric\367ter!\001n PE\377P-484 an\377d reject\377s subcla\377sses of \377builtin \377types. I\377f you ne\177ed to p%\000\376%\tthen se\375t\200\000e \047ann\377otation_\376<\000ing\047 di\355rb\000iv\242\000o F\377alse.Opc\237ode o\277\000m\000r\377ange 0-2\37755: Unde\327fini\000o \002: \357add_R\000edc\337tx us\202\001wi\367ce.\013\002was \374o\000\023\002.disab\307lee\032\014\013\002\034\nen\336$\001gcis\004\003dn\375o\260 fault \377__reduce\337__ du\257\002no\333n-\300 vi\216@__\277cinit_\216\001 \377bytes: t\357ake_\007\002 ca\376\266@d for l\027ist\025\002d\372 \r\t1\002\340/\002\037\001\004\023\260@;\016msg\377pack/cor\377e.pyxBas\367eDe\305!Buff\253er\000\r.\301\006c\223b_i_\017\020\266@s\255@e_\013\026\343rd\326\003N\016\026\000flo\367at8\006\021int1\312\001\0242\031\02441\024K\022st\245r}\021u`\025\025\0012\032\0254\0143\025\326\002En\326+\004\t\3163\023\014\314\31729\nas\332\047R\nwr\002\000\026_\363\047#\r\365%\005\020\370\"\035\020\210\370\"5\020\361\016.\220\000\366$\232\ru `\025\364&\036\021e\022\304A8\321\204\t\343\204\003\007Ctx\000\006\315\204\017\017\t\307\204\016.\007\300\245\206\007\005\014\245\206\001\030\014\301\206\001+\014st}r=\014tuple\232i\200\254c\252\000\000\006\372\205\017\017\t\364\205\016.\007p\303ut\325\207\003\005\013\320\207\001\027\013se\277quence\007\014t\371r>\013\304\213\002NoneT?ypeSaf\265\207\n\000\r\000\260\207\017\017\020\261\207\0165\016\240\207\006\006\021\245\207\001\001\024U2\031\02441\0248f\021uI\025\004\215\207\002\032\02543\025_\002\206\207\t\300!\364\206\033\340\017\020\2372\351\206\r\327\211\003O\016wr_\250\254%\314\206\021\031\02441\0248L\021upI\025\253&\264\206\0223\0258TM\265\214\003\377Error__P\373yx\001\000Dict_\377NextRef_}_\261\217\004e____\255\215\001v\003\002oc\013\001fun\003\002\347get\245\214\003!\000mai=n\047\001meta\265""\220\002\010\002\357odul=\002mro\377_entries\356M\001namT\002new~\\\001preparf\002\375p\210\000checks\357um__\n\001res\007ult\006\003h\004!\001\210\221\001\033\003?unpicke\000\360\215\r\300\014\020\257\213\t/\014\354\205\r\014\020,\017vt<\334\220\001\240!qual\317\005\250\220\005\354\337\216\016\302\220\006ex\324!set\345_\203%s\302(\363\216\016__t\367est\207Avers\374\310\222\001\216Dis_cor\277outine\334\222\001e\376\261\214\005asyncio\365.\025\006s\230\221\001orde\347rcl)\001\254@tra\277ceback\370\222\001c/dbuf\000\001_\271\221\002\333\224\002}d\310\220\002_from\311\221\003$\005\t\313\221\001e1\000\000\001_\222\211\001\216\225\002\325e\227\216\002_\005\004d\313`en\377ditemsli\267ttl\005\000gn\261\221\001p\003op\301\211\006\317\211\001\220\222\001\327\211\001\256\211\005\007\002ctrO\006\355\220\005\333\220\006rd\361\205\002\352\001\0032\010\0034\017\0038rd\177_strrd_\371\217\002\352\001\0042\t\0044\021\0048se\177lfsetde\342\223\002\357sort\000\001_ke\277ysstar\326\204\003s\377tructsysp\312\223\007\273\223\006\252\223\006\353\223\002str\363\223\002\262\370\213\002t\225\223\004\374!eu\355\002u\377pdateuse\321_\333\222\005\277\227\002\304\227\002s\317\217\005wr\250\277\217\004\316\207\004\326\207\0032\335\207\0034\344\207\0038\246\353\216\003wr\331\003\001\0042\t\0044\376\021\0048\200\001\330\004\n\210\367+\220Q\005\001.\250a\250\373v\260\010\000\340\004\013\210?\377\230!\2309\240A\240W\377\250A\200\001\340\004\037\230\377q\320 0\260\013\270;\377\300k\320QR\330\004\023\377\320\023#\2408\2501\250\377A\330\004\007\200|\2207\377\230!\330\0102\260!\320\3773F\300n\320TU\330\376A\0001\200\001\360\010\000\005\377\014\210>\230\021\230)\240\1771\240G\2507\260!\020\002\373\n\033,\001\021\220\024\220Q\377\330\010\020\220\007\220q\230\337\006\230l\250!I\001v\210\277W\220E\230\024\230\027\000\022\375\220S\000\027\220q\340\010\027\277\220t\2308\2407\034\003q\377\330\010\017\320\0171\260\024\377\260Q\260g\270[\310\007\351\310\037\000\005\n\001W\014V\2304\277\230v\240T\250\021D\0475""\317\240\007\240q\331\001J!A\330\377\010\013\2104\210t\2209\377\230F\240-\250q\260\001\377\330\010\014\210J\220a\220\374\250\001\025\0005\230\007\230q\240\357\004\240A\200\010 \t\240\021\257\240$\240a#\005y\231 m\344\346\"L\005\340\261 ]\002E\220\025\376a\000t\2306\240\023\240A_\330\014\020\220\017c\000\001\000\006\177\330\014\021\220\021\220%\177\003}q%(8\2405\250\007\304\000o\017\270q\300\310\000\017\2108\002n\314\003w\230e\205A\2501\307\001\375\017\357\002A\230R\230w\240\327a\240q\007\tVA\000\001\250\273\026\250\014\006;\230a!\002w\375\250\326\002\017\210u\220A\220\367T\230\021B\003v\220W\230\377A\230V\2404\240y\260\177\001\260\023\260A\260Q\250!-\026\301 v\230\006\002\032\340am\001vA\005u\250\312\"\033\2301\216`\334\372 \t\014\034\230A\003\022\035\230_U\240\047\250\021\353`\010F\005\347y\250\003\310\204\001\231A\035\230Z\277\240s\250!\2501\271F\340\377\010\025\220Q\220d\230\047\351\240\274`\211\004\340\334@E\220\027[\230\016\363\000v\250\245\002 \334@9L\270\000\337b\t\230\025\353\000\356\"\216\013\n\n\230%\236A\202a#\n\013\277\2305\240\001\240\021\007\016\014\253\230E\212`!\262a%\274\204\001\340\374\315g\355aq\220\007\220w\230\377g\240V\250=\270\001\270\377\035\300a\300q\340\010\024\273\220G\214#z\230\023\320 Q\377\340\010\r\210U\220$\220\337g\230T\240\031\325\002\r\0228\000\014\000\033,\r#\220X?\t\000\014\356\017\014\"\220Iq\004:\260Y\177\270a\270y\310\001\330}\000~\007\rz\310\021\330\035!\233\002L\"\003\016\000\340\010\367D\311\205\001I\201@\357a\200A\340\253Fw\240c\324\301A\017\001\035\327\000\021\356\205\001L\230\367\001\230\021\207c4\220q\230\335\n\303\205\001\320\004\"\354@\025\250\377a\250t\260:\270Q\320\377\004?\270q\330\010\036\230\367e\2406\214\207\001\036\230k\250\337\022\2502\250S\265C\013\210G6\220\036\301\205\001\337(\351SC\274\204\001\177\330\014\032\230!\2306\227`\000\001\005";
28910
+ static const char cstring[] = "\377(tree fr\377agment)0\377.3.26<B<\377H<I<Q<b<\377d<h<i<q?\377Illegal \377boolean \377value: N\377ot enoug\377h input \357data\022\000e t\377hat Cyth\377on is de\377liberate\377ly stric\367ter!\001n PE\377P-484 an\377d reject\377s subcla\377sses of \377builtin \377types. I\377f you ne\177ed to p%\000\376%\tthen se\375t\200\000e \047ann\377otation_\376<\000ing\047 di\355rb\000iv\242\000o F\377alse.Opc\237ode o\277\000m\000r\377ange 0-2\37755: Unde\327fini\000o \002: \357add_R\000edc\337tx us\202\001wi\367ce.\013\002was \374o\000\023\002.disab\307lee\032\014\013\002\034\nen\336$\001gcis\004\003dn\375o\260 fault \377__reduce\337__ du\257\002no\333n-\300 vi\216@__\277cinit_\216\001 \377bytes: t\357ake_\007\002 ca\376\266@d for l\027ist\025\002d\372 \r\t1\002\340/\002\037\001\004\023\260@;\016msg\377pack/cor\377e.pyxBas\367eDe\305!Buff\253er\000\r.\301\006c\223b_i_\017\020\266@s\255@e_\013\026\343rd\326\003N\016\026\000flo\367at8\006\021int1\312\001\0242\031\02441\024K\022st\245r}\021u`\025\025\0012\032\0254\0143\025\326\002En\326+\004\t\3163\023\014\314\31729\nas\332\047R\nwr\002\000\026_\363\047#\r\365%\005\020\370\"\035\020\210\370\"5\020\361\016.\220\000\366$\232\ru `\025\364&\036\021e\022\304A8\321\204\t\343\204\003\007Ctx\000\006\315\204\017\017\t\307\204\016.\007\300\245\206\007\005\014\245\206\001\030\014\301\206\001+\014st}r=\014tuple\232i\200\254c\252\000\000\006\372\205\017\017\t\364\205\016.\007p\303ut\325\207\003\005\013\320\207\001\027\013se\277quence\007\014t\371r>\013\304\213\002NoneT?ypeSaf\265\207\n\000\r\000\260\207\017\017\020\261\207\0165\016\240\207\006\006\021\245\207\001\001\024U2\031\02441\0248f\021uI\025\004\215\207\002\032\02543\025_\002\206\207\t\300!\364\206\033\340\017\020\2372\351\206\r\327\211\003O\016wr_\250\254%\314\206\021\031\02441\0248L\021upI\025\253&\264\206\0223\0258TM\265\214\003\377Error__P\373yx\001\000Dict_\377NextRef_}_\261\217\004e____\255\215\001v\003\002oc\013\001fun\003\002\347get\245\214\003!\000mai=n\047\001meta\265""\220\002\010\002\357odul=\002mro\377_entries\356M\001namT\002new~\\\001preparf\002\375p\210\000checks\357um__\n\001res\007ult\006\003h\004!\001\210\221\001\033\003?unpicke\000\360\215\r\300\014\020\257\213\t/\014\354\205\r\014\020,\017vt<\334\220\001\240!qual\317\005\250\220\005\354\337\216\016\302\220\006ex\324!set\345_\203%s\302(\363\216\016__t\367est\207Avers\374\310\222\001\216Dis_cor\277outine\334\222\001e\376\261\214\005asyncio\365.\025\006s\230\221\001orde\347rcl)\001\254@tra\277ceback\370\222\001c/dbuf\000\001_\271\221\002\333\224\002}d\310\220\002_from\311\221\003$\005\t\313\221\001e1\000\000\001_\222\211\001\216\225\002\325e\227\216\002_\005\004d\313`en\377ditemsli\267ttl\005\000gn\261\221\001p\003op\301\211\006\317\211\001\220\222\001\327\211\001\256\211\005\007\002ctrO\006\355\220\005\333\220\006rd\361\205\002\352\001\0032\010\0034\017\0038rd\177_strrd_\371\217\002\352\001\0042\t\0044\021\0048se\177lfsetde\342\223\002\357sort\000\001_ke\277ysstar\326\204\003s\377tructsysp\312\223\007\273\223\006\252\223\006\353\223\002str\363\223\002\262\370\213\002t\225\223\004\374!eu\355\002u\377pdateuse\321_\333\222\005\277\227\002\304\227\002s\317\217\005wr\250\277\217\004\316\207\004\326\207\0032\335\207\0034\344\207\0038\246\353\216\003wr\331\003\001\0042\t\0044\376\021\0048\200\001\330\004\n\210\367+\220Q\005\001.\250a\250\373v\260\010\000\340\004\013\210?\377\230!\2309\240A\240W\377\250A\200\001\340\004\037\230\377q\320 0\260\013\270;\377\300k\320QR\330\004\023\377\320\023#\2408\2501\250\377A\330\004\007\200|\2207\377\230!\330\0102\260!\320\3773F\300n\320TU\330\376A\0001\200\001\360\010\000\005\377\014\210>\230\021\230)\240\1771\240G\2507\260!\020\002\373\n\033,\001\021\220\024\220Q\377\330\010\020\220\007\220q\230\337\006\230l\250!I\001v\210\277W\220E\230\024\230\027\000\022\375\220S\000\027\220q\340\010\027\277\220t\2308\2407\034\003q\377\330\010\017\320\0171\260\024\377\260Q\260g\270[\310\007\351\310\037\000\005\n\001W\014V\2304\277\230v\240T\250\021D\0475""\317\240\007\240q\331\001J!A\330\377\010\013\2104\210t\2209\377\230F\240-\250q\260\001\377\330\010\014\210J\220a\220\374\250\001\025\0005\230\007\230q\240\357\004\240A\200\010 \t\240\021\257\240$\240a#\005y\231 m\344\346\"L\005\340\261 ]\002E\220\025\376a\000t\2306\240\023\240A_\330\014\020\220\017c\000\001\000\006\177\330\014\021\220\021\220%\177\003}q%(8\2405\250\007\304\000o\017\270q\300\310\000\017\2108\002n\314\003w\230e\205A\2501\307\001\375\017\357\002A\230R\230w\240\327a\240q\007\tVA\000\001\250\273\026\250\014\006;\230a!\002w\375\250\326\002\017\210u\220A\220\367T\230\021B\003v\220W\230\377A\230V\2404\240y\260\177\001\260\023\260A\260Q\250!-\026\301 v\230\006\002\032\340am\001vA\005u\250\312\"\033\2301\216`\334\372 \t\014\034\230A\003\022\035\230_U\240\047\250\021\353`\010F\005\347y\250\003\310\204\001\231A\035\230Z\277\240s\250!\2501\271F\340\377\010\025\220Q\220d\230\047\351\240\274`\211\004\340\334@E\220\027[\230\016\363\000v\250\245\002 \334@9L\270\000\337b\t\230\025\353\000\356\"\216\013\n\n\230%\236A\202a#\n\013\277\2305\240\001\240\021\007\016\014\253\230E\212`!\262a%\274\204\001\340\374\315g\355aq\220\007\220w\230\377g\240V\250=\270\001\270\377\035\300a\300q\340\010\024\273\220G\214#z\230\023\320 Q\377\340\010\r\210U\220$\220\337g\230T\240\031\325\002\r\0228\000\014\000\033,\r#\220X?\t\000\014\356\017\014\"\220Iq\004:\260Y\177\270a\270y\310\001\330}\000~\007\rz\310\021\330\035!\233\002L\"\003\016\000\340\010\367D\311\205\001I\201@\357a\200A\340\253Fw\240c\324\301A\017\001\035\327\000\021\356\205\001L\230\367\001\230\021\207c4\220q\230\335\n\303\205\001\320\004\"\354@\025\250\377a\250t\260:\270Q\320\377\004?\270q\330\010\036\230\367e\2406\214\207\001\036\230k\250\337\022\2502\250S\265C\013\210G6\220\036\301\205\001\337(\351SC\274\204\001\177\330\014\032\230!\2306\227`\000\001\005";
28911
28911
  PyObject *data = __Pyx_DecompressString_LZSS(cstring, 2299, 4654);
28912
28912
  #define __Pyx_DecompressString_UNUSED
28913
28913
  if (unlikely(!data)) __PYX_ERR(0, 1, __pyx_L1_error)
@@ -28916,7 +28916,7 @@ static const char cstring[] = "\377(tree fr\377agment)0\377.3.24<B<\377H<I<Q<b<\
28916
28916
  if (likely(bytes)); else { Py_DECREF(data); __PYX_ERR(0, 1, __pyx_L1_error) }
28917
28917
  #endif
28918
28918
  #else /* compression: none (4654 bytes) */
28919
- static const char bytes[] = "(tree fragment)0.3.24<B<H<I<Q<b<d<h<i<q?Illegal boolean value: Not enough input dataNote that Cython is deliberately stricter than PEP-484 and rejects subclasses of builtin types. If you need to pass subclasses then set the \047annotation_typing\047 directive to False.Opcode out of range 0-255: Undefined opcode: add_notedctx used twice.dctx was not used.disableectx used twice.ectx was not used.enablegcisenabledno default __reduce__ due to non-trivial __cinit__not bytes: take_bytes called for listtake_dict called for bytestake_list called for bytestake_str called for listtmsgpack/core.pyxBaseDecodeBufferBaseDecodeBuffer.__reduce_cython__BaseDecodeBuffer.__setstate_cython__BaseDecodeBuffer.rd_bytesBaseDecodeBuffer.rd_float8BaseDecodeBuffer.rd_int1BaseDecodeBuffer.rd_int2BaseDecodeBuffer.rd_int4BaseDecodeBuffer.rd_int8BaseDecodeBuffer.rd_strBaseDecodeBuffer.rd_uint1BaseDecodeBuffer.rd_uint2BaseDecodeBuffer.rd_uint4BaseDecodeBuffer.rd_uint8BaseEncodeBufferBaseEncodeBuffer.__reduce_cython__BaseEncodeBuffer.__setstate_cython__BaseEncodeBuffer.as_bytesBaseEncodeBuffer.wr_bytesBaseEncodeBuffer.wr_float8BaseEncodeBuffer.wr_int1BaseEncodeBuffer.wr_int2BaseEncodeBuffer.wr_int4BaseEncodeBuffer.wr_int8BaseEncodeBuffer.wr_strBaseEncodeBuffer.wr_uint1BaseEncodeBuffer.wr_uint2BaseEncodeBuffer.wr_uint4BaseEncodeBuffer.wr_uint8DecodeBufferDecodeCtxDecodeCtx.__reduce_cython__DecodeCtx.__setstate_cython__DecodeCtx.take_bytesDecodeCtx.take_dictDecodeCtx.take_listDecodeCtx.take_strDecodeCtx.take_tupleEncodeBufferEncodeCtxEncodeCtx.__reduce_cython__EncodeCtx.__setstate_cython__EncodeCtx.put_bytesEncodeCtx.put_dictEncodeCtx.put_sequenceEncodeCtx.put_strEncodeCtx.put_valueNoneTypeSafeDecodeBufferSafeDecodeBuffer.__reduce_cython__SafeDecodeBuffer.__setstate_cython__SafeDecodeBuffer.rd_float8SafeDecodeBuffer.rd_int1SafeDecodeBuffer.rd_int2SafeDecodeBuffer.rd_int4SafeDecodeBuffer.rd_int8SafeDecodeBuffer.rd_uint1SafeDecodeBuffer.rd_uint2SafeDecodeBuffer.rd_uint4SafeDecodeBuffer.rd_uint8SafeEnco""deBufferSafeEncodeBuffer.__reduce_cython__SafeEncodeBuffer.__setstate_cython__SafeEncodeBuffer.wr_float8SafeEncodeBuffer.wr_int1SafeEncodeBuffer.wr_int2SafeEncodeBuffer.wr_int4SafeEncodeBuffer.wr_int8SafeEncodeBuffer.wr_uint1SafeEncodeBuffer.wr_uint2SafeEncodeBuffer.wr_uint4SafeEncodeBuffer.wr_uint8TMsgpackError__Pyx_PyDict_NextRef__annotate____dict____doc____func____getstate____main____metaclass____module____mro_entries____name____new____prepare____pyx_checksum__pyx_result__pyx_state__pyx_type__pyx_unpickle_BaseDecodeBuffer__pyx_unpickle_BaseEncodeBuffer__pyx_unpickle_SafeDecodeBuffer__pyx_unpickle_SafeEncodeBuffer__pyx_vtable____qualname____reduce____reduce_cython____reduce_ex____set_name____setstate____setstate_cython____test____version___dict_is_coroutine_typeas_bytesasyncio.coroutinesbyteordercline_in_tracebackcodecdbufdbuf_take_valuedecode_from_bytesdecode_from_listebufebuf_put_valueencode_valueendextenditemslittlemsgnpackpopput_bytesput_dictput_sequenceput_strput_valuerd_bytesrd_float8rd_int1rd_int2rd_int4rd_int8rd_strrd_uint1rd_uint2rd_uint4rd_uint8selfsetdefaultsortsort_keysstartstatestructsystake_bytestake_dicttake_listtake_strtake_tupletmsgpack.coreunpackupdateuse_setstatevaluevalueswr_byteswr_float8wr_int1wr_int2wr_int4wr_int8wr_strwr_uint1wr_uint2wr_uint4wr_uint8\200\001\330\004\n\210+\220Q\200\001\330\004.\250a\250v\260Q\200\001\340\004\013\210?\230!\2309\240A\240W\250A\200\001\340\004\037\230q\320 0\260\013\270;\300k\320QR\330\004\023\320\023#\2408\2501\250A\330\004\007\200|\2207\230!\330\0102\260!\3203F\300n\320TU\330\004\013\2101\200\001\360\010\000\005\014\210>\230\021\230)\2401\240G\2507\260!\200\001\360\010\000\n\033\230!\330\010\021\220\024\220Q\330\010\020\220\007\220q\230\006\230l\250!\330\004\007\200v\210W\220E\230\024\230Q\330\010\022\220!\330\010\027\220q\340\010\027\220t\2308\2407\250!\330\004\007\200q\330\010\017\320\0171\260\024\260Q\260g\270[\310\007\310q\340\010\017\320\0171\260\024\260Q\260g\270[\310\001\200\001\360\010\000\n\033\230!""\330\010\021\220\024\220V\2304\230v\240T\250\021\330\010\020\220\007\220q\230\006\230l\250!\330\004\007\200v\210W\220E\230\024\230Q\330\010\022\220!\330\010\027\220q\340\010\027\220t\2305\240\007\240q\330\004\007\200q\330\010\017\320\0171\260\024\260Q\260g\270[\310\007\310q\340\010\017\320\0171\260\024\260Q\260g\270[\310\001\200A\330\010\013\2104\210t\2209\230F\240-\250q\260\001\330\010\014\210J\220a\220q\330\010\017\210t\2205\230\007\230q\240\004\240A\200A\330\010\013\2104\210t\2209\230F\240-\250q\260\001\330\010\014\210J\220a\220q\330\010\017\210t\2205\230\t\240\021\240$\240a\200A\330\010\013\2104\210y\230\006\230m\2501\250A\330\010\014\210J\220a\220q\340\010\020\220\001\330\010\014\210E\220\025\220a\220t\2306\240\023\240A\330\014\020\220\017\230q\240\001\330\014\020\220\017\230q\240\001\330\014\021\220\021\220%\220q\330\010\017\210q\200A\330\010\013\2104\210y\230\006\230m\2501\250A\330\010\014\210J\220a\220q\340\010\020\220\001\330\010\014\210E\220\025\220a\220t\2308\2405\250\007\250q\260\017\270q\300\001\330\010\017\210q\200A\330\010\014\210J\220a\220w\230e\2407\250!\2501\200A\330\010\017\210t\2209\230A\230R\230w\240a\240q\200A\330\010\017\210t\2209\230A\230V\2405\250\001\250\026\250q\200A\330\010\017\210t\220;\230a\230w\240a\240w\250a\200A\330\010\017\210u\220A\220T\230\021\200A\330\010\017\210v\220W\230A\230V\2404\240y\260\001\260\023\260A\260Q\200A\330\010\026\220a\220v\230Q\200A\330\010\032\230!\330\010\017\210t\220;\230a\230w\240a\240u\250A\200A\330\010\033\2301\330\010\017\210t\220;\230a\230w\240a\240u\250A\200A\330\010\034\230A\330\010\017\210t\220;\230a\230w\240a\240u\250A\200A\330\010\035\230U\240\047\250\021\250!\330\010\017\210t\220;\230a\230y\250\003\2501\250A\200A\330\010\035\230Z\240s\250!\2501\330\010\014\210J\220a\220q\340\010\025\220Q\220d\230\047\240\021\330\010\026\220a\220v\230Q\340\010\014\210E\220\027\230\016\240a\240v\250Q\200A\330\010 \240\004\240L\260\001\260\021\330\010\020\220\t\230\025\230a\230q\200A\330\010 \240\004\240L\260\001\260""\021\330\010\020\220\n\230%\230q\240\001\200A\330\010 \240\004\240L\260\001\260\021\330\010\020\220\013\2305\240\001\240\021\200A\330\010 \240\004\240L\260\001\260\021\330\010\020\220\014\230E\240\021\240!\200A\330\010%\240T\250\021\340\010\014\210J\220a\220q\330\010\013\2104\210q\220\007\220w\230g\240V\250=\270\001\270\035\300a\300q\340\010\024\220G\2301\330\010\017\210z\230\023\230A\230Q\340\010\r\210U\220$\220g\230T\240\031\250!\2501\330\r\022\220$\220g\230T\240\031\250!\2501\330\r\022\220$\220g\230T\240\031\250!\2501\330\r\022\220$\220g\230T\240\031\250!\2501\330\r\022\220$\220g\230T\240\031\250!\2501\330\r\022\220#\220X\230T\240\031\250!\2501\330\r\022\220#\220X\230T\240\031\250!\2501\330\r\022\220#\220X\230T\240\031\250!\2501\330\r\022\220\"\220I\230T\240\031\250!\250:\260Y\270a\270y\310\001\330\r\022\220\"\220I\230T\240\031\250!\250:\260Y\270a\270z\310\021\330\035!\240\031\250!\250:\260Y\270a\270z\310\021\340\010\026\220a\220v\230Q\330\010\014\210I\220Q\220a\200A\340\010\017\210t\220;\230a\230w\240c\250\021\250!\200A\340\010\035\230T\240\021\330\010\014\210L\230\001\230\021\330\010\017\210t\2204\220q\230\n\240$\240a\320\004\"\240\047\250\025\250a\250t\260:\270Q\320\004?\270q\330\010\036\230e\2406\250\021\330\010\036\230k\250\022\2502\250S\260\001\260\021\330\010\013\2106\220\036\230q\240\001\340\010\014\210J\220a\220q\330\010\025\220Q\220d\230\047\240\021\330\010\026\220a\220v\230Q\340\010\014\210C\210u\220A\330\014\032\230!\2306\240\021\330\014\032\230!\2306\240\021";
28919
+ static const char bytes[] = "(tree fragment)0.3.26<B<H<I<Q<b<d<h<i<q?Illegal boolean value: Not enough input dataNote that Cython is deliberately stricter than PEP-484 and rejects subclasses of builtin types. If you need to pass subclasses then set the \047annotation_typing\047 directive to False.Opcode out of range 0-255: Undefined opcode: add_notedctx used twice.dctx was not used.disableectx used twice.ectx was not used.enablegcisenabledno default __reduce__ due to non-trivial __cinit__not bytes: take_bytes called for listtake_dict called for bytestake_list called for bytestake_str called for listtmsgpack/core.pyxBaseDecodeBufferBaseDecodeBuffer.__reduce_cython__BaseDecodeBuffer.__setstate_cython__BaseDecodeBuffer.rd_bytesBaseDecodeBuffer.rd_float8BaseDecodeBuffer.rd_int1BaseDecodeBuffer.rd_int2BaseDecodeBuffer.rd_int4BaseDecodeBuffer.rd_int8BaseDecodeBuffer.rd_strBaseDecodeBuffer.rd_uint1BaseDecodeBuffer.rd_uint2BaseDecodeBuffer.rd_uint4BaseDecodeBuffer.rd_uint8BaseEncodeBufferBaseEncodeBuffer.__reduce_cython__BaseEncodeBuffer.__setstate_cython__BaseEncodeBuffer.as_bytesBaseEncodeBuffer.wr_bytesBaseEncodeBuffer.wr_float8BaseEncodeBuffer.wr_int1BaseEncodeBuffer.wr_int2BaseEncodeBuffer.wr_int4BaseEncodeBuffer.wr_int8BaseEncodeBuffer.wr_strBaseEncodeBuffer.wr_uint1BaseEncodeBuffer.wr_uint2BaseEncodeBuffer.wr_uint4BaseEncodeBuffer.wr_uint8DecodeBufferDecodeCtxDecodeCtx.__reduce_cython__DecodeCtx.__setstate_cython__DecodeCtx.take_bytesDecodeCtx.take_dictDecodeCtx.take_listDecodeCtx.take_strDecodeCtx.take_tupleEncodeBufferEncodeCtxEncodeCtx.__reduce_cython__EncodeCtx.__setstate_cython__EncodeCtx.put_bytesEncodeCtx.put_dictEncodeCtx.put_sequenceEncodeCtx.put_strEncodeCtx.put_valueNoneTypeSafeDecodeBufferSafeDecodeBuffer.__reduce_cython__SafeDecodeBuffer.__setstate_cython__SafeDecodeBuffer.rd_float8SafeDecodeBuffer.rd_int1SafeDecodeBuffer.rd_int2SafeDecodeBuffer.rd_int4SafeDecodeBuffer.rd_int8SafeDecodeBuffer.rd_uint1SafeDecodeBuffer.rd_uint2SafeDecodeBuffer.rd_uint4SafeDecodeBuffer.rd_uint8SafeEnco""deBufferSafeEncodeBuffer.__reduce_cython__SafeEncodeBuffer.__setstate_cython__SafeEncodeBuffer.wr_float8SafeEncodeBuffer.wr_int1SafeEncodeBuffer.wr_int2SafeEncodeBuffer.wr_int4SafeEncodeBuffer.wr_int8SafeEncodeBuffer.wr_uint1SafeEncodeBuffer.wr_uint2SafeEncodeBuffer.wr_uint4SafeEncodeBuffer.wr_uint8TMsgpackError__Pyx_PyDict_NextRef__annotate____dict____doc____func____getstate____main____metaclass____module____mro_entries____name____new____prepare____pyx_checksum__pyx_result__pyx_state__pyx_type__pyx_unpickle_BaseDecodeBuffer__pyx_unpickle_BaseEncodeBuffer__pyx_unpickle_SafeDecodeBuffer__pyx_unpickle_SafeEncodeBuffer__pyx_vtable____qualname____reduce____reduce_cython____reduce_ex____set_name____setstate____setstate_cython____test____version___dict_is_coroutine_typeas_bytesasyncio.coroutinesbyteordercline_in_tracebackcodecdbufdbuf_take_valuedecode_from_bytesdecode_from_listebufebuf_put_valueencode_valueendextenditemslittlemsgnpackpopput_bytesput_dictput_sequenceput_strput_valuerd_bytesrd_float8rd_int1rd_int2rd_int4rd_int8rd_strrd_uint1rd_uint2rd_uint4rd_uint8selfsetdefaultsortsort_keysstartstatestructsystake_bytestake_dicttake_listtake_strtake_tupletmsgpack.coreunpackupdateuse_setstatevaluevalueswr_byteswr_float8wr_int1wr_int2wr_int4wr_int8wr_strwr_uint1wr_uint2wr_uint4wr_uint8\200\001\330\004\n\210+\220Q\200\001\330\004.\250a\250v\260Q\200\001\340\004\013\210?\230!\2309\240A\240W\250A\200\001\340\004\037\230q\320 0\260\013\270;\300k\320QR\330\004\023\320\023#\2408\2501\250A\330\004\007\200|\2207\230!\330\0102\260!\3203F\300n\320TU\330\004\013\2101\200\001\360\010\000\005\014\210>\230\021\230)\2401\240G\2507\260!\200\001\360\010\000\n\033\230!\330\010\021\220\024\220Q\330\010\020\220\007\220q\230\006\230l\250!\330\004\007\200v\210W\220E\230\024\230Q\330\010\022\220!\330\010\027\220q\340\010\027\220t\2308\2407\250!\330\004\007\200q\330\010\017\320\0171\260\024\260Q\260g\270[\310\007\310q\340\010\017\320\0171\260\024\260Q\260g\270[\310\001\200\001\360\010\000\n\033\230!""\330\010\021\220\024\220V\2304\230v\240T\250\021\330\010\020\220\007\220q\230\006\230l\250!\330\004\007\200v\210W\220E\230\024\230Q\330\010\022\220!\330\010\027\220q\340\010\027\220t\2305\240\007\240q\330\004\007\200q\330\010\017\320\0171\260\024\260Q\260g\270[\310\007\310q\340\010\017\320\0171\260\024\260Q\260g\270[\310\001\200A\330\010\013\2104\210t\2209\230F\240-\250q\260\001\330\010\014\210J\220a\220q\330\010\017\210t\2205\230\007\230q\240\004\240A\200A\330\010\013\2104\210t\2209\230F\240-\250q\260\001\330\010\014\210J\220a\220q\330\010\017\210t\2205\230\t\240\021\240$\240a\200A\330\010\013\2104\210y\230\006\230m\2501\250A\330\010\014\210J\220a\220q\340\010\020\220\001\330\010\014\210E\220\025\220a\220t\2306\240\023\240A\330\014\020\220\017\230q\240\001\330\014\020\220\017\230q\240\001\330\014\021\220\021\220%\220q\330\010\017\210q\200A\330\010\013\2104\210y\230\006\230m\2501\250A\330\010\014\210J\220a\220q\340\010\020\220\001\330\010\014\210E\220\025\220a\220t\2308\2405\250\007\250q\260\017\270q\300\001\330\010\017\210q\200A\330\010\014\210J\220a\220w\230e\2407\250!\2501\200A\330\010\017\210t\2209\230A\230R\230w\240a\240q\200A\330\010\017\210t\2209\230A\230V\2405\250\001\250\026\250q\200A\330\010\017\210t\220;\230a\230w\240a\240w\250a\200A\330\010\017\210u\220A\220T\230\021\200A\330\010\017\210v\220W\230A\230V\2404\240y\260\001\260\023\260A\260Q\200A\330\010\026\220a\220v\230Q\200A\330\010\032\230!\330\010\017\210t\220;\230a\230w\240a\240u\250A\200A\330\010\033\2301\330\010\017\210t\220;\230a\230w\240a\240u\250A\200A\330\010\034\230A\330\010\017\210t\220;\230a\230w\240a\240u\250A\200A\330\010\035\230U\240\047\250\021\250!\330\010\017\210t\220;\230a\230y\250\003\2501\250A\200A\330\010\035\230Z\240s\250!\2501\330\010\014\210J\220a\220q\340\010\025\220Q\220d\230\047\240\021\330\010\026\220a\220v\230Q\340\010\014\210E\220\027\230\016\240a\240v\250Q\200A\330\010 \240\004\240L\260\001\260\021\330\010\020\220\t\230\025\230a\230q\200A\330\010 \240\004\240L\260\001\260""\021\330\010\020\220\n\230%\230q\240\001\200A\330\010 \240\004\240L\260\001\260\021\330\010\020\220\013\2305\240\001\240\021\200A\330\010 \240\004\240L\260\001\260\021\330\010\020\220\014\230E\240\021\240!\200A\330\010%\240T\250\021\340\010\014\210J\220a\220q\330\010\013\2104\210q\220\007\220w\230g\240V\250=\270\001\270\035\300a\300q\340\010\024\220G\2301\330\010\017\210z\230\023\230A\230Q\340\010\r\210U\220$\220g\230T\240\031\250!\2501\330\r\022\220$\220g\230T\240\031\250!\2501\330\r\022\220$\220g\230T\240\031\250!\2501\330\r\022\220$\220g\230T\240\031\250!\2501\330\r\022\220$\220g\230T\240\031\250!\2501\330\r\022\220#\220X\230T\240\031\250!\2501\330\r\022\220#\220X\230T\240\031\250!\2501\330\r\022\220#\220X\230T\240\031\250!\2501\330\r\022\220\"\220I\230T\240\031\250!\250:\260Y\270a\270y\310\001\330\r\022\220\"\220I\230T\240\031\250!\250:\260Y\270a\270z\310\021\330\035!\240\031\250!\250:\260Y\270a\270z\310\021\340\010\026\220a\220v\230Q\330\010\014\210I\220Q\220a\200A\340\010\017\210t\220;\230a\230w\240c\250\021\250!\200A\340\010\035\230T\240\021\330\010\014\210L\230\001\230\021\330\010\017\210t\2204\220q\230\n\240$\240a\320\004\"\240\047\250\025\250a\250t\260:\270Q\320\004?\270q\330\010\036\230e\2406\250\021\330\010\036\230k\250\022\2502\250S\260\001\260\021\330\010\013\2106\220\036\230q\240\001\340\010\014\210J\220a\220q\330\010\025\220Q\220d\230\047\240\021\330\010\026\220a\220v\230Q\340\010\014\210C\210u\220A\330\014\032\230!\2306\240\021\330\014\032\230!\2306\240\021";
28920
28920
  PyObject *data = NULL;
28921
28921
  #define __Pyx_DecompressString_UNUSED
28922
28922
  #define __Pyx_DecompressString_LZSS_UNUSED
@@ -1,7 +1,7 @@
1
1
  # THIS FILE IS AUTOMATICALLY CREATED BY build_pyx.py
2
2
  # DON'T EDIT THIS FILE. EDIT THE SOURCES, INSTEAD: tmsgpack/src-parts/*
3
3
 
4
- __version__ = "0.3.24"
4
+ __version__ = "0.3.26"
5
5
 
6
6
  from libc.stdint cimport int8_t, int16_t, int32_t, int64_t
7
7
  from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tmsgpack
3
- Version: 0.3.24
3
+ Version: 0.3.26
4
4
  Summary: Typed MessagePack serializer (inspired by but different from msgpack)
5
5
  Author-email: Yaakov Belch <yaakov.belch@gmail.com>
6
6
  License: ISC
@@ -1,197 +0,0 @@
1
- """Integration tests for TmsgpackCodec with DI and the feature registry."""
2
-
3
- import textwrap
4
- import pytest
5
- from pathlib import Path
6
-
7
- pytest.importorskip("semifun.di")
8
- pytest.importorskip("semifun.plugins")
9
-
10
- from tmsgpack.codec import NoDependencyInjector, TmsgpackCodec
11
- from semifun.di.injector import DependencyInjector
12
- from semifun.plugins.testing import create_registry_from_paths
13
- from semifun.plugins.registry import (
14
- _feature_map_from_registry,
15
- )
16
-
17
-
18
- # --- Write a test package with annotated types ---
19
-
20
- def _write_test_types_package(tmp_path: Path) -> Path:
21
- pkg = tmp_path / "test_codec_types"
22
- pkg.mkdir()
23
- (pkg / "__init__.py").write_text("")
24
- (pkg / "types.py").write_text(textwrap.dedent("""\
25
- import enum
26
- from dataclasses import dataclass
27
- from semifun.di.model import Inject
28
-
29
- _HTTP_STATUS = (200, 404, 403, 401, 500)
30
-
31
- #::testing_tmsgpack_codec:FailureSeverity
32
- class FailureSeverity(enum.IntEnum):
33
- OK = 0
34
- PROBLEM = 1
35
- NOT_AUTHORIZED = 2
36
- NOT_AUTHENTICATED = 3
37
- UNEXPECTED = 4
38
-
39
- @property
40
- def http_status_code(self) -> int:
41
- return _HTTP_STATUS[self]
42
-
43
- def __str__(self):
44
- return self.name
45
-
46
- #::testing_tmsgpack_codec:Foo
47
- @dataclass(frozen=True)
48
- class Foo:
49
- x: str
50
- y: int
51
-
52
- @dataclass(frozen=True)
53
- class Dbh:
54
- pass
55
-
56
- #::testing_tmsgpack_codec:Bar
57
- @dataclass(frozen=True)
58
- class Bar:
59
- dbh: Inject[Dbh]
60
- x: str
61
- """))
62
- return pkg
63
-
64
-
65
- @pytest.fixture
66
- def test_types(tmp_path):
67
- pkg = _write_test_types_package(tmp_path)
68
- registry = create_registry_from_paths(
69
- packages=[("test_codec_types", pkg)],
70
- )
71
- feature_map = _feature_map_from_registry(registry, "testing_tmsgpack_codec")
72
-
73
- import test_codec_types.types as t
74
- return t, feature_map
75
-
76
-
77
- def _make_injectors_map():
78
- _MISSING = object()
79
- # documented-default: mirrors FeatureMap.__call__ — omitting `default`
80
- # means "raise", which `None` cannot express.
81
- def lookup(name, default=_MISSING): # documented-default
82
- if default is not _MISSING:
83
- return default
84
- raise LookupError(name)
85
- return lookup
86
-
87
-
88
- @pytest.fixture
89
- def codec(test_types):
90
- t, feature_map = test_types
91
- di = DependencyInjector(injectors_map=_make_injectors_map(), seed_data={t.Dbh: t.Dbh()})
92
- # testing-seam: pass feature_map callable instead of a string
93
- return TmsgpackCodec(sort_keys=False, di=di, plugin_feature_type=feature_map), t
94
-
95
-
96
- @pytest.fixture
97
- def codec_no_di(test_types):
98
- t, feature_map = test_types
99
- # testing-seam: pass feature_map callable instead of a string
100
- return TmsgpackCodec(sort_keys=False, di=NoDependencyInjector(),
101
- plugin_feature_type=feature_map), t
102
-
103
-
104
- # --- Round-trip: plain dataclass ---
105
-
106
- def test_round_trip_dataclass(codec):
107
- codec, t = codec
108
- foo = t.Foo(x='hello', y=123)
109
- assert codec.decode(codec.encode(foo)) == foo
110
-
111
-
112
- # --- Round-trip: enum ---
113
-
114
- def test_round_trip_enum(codec):
115
- codec, t = codec
116
- for severity in t.FailureSeverity:
117
- assert codec.decode(codec.encode(severity)) == severity
118
-
119
-
120
- # --- Round-trip: dataclass with Inject[T] fields ---
121
-
122
- def test_round_trip_dataclass_with_inject(codec):
123
- codec, t = codec
124
- bar = t.Bar(dbh=t.Dbh(), x='world')
125
- data = codec.encode(bar)
126
- decoded = codec.decode(data)
127
- assert decoded.x == 'world'
128
- assert isinstance(decoded.dbh, t.Dbh)
129
-
130
-
131
- # --- NoDependencyInjector: plain types work without DI ---
132
-
133
- def test_no_di_dataclass(codec_no_di):
134
- codec, t = codec_no_di
135
- foo = t.Foo(x='abc', y=42)
136
- assert codec.decode(codec.encode(foo)) == foo
137
-
138
-
139
- def test_no_di_enum(codec_no_di):
140
- codec, t = codec_no_di
141
- baz = t.FailureSeverity.PROBLEM
142
- assert codec.decode(codec.encode(baz)) == baz
143
-
144
-
145
- # --- with_seed_data ---
146
-
147
- def test_with_seed_data_round_trip(codec):
148
- codec, t = codec
149
- other_dbh = t.Dbh()
150
- codec2 = codec.with_seed_data({t.Dbh: other_dbh})
151
- bar = t.Bar(dbh=t.Dbh(), x='test')
152
- decoded = codec2.decode(codec2.encode(bar))
153
- assert decoded.x == 'test'
154
- assert decoded.dbh is other_dbh
155
-
156
-
157
- # --- with_seed_data produces independent caches ---
158
-
159
- def test_with_seed_data_has_independent_caches(codec):
160
- codec, t = codec
161
- codec2 = codec.with_seed_data({t.Dbh: t.Dbh()})
162
- assert codec2.encoder_cache is not codec.encoder_cache
163
- assert codec2.decoder_cache is not codec.decoder_cache
164
-
165
-
166
- # --- Inject[T] without a DependencyInjector ---
167
-
168
- def test_inject_field_without_an_injector_fails_on_encode():
169
- """`NoDependencyInjector` does not recognise Inject[T], so the field is serialized.
170
-
171
- Documented in [[tmsgpack:di-on-decode]]: exclusion depends on the
172
- injector, so without one the codec tries to encode the injected value and
173
- fails there — not on decode.
174
- """
175
- from dataclasses import dataclass
176
-
177
- from tmsgpack.codec import NoDependencyInjector, TmsgpackCodec
178
- from semifun.di.model import Inject
179
-
180
- class DbHandle:
181
- pass
182
-
183
- @dataclass(frozen=True)
184
- class NeedsInjection:
185
- dbh: Inject[DbHandle]
186
- x: str
187
-
188
- def feature_map(feature, default=None):
189
- return {'NeedsInjection': NeedsInjection}.get(feature, default)
190
-
191
- codec = TmsgpackCodec(
192
- sort_keys=True,
193
- di=NoDependencyInjector(),
194
- plugin_feature_type=feature_map,
195
- )
196
- with pytest.raises(ValueError, match='Cannot encode this type'):
197
- codec.encode(NeedsInjection(dbh=DbHandle(), x='hi'))
@@ -1,240 +0,0 @@
1
- """Tests for TmsgpackCustom: custom encode/decode for non-dataclass types."""
2
-
3
- import struct
4
- import textwrap
5
- import pytest
6
- from pathlib import Path
7
-
8
- pytest.importorskip("semifun.di")
9
- pytest.importorskip("semifun.plugins")
10
-
11
- from tmsgpack.codec import NoDependencyInjector, TmsgpackCodec
12
- from semifun.di.injector import DependencyInjector
13
- from semifun.plugins.testing import create_registry_from_paths
14
- from semifun.plugins.registry import _feature_map_from_registry
15
-
16
-
17
- # --- Write test packages with custom codec types ---
18
-
19
- def _write_custom_types_package(tmp_path: Path) -> Path:
20
- pkg = tmp_path / "test_custom_types"
21
- pkg.mkdir()
22
- (pkg / "__init__.py").write_text("")
23
- (pkg / "types.py").write_text(textwrap.dedent("""\
24
- import struct
25
- from tmsgpack.codec_custom import TmsgpackCustom
26
-
27
- # --- Target types (not dataclasses) ---
28
-
29
- class Pair:
30
- def __init__(self, x, y):
31
- self.x = x
32
- self.y = y
33
- def __eq__(self, other):
34
- return type(other) is Pair and self.x == other.x and self.y == other.y
35
-
36
- class Point:
37
- def __init__(self, x, y):
38
- self.x = x
39
- self.y = y
40
- def __eq__(self, other):
41
- return type(other) is Point and self.x == other.x and self.y == other.y
42
-
43
- # --- Dict mode ---
44
-
45
- #::testing_custom_codec:Pair=CodecPairDict
46
- class CodecPairDict(TmsgpackCustom):
47
- def encode_dict(obj):
48
- return {'x': obj.x, 'y': obj.y}
49
- def decode_dict(*, x, y):
50
- return Pair(x=x, y=y)
51
-
52
- # --- List mode ---
53
-
54
- #::testing_custom_codec:Point=CodecPointList
55
- class CodecPointList(TmsgpackCustom):
56
- def encode_list(obj):
57
- return [obj.x, obj.y]
58
- def decode_list(x, y):
59
- return Point(x, y)
60
- """))
61
- (pkg / "types_bytes.py").write_text(textwrap.dedent("""\
62
- import struct
63
- from tmsgpack.codec_custom import TmsgpackCustom
64
-
65
- class FloatPair:
66
- def __init__(self, x, y):
67
- self.x = x
68
- self.y = y
69
- def __eq__(self, other):
70
- return (type(other) is FloatPair
71
- and self.x == other.x and self.y == other.y)
72
-
73
- #::testing_custom_codec:FloatPair=CodecFloatPairBytes
74
- class CodecFloatPairBytes(TmsgpackCustom):
75
- def encode_bytes(obj):
76
- return struct.pack('<ff', obj.x, obj.y)
77
- def decode_bytes(data):
78
- x, y = struct.unpack('<ff', data)
79
- return FloatPair(x, y)
80
- """))
81
- return pkg
82
-
83
-
84
- @pytest.fixture
85
- def test_types(tmp_path):
86
- pkg = _write_custom_types_package(tmp_path)
87
- registry = create_registry_from_paths(
88
- packages=[("test_custom_types", pkg)],
89
- )
90
- feature_map = _feature_map_from_registry(registry, "testing_custom_codec")
91
- import test_custom_types.types as t
92
- import test_custom_types.types_bytes as tb
93
- return t, tb, feature_map
94
-
95
-
96
- @pytest.fixture
97
- def codec(test_types):
98
- t, tb, feature_map = test_types
99
- return TmsgpackCodec(sort_keys=False, di=NoDependencyInjector(),
100
- plugin_feature_type=feature_map), t, tb
101
-
102
-
103
- # --- Dict mode round-trip ---
104
-
105
- def test_dict_mode_round_trip(codec):
106
- codec, t, tb = codec
107
- pair = t.Pair(x='hello', y=42)
108
- data = codec.encode(pair)
109
- restored = codec.decode(data)
110
- assert restored == pair
111
-
112
-
113
- # --- List mode round-trip ---
114
-
115
- def test_list_mode_round_trip(codec):
116
- codec, t, tb = codec
117
- point = t.Point(x=10, y=20)
118
- data = codec.encode(point)
119
- restored = codec.decode(data)
120
- assert restored == point
121
-
122
-
123
- # --- Bytes mode round-trip ---
124
-
125
- def test_bytes_mode_round_trip(codec):
126
- codec, t, tb = codec
127
- fp = tb.FloatPair(x=1.5, y=2.5)
128
- data = codec.encode(fp)
129
- restored = codec.decode(data)
130
- assert restored == fp
131
-
132
-
133
- # --- Content hashing works with custom types ---
134
-
135
- def test_content_hash_dict_mode(codec):
136
- codec, t, tb = codec
137
- codec_sorted = TmsgpackCodec(sort_keys=True, di=NoDependencyInjector(),
138
- plugin_feature_type=codec.plugin_feature_type)
139
- pair = t.Pair(x='a', y=1)
140
- h = codec_sorted.hash_to_str(pair)
141
- assert isinstance(h, str)
142
- assert len(h) == 22
143
- # Same value, same hash
144
- assert codec_sorted.hash_to_str(t.Pair(x='a', y=1)) == h
145
-
146
-
147
- # --- Nested values in custom types ---
148
-
149
- def test_dict_mode_nested_values(codec):
150
- codec, t, tb = codec
151
- pair = t.Pair(x=[1, 2, 3], y={'a': True})
152
- data = codec.encode(pair)
153
- restored = codec.decode(data)
154
- assert restored == pair
155
-
156
-
157
- # --- Validation: mismatched modes ---
158
-
159
- def test_mismatched_modes_raises():
160
- from tmsgpack.codec_custom import TmsgpackCustom
161
- with pytest.raises(TypeError, match="mode mismatch"):
162
- class Bad(TmsgpackCustom):
163
- def encode_dict(obj): return {}
164
- def decode_list(x): return None
165
-
166
-
167
- def test_no_encode_raises():
168
- from tmsgpack.codec_custom import TmsgpackCustom
169
- with pytest.raises(TypeError, match="encode_dict, encode_list, encode_bytes"):
170
- class Bad(TmsgpackCustom):
171
- def decode_dict(*, x): return None
172
-
173
-
174
- def test_no_decode_raises():
175
- from tmsgpack.codec_custom import TmsgpackCustom
176
- with pytest.raises(TypeError, match="decode_dict, decode_list, decode_bytes"):
177
- class Bad(TmsgpackCustom):
178
- def encode_dict(obj): return {}
179
-
180
-
181
- # --- DI on decode ---
182
-
183
- def _write_di_types_package(tmp_path: Path) -> Path:
184
- pkg = tmp_path / "test_custom_di_types"
185
- pkg.mkdir()
186
- (pkg / "__init__.py").write_text("")
187
- (pkg / "types.py").write_text(textwrap.dedent("""\
188
- from tmsgpack.codec_custom import TmsgpackCustom
189
- from semifun.di.model import Inject
190
-
191
- class Cache:
192
- def __init__(self):
193
- self.store = {}
194
- def get(self, key):
195
- return self.store[key]
196
- def put(self, key, value):
197
- self.store[key] = value
198
-
199
- class Widget:
200
- def __init__(self, name, data):
201
- self.name = name
202
- self.data = data
203
- def __eq__(self, other):
204
- return (type(other) is Widget
205
- and self.name == other.name
206
- and self.data == other.data)
207
-
208
- #::testing_custom_di_codec:Widget=CodecWidget
209
- class CodecWidget(TmsgpackCustom):
210
- def encode_dict(obj, cache: Inject[Cache]):
211
- cache.put(obj.name, obj.data)
212
- return {'name': obj.name}
213
- def decode_dict(*, name, cache: Inject[Cache]):
214
- data = cache.get(name)
215
- return Widget(name=name, data=data)
216
- """))
217
- return pkg
218
-
219
-
220
- def test_di_on_encode_and_decode(tmp_path):
221
- pkg = _write_di_types_package(tmp_path)
222
- registry = create_registry_from_paths(
223
- packages=[("test_custom_di_types", pkg)],
224
- )
225
- feature_map = _feature_map_from_registry(registry, "testing_custom_di_codec")
226
- import test_custom_di_types.types as t
227
-
228
- cache = t.Cache()
229
- injectors_map = lambda name, default=None: default
230
- di = DependencyInjector(injectors_map=injectors_map, seed_data={t.Cache: cache})
231
- codec = TmsgpackCodec(sort_keys=False, di=di, plugin_feature_type=feature_map)
232
-
233
- widget = t.Widget(name='gizmo', data=[1, 2, 3])
234
- data = codec.encode(widget)
235
-
236
- # The cache was populated during encode
237
- assert cache.store == {'gizmo': [1, 2, 3]}
238
-
239
- restored = codec.decode(data)
240
- assert restored == widget
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes