tmsgpack 0.1.0__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 (41) hide show
  1. tmsgpack-0.1.0/COPYING +14 -0
  2. tmsgpack-0.1.0/MANIFEST.in +5 -0
  3. tmsgpack-0.1.0/PKG-INFO +425 -0
  4. tmsgpack-0.1.0/README.md +401 -0
  5. tmsgpack-0.1.0/pyproject.toml +13 -0
  6. tmsgpack-0.1.0/setup.cfg +33 -0
  7. tmsgpack-0.1.0/setup.py +94 -0
  8. tmsgpack-0.1.0/test/contexts_for_tests.py +19 -0
  9. tmsgpack-0.1.0/test/test_buffer.py +32 -0
  10. tmsgpack-0.1.0/test/test_case.py +137 -0
  11. tmsgpack-0.1.0/test/test_except.py +35 -0
  12. tmsgpack-0.1.0/test/test_format.py +95 -0
  13. tmsgpack-0.1.0/test/test_limits.py +151 -0
  14. tmsgpack-0.1.0/test/test_memoryview.py +100 -0
  15. tmsgpack-0.1.0/test/test_newspec.py +70 -0
  16. tmsgpack-0.1.0/test/test_pack.py +147 -0
  17. tmsgpack-0.1.0/test/test_seq.py +41 -0
  18. tmsgpack-0.1.0/test/test_sequnpack.py +133 -0
  19. tmsgpack-0.1.0/test/test_subtype.py +26 -0
  20. tmsgpack-0.1.0/test/test_trace_from_x.py +95 -0
  21. tmsgpack-0.1.0/test/test_typed_objects.py +87 -0
  22. tmsgpack-0.1.0/test/test_unpack.py +33 -0
  23. tmsgpack-0.1.0/tmsgpack/__init__.py +157 -0
  24. tmsgpack-0.1.0/tmsgpack/_ctmsgpack.cpp +19504 -0
  25. tmsgpack-0.1.0/tmsgpack/_ctmsgpack.pyx +4 -0
  26. tmsgpack-0.1.0/tmsgpack/_packer.pyx +290 -0
  27. tmsgpack-0.1.0/tmsgpack/_unpacker.pyx +399 -0
  28. tmsgpack-0.1.0/tmsgpack/buff_converter.h +8 -0
  29. tmsgpack-0.1.0/tmsgpack/exceptions.py +48 -0
  30. tmsgpack-0.1.0/tmsgpack/ext.py +0 -0
  31. tmsgpack-0.1.0/tmsgpack/fallback.py +673 -0
  32. tmsgpack-0.1.0/tmsgpack/pack.h +93 -0
  33. tmsgpack-0.1.0/tmsgpack/pack_template.h +728 -0
  34. tmsgpack-0.1.0/tmsgpack/sysdep.h +194 -0
  35. tmsgpack-0.1.0/tmsgpack/unpack.h +274 -0
  36. tmsgpack-0.1.0/tmsgpack/unpack_define.h +95 -0
  37. tmsgpack-0.1.0/tmsgpack/unpack_template.h +401 -0
  38. tmsgpack-0.1.0/tmsgpack.egg-info/PKG-INFO +425 -0
  39. tmsgpack-0.1.0/tmsgpack.egg-info/SOURCES.txt +40 -0
  40. tmsgpack-0.1.0/tmsgpack.egg-info/dependency_links.txt +1 -0
  41. tmsgpack-0.1.0/tmsgpack.egg-info/top_level.txt +1 -0
tmsgpack-0.1.0/COPYING ADDED
@@ -0,0 +1,14 @@
1
+ Copyright (C) 2008-2011 INADA Naoki <songofacandy@gmail.com>
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
14
+
@@ -0,0 +1,5 @@
1
+ include setup.py
2
+ include COPYING
3
+ include README.md
4
+ recursive-include tmsgpack *.h *.c *.pyx *.cpp
5
+ recursive-include test *.py
@@ -0,0 +1,425 @@
1
+ Metadata-Version: 2.4
2
+ Name: tmsgpack
3
+ Version: 0.1.0
4
+ Summary: Typed MessagePack serializer (similar but incompatible with msgpack)
5
+ Home-page: https://github.com/Yaakov-Belch/tmsgpack
6
+ Author: Inada Naoki, Yaakov Belch
7
+ Author-email: yaakov.belch@gmail.com
8
+ License: Apache-2.0
9
+ Project-URL: Source, https://github.com/Yaakov-Belch/tmsgpack
10
+ Project-URL: Tracker, https://github.com/Yaakov-Belch/tmsgpack/issues
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Programming Language :: Python :: Implementation :: CPython
19
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
20
+ Classifier: Intended Audience :: Developers
21
+ Description-Content-Type: text/markdown
22
+ License-File: COPYING
23
+ Dynamic: license-file
24
+
25
+ # tmsgpack: Typed MessagePack-inspired pack/unpack component
26
+
27
+ The tmsgpack format expresses **typed objects**: maps and arrays (or: dicts and lists)
28
+ with an `object_type` property.
29
+
30
+ Unlike msgpack and pickle, this is not a batteries-included end-to-end serialization
31
+ solution. It is a composable component that helps you to build end-to-end communication
32
+ solutions.
33
+
34
+ Your system solution design will make decisions on:
35
+ * What objects are serializable and what objects are not.
36
+ * What code to use (and, maybe, dynamically load) to instantiate serialized objects.
37
+ * How to represent objects that are unpacked but not supposed to 'live' in this process.
38
+ * How to share dynamic data between different packs/unpacks.
39
+ * How to asynchronously load and integrate shared data from different sources.
40
+ * How to map typed object meaning between different programming languages.
41
+ * Whether and how to convert persisted "old" data to current, new semantics (schemas).
42
+ * How much to attach explicit meaning and predictable schemas to your object types.
43
+ * Whether or not to use the 'expression execution' capabilities of tmsgpack.
44
+ * etc.
45
+
46
+ This python package makes a minimal (backwards-incompatible) modification to the
47
+ msgpack format to make all this elegantly possible. This package is based on
48
+ `msgpack v1.0.5`.
49
+
50
+ ## TODO: Installation
51
+
52
+ ## Usage
53
+ Packing and unpacking data is controlled by `pack_ctrl` and `unpack_ctrl` objects (see
54
+ below for details how to create them):
55
+ ```python
56
+ from tmsgpack import packb, unpackb
57
+ packed = packb(data, pack_ctrl=pack_ctrl)
58
+ unpacked = unpackb(packed, unpack_ctrl=unpack_ctrl)
59
+ ```
60
+
61
+ ## Streaming unpacking
62
+ For multiple uses, you can use packer and unpacker objects:
63
+ ```python
64
+ from tmsgpack import Packer
65
+ packer = Packer(pack_ctrl=pack_ctrl)
66
+
67
+ packed = packer.pack(data) # Send these packages via a socket...
68
+
69
+ ---
70
+ from tmsgpack import Packer, Unpacker
71
+
72
+ unpacker = Unpacker(unpack_ctrl=unpack_ctrl)
73
+ while buf := sock.recv(1024**2):
74
+ unpacker.feed(buf)
75
+ for o in unpacker:
76
+ process(o)
77
+ ```
78
+
79
+ ## Minimal pack_ctrl and unpack_ctrl objects
80
+ Minimal controllers allow only JSON-like objects and raise errors when you ask for more
81
+ (below, we show examples for more useful controllers):
82
+ ```python
83
+ from tmsgpack import PackConfig, UnpackConfig
84
+ from dataclasses import dataclass
85
+
86
+ @dataclass
87
+ class MinimalPackCtrl:
88
+ def from_obj(self, obj):
89
+ raise TypeError(f'Cannot serialize {type(obj)} object.')
90
+ options: PackConfig
91
+
92
+ @dataclass
93
+ class MinimalUnpackCtrl:
94
+ def from_dict(self, ctype, dct):
95
+ raise ValueError(f'Unpack type not supported: {ctype} data: {dct}')
96
+ def from_list(self, ctype, lst):
97
+ raise ValueError(f'Unpack type not supported: {ctype} data: {lst}')
98
+ options: UnpackConfig
99
+
100
+ def pctrl(**kwargs): return MinimalPackCtrl(options=PackConfig(**kwargs))
101
+ def uctrl(**kwargs): return MinimalUnpackCtrl(options=UnpackConfig(**kwargs))
102
+
103
+ minimal_pack_ctrl = pctrl()
104
+ minimal_unpack_ctrl = uctrl()
105
+ ```
106
+
107
+ ## The API and configuration
108
+ As you see, the `pack_ctrl` object provides a method `from_obj`. The `unpack_ctrl`
109
+ object provides the methods `from_dict` and `from_array`:
110
+ ```python
111
+ as_dict, data_type, data = pack_ctrl.from(obj)
112
+
113
+ # When `as_dict` is true, then `data` should be a dictionary.
114
+ # When `as_dict` is false, then `data` should be a list.
115
+
116
+ unpacked = unpack_ctrl.from_dict(data_type, data) # used when as_dict is true.
117
+ unpacked = unpack_ctrl.from_list(data_type, data) # used when as_dict is false.
118
+ ```
119
+
120
+ ## PackConfig configuration objects for pack_ctrl
121
+ `PackConfig` objects provide the following options:
122
+ ```python
123
+ from tmsgpack import PackConfig
124
+
125
+ config = PackConfig(
126
+ use_single_float=False, use_bin_type=True,
127
+ tuple_as_list=True, strict_types=False,
128
+ unicode_errors='strict', sort_keys=False,
129
+ )
130
+ """
131
+ Config object for pack_ctrl.options
132
+
133
+ :param bool use_single_float:
134
+ Use single precision float type for float. (default: False)
135
+
136
+ :param bool use_bin_type:
137
+ Use bin type introduced in tmsgpack spec 2.0 for bytes.
138
+ It also enables str8 type for unicode. (default: True)
139
+
140
+ :param bool tuple_as_list:
141
+ If true, tuples are serialized as lists. (default: True)
142
+ Otherwise, tuples are passed to pack_ctrl.from_obj(?).
143
+
144
+ :param bool strict_types:
145
+ If set to true, types will be checked to be exact. (default: False)
146
+ Derived classes are distinct and passed to pack_ctrl.from_obj(?).
147
+ Dicts, lists and tuples are not affected by strict_types.
148
+
149
+ :param str unicode_errors:
150
+ The error handler for encoding unicode. (default: 'strict')
151
+ DO NOT USE THIS!! This option is kept for very specific usage.
152
+
153
+ :param bool sort_keys:
154
+ Sort output dictionaries by key. (default: False)
155
+ """
156
+ ```
157
+ ## UnpackConfig configuration objects for unpack_ctrl
158
+ `UnpackConfig` objects provide the following options:
159
+ ```python
160
+ from tmsgpack import UnpackConfig
161
+
162
+ config = UnpackConfig(
163
+ read_size=16*1024, use_tuple=False, raw=False,
164
+ strict_dict_key=False, object_as_pairs=False,
165
+ unicode_errors='strict', max_buffer_size=0,
166
+ max_str_len=-1, max_bin_len=-1, max_list_len=-1, max_dict_len=-1,
167
+ )
168
+ """
169
+ Config object for unpack_ctrl.options
170
+
171
+ :param int read_size:
172
+ Used as `file_like.read(read_size)`. (default: `min(16*1024, max_buffer_size)`)
173
+
174
+ :param bool use_tuple:
175
+ If true, unpack a tmsgpack list as a Python tuple. (default: False)
176
+
177
+ :param bool raw:
178
+ If true, unpack tmsgpack strings (raw) to Python bytes.
179
+ Otherwise, unpack to Python str by decoding with UTF-8 encoding (default: False).
180
+
181
+ :param bool strict_dict_key:
182
+ If true only str or bytes are accepted for dict (dict) keys. (default: False).
183
+
184
+ :param callable object_as_pairs:
185
+ If true, handles dicts as tuples of pairs.
186
+ Otherwise, as dicts (default: False).
187
+
188
+ :param str unicode_errors:
189
+ The error handler for decoding unicode. (default: 'strict')
190
+ This option should be used only when you have tmsgpack data which
191
+ contains invalid UTF-8 string.
192
+
193
+ :param int max_buffer_size:
194
+ Limits size of data waiting unpacked. 0 means 2**32-1.
195
+ The default value is 100*1024*1024 (100MiB).
196
+ Raises `BufferFull` exception when it is insufficient.
197
+ You should set this parameter when unpacking data from untrusted source.
198
+
199
+ :param int max_str_len:
200
+ Limits max length of str. (default: max_buffer_size)
201
+
202
+ :param int max_bin_len:
203
+ Limits max length of bin. (default: max_buffer_size)
204
+
205
+ :param int max_list_len:
206
+ Limits max length of list.
207
+ (default: max_buffer_size)
208
+
209
+ :param int max_dict_len:
210
+ Limits max length of dict.
211
+ (default: max_buffer_size//2)
212
+ """
213
+ ```
214
+ ## Packing and Unpacking dataclass objects
215
+ Here are the parts of one unit test that shows end-to-end packing and unpacking
216
+ of dataclass objects:
217
+
218
+ For the setup, we import tools and define the controllers:
219
+ ```python
220
+ from tmsgpack import packb, unpackb, PackConfig, UnpackConfig
221
+ from dataclasses import dataclass, is_dataclass, fields
222
+ from typing import Dict
223
+
224
+ @dataclass
225
+ class TypedPackCtrl:
226
+ def from_obj(self, obj):
227
+ if type(obj) is tuple: return False, 'tuple', obj # Special case for tuples.
228
+ if not is_dataclass(obj): raise TypeError(f'Cannot serialize {type(obj)} object.')
229
+ as_dict = not getattr(obj, 'as_list', False)
230
+ object_type = obj.__class__.__name__
231
+ if as_dict:
232
+ data = {
233
+ field.name: getattr(obj, field.name)
234
+ for field in fields(obj)
235
+ }
236
+ else:
237
+ data = [
238
+ getattr(obj, field.name)
239
+ for field in fields(obj)
240
+ ]
241
+ return as_dict, object_type, data
242
+ options: PackConfig
243
+
244
+ def pack(self, data):
245
+ return packb(data, pack_ctrl=self)
246
+
247
+ @dataclass
248
+ class TypedUnpackCtrl:
249
+ constructors: Dict[str, callable]
250
+ def from_dict(self, ctype, data): return self.constructors[ctype](**data)
251
+ def from_list(self, ctype, data): return self.constructors[ctype]( *data)
252
+ options: UnpackConfig
253
+
254
+ def unpack(self, packed):
255
+ return unpackb(packed, unpack_ctrl=self)
256
+
257
+ def pc(**kwargs): return TypedPackCtrl(options=PackConfig(**kwargs))
258
+ def uc(fns, **kwargs):
259
+ return TypedUnpackCtrl(
260
+ constructors={fn.__name__:fn for fn in fns},
261
+ options=UnpackConfig(**kwargs),
262
+ )
263
+ ```
264
+ Notes:
265
+ * For conveninence, we added methods `pack_ctrl.pack(data)` and
266
+ `unpack_ctrl.unpack(packed)`.
267
+ * The method `pack_ctrl.from_obj` decides whether to represent the dataclass object
268
+ as a key-value dict or as a more compact list of values.
269
+ * It extracts the properties of the `obj` and sets the values `as_dict`, `object_type`
270
+ and `data` appropriately.
271
+ * In this implementation, object types are the unqualified class names. There is
272
+ a possibility that one class from one package can have the same name as a different
273
+ class from a different package.
274
+ * Fully resolving naming spaces is a deep design problem. You need to decide what
275
+ you mean by 'meaning'. Here, we exploit this overloadability...
276
+ * The `unpack_ctrl` object is created with a list of available constructor functions.
277
+
278
+ Now, we can define the data classes to be packed and unpacked:
279
+ ```python
280
+ @dataclass
281
+ class Foo:
282
+ x: int = 1
283
+ y: str = 'Y'
284
+
285
+ @dataclass
286
+ class Bar:
287
+ x: int = 1
288
+ y: str = 'Y'
289
+ as_list = True
290
+
291
+ @dataclass
292
+ class Add:
293
+ x: int = 10
294
+ y: int = 20
295
+
296
+ class Expr:
297
+ @staticmethod
298
+ def Add(x:int, y:int): return x+y
299
+ @staticmethod
300
+ def tuple(*args): return args
301
+ ```
302
+ Notes:
303
+ * Class Bar has a class property `as_list=True`. It will be packed compactly as
304
+ a parameter value list.
305
+ * The function (static method) `Expr.Add` has the same name as the class constructor
306
+ `Add`. We will soon exploit this...
307
+
308
+ Here is a simple test runner to be used for several tests:
309
+ ```python
310
+ def run(input, expected=None):
311
+ if expected is None: expected = input
312
+
313
+ constructors = [Foo, Bar, Expr.tuple, Expr.Add]
314
+
315
+ pack_ctrl = pc(tuple_as_list=False) # We want to distinguish between tuples and lists.
316
+ unpack_ctrl = uc(constructors)
317
+
318
+ packed = pack_ctrl.pack(input)
319
+ output = unpack_ctrl.unpack(packed)
320
+
321
+ assert output == expected
322
+ ```
323
+
324
+ And here are the first tests. The `Foo()` and `Bar()` objects are packed and unpacked
325
+ correctly:
326
+ ```python
327
+ def test_typed_foobar():
328
+ run(Foo()) # Encoded as a typed dict
329
+ run(Bar()) # Encoded as a typed list
330
+ run((1,2,3)) # Tuples are encoded as a typed list with object_type='tuple'
331
+ ```
332
+
333
+ The second test shows that we can encode an object tree into an
334
+ binary expression buffer. When unpacking this expression buffer, the expression
335
+ is evaluated.
336
+ ```python
337
+ def test_simple_expression():
338
+ run(Add(Add(1,2), Add(2,3)), 8) # Unpacking is expression evaluation.
339
+ ```
340
+ Variables (parameters) can be implemented using a dictionary inside the `unpack_ctrl`
341
+ object.
342
+
343
+ ## Development: Environment and testing
344
+
345
+ ```
346
+ # Clone the repository
347
+ cd ~/git/
348
+ # git clone https://github.com/Yaakov-Belch/tmsgpack.git # You need permissions for that.
349
+ git clone https://github.com/Yaakov-Belch/tmsgpack.git
350
+
351
+ # Create a virtual environment in your project directory
352
+ cd ~/git/tmsgpack
353
+ python -m venv venv
354
+
355
+ # Activate the virtual environment
356
+ source venv/bin/activate
357
+
358
+ # Upgrade pip (recommended)
359
+ pip install --upgrade pip
360
+
361
+ # Install the required dependencies
362
+ pip install -r requirements.txt
363
+
364
+ # Now you can run the tests
365
+ make test
366
+
367
+ # Run one test
368
+ pytest -v test/test_typed_objects.py
369
+ ```
370
+
371
+ ## Build and publish to PyPI
372
+ Follow the steps above in "Development": Clone this repository, create and activate
373
+ a virtual environment, upgrade pip, install required dependencies; make test.
374
+ ```bash
375
+ cd ~/git/tmsgpack
376
+ source venv/bin/activate
377
+
378
+ pip install build twine
379
+ python -m build # puts results in dist/
380
+
381
+ # Test the new package -- in a fresh test environment:
382
+
383
+ # Create a fresh test environment
384
+ deactivate
385
+ python -m venv venv-test
386
+ source venv-test/bin/activate
387
+ pip install --upgrade pip
388
+ pip install dist/tmsgpack-0.1.0-cp310-cp310-linux_x86_64.whl
389
+ ```
390
+
391
+ Obsolete:
392
+ cd ~/git/tmsgpack
393
+ python -m venv venv
394
+ source venv/bin/activate
395
+ python --version
396
+
397
+
398
+ cd ~/git/tmsgpack
399
+ python -m venv venv-test
400
+ source venv-test/bin/activate
401
+ python --version
402
+
403
+
404
+ ## The tmsgpack format (version 0.1.0)
405
+ The msgpack format defines two types of containers: maps and arrays (dicts and lists).
406
+ They are encoded by a `container_header` that identifies the container type and the
407
+ number of key-value pairs or array-elements that follow after the `container_header`:
408
+ ```
409
+ dict_container_header(3) key1 value1 key2 value2 key3 value3
410
+ array_container_header(3) element1 element2 element3
411
+ ```
412
+ The tmsgpack format uses the same rules -- but adds an `object_type` entry right
413
+ after every `container_header`:
414
+ ```
415
+ dict_container_header(3) object_type key1 value1 key2 value2 key3 value3
416
+ array_container_header(3) object_type element1 element2 element3
417
+ ```
418
+
419
+ ## Future extensibility
420
+ In msgpack, possible `ExtType` values for the first `data_header` byte declare
421
+ this data element as an msgpack extension. The tmsgpack format does not use this
422
+ extension mechanism -- and these eight values are available for future extensions.
423
+
424
+ The value `0xC1` is never used by the original msgpack specification. It is also
425
+ available for future extensions of the tmsgpack format.