summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Gottberg <13184659+el-chogo@users.noreply.github.com>2022-07-17 17:09:40 +0000
committerGitHub <noreply@github.com>2022-07-17 23:09:40 +0600
commit10a673b17c3774adc52876699c6a2ee3780513ba (patch)
tree6723385bc7942ec2770f2b6d9561e2183a93456b
parent4440c649e0497e74791f65474d684595542ec36a (diff)
downloadkombu-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.rst10
-rw-r--r--kombu/utils/json.py6
-rw-r--r--t/unit/utils/test_json.py15
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):