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/__init__.py +16 -0
- transit/_accel.py +27 -0
- transit/_native.pyd +0 -0
- transit/class_hash.py +52 -0
- transit/constants.py +23 -0
- transit/decoder.py +185 -0
- transit/helpers.py +41 -0
- transit/read_handlers.py +183 -0
- transit/reader.py +137 -0
- transit/rolling_cache.py +104 -0
- transit/sosjson.py +129 -0
- transit/transit_types.py +267 -0
- transit/write_handlers.py +353 -0
- transit/writer.py +443 -0
- transit_python-0.8.321.dist-info/METADATA +302 -0
- transit_python-0.8.321.dist-info/RECORD +19 -0
- transit_python-0.8.321.dist-info/WHEEL +5 -0
- transit_python-0.8.321.dist-info/licenses/LICENSE +202 -0
- transit_python-0.8.321.dist-info/top_level.txt +1 -0
transit/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
__version__ = "0.8.321"
|
transit/_accel.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
## Copyright 2026 Vendekagon Labs LLC.
|
|
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
|
+
|
|
15
|
+
# The optional native extension (transit._native, built from transit-format-c).
|
|
16
|
+
# Reader and Writer use it when it's available, unless TRANSIT_PUREPYTHON is
|
|
17
|
+
# set; otherwise, or for anything it doesn't handle, they use pure Python.
|
|
18
|
+
import os
|
|
19
|
+
|
|
20
|
+
native = None
|
|
21
|
+
if not os.environ.get("TRANSIT_PUREPYTHON"):
|
|
22
|
+
try:
|
|
23
|
+
from transit import _native as native
|
|
24
|
+
except ImportError:
|
|
25
|
+
native = None
|
|
26
|
+
|
|
27
|
+
FORMATS = {"json": 0, "json_verbose": 1, "msgpack": 2}
|
transit/_native.pyd
ADDED
|
Binary file
|
transit/class_hash.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
from collections.abc import MutableMapping
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ClassDict(MutableMapping):
|
|
19
|
+
"""A dictionary that looks up class/type keys with inheritance."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, *args, **kwargs):
|
|
22
|
+
self.store = dict()
|
|
23
|
+
self.update(dict(*args, **kwargs))
|
|
24
|
+
|
|
25
|
+
def __getitem__(self, key):
|
|
26
|
+
key = key if isinstance(key, type) else type(key)
|
|
27
|
+
if key in self.store:
|
|
28
|
+
return self.store[key]
|
|
29
|
+
else:
|
|
30
|
+
for t in key.__bases__:
|
|
31
|
+
value = t in self.store and self.store[t]
|
|
32
|
+
if value:
|
|
33
|
+
return value
|
|
34
|
+
# only use mro if __bases__ doesn't work to
|
|
35
|
+
# avoid its perf overhead.
|
|
36
|
+
for t in key.mro():
|
|
37
|
+
value = t in self.store and self.store[t]
|
|
38
|
+
if value:
|
|
39
|
+
return value
|
|
40
|
+
raise KeyError("No handler found for: " + str(key))
|
|
41
|
+
|
|
42
|
+
def __setitem__(self, key, value):
|
|
43
|
+
self.store[key] = value
|
|
44
|
+
|
|
45
|
+
def __delitem__(self, key):
|
|
46
|
+
del self.store[key]
|
|
47
|
+
|
|
48
|
+
def __iter__(self):
|
|
49
|
+
return iter(self.store)
|
|
50
|
+
|
|
51
|
+
def __len__(self):
|
|
52
|
+
return len(self.store)
|
transit/constants.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## Copyright 2014 Cognitect. All Rights Reserved.
|
|
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
|
+
|
|
15
|
+
# These are constants used throughout the source code.
|
|
16
|
+
# They refer to Tag codes used when reading and wrting transit.
|
|
17
|
+
|
|
18
|
+
ESC = "~"
|
|
19
|
+
SUB = "^"
|
|
20
|
+
RES = "`"
|
|
21
|
+
TAG = "~#"
|
|
22
|
+
QUOTE = "'"
|
|
23
|
+
MAP_AS_ARR = "^ "
|
transit/decoder.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
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
|
+
from transit import transit_types
|
|
17
|
+
from transit import read_handlers as rh
|
|
18
|
+
from transit.constants import MAP_AS_ARR, ESC, SUB, RES
|
|
19
|
+
from transit.helpers import pairs
|
|
20
|
+
from transit.rolling_cache import RollingCache
|
|
21
|
+
from transit.transit_types import true, false
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Tag:
|
|
25
|
+
def __init__(self, tag):
|
|
26
|
+
self.tag = tag
|
|
27
|
+
|
|
28
|
+
default_options = {"decoders": {"_": rh.NoneHandler,
|
|
29
|
+
":": rh.KeywordHandler,
|
|
30
|
+
"$": rh.SymbolHandler,
|
|
31
|
+
"?": rh.BooleanHandler,
|
|
32
|
+
"i": rh.IntHandler,
|
|
33
|
+
"d": rh.FloatHandler,
|
|
34
|
+
"f": rh.BigDecimalHandler,
|
|
35
|
+
"u": rh.UuidHandler,
|
|
36
|
+
"r": rh.UriHandler,
|
|
37
|
+
"t": rh.DateHandler,
|
|
38
|
+
"m": rh.DateHandler,
|
|
39
|
+
"n": rh.BigIntegerHandler,
|
|
40
|
+
"z": rh.SpecialNumbersHandler,
|
|
41
|
+
"link": rh.LinkHandler,
|
|
42
|
+
"list": rh.ListHandler,
|
|
43
|
+
"set": rh.SetHandler,
|
|
44
|
+
"cmap": rh.CmapHandler,
|
|
45
|
+
"'": rh.IdentityHandler},
|
|
46
|
+
"default_decoder": rh.DefaultHandler}
|
|
47
|
+
|
|
48
|
+
ground_decoders = {"_": rh.NoneHandler,
|
|
49
|
+
"?": rh.BooleanHandler,
|
|
50
|
+
"i": rh.IntHandler,
|
|
51
|
+
"'": rh.IdentityHandler}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Decoder:
|
|
55
|
+
"""The Decoder is the lowest level entry point for parsing, decoding, and
|
|
56
|
+
fully converting Transit data into Python objects.
|
|
57
|
+
|
|
58
|
+
During the creation of a Decoder object, you can specify custom options
|
|
59
|
+
in a dictionary. One such option is 'decoders'. Note that while you
|
|
60
|
+
can specify your own decoders and override many of the built in decoders,
|
|
61
|
+
some decoders are silently enforced and cannot be overriden. These are
|
|
62
|
+
known as Ground Decoders, and are needed to maintain bottom-tier
|
|
63
|
+
compatibility.
|
|
64
|
+
"""
|
|
65
|
+
def __init__(self, options=None):
|
|
66
|
+
self.options = default_options.copy()
|
|
67
|
+
self.options.update(options or {})
|
|
68
|
+
|
|
69
|
+
# Copy so that registering decoders on one Decoder doesn't leak into
|
|
70
|
+
# default_options (and so every other Decoder).
|
|
71
|
+
self.decoders = dict(self.options["decoders"])
|
|
72
|
+
# Always ensure we control the ground decoders
|
|
73
|
+
self.decoders.update(ground_decoders)
|
|
74
|
+
|
|
75
|
+
def decode(self, node, cache=None, as_map_key=False):
|
|
76
|
+
"""Given a node of data (any supported decodeable obj - string, dict,
|
|
77
|
+
list), return the decoded object. Optionally set the current decode
|
|
78
|
+
cache [None]. If None, a new RollingCache is instantiated and used.
|
|
79
|
+
You may also hit to the decoder that this node is to be treated as a
|
|
80
|
+
map key [False]. This is used internally.
|
|
81
|
+
"""
|
|
82
|
+
if not cache:
|
|
83
|
+
cache = RollingCache()
|
|
84
|
+
return self._decode(node, cache, as_map_key)
|
|
85
|
+
|
|
86
|
+
def _decode(self, node, cache, as_map_key):
|
|
87
|
+
if isinstance(node, str):
|
|
88
|
+
return self.decode_string(node, cache, as_map_key)
|
|
89
|
+
elif isinstance(node, bytes):
|
|
90
|
+
return self.decode_string(node.decode("utf-8"), cache, as_map_key)
|
|
91
|
+
elif isinstance(node, dict):
|
|
92
|
+
return self.decode_hash(node, cache, as_map_key)
|
|
93
|
+
elif isinstance(node, list):
|
|
94
|
+
return self.decode_list(node, cache, as_map_key)
|
|
95
|
+
elif isinstance(node, bool):
|
|
96
|
+
return true if node else false
|
|
97
|
+
return node
|
|
98
|
+
|
|
99
|
+
def decode_list(self, node, cache, as_map_key):
|
|
100
|
+
"""Special case decodes map-as-array.
|
|
101
|
+
Otherwise lists are treated as Python lists.
|
|
102
|
+
|
|
103
|
+
Arguments follow the same convention as the top-level 'decode'
|
|
104
|
+
function.
|
|
105
|
+
"""
|
|
106
|
+
if node:
|
|
107
|
+
if node[0] == MAP_AS_ARR:
|
|
108
|
+
# key must be decoded before value for caching to work.
|
|
109
|
+
returned_dict = {}
|
|
110
|
+
for k, v in pairs(node[1:]):
|
|
111
|
+
key = self._decode(k, cache, True)
|
|
112
|
+
val = self._decode(v, cache, as_map_key)
|
|
113
|
+
returned_dict[key] = val
|
|
114
|
+
return transit_types.frozendict(returned_dict)
|
|
115
|
+
|
|
116
|
+
# Each element must be decoded exactly once, in order, to keep
|
|
117
|
+
# the cache in step with the writer.
|
|
118
|
+
decoded = self._decode(node[0], cache, as_map_key)
|
|
119
|
+
if isinstance(decoded, Tag):
|
|
120
|
+
return self.decode_tag(decoded.tag,
|
|
121
|
+
self._decode(node[1], cache, as_map_key))
|
|
122
|
+
return (decoded,) + tuple(self._decode(x, cache, as_map_key)
|
|
123
|
+
for x in node[1:])
|
|
124
|
+
return ()
|
|
125
|
+
|
|
126
|
+
def decode_string(self, string, cache, as_map_key):
|
|
127
|
+
"""Decode a string - arguments follow the same convention as the
|
|
128
|
+
top-level 'decode' function.
|
|
129
|
+
"""
|
|
130
|
+
return self.parse_string(cache.decode(string, as_map_key),
|
|
131
|
+
cache, as_map_key)
|
|
132
|
+
|
|
133
|
+
def decode_tag(self, tag, rep):
|
|
134
|
+
decoder = self.decoders.get(tag, None)
|
|
135
|
+
if decoder:
|
|
136
|
+
return decoder.from_rep(rep)
|
|
137
|
+
else:
|
|
138
|
+
return self.options["default_decoder"].from_rep(tag, rep)
|
|
139
|
+
|
|
140
|
+
def decode_hash(self, hash, cache, as_map_key):
|
|
141
|
+
if len(hash) != 1:
|
|
142
|
+
h = {}
|
|
143
|
+
for k, v in hash.items():
|
|
144
|
+
# crude/verbose implementation, but this is only version that
|
|
145
|
+
# plays nice w/cache for both msgpack and json thus far.
|
|
146
|
+
# -- e.g., we have to specify encode/decode order for key/val
|
|
147
|
+
# -- explicitly, all implicit ordering has broken in corner
|
|
148
|
+
# -- cases, thus these extraneous seeming assignments
|
|
149
|
+
key = self._decode(k, cache, True)
|
|
150
|
+
val = self._decode(v, cache, False)
|
|
151
|
+
h[key] = val
|
|
152
|
+
return transit_types.frozendict(h)
|
|
153
|
+
else:
|
|
154
|
+
key = list(hash)[0]
|
|
155
|
+
value = hash[key]
|
|
156
|
+
key = self._decode(key, cache, True)
|
|
157
|
+
if isinstance(key, Tag):
|
|
158
|
+
return self.decode_tag(key.tag,
|
|
159
|
+
self._decode(value, cache, as_map_key))
|
|
160
|
+
return transit_types.frozendict({key: self._decode(value, cache, False)})
|
|
161
|
+
|
|
162
|
+
def parse_string(self, string, cache, as_map_key):
|
|
163
|
+
if string.startswith(ESC):
|
|
164
|
+
m = string[1]
|
|
165
|
+
if m in self.decoders:
|
|
166
|
+
return self.decoders[m].from_rep(string[2:])
|
|
167
|
+
elif m == ESC or m == SUB or m == RES:
|
|
168
|
+
return string[1:]
|
|
169
|
+
elif m == "#":
|
|
170
|
+
return Tag(string[2:])
|
|
171
|
+
else:
|
|
172
|
+
return self.options["default_decoder"].from_rep(string[1],
|
|
173
|
+
string[2:])
|
|
174
|
+
return string
|
|
175
|
+
|
|
176
|
+
def register(self, key_or_tag, obj):
|
|
177
|
+
"""Register a custom Transit tag and new parsing function with the
|
|
178
|
+
decoder. Also, you can optionally set the 'default_decoder' with
|
|
179
|
+
this function. Your new tag and parse/decode function will be added
|
|
180
|
+
to the interal dictionary of decoders for this Decoder object.
|
|
181
|
+
"""
|
|
182
|
+
if key_or_tag == "default_decoder":
|
|
183
|
+
self.options["default_decoder"] = obj
|
|
184
|
+
else:
|
|
185
|
+
self.decoders[key_or_tag] = obj
|
transit/helpers.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
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 itertools
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def mapcat(f, i):
|
|
20
|
+
return itertools.chain.from_iterable(map(f, i))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def pairs(i):
|
|
24
|
+
return zip(*[iter(i)] * 2)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
cycle = itertools.cycle
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def take(n, i):
|
|
31
|
+
return itertools.islice(i, 0, n)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def require_msgpack():
|
|
35
|
+
"""Import msgpack, which is only needed for the msgpack protocol."""
|
|
36
|
+
try:
|
|
37
|
+
import msgpack
|
|
38
|
+
except ImportError as e:
|
|
39
|
+
raise ImportError("The msgpack protocol requires the msgpack package; "
|
|
40
|
+
"install it with: pip install 'transit-python[msgpack]'") from e
|
|
41
|
+
return msgpack
|
transit/read_handlers.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
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 datetime
|
|
17
|
+
import re
|
|
18
|
+
import uuid
|
|
19
|
+
from decimal import Decimal
|
|
20
|
+
|
|
21
|
+
from transit import transit_types
|
|
22
|
+
from transit.helpers import pairs
|
|
23
|
+
|
|
24
|
+
## Read handlers are used by the decoder when parsing/reading in Transit
|
|
25
|
+
## data and returning Python objects
|
|
26
|
+
|
|
27
|
+
EPOCH = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
|
|
28
|
+
|
|
29
|
+
_RFC3339 = re.compile(r"(\d{4}-\d\d-\d\d)[Tt ](\d\d:\d\d:\d\d)(?:\.(\d+))?"
|
|
30
|
+
r"([Zz]|[+-]\d\d:\d\d)?$")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def parse_rfc3339(s):
|
|
34
|
+
"""Parse an RFC 3339 timestamp into a UTC datetime, to the millisecond
|
|
35
|
+
(transit times are points in time, with millisecond precision, as they
|
|
36
|
+
are for the other transit implementations).
|
|
37
|
+
|
|
38
|
+
Normalizes what datetime.fromisoformat can't handle on every supported
|
|
39
|
+
Python ("Z", fractional seconds that aren't 3 or 6 digits). A missing
|
|
40
|
+
offset is taken to be UTC.
|
|
41
|
+
"""
|
|
42
|
+
m = _RFC3339.match(s)
|
|
43
|
+
if not m:
|
|
44
|
+
d = datetime.datetime.fromisoformat(s)
|
|
45
|
+
else:
|
|
46
|
+
date, time, frac, offset = m.groups()
|
|
47
|
+
if frac:
|
|
48
|
+
time += "." + frac[:6].ljust(6, "0")
|
|
49
|
+
if offset is None or offset in "Zz":
|
|
50
|
+
offset = "+00:00"
|
|
51
|
+
d = datetime.datetime.fromisoformat(date + "T" + time + offset)
|
|
52
|
+
if d.tzinfo is None:
|
|
53
|
+
d = d.replace(tzinfo=datetime.timezone.utc)
|
|
54
|
+
d = d.astimezone(datetime.timezone.utc)
|
|
55
|
+
return d.replace(microsecond=d.microsecond // 1000 * 1000)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class DefaultHandler:
|
|
59
|
+
@staticmethod
|
|
60
|
+
def from_rep(t, v):
|
|
61
|
+
return transit_types.TaggedValue(t, v)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class NoneHandler:
|
|
65
|
+
@staticmethod
|
|
66
|
+
def from_rep(_):
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class KeywordHandler:
|
|
71
|
+
@staticmethod
|
|
72
|
+
def from_rep(v):
|
|
73
|
+
return transit_types.Keyword(v)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class SymbolHandler:
|
|
77
|
+
@staticmethod
|
|
78
|
+
def from_rep(v):
|
|
79
|
+
return transit_types.Symbol(v)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class BigDecimalHandler:
|
|
83
|
+
@staticmethod
|
|
84
|
+
def from_rep(v):
|
|
85
|
+
return Decimal(v)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class BooleanHandler:
|
|
89
|
+
@staticmethod
|
|
90
|
+
def from_rep(x):
|
|
91
|
+
return transit_types.true if x == "t" else transit_types.false
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class IntHandler:
|
|
95
|
+
@staticmethod
|
|
96
|
+
def from_rep(v):
|
|
97
|
+
return int(v)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class FloatHandler:
|
|
101
|
+
@staticmethod
|
|
102
|
+
def from_rep(v):
|
|
103
|
+
return float(v)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class UuidHandler:
|
|
107
|
+
@staticmethod
|
|
108
|
+
def from_rep(u):
|
|
109
|
+
"""Given a string, or a pair of signed 64 bit ints (most significant
|
|
110
|
+
first), return a UUID object."""
|
|
111
|
+
if isinstance(u, str):
|
|
112
|
+
return uuid.UUID(u)
|
|
113
|
+
mask = (1 << 64) - 1
|
|
114
|
+
return uuid.UUID(int=(u[0] & mask) << 64 | (u[1] & mask))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class UriHandler:
|
|
118
|
+
@staticmethod
|
|
119
|
+
def from_rep(u):
|
|
120
|
+
return transit_types.URI(u)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class DateHandler:
|
|
124
|
+
@staticmethod
|
|
125
|
+
def from_rep(d):
|
|
126
|
+
if isinstance(d, int):
|
|
127
|
+
return DateHandler._convert_timestamp(d)
|
|
128
|
+
if "T" in d:
|
|
129
|
+
return parse_rfc3339(d)
|
|
130
|
+
return DateHandler._convert_timestamp(int(d))
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def _convert_timestamp(ms):
|
|
134
|
+
"""Given a timestamp in ms, return a DateTime object."""
|
|
135
|
+
return EPOCH + datetime.timedelta(milliseconds=ms)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class BigIntegerHandler:
|
|
139
|
+
@staticmethod
|
|
140
|
+
def from_rep(d):
|
|
141
|
+
return int(d)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class LinkHandler:
|
|
145
|
+
@staticmethod
|
|
146
|
+
def from_rep(l):
|
|
147
|
+
return transit_types.Link(**l)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class ListHandler:
|
|
151
|
+
@staticmethod
|
|
152
|
+
def from_rep(l):
|
|
153
|
+
return l
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class SetHandler:
|
|
157
|
+
@staticmethod
|
|
158
|
+
def from_rep(s):
|
|
159
|
+
return frozenset(s)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class CmapHandler:
|
|
163
|
+
@staticmethod
|
|
164
|
+
def from_rep(cmap):
|
|
165
|
+
return transit_types.frozendict(pairs(cmap))
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class IdentityHandler:
|
|
169
|
+
@staticmethod
|
|
170
|
+
def from_rep(i):
|
|
171
|
+
return i
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class SpecialNumbersHandler:
|
|
175
|
+
@staticmethod
|
|
176
|
+
def from_rep(z):
|
|
177
|
+
if z == 'NaN':
|
|
178
|
+
return float('Nan')
|
|
179
|
+
if z == 'INF':
|
|
180
|
+
return float('Inf')
|
|
181
|
+
if z == '-INF':
|
|
182
|
+
return float('-Inf')
|
|
183
|
+
raise ValueError("Don't know how to handle: " + str(z) + " as \"z\"")
|
transit/reader.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
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
|
+
|
|
18
|
+
from transit import sosjson
|
|
19
|
+
from transit._accel import FORMATS, native
|
|
20
|
+
from transit.decoder import Decoder
|
|
21
|
+
from transit.helpers import require_msgpack
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Reader:
|
|
25
|
+
"""The top-level object for reading in Transit data and converting it to
|
|
26
|
+
Python objects. During initialization, you must specify the protocol used
|
|
27
|
+
for unmarshalling the data- json or msgpack. The msgpack protocol requires
|
|
28
|
+
the msgpack package (pip install 'transit-python[msgpack]').
|
|
29
|
+
"""
|
|
30
|
+
def __init__(self, protocol="json"):
|
|
31
|
+
self.protocol = protocol
|
|
32
|
+
self.customized = False
|
|
33
|
+
if protocol in ("json", "json_verbose"):
|
|
34
|
+
self.reader = JsonUnmarshaler()
|
|
35
|
+
elif protocol == "msgpack":
|
|
36
|
+
try:
|
|
37
|
+
self.reader = MsgPackUnmarshaler()
|
|
38
|
+
self.unpacker = self.reader.unpacker
|
|
39
|
+
except ImportError:
|
|
40
|
+
# the native extension reads msgpack without the package
|
|
41
|
+
if native is None:
|
|
42
|
+
raise
|
|
43
|
+
self.reader = None
|
|
44
|
+
else:
|
|
45
|
+
raise ValueError("'" + protocol + "' is not a supported. " +
|
|
46
|
+
"Protocol must be:" +
|
|
47
|
+
"'json', 'json_verbose', or 'msgpack'.")
|
|
48
|
+
|
|
49
|
+
def read(self, stream):
|
|
50
|
+
"""Given a readable file descriptor object (something `load`able by
|
|
51
|
+
msgpack or json), read the data, and return the Python representation
|
|
52
|
+
of the contents. One-shot reader.
|
|
53
|
+
"""
|
|
54
|
+
if native is not None and not self.customized:
|
|
55
|
+
return native.loads(stream.read(), FORMATS[self.protocol])
|
|
56
|
+
return self.reader.load(stream)
|
|
57
|
+
|
|
58
|
+
def register(self, key_or_tag, f_val):
|
|
59
|
+
"""Register a custom transit tag and decoder/parser function for use
|
|
60
|
+
during reads.
|
|
61
|
+
"""
|
|
62
|
+
self.customized = True
|
|
63
|
+
self.reader.decoder.register(key_or_tag, f_val)
|
|
64
|
+
|
|
65
|
+
def readeach(self, stream, **kwargs):
|
|
66
|
+
"""Read each object from stream as it becomes available, as a
|
|
67
|
+
generator. Stops at the end of the stream.
|
|
68
|
+
|
|
69
|
+
Data is read from stream as it arrives rather than waiting for a full
|
|
70
|
+
buffer, so this works over pipes and sockets. For text streams wrapping
|
|
71
|
+
a binary buffer (like sys.stdin), the underlying buffer is read.
|
|
72
|
+
|
|
73
|
+
For msgpack, stream may be None, in which case the objects are taken
|
|
74
|
+
from data fed to the `unpacker` property with `unpacker.feed()`.
|
|
75
|
+
"""
|
|
76
|
+
if native is not None and not self.customized and stream is not None:
|
|
77
|
+
yield from self._native_readeach(stream)
|
|
78
|
+
return
|
|
79
|
+
if self.reader is None:
|
|
80
|
+
require_msgpack()
|
|
81
|
+
for o in self.reader.loadeach(stream):
|
|
82
|
+
yield o
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _native_readeach(self, stream):
|
|
86
|
+
s = native.stream_new(FORMATS[self.protocol])
|
|
87
|
+
for chunk in sosjson.chunks_raw(stream):
|
|
88
|
+
native.stream_feed(s, chunk)
|
|
89
|
+
while True:
|
|
90
|
+
found, value = native.stream_read(s)
|
|
91
|
+
if not found:
|
|
92
|
+
break
|
|
93
|
+
yield value
|
|
94
|
+
native.stream_end(s)
|
|
95
|
+
while True:
|
|
96
|
+
found, value = native.stream_read(s)
|
|
97
|
+
if not found:
|
|
98
|
+
return
|
|
99
|
+
yield value
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class JsonUnmarshaler:
|
|
103
|
+
"""The top-level Unmarshaler used by the Reader for JSON payloads. While
|
|
104
|
+
you may use this directly, it is strongly discouraged.
|
|
105
|
+
"""
|
|
106
|
+
def __init__(self):
|
|
107
|
+
self.decoder = Decoder()
|
|
108
|
+
|
|
109
|
+
def load(self, stream):
|
|
110
|
+
return self.decoder.decode(json.load(stream))
|
|
111
|
+
|
|
112
|
+
def loadeach(self, stream):
|
|
113
|
+
for o in sosjson.items(stream):
|
|
114
|
+
yield self.decoder.decode(o)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class MsgPackUnmarshaler:
|
|
118
|
+
"""The top-level Unmarshaler used by the Reader for MsgPack payloads.
|
|
119
|
+
While you may use this directly, it is strongly discouraged.
|
|
120
|
+
"""
|
|
121
|
+
def __init__(self):
|
|
122
|
+
self.msgpack = require_msgpack()
|
|
123
|
+
self.decoder = Decoder()
|
|
124
|
+
self.unpacker = self.msgpack.Unpacker(strict_map_key=False)
|
|
125
|
+
|
|
126
|
+
def load(self, stream):
|
|
127
|
+
return self.decoder.decode(self.msgpack.unpack(stream, strict_map_key=False))
|
|
128
|
+
|
|
129
|
+
def loadeach(self, stream):
|
|
130
|
+
for o in self.unpacker:
|
|
131
|
+
yield self.decoder.decode(o)
|
|
132
|
+
if stream is None:
|
|
133
|
+
return
|
|
134
|
+
for data in sosjson.chunks_raw(stream):
|
|
135
|
+
self.unpacker.feed(data)
|
|
136
|
+
for o in self.unpacker:
|
|
137
|
+
yield self.decoder.decode(o)
|