diff options
author | Carlos Gottberg <13184659+el-chogo@users.noreply.github.com> | 2022-07-17 17:09:40 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-17 23:09:40 +0600 |
commit | 10a673b17c3774adc52876699c6a2ee3780513ba (patch) | |
tree | 6723385bc7942ec2770f2b6d9561e2183a93456b | |
parent | 4440c649e0497e74791f65474d684595542ec36a (diff) | |
download | kombu-10a673b17c3774adc52876699c6a2ee3780513ba.tar.gz |
Avoid losing type of UUID when serializing/deserializing (#1575)
* Avoid losing type of UUID when serializing/deserializing
Serializing UUIDs as strs and deserializing them as strs can lead to
somewhat obscure bugs such as the one that led to the creation of this
PR in django-cacheback
https://github.com/codeinthehole/django-cacheback/pull/100 which some
would call unexpected behaviour.
After all, an UUID is altogether a different type from strs and if
bytes got their own serializer/deserializer for UUID the same logic
should apply.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Update documentation for JSON serialization
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
-rw-r--r-- | docs/userguide/serialization.rst | 10 | ||||
-rw-r--r-- | kombu/utils/json.py | 6 | ||||
-rw-r--r-- | t/unit/utils/test_json.py | 15 |
3 files changed, 25 insertions, 6 deletions
diff --git a/docs/userguide/serialization.rst b/docs/userguide/serialization.rst index f5cb8125..86eb04bb 100644 --- a/docs/userguide/serialization.rst +++ b/docs/userguide/serialization.rst @@ -38,11 +38,17 @@ Each option has its advantages and disadvantages. The primary disadvantage to `JSON` is that it limits you to the following data types: strings, Unicode, floats, boolean, - dictionaries, and lists. Decimals and dates are notably missing. + dictionaries, lists, decimals, DjangoPromise, datetimes, dates, + time, bytes and UUIDs. + + For dates, datetimes, UUIDs and bytes the serializer will generate + a dict that will later instruct the deserializer how to produce + the right type. Also, binary data will be transferred using Base64 encoding, which will cause the transferred data to be around 34% larger than an - encoding which supports native binary types. + encoding which supports native binary types. This will only happen + if the bytes object can't be decoded into utf8. However, if your data fits inside the above constraints and you need cross-language support, the default setting of `JSON` diff --git a/kombu/utils/json.py b/kombu/utils/json.py index 26583d61..a5a020c1 100644 --- a/kombu/utils/json.py +++ b/kombu/utils/json.py @@ -29,7 +29,7 @@ class JSONEncoder(_encoder_cls): def default(self, o, dates=(datetime.datetime, datetime.date), times=(datetime.time,), - textual=(decimal.Decimal, uuid.UUID, DjangoPromise), + textual=(decimal.Decimal, DjangoPromise), isinstance=isinstance, datetime=datetime.datetime, text_t=str): @@ -44,6 +44,8 @@ class JSONEncoder(_encoder_cls): return {"datetime": r, "__datetime__": True} elif isinstance(o, times): return o.isoformat() + elif isinstance(o, uuid.UUID): + return {"uuid": str(o), "__uuid__": True, "version": o.version} elif isinstance(o, textual): return text_t(o) elif isinstance(o, bytes): @@ -75,6 +77,8 @@ def object_hook(dct): return dct["bytes"].encode("utf-8") if "__base64__" in dct: return base64.b64decode(dct["bytes"].encode("utf-8")) + if "__uuid__" in dct: + return uuid.UUID(dct["uuid"], version=dct["version"]) return dct diff --git a/t/unit/utils/test_json.py b/t/unit/utils/test_json.py index 54d559e4..0f1f02b2 100644 --- a/t/unit/utils/test_json.py +++ b/t/unit/utils/test_json.py @@ -1,10 +1,10 @@ from __future__ import annotations +import uuid from collections import namedtuple from datetime import datetime from decimal import Decimal from unittest.mock import MagicMock, Mock -from uuid import uuid4 import pytest import pytz @@ -61,8 +61,17 @@ class test_JSONEncoder: assert loads(dumps(Foo(123))) == [123] def test_UUID(self): - id = uuid4() - assert loads(dumps({'u': id})), {'u': str(id)} + constructors = [ + uuid.uuid1, + lambda: uuid.uuid3(uuid.NAMESPACE_URL, "https://example.org"), + uuid.uuid4, + lambda: uuid.uuid5(uuid.NAMESPACE_URL, "https://example.org"), + ] + for constructor in constructors: + id = constructor() + loaded_value = loads(dumps({'u': id})) + assert loaded_value, {'u': id} + assert loaded_value["u"].version == id.version def test_default(self): with pytest.raises(TypeError): |