Skip to content

Commit

Permalink
JSON: Encoder now handles datetimes, django Promise, UUID and Decimal.
Browse files Browse the repository at this point in the history
  • Loading branch information
ask committed Jul 8, 2016
1 parent 7ccec0b commit 55452a1
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 10 deletions.
43 changes: 43 additions & 0 deletions kombu/tests/utils/test_json.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
from __future__ import absolute_import, unicode_literals

import pytz

from datetime import datetime
from decimal import Decimal
from uuid import uuid4

from kombu.five import text_t
from kombu.utils.encoding import str_to_bytes
from kombu.utils.json import _DecodeError, dumps, loads

Expand All @@ -15,6 +22,42 @@ def __json__(self):
return self.data


class test_JSONEncoder(Case):

def test_datetime(self):
now = datetime.utcnow()
now_utc = now.replace(tzinfo=pytz.utc)
stripped = datetime(*now.timetuple()[:3])
serialized = loads(dumps({
'datetime': now,
'tz': now_utc,
'date': now.date(),
'time': now.time()},
))
self.assertDictEqual(serialized, {
'datetime': now.isoformat(),
'tz': '{0}Z'.format(now_utc.isoformat().split('+', 1)[0]),
'time': now.time().isoformat(),
'date': stripped.isoformat(),
})

def test_Decimal(self):
d = Decimal('3314132.13363235235324234123213213214134')
self.assertDictEqual(loads(dumps({'d': d})), {
'd': text_t(d),
})

def test_UUID(self):
id = uuid4()
self.assertDictEqual(loads(dumps({'u': id})), {
'u': text_t(id),
})

def test_default(self):
with self.assertRaises(TypeError):
dumps({'o': object()})


class test_dumps_loads(Case):

def test_dumps_custom_object(self):
Expand Down
48 changes: 38 additions & 10 deletions kombu/utils/json.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals

import datetime
import decimal
import json as stdjson
import sys
import uuid

from kombu.five import buffer_t, text_t, bytes_t

try:
import simplejson as json
from django.utils.functional import Promise as DjangoPromise
except ImportError: # pragma: no cover
import json # noqa
class DjangoPromise(object): # noqa
pass

try:
import simplejson as json
_json_extra_kwargs = {'use_decimal': False}
except ImportError: # pragma: no cover
import json # noqa
_json_extra_kwargs = {} # noqa

class _DecodeError(Exception): # noqa
pass
Expand All @@ -23,17 +34,34 @@ class _DecodeError(Exception): # noqa

class JSONEncoder(_encoder_cls):

def default(self, obj, _super=_encoder_cls.default):
try:
reducer = obj.__json__
except AttributeError:
return _super(self, obj)
else:
def default(self, o,
dates=(datetime.datetime, datetime.date),
times=(datetime.time,),
textual=(decimal.Decimal, uuid.UUID, DjangoPromise),
isinstance=isinstance,
datetime=datetime.datetime,
text_type=text_t):
reducer = getattr(o, '__json__', None)
if reducer is not None:
return reducer()
else:
if isinstance(o, dates):
if not isinstance(o, datetime):
o = datetime(o.year, o.month, o.day, 0, 0, 0, 0)
r = o.isoformat()
if r.endswith("+00:00"):
r = r[:-6] + "Z"
return r
elif isinstance(o, times):
return o.isoformat()
elif isinstance(o, textual):
return text_type(o)
return super(JSONEncoder, self).default(o)


def dumps(s, _dumps=json.dumps, cls=JSONEncoder):
return _dumps(s, cls=cls)
def dumps(s, _dumps=json.dumps, cls=JSONEncoder,
default_kwargs=_json_extra_kwargs, **kwargs):
return _dumps(s, cls=cls, **dict(default_kwargs, **kwargs))


def loads(s, _loads=json.loads, decode_bytes=IS_PY3):
Expand Down
1 change: 1 addition & 0 deletions requirements/test.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pytz>dev
case>=1.2.2

1 comment on commit 55452a1

@penoux
Copy link

@penoux penoux commented on 55452a1 Jul 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the prompt fix!
I suppose it will be on 4.0.0 only?

Please sign in to comment.