Show More
@@ -0,0 +1,58 b'' | |||
|
1 | language: python | |
|
2 | sudo: false | |
|
3 | ||
|
4 | stages: | |
|
5 | - name: test | |
|
6 | - name: deploy to pypi | |
|
7 | if: type = push AND tag =~ ^\d+\.\d+\.\d+ | |
|
8 | ||
|
9 | jobs: | |
|
10 | fast_finish: true | |
|
11 | include: | |
|
12 | - env: TOXENV=flake8 | |
|
13 | ||
|
14 | - env: TOXENV=pypy | |
|
15 | python: pypy3 | |
|
16 | ||
|
17 | - env: TOXENV=pypy3 | |
|
18 | python: pypy3 | |
|
19 | ||
|
20 | - env: TOXENV=py27 | |
|
21 | python: "2.7" | |
|
22 | after_success: &after_success | |
|
23 | - pip install coveralls | |
|
24 | - coveralls | |
|
25 | ||
|
26 | - env: TOXENV=py33 | |
|
27 | python: "3.3" | |
|
28 | after_success: *after_success | |
|
29 | ||
|
30 | - env: TOXENV=py34 | |
|
31 | python: "3.4" | |
|
32 | after_success: *after_success | |
|
33 | ||
|
34 | - env: TOXENV=py35 | |
|
35 | python: "3.5" | |
|
36 | after_success: *after_success | |
|
37 | ||
|
38 | - env: TOXENV=py36 | |
|
39 | python: "3.6" | |
|
40 | after_success: *after_success | |
|
41 | ||
|
42 | - stage: deploy to pypi | |
|
43 | install: pip install "setuptools >= 36.2.7" | |
|
44 | script: skip | |
|
45 | deploy: | |
|
46 | provider: pypi | |
|
47 | user: agronholm | |
|
48 | password: | |
|
49 | secure: QZ5qoxsrzns/b27adWNzh/OAJp86yRuxTyAFhvas/pbkiALdlT/+PGyhJBnpe+7WBTWnkIXl+YU//voJ0btf6DJcWwgRavMsy22LJJPkvvK+2DHiZ//DbpLbqKWc74y4moce29BCajFTm9JkVwcL2dgN9WuZt+Tay0efcP4sESLxo5lIGdlaQbu+9zVs61Z4Ov+yyEMO/j3LeKshNmUq+84CveQWMiXndXBfJX5TWwjahmUNDp5fMctJxr4fqgL4HCTVQhU79dPc00yDEGS45QkpP8JDrF1DQvU5Ht4COz/Lvzt11pwsAvws2ddclqBUCQsGaWvEWH5rxZTYx/MaMVdTctaUVNoT0wnFUsXXZkomQV0x8vb5RtRLDrKwXosXlSEqnRyiKhdgHGoswHvB7XF5BtQ5RmydRX77pwEGmFd3lqRif2bos0MEeOJA8Xds0TGOKO4PyokBnj/a0tjT2LEVxObmTT6grz5QPXi386AWgxbNl0Lp7cnkSpCqC1hEHVqrDlbtu7uvfGwwe/sYlEcQ07PNCvFoR2GXJawbeHmJRfz+KXjffrt2yCzc671FL1goUysHKdBCppvUInI8FCMQpVWEh5MmQJKB4IpDrhqfo0VS+NNZgZ8lFStq27Pmwqf1HUTGlaDi9VQ0Vo7tW5j4JbD/JvOQSb3j9DjUFps= | |
|
50 | distributions: sdist bdist_wheel | |
|
51 | on: | |
|
52 | tags: true | |
|
53 | ||
|
54 | install: | |
|
55 | - pip install "setuptools >= 36.2.7" | |
|
56 | - pip install tox | |
|
57 | ||
|
58 | script: tox |
@@ -0,0 +1,19 b'' | |||
|
1 | This is the MIT license: http://www.opensource.org/licenses/mit-license.php | |
|
2 | ||
|
3 | Copyright (c) Alex GrΓΆnholm | |
|
4 | ||
|
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this | |
|
6 | software and associated documentation files (the "Software"), to deal in the Software | |
|
7 | without restriction, including without limitation the rights to use, copy, modify, merge, | |
|
8 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons | |
|
9 | to whom the Software is furnished to do so, subject to the following conditions: | |
|
10 | ||
|
11 | The above copyright notice and this permission notice shall be included in all copies or | |
|
12 | substantial portions of the Software. | |
|
13 | ||
|
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | |
|
15 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | |
|
16 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE | |
|
17 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | |
|
18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
|
19 | DEALINGS IN THE SOFTWARE. |
@@ -0,0 +1,24 b'' | |||
|
1 | .. image:: https://travis-ci.org/agronholm/cbor2.svg?branch=master | |
|
2 | :target: https://travis-ci.org/agronholm/cbor2 | |
|
3 | :alt: Build Status | |
|
4 | .. image:: https://coveralls.io/repos/github/agronholm/cbor2/badge.svg?branch=master | |
|
5 | :target: https://coveralls.io/github/agronholm/cbor2?branch=master | |
|
6 | :alt: Code Coverage | |
|
7 | ||
|
8 | This library provides encoding and decoding for the Concise Binary Object Representation (CBOR) | |
|
9 | (`RFC 7049`_) serialization format. | |
|
10 | ||
|
11 | There exists another Python CBOR implementation (cbor) which is faster on CPython due to its C | |
|
12 | extensions. On PyPy, cbor2 and cbor are almost identical in performance. The other implementation | |
|
13 | also lacks documentation and a comprehensive test suite, does not support most standard extension | |
|
14 | tags and is known to crash (segfault) when passed a cyclic structure (say, a list containing | |
|
15 | itself). | |
|
16 | ||
|
17 | .. _RFC 7049: https://tools.ietf.org/html/rfc7049 | |
|
18 | ||
|
19 | Project links | |
|
20 | ------------- | |
|
21 | ||
|
22 | * `Documentation <http://cbor2.readthedocs.org/>`_ | |
|
23 | * `Source code <https://github.com/agronholm/cbor2>`_ | |
|
24 | * `Issue tracker <https://github.com/agronholm/cbor2/issues>`_ |
@@ -0,0 +1,3 b'' | |||
|
1 | from .decoder import load, loads, CBORDecoder, CBORDecodeError # noqa | |
|
2 | from .encoder import dump, dumps, CBOREncoder, CBOREncodeError, shareable_encoder # noqa | |
|
3 | from .types import CBORTag, CBORSimpleValue, undefined # noqa |
@@ -0,0 +1,101 b'' | |||
|
1 | from math import ldexp | |
|
2 | import struct | |
|
3 | import sys | |
|
4 | ||
|
5 | ||
|
6 | if sys.version_info.major < 3: | |
|
7 | from datetime import tzinfo, timedelta | |
|
8 | ||
|
9 | class timezone(tzinfo): | |
|
10 | def __init__(self, offset): | |
|
11 | self.offset = offset | |
|
12 | ||
|
13 | def utcoffset(self, dt): | |
|
14 | return self.offset | |
|
15 | ||
|
16 | def dst(self, dt): | |
|
17 | return timedelta(0) | |
|
18 | ||
|
19 | def tzname(self, dt): | |
|
20 | return 'UTC+00:00' | |
|
21 | ||
|
22 | def as_unicode(string): | |
|
23 | return string.decode('utf-8') | |
|
24 | ||
|
25 | def iteritems(self): | |
|
26 | return self.iteritems() | |
|
27 | ||
|
28 | def bytes_from_list(values): | |
|
29 | return bytes(bytearray(values)) | |
|
30 | ||
|
31 | byte_as_integer = ord | |
|
32 | timezone.utc = timezone(timedelta(0)) | |
|
33 | xrange = xrange # noqa: F821 | |
|
34 | long = long # noqa: F821 | |
|
35 | unicode = unicode # noqa: F821 | |
|
36 | else: | |
|
37 | from datetime import timezone | |
|
38 | ||
|
39 | def byte_as_integer(bytestr): | |
|
40 | return bytestr[0] | |
|
41 | ||
|
42 | def as_unicode(string): | |
|
43 | return string | |
|
44 | ||
|
45 | def iteritems(self): | |
|
46 | return self.items() | |
|
47 | ||
|
48 | xrange = range | |
|
49 | long = int | |
|
50 | unicode = str | |
|
51 | bytes_from_list = bytes | |
|
52 | ||
|
53 | ||
|
54 | if sys.version_info.major >= 3 and sys.version_info.minor >= 6: | |
|
55 | # Python 3.6 added 16 bit floating point to struct | |
|
56 | ||
|
57 | def pack_float16(value): | |
|
58 | try: | |
|
59 | return struct.pack('>Be', 0xf9, value) | |
|
60 | except OverflowError: | |
|
61 | return False | |
|
62 | ||
|
63 | def unpack_float16(payload): | |
|
64 | return struct.unpack('>e', payload)[0] | |
|
65 | else: | |
|
66 | def pack_float16(value): | |
|
67 | # Based on node-cbor by hildjj | |
|
68 | # which was based in turn on Carsten Borman's cn-cbor | |
|
69 | u32 = struct.pack('>f', value) | |
|
70 | u = struct.unpack('>I', u32)[0] | |
|
71 | ||
|
72 | if u & 0x1FFF != 0: | |
|
73 | return False | |
|
74 | ||
|
75 | s16 = (u >> 16) & 0x8000 | |
|
76 | exponent = (u >> 23) & 0xff | |
|
77 | mantissa = u & 0x7fffff | |
|
78 | ||
|
79 | if 113 <= exponent <= 142: | |
|
80 | s16 += ((exponent - 112) << 10) + (mantissa >> 13) | |
|
81 | elif 103 <= exponent < 113: | |
|
82 | if mantissa & ((1 << (126 - exponent)) - 1): | |
|
83 | return False | |
|
84 | ||
|
85 | s16 += ((mantissa + 0x800000) >> (126 - exponent)) | |
|
86 | else: | |
|
87 | return False | |
|
88 | ||
|
89 | return struct.pack('>BH', 0xf9, s16) | |
|
90 | ||
|
91 | def unpack_float16(payload): | |
|
92 | # Code adapted from RFC 7049, appendix D | |
|
93 | def decode_single(single): | |
|
94 | return struct.unpack("!f", struct.pack("!I", single))[0] | |
|
95 | ||
|
96 | payload = struct.unpack('>H', payload)[0] | |
|
97 | value = (payload & 0x7fff) << 13 | (payload & 0x8000) << 16 | |
|
98 | if payload & 0x7c00 != 0x7c00: | |
|
99 | return ldexp(decode_single(value), 112) | |
|
100 | ||
|
101 | return decode_single(value | 0x7f800000) |
@@ -0,0 +1,407 b'' | |||
|
1 | import re | |
|
2 | import struct | |
|
3 | from datetime import datetime, timedelta | |
|
4 | from io import BytesIO | |
|
5 | ||
|
6 | from .compat import timezone, xrange, byte_as_integer, unpack_float16 | |
|
7 | from .types import CBORTag, undefined, break_marker, CBORSimpleValue | |
|
8 | ||
|
9 | timestamp_re = re.compile(r'^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)' | |
|
10 | r'(?:\.(\d+))?(?:Z|([+-]\d\d):(\d\d))$') | |
|
11 | ||
|
12 | ||
|
13 | class CBORDecodeError(Exception): | |
|
14 | """Raised when an error occurs deserializing a CBOR datastream.""" | |
|
15 | ||
|
16 | ||
|
17 | def decode_uint(decoder, subtype, shareable_index=None, allow_indefinite=False): | |
|
18 | # Major tag 0 | |
|
19 | if subtype < 24: | |
|
20 | return subtype | |
|
21 | elif subtype == 24: | |
|
22 | return struct.unpack('>B', decoder.read(1))[0] | |
|
23 | elif subtype == 25: | |
|
24 | return struct.unpack('>H', decoder.read(2))[0] | |
|
25 | elif subtype == 26: | |
|
26 | return struct.unpack('>L', decoder.read(4))[0] | |
|
27 | elif subtype == 27: | |
|
28 | return struct.unpack('>Q', decoder.read(8))[0] | |
|
29 | elif subtype == 31 and allow_indefinite: | |
|
30 | return None | |
|
31 | else: | |
|
32 | raise CBORDecodeError('unknown unsigned integer subtype 0x%x' % subtype) | |
|
33 | ||
|
34 | ||
|
35 | def decode_negint(decoder, subtype, shareable_index=None): | |
|
36 | # Major tag 1 | |
|
37 | uint = decode_uint(decoder, subtype) | |
|
38 | return -uint - 1 | |
|
39 | ||
|
40 | ||
|
41 | def decode_bytestring(decoder, subtype, shareable_index=None): | |
|
42 | # Major tag 2 | |
|
43 | length = decode_uint(decoder, subtype, allow_indefinite=True) | |
|
44 | if length is None: | |
|
45 | # Indefinite length | |
|
46 | buf = bytearray() | |
|
47 | while True: | |
|
48 | initial_byte = byte_as_integer(decoder.read(1)) | |
|
49 | if initial_byte == 255: | |
|
50 | return buf | |
|
51 | else: | |
|
52 | length = decode_uint(decoder, initial_byte & 31) | |
|
53 | value = decoder.read(length) | |
|
54 | buf.extend(value) | |
|
55 | else: | |
|
56 | return decoder.read(length) | |
|
57 | ||
|
58 | ||
|
59 | def decode_string(decoder, subtype, shareable_index=None): | |
|
60 | # Major tag 3 | |
|
61 | return decode_bytestring(decoder, subtype).decode('utf-8') | |
|
62 | ||
|
63 | ||
|
64 | def decode_array(decoder, subtype, shareable_index=None): | |
|
65 | # Major tag 4 | |
|
66 | items = [] | |
|
67 | decoder.set_shareable(shareable_index, items) | |
|
68 | length = decode_uint(decoder, subtype, allow_indefinite=True) | |
|
69 | if length is None: | |
|
70 | # Indefinite length | |
|
71 | while True: | |
|
72 | value = decoder.decode() | |
|
73 | if value is break_marker: | |
|
74 | break | |
|
75 | else: | |
|
76 | items.append(value) | |
|
77 | else: | |
|
78 | for _ in xrange(length): | |
|
79 | item = decoder.decode() | |
|
80 | items.append(item) | |
|
81 | ||
|
82 | return items | |
|
83 | ||
|
84 | ||
|
85 | def decode_map(decoder, subtype, shareable_index=None): | |
|
86 | # Major tag 5 | |
|
87 | dictionary = {} | |
|
88 | decoder.set_shareable(shareable_index, dictionary) | |
|
89 | length = decode_uint(decoder, subtype, allow_indefinite=True) | |
|
90 | if length is None: | |
|
91 | # Indefinite length | |
|
92 | while True: | |
|
93 | key = decoder.decode() | |
|
94 | if key is break_marker: | |
|
95 | break | |
|
96 | else: | |
|
97 | value = decoder.decode() | |
|
98 | dictionary[key] = value | |
|
99 | else: | |
|
100 | for _ in xrange(length): | |
|
101 | key = decoder.decode() | |
|
102 | value = decoder.decode() | |
|
103 | dictionary[key] = value | |
|
104 | ||
|
105 | if decoder.object_hook: | |
|
106 | return decoder.object_hook(decoder, dictionary) | |
|
107 | else: | |
|
108 | return dictionary | |
|
109 | ||
|
110 | ||
|
111 | def decode_semantic(decoder, subtype, shareable_index=None): | |
|
112 | # Major tag 6 | |
|
113 | tagnum = decode_uint(decoder, subtype) | |
|
114 | ||
|
115 | # Special handling for the "shareable" tag | |
|
116 | if tagnum == 28: | |
|
117 | shareable_index = decoder._allocate_shareable() | |
|
118 | return decoder.decode(shareable_index) | |
|
119 | ||
|
120 | value = decoder.decode() | |
|
121 | semantic_decoder = semantic_decoders.get(tagnum) | |
|
122 | if semantic_decoder: | |
|
123 | return semantic_decoder(decoder, value, shareable_index) | |
|
124 | ||
|
125 | tag = CBORTag(tagnum, value) | |
|
126 | if decoder.tag_hook: | |
|
127 | return decoder.tag_hook(decoder, tag, shareable_index) | |
|
128 | else: | |
|
129 | return tag | |
|
130 | ||
|
131 | ||
|
132 | def decode_special(decoder, subtype, shareable_index=None): | |
|
133 | # Simple value | |
|
134 | if subtype < 20: | |
|
135 | return CBORSimpleValue(subtype) | |
|
136 | ||
|
137 | # Major tag 7 | |
|
138 | return special_decoders[subtype](decoder) | |
|
139 | ||
|
140 | ||
|
141 | # | |
|
142 | # Semantic decoders (major tag 6) | |
|
143 | # | |
|
144 | ||
|
145 | def decode_datetime_string(decoder, value, shareable_index=None): | |
|
146 | # Semantic tag 0 | |
|
147 | match = timestamp_re.match(value) | |
|
148 | if match: | |
|
149 | year, month, day, hour, minute, second, micro, offset_h, offset_m = match.groups() | |
|
150 | if offset_h: | |
|
151 | tz = timezone(timedelta(hours=int(offset_h), minutes=int(offset_m))) | |
|
152 | else: | |
|
153 | tz = timezone.utc | |
|
154 | ||
|
155 | return datetime(int(year), int(month), int(day), int(hour), int(minute), int(second), | |
|
156 | int(micro or 0), tz) | |
|
157 | else: | |
|
158 | raise CBORDecodeError('invalid datetime string: {}'.format(value)) | |
|
159 | ||
|
160 | ||
|
161 | def decode_epoch_datetime(decoder, value, shareable_index=None): | |
|
162 | # Semantic tag 1 | |
|
163 | return datetime.fromtimestamp(value, timezone.utc) | |
|
164 | ||
|
165 | ||
|
166 | def decode_positive_bignum(decoder, value, shareable_index=None): | |
|
167 | # Semantic tag 2 | |
|
168 | from binascii import hexlify | |
|
169 | return int(hexlify(value), 16) | |
|
170 | ||
|
171 | ||
|
172 | def decode_negative_bignum(decoder, value, shareable_index=None): | |
|
173 | # Semantic tag 3 | |
|
174 | return -decode_positive_bignum(decoder, value) - 1 | |
|
175 | ||
|
176 | ||
|
177 | def decode_fraction(decoder, value, shareable_index=None): | |
|
178 | # Semantic tag 4 | |
|
179 | from decimal import Decimal | |
|
180 | exp = Decimal(value[0]) | |
|
181 | mantissa = Decimal(value[1]) | |
|
182 | return mantissa * (10 ** exp) | |
|
183 | ||
|
184 | ||
|
185 | def decode_bigfloat(decoder, value, shareable_index=None): | |
|
186 | # Semantic tag 5 | |
|
187 | from decimal import Decimal | |
|
188 | exp = Decimal(value[0]) | |
|
189 | mantissa = Decimal(value[1]) | |
|
190 | return mantissa * (2 ** exp) | |
|
191 | ||
|
192 | ||
|
193 | def decode_sharedref(decoder, value, shareable_index=None): | |
|
194 | # Semantic tag 29 | |
|
195 | try: | |
|
196 | shared = decoder._shareables[value] | |
|
197 | except IndexError: | |
|
198 | raise CBORDecodeError('shared reference %d not found' % value) | |
|
199 | ||
|
200 | if shared is None: | |
|
201 | raise CBORDecodeError('shared value %d has not been initialized' % value) | |
|
202 | else: | |
|
203 | return shared | |
|
204 | ||
|
205 | ||
|
206 | def decode_rational(decoder, value, shareable_index=None): | |
|
207 | # Semantic tag 30 | |
|
208 | from fractions import Fraction | |
|
209 | return Fraction(*value) | |
|
210 | ||
|
211 | ||
|
212 | def decode_regexp(decoder, value, shareable_index=None): | |
|
213 | # Semantic tag 35 | |
|
214 | return re.compile(value) | |
|
215 | ||
|
216 | ||
|
217 | def decode_mime(decoder, value, shareable_index=None): | |
|
218 | # Semantic tag 36 | |
|
219 | from email.parser import Parser | |
|
220 | return Parser().parsestr(value) | |
|
221 | ||
|
222 | ||
|
223 | def decode_uuid(decoder, value, shareable_index=None): | |
|
224 | # Semantic tag 37 | |
|
225 | from uuid import UUID | |
|
226 | return UUID(bytes=value) | |
|
227 | ||
|
228 | ||
|
229 | def decode_set(decoder, value, shareable_index=None): | |
|
230 | # Semantic tag 258 | |
|
231 | return set(value) | |
|
232 | ||
|
233 | ||
|
234 | # | |
|
235 | # Special decoders (major tag 7) | |
|
236 | # | |
|
237 | ||
|
238 | def decode_simple_value(decoder, shareable_index=None): | |
|
239 | return CBORSimpleValue(struct.unpack('>B', decoder.read(1))[0]) | |
|
240 | ||
|
241 | ||
|
242 | def decode_float16(decoder, shareable_index=None): | |
|
243 | payload = decoder.read(2) | |
|
244 | return unpack_float16(payload) | |
|
245 | ||
|
246 | ||
|
247 | def decode_float32(decoder, shareable_index=None): | |
|
248 | return struct.unpack('>f', decoder.read(4))[0] | |
|
249 | ||
|
250 | ||
|
251 | def decode_float64(decoder, shareable_index=None): | |
|
252 | return struct.unpack('>d', decoder.read(8))[0] | |
|
253 | ||
|
254 | ||
|
255 | major_decoders = { | |
|
256 | 0: decode_uint, | |
|
257 | 1: decode_negint, | |
|
258 | 2: decode_bytestring, | |
|
259 | 3: decode_string, | |
|
260 | 4: decode_array, | |
|
261 | 5: decode_map, | |
|
262 | 6: decode_semantic, | |
|
263 | 7: decode_special | |
|
264 | } | |
|
265 | ||
|
266 | special_decoders = { | |
|
267 | 20: lambda self: False, | |
|
268 | 21: lambda self: True, | |
|
269 | 22: lambda self: None, | |
|
270 | 23: lambda self: undefined, | |
|
271 | 24: decode_simple_value, | |
|
272 | 25: decode_float16, | |
|
273 | 26: decode_float32, | |
|
274 | 27: decode_float64, | |
|
275 | 31: lambda self: break_marker | |
|
276 | } | |
|
277 | ||
|
278 | semantic_decoders = { | |
|
279 | 0: decode_datetime_string, | |
|
280 | 1: decode_epoch_datetime, | |
|
281 | 2: decode_positive_bignum, | |
|
282 | 3: decode_negative_bignum, | |
|
283 | 4: decode_fraction, | |
|
284 | 5: decode_bigfloat, | |
|
285 | 29: decode_sharedref, | |
|
286 | 30: decode_rational, | |
|
287 | 35: decode_regexp, | |
|
288 | 36: decode_mime, | |
|
289 | 37: decode_uuid, | |
|
290 | 258: decode_set | |
|
291 | } | |
|
292 | ||
|
293 | ||
|
294 | class CBORDecoder(object): | |
|
295 | """ | |
|
296 | Deserializes a CBOR encoded byte stream. | |
|
297 | ||
|
298 | :param tag_hook: Callable that takes 3 arguments: the decoder instance, the | |
|
299 | :class:`~cbor2.types.CBORTag` and the shareable index for the resulting object, if any. | |
|
300 | This callback is called for any tags for which there is no built-in decoder. | |
|
301 | The return value is substituted for the CBORTag object in the deserialized output. | |
|
302 | :param object_hook: Callable that takes 2 arguments: the decoder instance and the dictionary. | |
|
303 | This callback is called for each deserialized :class:`dict` object. | |
|
304 | The return value is substituted for the dict in the deserialized output. | |
|
305 | """ | |
|
306 | ||
|
307 | __slots__ = ('fp', 'tag_hook', 'object_hook', '_shareables') | |
|
308 | ||
|
309 | def __init__(self, fp, tag_hook=None, object_hook=None): | |
|
310 | self.fp = fp | |
|
311 | self.tag_hook = tag_hook | |
|
312 | self.object_hook = object_hook | |
|
313 | self._shareables = [] | |
|
314 | ||
|
315 | def _allocate_shareable(self): | |
|
316 | self._shareables.append(None) | |
|
317 | return len(self._shareables) - 1 | |
|
318 | ||
|
319 | def set_shareable(self, index, value): | |
|
320 | """ | |
|
321 | Set the shareable value for the last encountered shared value marker, if any. | |
|
322 | ||
|
323 | If the given index is ``None``, nothing is done. | |
|
324 | ||
|
325 | :param index: the value of the ``shared_index`` argument to the decoder | |
|
326 | :param value: the shared value | |
|
327 | ||
|
328 | """ | |
|
329 | if index is not None: | |
|
330 | self._shareables[index] = value | |
|
331 | ||
|
332 | def read(self, amount): | |
|
333 | """ | |
|
334 | Read bytes from the data stream. | |
|
335 | ||
|
336 | :param int amount: the number of bytes to read | |
|
337 | ||
|
338 | """ | |
|
339 | data = self.fp.read(amount) | |
|
340 | if len(data) < amount: | |
|
341 | raise CBORDecodeError('premature end of stream (expected to read {} bytes, got {} ' | |
|
342 | 'instead)'.format(amount, len(data))) | |
|
343 | ||
|
344 | return data | |
|
345 | ||
|
346 | def decode(self, shareable_index=None): | |
|
347 | """ | |
|
348 | Decode the next value from the stream. | |
|
349 | ||
|
350 | :raises CBORDecodeError: if there is any problem decoding the stream | |
|
351 | ||
|
352 | """ | |
|
353 | try: | |
|
354 | initial_byte = byte_as_integer(self.fp.read(1)) | |
|
355 | major_type = initial_byte >> 5 | |
|
356 | subtype = initial_byte & 31 | |
|
357 | except Exception as e: | |
|
358 | raise CBORDecodeError('error reading major type at index {}: {}' | |
|
359 | .format(self.fp.tell(), e)) | |
|
360 | ||
|
361 | decoder = major_decoders[major_type] | |
|
362 | try: | |
|
363 | return decoder(self, subtype, shareable_index) | |
|
364 | except CBORDecodeError: | |
|
365 | raise | |
|
366 | except Exception as e: | |
|
367 | raise CBORDecodeError('error decoding value at index {}: {}'.format(self.fp.tell(), e)) | |
|
368 | ||
|
369 | def decode_from_bytes(self, buf): | |
|
370 | """ | |
|
371 | Wrap the given bytestring as a file and call :meth:`decode` with it as the argument. | |
|
372 | ||
|
373 | This method was intended to be used from the ``tag_hook`` hook when an object needs to be | |
|
374 | decoded separately from the rest but while still taking advantage of the shared value | |
|
375 | registry. | |
|
376 | ||
|
377 | """ | |
|
378 | old_fp = self.fp | |
|
379 | self.fp = BytesIO(buf) | |
|
380 | retval = self.decode() | |
|
381 | self.fp = old_fp | |
|
382 | return retval | |
|
383 | ||
|
384 | ||
|
385 | def loads(payload, **kwargs): | |
|
386 | """ | |
|
387 | Deserialize an object from a bytestring. | |
|
388 | ||
|
389 | :param bytes payload: the bytestring to serialize | |
|
390 | :param kwargs: keyword arguments passed to :class:`~.CBORDecoder` | |
|
391 | :return: the deserialized object | |
|
392 | ||
|
393 | """ | |
|
394 | fp = BytesIO(payload) | |
|
395 | return CBORDecoder(fp, **kwargs).decode() | |
|
396 | ||
|
397 | ||
|
398 | def load(fp, **kwargs): | |
|
399 | """ | |
|
400 | Deserialize an object from an open file. | |
|
401 | ||
|
402 | :param fp: the input file (any file-like object) | |
|
403 | :param kwargs: keyword arguments passed to :class:`~.CBORDecoder` | |
|
404 | :return: the deserialized object | |
|
405 | ||
|
406 | """ | |
|
407 | return CBORDecoder(fp, **kwargs).decode() |
@@ -0,0 +1,425 b'' | |||
|
1 | import re | |
|
2 | import struct | |
|
3 | from collections import OrderedDict, defaultdict | |
|
4 | from contextlib import contextmanager | |
|
5 | from functools import wraps | |
|
6 | from datetime import datetime, date, time | |
|
7 | from io import BytesIO | |
|
8 | ||
|
9 | from .compat import ( | |
|
10 | iteritems, timezone, long, unicode, as_unicode, bytes_from_list, pack_float16, unpack_float16) | |
|
11 | from .types import CBORTag, undefined, CBORSimpleValue | |
|
12 | ||
|
13 | ||
|
14 | class CBOREncodeError(Exception): | |
|
15 | """Raised when an error occurs while serializing an object into a CBOR datastream.""" | |
|
16 | ||
|
17 | ||
|
18 | def shareable_encoder(func): | |
|
19 | """ | |
|
20 | Wrap the given encoder function to gracefully handle cyclic data structures. | |
|
21 | ||
|
22 | If value sharing is enabled, this marks the given value shared in the datastream on the | |
|
23 | first call. If the value has already been passed to this method, a reference marker is | |
|
24 | instead written to the data stream and the wrapped function is not called. | |
|
25 | ||
|
26 | If value sharing is disabled, only infinite recursion protection is done. | |
|
27 | ||
|
28 | """ | |
|
29 | @wraps(func) | |
|
30 | def wrapper(encoder, value, *args, **kwargs): | |
|
31 | value_id = id(value) | |
|
32 | container, container_index = encoder._shared_containers.get(value_id, (None, None)) | |
|
33 | if encoder.value_sharing: | |
|
34 | if container is value: | |
|
35 | # Generate a reference to the previous index instead of encoding this again | |
|
36 | encoder.write(encode_length(0xd8, 0x1d)) | |
|
37 | encode_int(encoder, container_index) | |
|
38 | else: | |
|
39 | # Mark the container as shareable | |
|
40 | encoder._shared_containers[value_id] = (value, len(encoder._shared_containers)) | |
|
41 | encoder.write(encode_length(0xd8, 0x1c)) | |
|
42 | func(encoder, value, *args, **kwargs) | |
|
43 | else: | |
|
44 | if container is value: | |
|
45 | raise CBOREncodeError('cyclic data structure detected but value sharing is ' | |
|
46 | 'disabled') | |
|
47 | else: | |
|
48 | encoder._shared_containers[value_id] = (value, None) | |
|
49 | func(encoder, value, *args, **kwargs) | |
|
50 | del encoder._shared_containers[value_id] | |
|
51 | ||
|
52 | return wrapper | |
|
53 | ||
|
54 | ||
|
55 | def encode_length(major_tag, length): | |
|
56 | if length < 24: | |
|
57 | return struct.pack('>B', major_tag | length) | |
|
58 | elif length < 256: | |
|
59 | return struct.pack('>BB', major_tag | 24, length) | |
|
60 | elif length < 65536: | |
|
61 | return struct.pack('>BH', major_tag | 25, length) | |
|
62 | elif length < 4294967296: | |
|
63 | return struct.pack('>BL', major_tag | 26, length) | |
|
64 | else: | |
|
65 | return struct.pack('>BQ', major_tag | 27, length) | |
|
66 | ||
|
67 | ||
|
68 | def encode_int(encoder, value): | |
|
69 | # Big integers (2 ** 64 and over) | |
|
70 | if value >= 18446744073709551616 or value < -18446744073709551616: | |
|
71 | if value >= 0: | |
|
72 | major_type = 0x02 | |
|
73 | else: | |
|
74 | major_type = 0x03 | |
|
75 | value = -value - 1 | |
|
76 | ||
|
77 | values = [] | |
|
78 | while value > 0: | |
|
79 | value, remainder = divmod(value, 256) | |
|
80 | values.insert(0, remainder) | |
|
81 | ||
|
82 | payload = bytes_from_list(values) | |
|
83 | encode_semantic(encoder, CBORTag(major_type, payload)) | |
|
84 | elif value >= 0: | |
|
85 | encoder.write(encode_length(0, value)) | |
|
86 | else: | |
|
87 | encoder.write(encode_length(0x20, abs(value) - 1)) | |
|
88 | ||
|
89 | ||
|
90 | def encode_bytestring(encoder, value): | |
|
91 | encoder.write(encode_length(0x40, len(value)) + value) | |
|
92 | ||
|
93 | ||
|
94 | def encode_bytearray(encoder, value): | |
|
95 | encode_bytestring(encoder, bytes(value)) | |
|
96 | ||
|
97 | ||
|
98 | def encode_string(encoder, value): | |
|
99 | encoded = value.encode('utf-8') | |
|
100 | encoder.write(encode_length(0x60, len(encoded)) + encoded) | |
|
101 | ||
|
102 | ||
|
103 | @shareable_encoder | |
|
104 | def encode_array(encoder, value): | |
|
105 | encoder.write(encode_length(0x80, len(value))) | |
|
106 | for item in value: | |
|
107 | encoder.encode(item) | |
|
108 | ||
|
109 | ||
|
110 | @shareable_encoder | |
|
111 | def encode_map(encoder, value): | |
|
112 | encoder.write(encode_length(0xa0, len(value))) | |
|
113 | for key, val in iteritems(value): | |
|
114 | encoder.encode(key) | |
|
115 | encoder.encode(val) | |
|
116 | ||
|
117 | ||
|
118 | def encode_sortable_key(encoder, value): | |
|
119 | """Takes a key and calculates the length of its optimal byte representation""" | |
|
120 | encoded = encoder.encode_to_bytes(value) | |
|
121 | return len(encoded), encoded | |
|
122 | ||
|
123 | ||
|
124 | @shareable_encoder | |
|
125 | def encode_canonical_map(encoder, value): | |
|
126 | """Reorder keys according to Canonical CBOR specification""" | |
|
127 | keyed_keys = ((encode_sortable_key(encoder, key), key) for key in value.keys()) | |
|
128 | encoder.write(encode_length(0xa0, len(value))) | |
|
129 | for sortkey, realkey in sorted(keyed_keys): | |
|
130 | encoder.write(sortkey[1]) | |
|
131 | encoder.encode(value[realkey]) | |
|
132 | ||
|
133 | ||
|
134 | def encode_semantic(encoder, value): | |
|
135 | encoder.write(encode_length(0xc0, value.tag)) | |
|
136 | encoder.encode(value.value) | |
|
137 | ||
|
138 | ||
|
139 | # | |
|
140 | # Semantic decoders (major tag 6) | |
|
141 | # | |
|
142 | ||
|
143 | def encode_datetime(encoder, value): | |
|
144 | # Semantic tag 0 | |
|
145 | if not value.tzinfo: | |
|
146 | if encoder.timezone: | |
|
147 | value = value.replace(tzinfo=encoder.timezone) | |
|
148 | else: | |
|
149 | raise CBOREncodeError( | |
|
150 | 'naive datetime encountered and no default timezone has been set') | |
|
151 | ||
|
152 | if encoder.datetime_as_timestamp: | |
|
153 | from calendar import timegm | |
|
154 | timestamp = timegm(value.utctimetuple()) + value.microsecond // 1000000 | |
|
155 | encode_semantic(encoder, CBORTag(1, timestamp)) | |
|
156 | else: | |
|
157 | datestring = as_unicode(value.isoformat().replace('+00:00', 'Z')) | |
|
158 | encode_semantic(encoder, CBORTag(0, datestring)) | |
|
159 | ||
|
160 | ||
|
161 | def encode_date(encoder, value): | |
|
162 | value = datetime.combine(value, time()).replace(tzinfo=timezone.utc) | |
|
163 | encode_datetime(encoder, value) | |
|
164 | ||
|
165 | ||
|
166 | def encode_decimal(encoder, value): | |
|
167 | # Semantic tag 4 | |
|
168 | if value.is_nan(): | |
|
169 | encoder.write(b'\xf9\x7e\x00') | |
|
170 | elif value.is_infinite(): | |
|
171 | encoder.write(b'\xf9\x7c\x00' if value > 0 else b'\xf9\xfc\x00') | |
|
172 | else: | |
|
173 | dt = value.as_tuple() | |
|
174 | mantissa = sum(d * 10 ** i for i, d in enumerate(reversed(dt.digits))) | |
|
175 | with encoder.disable_value_sharing(): | |
|
176 | encode_semantic(encoder, CBORTag(4, [dt.exponent, mantissa])) | |
|
177 | ||
|
178 | ||
|
179 | def encode_rational(encoder, value): | |
|
180 | # Semantic tag 30 | |
|
181 | with encoder.disable_value_sharing(): | |
|
182 | encode_semantic(encoder, CBORTag(30, [value.numerator, value.denominator])) | |
|
183 | ||
|
184 | ||
|
185 | def encode_regexp(encoder, value): | |
|
186 | # Semantic tag 35 | |
|
187 | encode_semantic(encoder, CBORTag(35, as_unicode(value.pattern))) | |
|
188 | ||
|
189 | ||
|
190 | def encode_mime(encoder, value): | |
|
191 | # Semantic tag 36 | |
|
192 | encode_semantic(encoder, CBORTag(36, as_unicode(value.as_string()))) | |
|
193 | ||
|
194 | ||
|
195 | def encode_uuid(encoder, value): | |
|
196 | # Semantic tag 37 | |
|
197 | encode_semantic(encoder, CBORTag(37, value.bytes)) | |
|
198 | ||
|
199 | ||
|
200 | def encode_set(encoder, value): | |
|
201 | # Semantic tag 258 | |
|
202 | encode_semantic(encoder, CBORTag(258, tuple(value))) | |
|
203 | ||
|
204 | ||
|
205 | def encode_canonical_set(encoder, value): | |
|
206 | # Semantic tag 258 | |
|
207 | values = sorted([(encode_sortable_key(encoder, key), key) for key in value]) | |
|
208 | encode_semantic(encoder, CBORTag(258, [key[1] for key in values])) | |
|
209 | ||
|
210 | ||
|
211 | # | |
|
212 | # Special encoders (major tag 7) | |
|
213 | # | |
|
214 | ||
|
215 | def encode_simple_value(encoder, value): | |
|
216 | if value.value < 20: | |
|
217 | encoder.write(struct.pack('>B', 0xe0 | value.value)) | |
|
218 | else: | |
|
219 | encoder.write(struct.pack('>BB', 0xf8, value.value)) | |
|
220 | ||
|
221 | ||
|
222 | def encode_float(encoder, value): | |
|
223 | # Handle special values efficiently | |
|
224 | import math | |
|
225 | if math.isnan(value): | |
|
226 | encoder.write(b'\xf9\x7e\x00') | |
|
227 | elif math.isinf(value): | |
|
228 | encoder.write(b'\xf9\x7c\x00' if value > 0 else b'\xf9\xfc\x00') | |
|
229 | else: | |
|
230 | encoder.write(struct.pack('>Bd', 0xfb, value)) | |
|
231 | ||
|
232 | ||
|
233 | def encode_minimal_float(encoder, value): | |
|
234 | # Handle special values efficiently | |
|
235 | import math | |
|
236 | if math.isnan(value): | |
|
237 | encoder.write(b'\xf9\x7e\x00') | |
|
238 | elif math.isinf(value): | |
|
239 | encoder.write(b'\xf9\x7c\x00' if value > 0 else b'\xf9\xfc\x00') | |
|
240 | else: | |
|
241 | encoded = struct.pack('>Bf', 0xfa, value) | |
|
242 | if struct.unpack('>Bf', encoded)[1] != value: | |
|
243 | encoded = struct.pack('>Bd', 0xfb, value) | |
|
244 | encoder.write(encoded) | |
|
245 | else: | |
|
246 | f16 = pack_float16(value) | |
|
247 | if f16 and unpack_float16(f16[1:]) == value: | |
|
248 | encoder.write(f16) | |
|
249 | else: | |
|
250 | encoder.write(encoded) | |
|
251 | ||
|
252 | ||
|
253 | def encode_boolean(encoder, value): | |
|
254 | encoder.write(b'\xf5' if value else b'\xf4') | |
|
255 | ||
|
256 | ||
|
257 | def encode_none(encoder, value): | |
|
258 | encoder.write(b'\xf6') | |
|
259 | ||
|
260 | ||
|
261 | def encode_undefined(encoder, value): | |
|
262 | encoder.write(b'\xf7') | |
|
263 | ||
|
264 | ||
|
265 | default_encoders = OrderedDict([ | |
|
266 | (bytes, encode_bytestring), | |
|
267 | (bytearray, encode_bytearray), | |
|
268 | (unicode, encode_string), | |
|
269 | (int, encode_int), | |
|
270 | (long, encode_int), | |
|
271 | (float, encode_float), | |
|
272 | (('decimal', 'Decimal'), encode_decimal), | |
|
273 | (bool, encode_boolean), | |
|
274 | (type(None), encode_none), | |
|
275 | (tuple, encode_array), | |
|
276 | (list, encode_array), | |
|
277 | (dict, encode_map), | |
|
278 | (defaultdict, encode_map), | |
|
279 | (OrderedDict, encode_map), | |
|
280 | (type(undefined), encode_undefined), | |
|
281 | (datetime, encode_datetime), | |
|
282 | (date, encode_date), | |
|
283 | (type(re.compile('')), encode_regexp), | |
|
284 | (('fractions', 'Fraction'), encode_rational), | |
|
285 | (('email.message', 'Message'), encode_mime), | |
|
286 | (('uuid', 'UUID'), encode_uuid), | |
|
287 | (CBORSimpleValue, encode_simple_value), | |
|
288 | (CBORTag, encode_semantic), | |
|
289 | (set, encode_set), | |
|
290 | (frozenset, encode_set) | |
|
291 | ]) | |
|
292 | ||
|
293 | canonical_encoders = OrderedDict([ | |
|
294 | (float, encode_minimal_float), | |
|
295 | (dict, encode_canonical_map), | |
|
296 | (defaultdict, encode_canonical_map), | |
|
297 | (OrderedDict, encode_canonical_map), | |
|
298 | (set, encode_canonical_set), | |
|
299 | (frozenset, encode_canonical_set) | |
|
300 | ]) | |
|
301 | ||
|
302 | ||
|
303 | class CBOREncoder(object): | |
|
304 | """ | |
|
305 | Serializes objects to a byte stream using Concise Binary Object Representation. | |
|
306 | ||
|
307 | :param datetime_as_timestamp: set to ``True`` to serialize datetimes as UNIX timestamps | |
|
308 | (this makes datetimes more concise on the wire but loses the time zone information) | |
|
309 | :param datetime.tzinfo timezone: the default timezone to use for serializing naive datetimes | |
|
310 | :param value_sharing: if ``True``, allows more efficient serializing of repeated values and, | |
|
311 | more importantly, cyclic data structures, at the cost of extra line overhead | |
|
312 | :param default: a callable that is called by the encoder with three arguments | |
|
313 | (encoder, value, file object) when no suitable encoder has been found, and should use the | |
|
314 | methods on the encoder to encode any objects it wants to add to the data stream | |
|
315 | :param canonical: Forces mapping types to be output in a stable order to guarantee that the | |
|
316 | output will always produce the same hash given the same input. | |
|
317 | """ | |
|
318 | ||
|
319 | __slots__ = ('fp', 'datetime_as_timestamp', 'timezone', 'default', 'value_sharing', | |
|
320 | 'json_compatible', '_shared_containers', '_encoders') | |
|
321 | ||
|
322 | def __init__(self, fp, datetime_as_timestamp=False, timezone=None, value_sharing=False, | |
|
323 | default=None, canonical=False): | |
|
324 | self.fp = fp | |
|
325 | self.datetime_as_timestamp = datetime_as_timestamp | |
|
326 | self.timezone = timezone | |
|
327 | self.value_sharing = value_sharing | |
|
328 | self.default = default | |
|
329 | self._shared_containers = {} # indexes used for value sharing | |
|
330 | self._encoders = default_encoders.copy() | |
|
331 | if canonical: | |
|
332 | self._encoders.update(canonical_encoders) | |
|
333 | ||
|
334 | def _find_encoder(self, obj_type): | |
|
335 | from sys import modules | |
|
336 | ||
|
337 | for type_, enc in list(iteritems(self._encoders)): | |
|
338 | if type(type_) is tuple: | |
|
339 | modname, typename = type_ | |
|
340 | imported_type = getattr(modules.get(modname), typename, None) | |
|
341 | if imported_type is not None: | |
|
342 | del self._encoders[type_] | |
|
343 | self._encoders[imported_type] = enc | |
|
344 | type_ = imported_type | |
|
345 | else: # pragma: nocover | |
|
346 | continue | |
|
347 | ||
|
348 | if issubclass(obj_type, type_): | |
|
349 | self._encoders[obj_type] = enc | |
|
350 | return enc | |
|
351 | ||
|
352 | return None | |
|
353 | ||
|
354 | @contextmanager | |
|
355 | def disable_value_sharing(self): | |
|
356 | """Disable value sharing in the encoder for the duration of the context block.""" | |
|
357 | old_value_sharing = self.value_sharing | |
|
358 | self.value_sharing = False | |
|
359 | yield | |
|
360 | self.value_sharing = old_value_sharing | |
|
361 | ||
|
362 | def write(self, data): | |
|
363 | """ | |
|
364 | Write bytes to the data stream. | |
|
365 | ||
|
366 | :param data: the bytes to write | |
|
367 | ||
|
368 | """ | |
|
369 | self.fp.write(data) | |
|
370 | ||
|
371 | def encode(self, obj): | |
|
372 | """ | |
|
373 | Encode the given object using CBOR. | |
|
374 | ||
|
375 | :param obj: the object to encode | |
|
376 | ||
|
377 | """ | |
|
378 | obj_type = obj.__class__ | |
|
379 | encoder = self._encoders.get(obj_type) or self._find_encoder(obj_type) or self.default | |
|
380 | if not encoder: | |
|
381 | raise CBOREncodeError('cannot serialize type %s' % obj_type.__name__) | |
|
382 | ||
|
383 | encoder(self, obj) | |
|
384 | ||
|
385 | def encode_to_bytes(self, obj): | |
|
386 | """ | |
|
387 | Encode the given object to a byte buffer and return its value as bytes. | |
|
388 | ||
|
389 | This method was intended to be used from the ``default`` hook when an object needs to be | |
|
390 | encoded separately from the rest but while still taking advantage of the shared value | |
|
391 | registry. | |
|
392 | ||
|
393 | """ | |
|
394 | old_fp = self.fp | |
|
395 | self.fp = fp = BytesIO() | |
|
396 | self.encode(obj) | |
|
397 | self.fp = old_fp | |
|
398 | return fp.getvalue() | |
|
399 | ||
|
400 | ||
|
401 | def dumps(obj, **kwargs): | |
|
402 | """ | |
|
403 | Serialize an object to a bytestring. | |
|
404 | ||
|
405 | :param obj: the object to serialize | |
|
406 | :param kwargs: keyword arguments passed to :class:`~.CBOREncoder` | |
|
407 | :return: the serialized output | |
|
408 | :rtype: bytes | |
|
409 | ||
|
410 | """ | |
|
411 | fp = BytesIO() | |
|
412 | dump(obj, fp, **kwargs) | |
|
413 | return fp.getvalue() | |
|
414 | ||
|
415 | ||
|
416 | def dump(obj, fp, **kwargs): | |
|
417 | """ | |
|
418 | Serialize an object to a file. | |
|
419 | ||
|
420 | :param obj: the object to serialize | |
|
421 | :param fp: a file-like object | |
|
422 | :param kwargs: keyword arguments passed to :class:`~.CBOREncoder` | |
|
423 | ||
|
424 | """ | |
|
425 | CBOREncoder(fp, **kwargs).encode(obj) |
@@ -0,0 +1,55 b'' | |||
|
1 | class CBORTag(object): | |
|
2 | """ | |
|
3 | Represents a CBOR semantic tag. | |
|
4 | ||
|
5 | :param int tag: tag number | |
|
6 | :param value: encapsulated value (any object) | |
|
7 | """ | |
|
8 | ||
|
9 | __slots__ = 'tag', 'value' | |
|
10 | ||
|
11 | def __init__(self, tag, value): | |
|
12 | self.tag = tag | |
|
13 | self.value = value | |
|
14 | ||
|
15 | def __eq__(self, other): | |
|
16 | if isinstance(other, CBORTag): | |
|
17 | return self.tag == other.tag and self.value == other.value | |
|
18 | return NotImplemented | |
|
19 | ||
|
20 | def __repr__(self): | |
|
21 | return 'CBORTag({self.tag}, {self.value!r})'.format(self=self) | |
|
22 | ||
|
23 | ||
|
24 | class CBORSimpleValue(object): | |
|
25 | """ | |
|
26 | Represents a CBOR "simple value". | |
|
27 | ||
|
28 | :param int value: the value (0-255) | |
|
29 | """ | |
|
30 | ||
|
31 | __slots__ = 'value' | |
|
32 | ||
|
33 | def __init__(self, value): | |
|
34 | if value < 0 or value > 255: | |
|
35 | raise TypeError('simple value too big') | |
|
36 | self.value = value | |
|
37 | ||
|
38 | def __eq__(self, other): | |
|
39 | if isinstance(other, CBORSimpleValue): | |
|
40 | return self.value == other.value | |
|
41 | elif isinstance(other, int): | |
|
42 | return self.value == other | |
|
43 | return NotImplemented | |
|
44 | ||
|
45 | def __repr__(self): | |
|
46 | return 'CBORSimpleValue({self.value})'.format(self=self) | |
|
47 | ||
|
48 | ||
|
49 | class UndefinedType(object): | |
|
50 | __slots__ = () | |
|
51 | ||
|
52 | ||
|
53 | #: Represents the "undefined" value. | |
|
54 | undefined = UndefinedType() | |
|
55 | break_marker = object() |
General Comments 0
You need to be logged in to leave comments.
Login now