Skip to content
This repository has been archived by the owner on Jun 5, 2023. It is now read-only.

json_parser: support both naive and aware datetime, date and time types #73

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Those requirements are currently:
itty==0.6.4
redis==1.34.1
pystache==0.2.0
python-dateutil==1.5

If you'd rather install from the git repository, that's easy too::

Expand Down
70 changes: 33 additions & 37 deletions pyres/json_parser.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,46 @@
from datetime import datetime
from datetime import datetime, date, time
from dateutil.parser import parse

try:
#import simplejson as json
import json
except ImportError:
import simplejson as json

DATE_FORMAT = '%Y-%m-%dT%H:%M:%S'
DATE_PREFIX = '@D:'
SUPPORTED_TYPES = {datetime, date, time}
assert len(SUPPORTED_TYPES) == len({c.__name__ for c in SUPPORTED_TYPES})
SUPPORTED_TYPES_NAME2CLASS = {c.__name__: c for c in SUPPORTED_TYPES}

class CustomJSONEncoder(json.JSONEncoder):

def default(self, o):
if isinstance(o, datetime):
return o.strftime(DATE_PREFIX + DATE_FORMAT)
return json.JSONEncoder.default(self, o)
def default(self, obj):
type_ = type(obj)
if type_ in SUPPORTED_TYPES:
if type_ in {datetime, date, time}:
return {'__type__': type_.__name__, '__value__': obj.isoformat()}
return json.JSONEncoder.default(self, obj)


class CustomJSONDecoder(json.JSONDecoder):
def __init__(self, **kw):
json.JSONDecoder.__init__(self, object_hook=self.dict_to_object, **kw)

def dict_to_object(self, d):
type_ = SUPPORTED_TYPES_NAME2CLASS.get(d.get('__type__'))
if type_ in SUPPORTED_TYPES:
if type_ in {datetime, date, time}:
dt = parse(d.get('__value__'))
if type_ is datetime:
return dt
elif type_ is date:
return dt.date()
else:
return dt.timetz()
return d


def dumps(obj):
return json.dumps(obj, cls=CustomJSONEncoder)


def decode(self, json_string):
decoded = json.loads(json_string)
return self.convert(decoded)

def convert(self, value):
if isinstance(value, basestring) and value.startswith(DATE_PREFIX):
try:
return datetime.strptime(value[len(DATE_PREFIX):], DATE_FORMAT)
except ValueError:
return value
elif isinstance(value, dict):
for k, v in value.iteritems():
new = self.convert(v)
if new != v:
value[k] = new
elif isinstance(value, list):
for k, v in enumerate(value):
new = self.convert(v)
if new != v:
value[k] = new
return value


def dumps(values):
return json.dumps(values, cls=CustomJSONEncoder)


def loads(string):
return json.loads(string, cls=CustomJSONDecoder)
def loads(s):
return json.loads(s, cls=CustomJSONDecoder)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ itty==0.6.2
redis>=1.34.1
pystache==0.1.0
setproctitle>=1.0
python-dateutil==1.5 (2.0 is for python 3.x)
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
'itty>=0.6.2',
'redis>=1.34.1',
'pystache>=0.1.0',
'setproctitle>=1.0'
'setproctitle>=1.0',
'python-dateutil==1.5'
],
classifiers = [
'Development Status :: 4 - Beta',
Expand Down
52 changes: 36 additions & 16 deletions tests/test_json.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,44 @@
from datetime import datetime
from dateutil.tz import tzutc
from tests import PyResTests
import pyres.json_parser as json

class JSONTests(PyResTests):
def test_encode_decode_date(self):
dt = datetime(1972, 1, 22);
encoded = json.dumps({'dt': dt})
decoded = json.loads(encoded)
assert decoded['dt'] == dt
def test(self):
naive_now = datetime.now()
aware_now = datetime.now(tzutc())
for now in [naive_now, aware_now]:
for obj in [now, now.date(), now.timetz(), now.time()]:
encoded = json.dumps(obj)
decoded = json.loads(encoded)
assert obj == decoded

def test_dates_in_lists(self):
dates = [datetime.now() for i in range(50)]
decoded = json.loads(json.dumps(dates))
for value in dates:
assert isinstance(value, datetime)
def test_in_list(self):
naive_now = datetime.now()
aware_now = datetime.now(tzutc())
for now in [naive_now, aware_now]:
for obj in [now, now.date(), now.timetz(), now.time()]:
to_encode = [1, obj]
encoded = json.dumps(to_encode)
decoded = json.loads(encoded)
assert to_encode == decoded

def test_dates_in_dict(self):
dates = dict((i, datetime.now()) for i in range(50))
decoded = json.loads(json.dumps(dates))
for i, value in dates.items():
assert isinstance(i, int)
assert isinstance(value, datetime)
def test_in_dict(self):
naive_now = datetime.now()
aware_now = datetime.now(tzutc())
for now in [naive_now, aware_now]:
for obj in [now, now.date(), now.timetz(), now.time()]:
to_encode = dict(a=1, b=obj)
encoded = json.dumps(to_encode)
decoded = json.loads(encoded)
assert to_encode == decoded

def test_complex(self):
naive_now = datetime.now()
aware_now = datetime.now(tzutc())
for now in [naive_now, aware_now]:
for obj in [now, now.date(), now.timetz(), now.time()]:
to_encode = dict(a=1, b=obj, c=dict(c1=2, c2=obj), d=[3, obj])
encoded = json.dumps(to_encode)
decoded = json.loads(encoded)
assert to_encode == decoded