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