transit-python 0.8.321__cp310-abi3-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
transit/writer.py ADDED
@@ -0,0 +1,443 @@
1
+ ## Copyright 2014 Cognitect. All Rights Reserved.
2
+ ## Copyright 2026 Vendekagon Labs LLC.
3
+ ##
4
+ ## Licensed under the Apache License, Version 2.0 (the "License");
5
+ ## you may not use this file except in compliance with the License.
6
+ ## You may obtain a copy of the License at
7
+ ##
8
+ ## http://www.apache.org/licenses/LICENSE-2.0
9
+ ##
10
+ ## Unless required by applicable law or agreed to in writing, software
11
+ ## distributed under the License is distributed on an "AS-IS" BASIS,
12
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ ## See the License for the specific language governing permissions and
14
+ ## limitations under the License.
15
+
16
+ import json
17
+ import re
18
+
19
+ from transit.constants import SUB, ESC, RES, MAP_AS_ARR, QUOTE
20
+ from transit.rolling_cache import RollingCache
21
+ from transit.write_handlers import WriteHandler
22
+ from transit.transit_types import TaggedValue
23
+ from transit.helpers import require_msgpack
24
+ from transit._accel import FORMATS, native
25
+
26
+
27
+ class Writer:
28
+ """The top-level object for writing out Python objects and converting them
29
+ to Transit data. During initialization, you must specify the protocol used
30
+ for marshalling the data- json or msgpack (which requires the msgpack
31
+ package: pip install 'transit-python[msgpack]'). You must also specify the io
32
+ source used for writing (a file descriptor). You may optionally pass in
33
+ an options dictionary that will be forwarded onto the Marshaler.
34
+ The cache is enabled by default.
35
+ """
36
+ def __init__(self, io, protocol="json", opts=None):
37
+ opts = {"cache_enabled": True} if opts is None else opts
38
+ self.io = io
39
+ self.protocol = protocol
40
+ self.customized = False
41
+ self.marshaler = None
42
+ if protocol == "msgpack" and native is not None:
43
+ try:
44
+ self.marshaler = MsgPackMarshaler(io, opts=opts)
45
+ except ImportError:
46
+ pass # the native extension writes msgpack without the package
47
+ elif protocol == "json":
48
+ self.marshaler = JsonMarshaler(io, opts=opts)
49
+ elif protocol == "json_verbose":
50
+ self.marshaler = VerboseJsonMarshaler(io, opts=opts)
51
+ elif protocol == "msgpack":
52
+ self.marshaler = MsgPackMarshaler(io, opts=opts)
53
+ else:
54
+ raise ValueError("'" + protocol + "' is not a supported protocol. " +
55
+ "Protocol must be 'json', 'json_verbose', or 'msgpack'.")
56
+
57
+ def write(self, obj):
58
+ """Given a Python object, marshal it into Transit data and write it to
59
+ the 'io' source.
60
+ """
61
+ if native is not None and not self.customized:
62
+ try:
63
+ out = native.dumps(obj, FORMATS[self.protocol])
64
+ except native.Unsupported:
65
+ pass # a type the extension doesn't handle: pure Python
66
+ else:
67
+ self.io.write(out)
68
+ self.io.flush()
69
+ return
70
+ if self.marshaler is None:
71
+ require_msgpack()
72
+ self.marshaler.marshal_top(obj)
73
+
74
+ def register(self, obj_type, handler_class):
75
+ """Register custom converters for object types present in your
76
+ application. This allows you to extend Transit to encode new types.
77
+ You must specify the obj type to be encoded, and the handler class
78
+ that should be used by the Marshaler during write-time.
79
+ """
80
+ self.customized = True
81
+ if self.marshaler is None:
82
+ require_msgpack()
83
+ self.marshaler.register(obj_type, handler_class)
84
+
85
+
86
+ def flatten_map(m):
87
+ """Expand a dictionary's items into a flat list
88
+ """
89
+ # This is the fastest way to do this in Python
90
+ return [item for t in m.items() for item in t]
91
+
92
+
93
+ def re_fn(pat):
94
+ compiled = re.compile(pat)
95
+ def re_inner_fn(value):
96
+ return compiled.match(value)
97
+
98
+ return re_inner_fn
99
+
100
+ is_escapable = re_fn("^" + re.escape(SUB) + "|" + ESC + "|" + RES)
101
+
102
+
103
+ def escape(s):
104
+ if is_escapable(s):
105
+ return ESC+s
106
+ else:
107
+ return s
108
+
109
+
110
+ class Marshaler:
111
+ """The base Marshaler from which all Marshalers inherit.
112
+
113
+ The Marshaler specifies how to emit Transit data given encodeable Python
114
+ objects. The end of this process is specialized by other Marshalers to
115
+ covert the final result into an on-the-wire payload (JSON or MsgPack).
116
+ """
117
+ native_map_keys = False
118
+
119
+ def __init__(self, opts=None):
120
+ self.opts = {} if opts is None else opts
121
+ self._init_handlers()
122
+
123
+ def _init_handlers(self):
124
+ self.handlers = WriteHandler()
125
+
126
+ def are_stringable_keys(self, m):
127
+ """Test whether the keys within a map are stringable - a simple map,
128
+ that can be optimized and whose keys can be cached
129
+ """
130
+ for x in m.keys():
131
+ if len(self.handlers[x].tag(x)) != 1:
132
+ return False
133
+ return True
134
+
135
+ def emit_nil(self, _, as_map_key, cache):
136
+ return self.emit_string(ESC, "_", "", True, cache) if as_map_key else self.emit_object(None)
137
+
138
+ def emit_string(self, prefix, tag, string, as_map_key, cache):
139
+ encoded = cache.encode(prefix + tag + string, as_map_key)
140
+ # TODO: Remove this optimization for the time being - it breaks cache
141
+ #if "cache_enabled" in self.opts and is_cacheable(encoded, as_map_key):
142
+ # return self.emit_object(cache.value_to_key[encoded], as_map_key)
143
+ return self.emit_object(encoded, as_map_key)
144
+
145
+ def emit_boolean(self, b, as_map_key, cache):
146
+ return self.emit_string(ESC, "?", b, True, cache) if as_map_key else self.emit_object(b)
147
+
148
+ def emit_int(self, tag, i, rep, as_map_key, cache):
149
+ if isinstance(rep, int) and i <= self.opts["max_int"] and i >= self.opts["min_int"]:
150
+ return self.emit_object(i, as_map_key)
151
+ else:
152
+ return self.emit_string(ESC, tag, str(rep), as_map_key, cache)
153
+
154
+ def emit_double(self, d, as_map_key, cache):
155
+ return self.emit_string(ESC, "d", d, True, cache) if as_map_key else self.emit_object(d)
156
+
157
+ def emit_array(self, a, _, cache):
158
+ self.emit_array_start(len(a))
159
+ for x in a:
160
+ self.marshal(x, False, cache)
161
+ self.emit_array_end()
162
+
163
+ def emit_map(self, m, _, cache):# use map as object from above, have to overwrite default parser.
164
+ self.emit_map_start(len(m))
165
+ for k, v in m.items():
166
+ self.marshal(k, True, cache)
167
+ self.marshal(v, False, cache)
168
+ self.emit_map_end()
169
+
170
+ def emit_cmap(self, m, _, cache):
171
+ self.emit_map_start(1)
172
+ self.emit_string(ESC, "#", "cmap", True, cache)
173
+ self.marshal(flatten_map(m), False, cache)
174
+ self.emit_map_end()
175
+
176
+ def emit_tagged(self, tag, rep, cache):
177
+ self.emit_array_start(2)
178
+ self.emit_string(ESC, "#", tag, False, cache)
179
+ self.marshal(rep, False, cache)
180
+ self.emit_array_end()
181
+
182
+ def emit_encoded(self, tag, handler, obj, as_map_key, cache):
183
+ rep = handler.rep(obj)
184
+ if len(tag) == 1:
185
+ if isinstance(rep, str):
186
+ self.emit_string(ESC, tag, rep, as_map_key, cache)
187
+ elif as_map_key or self.opts["prefer_strings"]:
188
+ rep = handler.string_rep(obj)
189
+ if isinstance(rep, str):
190
+ self.emit_string(ESC, tag, rep, as_map_key, cache)
191
+ else:
192
+ raise AssertionError("Cannot be encoded as string: " + str({"tag": tag,
193
+ "rep": rep,
194
+ "obj": obj}))
195
+ else:
196
+ self.emit_tagged(tag, rep, cache)
197
+ elif as_map_key:
198
+ raise AssertionError("Cannot be used as a map key: " + str({"tag": tag,
199
+ "rep": rep,
200
+ "obj": obj}))
201
+ else:
202
+ self.emit_tagged(tag, rep, cache)
203
+
204
+ def marshal(self, obj, as_map_key, cache):
205
+ """Marshal an individual obj, potentially as part of another container
206
+ object (like a list/dictionary/etc). Specify if this object is a key
207
+ to a map/dict, and pass in the current cache being used.
208
+ This method should only be called by a top-level marshalling call
209
+ and should not be considered an entry-point for integration.
210
+ """
211
+ handler = self.handlers[obj]
212
+ tag = handler.tag(obj)
213
+ f = marshal_dispatch.get(tag)
214
+ if as_map_key and self.native_map_keys and tag in ("_", "?", "i", "d"):
215
+ # msgpack map keys can be any type: like transit-java, write
216
+ # nil, booleans, ints and floats as themselves
217
+ as_map_key = False
218
+
219
+ if f:
220
+ f(self, obj, handler.string_rep(obj) if as_map_key else handler.rep(obj), as_map_key, cache)
221
+ else:
222
+ self.emit_encoded(tag, handler, obj, as_map_key, cache)
223
+
224
+ def marshal_top(self, obj, cache=None):
225
+ """Given a complete object that needs to be marshaled into Transit
226
+ data, and optionally a cache, dispatch accordingly, and flush the data
227
+ directly into the IO stream.
228
+ """
229
+ if cache is None:
230
+ cache = RollingCache()
231
+
232
+ handler = self.handlers[obj]
233
+
234
+ tag = handler.tag(obj)
235
+ if tag:
236
+ if len(tag) == 1:
237
+ self.marshal(TaggedValue(QUOTE, obj), False, cache)
238
+ else:
239
+ self.marshal(obj, False, cache)
240
+ self.flush()
241
+ else:
242
+ raise AssertionError("Handler must provide a non-nil tag: " + str(handler))
243
+
244
+ def dispatch_map(self, rep, as_map_key, cache):
245
+ """Used to determine and dipatch the writing of a map - a simple
246
+ map with strings as keys, or a complex map, whose keys are also
247
+ compound types.
248
+ """
249
+ if self.are_stringable_keys(rep):
250
+ return self.emit_map(rep, as_map_key, cache)
251
+ return self.emit_cmap(rep, as_map_key, cache)
252
+
253
+ def register(self, obj_type, handler_class):
254
+ """Register custom converters for object types present in your
255
+ application. This allows you to extend Transit to encode new types.
256
+ You must specify the obj type to be encoded, and the handler class
257
+ that should be used by this marshaller.
258
+ """
259
+ self.handlers[obj_type] = handler_class
260
+
261
+ marshal_dispatch = {"_": lambda self, obj, rep, as_map_key, cache: self.emit_nil(rep, as_map_key, cache),
262
+ "?": lambda self, obj, rep, as_map_key, cache: self.emit_boolean(rep, as_map_key, cache),
263
+ "s": lambda self, obj, rep, as_map_key, cache: self.emit_string("", "", escape(rep), as_map_key, cache),
264
+ "i": lambda self, i, rep, as_map_key, cache: self.emit_int("i", i, rep, as_map_key, cache),
265
+ "n": lambda self, i, rep, as_map_key, cache: self.emit_int("n", i, rep, as_map_key, cache),
266
+ "d": lambda self, obj, rep, as_map_key, cache: self.emit_double(rep, as_map_key, cache),
267
+ "'": lambda self, obj, rep, _, cache: self.emit_tagged("'", rep, cache),
268
+ "array": lambda self, obj, rep, as_map_key, cache: self.emit_array(rep, as_map_key, cache),
269
+ "map": lambda self, obj, rep, as_map_key, cache: self.dispatch_map(rep, as_map_key, cache)}
270
+
271
+
272
+ class MsgPackMarshaler(Marshaler):
273
+ """The Marshaler tailor to MsgPack. To use this Marshaler, specify the
274
+ 'msgpack' protocol when creating a Writer.
275
+ """
276
+ native_map_keys = True
277
+ MSGPACK_MAX_INT = pow(2, 63) - 1
278
+ MSGPACK_MIN_INT = -pow(2, 63)
279
+
280
+ default_opts = {"prefer_strings": False,
281
+ "max_int": MSGPACK_MAX_INT,
282
+ "min_int": MSGPACK_MIN_INT}
283
+
284
+ def __init__(self, io, opts=None):
285
+ self.io = io
286
+ self.packer = require_msgpack().Packer(autoreset=False)
287
+ nopts = MsgPackMarshaler.default_opts.copy()
288
+ nopts.update(opts or {})
289
+ Marshaler.__init__(self, nopts)
290
+
291
+ def emit_cmap(self, m, _, cache):
292
+ # transit-java writes cmaps as ["~#cmap", [...]] in msgpack (and as
293
+ # {"~#cmap": [...]} in JSON)
294
+ self.emit_tagged("cmap", flatten_map(m), cache)
295
+
296
+ def emit_array_start(self, size):
297
+ self.packer.pack_array_header(size)
298
+
299
+ def emit_array_end(self):
300
+ pass
301
+
302
+ def emit_map_start(self, size):
303
+ self.packer.pack_map_header(size)
304
+
305
+ def emit_map_end(self):
306
+ pass
307
+
308
+ def emit_object(self, obj, as_map_key=False):
309
+ self.packer.pack(obj)
310
+
311
+ def flush(self):
312
+ self.io.write(self.packer.bytes())
313
+ self.io.flush()
314
+ self.packer.reset()
315
+
316
+ class JsonMarshaler(Marshaler):
317
+ """The Marshaler tailor to JSON. To use this Marshaler, specify the
318
+ 'json' protocol when creating a Writer.
319
+ """
320
+ JSON_MAX_INT = pow(2, 53) - 1
321
+ JSON_MIN_INT = -pow(2, 53) + 1
322
+
323
+ default_opts = {"prefer_strings": True,
324
+ "max_int": JSON_MAX_INT,
325
+ "min_int": JSON_MIN_INT}
326
+
327
+ def __init__(self, io, opts=None):
328
+ self.io = io
329
+ nopts = JsonMarshaler.default_opts.copy()
330
+ nopts.update(opts or {})
331
+ self.started = [True]
332
+ self.is_key = [None]
333
+ Marshaler.__init__(self, nopts)
334
+
335
+ def flush(self):
336
+ # Each top level value is a separate JSON text; reset separator state
337
+ # so writing a stream of values doesn't emit a leading comma.
338
+ self.started = [True]
339
+ self.is_key = [None]
340
+ self.io.flush()
341
+
342
+ def push_level(self):
343
+ self.started.append(True)
344
+ self.is_key.append(None)
345
+
346
+ def pop_level(self):
347
+ self.started.pop()
348
+ self.is_key.pop()
349
+
350
+ def push_map(self):
351
+ self.started.append(True)
352
+ self.is_key.append(True)
353
+
354
+ def write_sep(self):
355
+ if self.started[-1]:
356
+ self.started[-1] = False
357
+ else:
358
+ last = self.is_key[-1]
359
+ if last:
360
+ self.io.write(":")
361
+ self.is_key[-1] = False
362
+ elif last is False:
363
+ self.io.write(",")
364
+ self.is_key[-1] = True
365
+ else:
366
+ self.io.write(",")
367
+
368
+ def emit_array_start(self, size):
369
+ self.write_sep()
370
+ self.io.write("[")
371
+ self.push_level()
372
+
373
+ def emit_array_end(self):
374
+ self.pop_level()
375
+ self.io.write("]")
376
+
377
+ def emit_map(self, m, _, cache):
378
+ """Emits array as per default JSON spec."""
379
+ self.emit_array_start(None)
380
+ self.emit_object(MAP_AS_ARR)
381
+ for k, v in m.items():
382
+ self.marshal(k, True, cache)
383
+ self.marshal(v, False, cache)
384
+ self.emit_array_end()
385
+
386
+ def emit_map_start(self, size):
387
+ self.write_sep()
388
+ self.io.write("{")
389
+ self.push_map()
390
+
391
+ def emit_map_end(self):
392
+ self.pop_level()
393
+ self.io.write("}")
394
+
395
+ def emit_object(self, obj, as_map_key=False):
396
+ tp = type(obj)
397
+ self.write_sep()
398
+ if tp is str:
399
+ self.io.write(json.dumps(obj, ensure_ascii=False))
400
+ elif tp is int or tp is float:
401
+ self.io.write(str(obj))
402
+ elif tp is bool:
403
+ self.io.write("true" if obj else "false")
404
+ elif obj is None:
405
+ self.io.write("null")
406
+ else:
407
+ raise AssertionError("Don't know how to encode: " + str(obj) + " of type: " + str(type(obj)))
408
+
409
+
410
+ class VerboseSettings:
411
+ """Mixin for JsonMarshaler that adds support for Verbose output/input.
412
+ Verbosity is only suggest for debuging/inspecting purposes.
413
+ """
414
+ @staticmethod
415
+ def _verbose_handlers(handlers):
416
+ for k, v in handlers.items():
417
+ if hasattr(v, "verbose_handler"):
418
+ handlers[k] = v.verbose_handler()
419
+ return handlers
420
+
421
+ def _init_handlers(self):
422
+ self.handlers = self._verbose_handlers(WriteHandler())
423
+
424
+ def emit_string(self, prefix, tag, string, as_map_key, cache):
425
+ return self.emit_object(prefix + tag + string, as_map_key)
426
+
427
+ def emit_map(self, m, _, cache):
428
+ self.emit_map_start(len(m))
429
+ for k, v in m.items():
430
+ self.marshal(k, True, cache)
431
+ self.marshal(v, False, cache)
432
+ self.emit_map_end()
433
+
434
+ def emit_tagged(self, tag, rep, cache):
435
+ self.emit_map_start(1)
436
+ self.emit_object(ESC + "#" + tag, True)
437
+ self.marshal(rep, False, cache)
438
+ self.emit_map_end()
439
+
440
+
441
+ class VerboseJsonMarshaler(VerboseSettings, JsonMarshaler):
442
+ """JsonMarshaler class with VerboseSettings mixin."""
443
+ pass # all from inheritance and mixin