Skip to content

Commit f035453

Browse files
committed
allow user override of reserved msgpack ext types
1 parent 54d296f commit f035453

File tree

3 files changed

+57
-28
lines changed

3 files changed

+57
-28
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ If a non-byte-string argument is passed to `umsgpack.unpackb()`, it will raise a
400400
* Python tuples and lists are both packed into the msgpack array format
401401
* Python float types are packed into the msgpack float32 or float64 format depending on the system's `sys.float_info`
402402
* The Python `datetime.datetime` type is packed into, and unpacked from, the msgpack `timestamp` format
403-
* Note that this Python type only supports microsecond resolution, while the msgpack `timestamp` format supports nanosecond resolution. Timestamps with finer than microsecond resolution will lose precision during unpacking.
403+
* Note that this Python type only supports microsecond resolution, while the msgpack `timestamp` format supports nanosecond resolution. Timestamps with finer than microsecond resolution will lose precision during unpacking. Users may override the packing and unpacking of the msgpack `timestamp` format with a custom type for alternate behavior.
404404

405405
## Testing
406406

test_umsgpack.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,22 @@
334334
b"\xd7\x30\x93\xc4\x03\x61\x62\x63\x7b\xc3"],
335335
]
336336

337+
override_ext_handlers = {
338+
datetime.datetime:
339+
lambda obj: umsgpack.Ext(0x40, obj.strftime("%Y%m%dT%H:%M:%S.%f").encode()),
340+
-0x01:
341+
lambda ext: ext,
342+
}
343+
344+
override_ext_handlers_test_vectors = [
345+
["pack override",
346+
datetime.datetime(2000, 1, 1, 10, 5, 2, 0, umsgpack._utc_tzinfo),
347+
b'\xc7\x18@20000101T10:05:02.000000'],
348+
["unpack override",
349+
umsgpack.Ext(-0x01, b"\x00\xbb\xcc\xdd\x01\x02\x03\x04\x05\x06\x07\x08"),
350+
b'\xc7\x0c\xff\x00\xbb\xcc\xdd\x01\x02\x03\x04\x05\x06\x07\x08'],
351+
]
352+
337353
# These are the only global variables that should be exported by umsgpack
338354
exported_vars_test_vector = [
339355
"Ext",
@@ -492,10 +508,7 @@ def test_unpack_ordered_dict(self):
492508

493509
def test_ext_exceptions(self):
494510
with self.assertRaises(TypeError):
495-
_ = umsgpack.Ext(-1, b"")
496-
497-
with self.assertRaises(TypeError):
498-
_ = umsgpack.Ext(128, b"")
511+
_ = umsgpack.Ext(5.0, b"")
499512

500513
with self.assertRaises(TypeError):
501514
_ = umsgpack.Ext(0, u"unicode string")
@@ -527,6 +540,26 @@ def test_pack_force_float_precision(self):
527540
packed = umsgpack.packb(obj, force_float_precision=precision)
528541
self.assertEqual(packed, data)
529542

543+
def test_pack_ext_override(self):
544+
# Test overridden packing of datetime.datetime
545+
(name, obj, data) = override_ext_handlers_test_vectors[0]
546+
obj_repr = repr(obj)
547+
print("\tTesting %s: object %s" %
548+
(name, obj_repr if len(obj_repr) < 24 else obj_repr[0:24] + "..."))
549+
550+
packed = umsgpack.packb(obj, ext_handlers=override_ext_handlers)
551+
self.assertEqual(packed, data)
552+
553+
def test_unpack_ext_override(self):
554+
# Test overridden unpacking of Ext type -1
555+
(name, obj, data) = override_ext_handlers_test_vectors[1]
556+
obj_repr = repr(obj)
557+
print("\tTesting %s: object %s" %
558+
(name, obj_repr if len(obj_repr) < 24 else obj_repr[0:24] + "..."))
559+
560+
unpacked = umsgpack.unpackb(data, ext_handlers=override_ext_handlers)
561+
self.assertEqual(unpacked, obj)
562+
530563
def test_streaming_writer(self):
531564
# Try first composite test vector
532565
(_, obj, data) = composite_test_vectors[0]

umsgpack.py

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,9 @@ def __init__(self, type, data):
7272
Construct a new Ext object.
7373
7474
Args:
75-
type: application-defined type integer from 0 to 127
75+
type: application-defined type integer
7676
data: application-defined data byte array
7777
78-
Raises:
79-
TypeError:
80-
Specified ext type is outside of 0 to 127 range.
81-
8278
Example:
8379
>>> foo = umsgpack.Ext(0x05, b"\x01\x02\x03")
8480
>>> umsgpack.packb({u"special stuff": foo, u"awesome": True})
@@ -88,9 +84,9 @@ def __init__(self, type, data):
8884
Ext Object (Type: 0x05, Data: 01 02 03)
8985
>>>
9086
"""
91-
# Application ext type should be 0 <= type <= 127
92-
if not isinstance(type, int) or not (type >= 0 and type <= 127):
93-
raise TypeError("ext type out of range")
87+
# Check type is type int
88+
if not isinstance(type, int):
89+
raise TypeError("ext type is not type integer")
9490
# Check data is type bytes
9591
elif sys.version_info[0] == 3 and not isinstance(data, bytes):
9692
raise TypeError("ext data is not type \'bytes\'")
@@ -739,38 +735,38 @@ def _unpack_ext(code, fp, options):
739735
ext_type = struct.unpack("b", _read_except(fp, 1))[0]
740736
ext_data = _read_except(fp, length)
741737

742-
# Timestamp extension
743-
if ext_type == -1:
744-
return _unpack_ext_timestamp(code, ext_data, options)
745-
746-
# Application extension
738+
# Create extension object
747739
ext = Ext(ext_type, ext_data)
748740

749741
# Unpack with ext handler, if we have one
750742
ext_handlers = options.get("ext_handlers")
751743
if ext_handlers and ext.type in ext_handlers:
752-
ext = ext_handlers[ext.type](ext)
744+
return ext_handlers[ext.type](ext)
745+
746+
# Timestamp extension
747+
if ext.type == -1:
748+
return _unpack_ext_timestamp(ext, options)
753749

754750
return ext
755751

756752

757-
def _unpack_ext_timestamp(code, data, options):
758-
if len(data) == 4:
753+
def _unpack_ext_timestamp(ext, options):
754+
if len(ext.data) == 4:
759755
# 32-bit timestamp
760-
seconds = struct.unpack(">I", data)[0]
756+
seconds = struct.unpack(">I", ext.data)[0]
761757
microseconds = 0
762-
elif len(data) == 8:
758+
elif len(ext.data) == 8:
763759
# 64-bit timestamp
764-
value = struct.unpack(">Q", data)[0]
760+
value = struct.unpack(">Q", ext.data)[0]
765761
seconds = value & 0x3ffffffff
766762
microseconds = (value >> 34) // 1000
767-
elif len(data) == 12:
763+
elif len(ext.data) == 12:
768764
# 96-bit timestamp
769-
seconds = struct.unpack(">q", data[4:12])[0]
770-
microseconds = struct.unpack(">I", data[0:4])[0] // 1000
765+
seconds = struct.unpack(">q", ext.data[4:12])[0]
766+
microseconds = struct.unpack(">I", ext.data[0:4])[0] // 1000
771767
else:
772768
raise UnsupportedTimestampException(
773-
"unsupported timestamp with data length %d" % len(data))
769+
"unsupported timestamp with data length %d" % len(ext.data))
774770

775771
return _epoch + datetime.timedelta(seconds=seconds,
776772
microseconds=microseconds)

0 commit comments

Comments
 (0)