| 1 |
"""JSON conversion support for Dejavu Units.""" |
|---|
| 2 |
|
|---|
| 3 |
import datetime |
|---|
| 4 |
import decimal |
|---|
| 5 |
import time |
|---|
| 6 |
|
|---|
| 7 |
import dejavu |
|---|
| 8 |
from simplejson import JSONEncoder, JSONDecoder |
|---|
| 9 |
|
|---|
| 10 |
__all__ = ["Encoder", "Decoder", "Converter"] |
|---|
| 11 |
|
|---|
| 12 |
|
|---|
| 13 |
class Encoder(JSONEncoder): |
|---|
| 14 |
"""Extends the base simplejson JSONEncoder for date and decimal types.""" |
|---|
| 15 |
|
|---|
| 16 |
def push_date(self, d): |
|---|
| 17 |
"""Serialize the given datetime.date object to a JSON string.""" |
|---|
| 18 |
|
|---|
| 19 |
return "%04d-%02d-%02d" % (d.year, d.month, d.day) |
|---|
| 20 |
|
|---|
| 21 |
def push_time(self, t): |
|---|
| 22 |
"""Serialize the given datetime.time object to a JSON string.""" |
|---|
| 23 |
|
|---|
| 24 |
return "%02d:%02d:%02d" % (t.hour, t.minute, t.second) |
|---|
| 25 |
|
|---|
| 26 |
def push_datetime(self, dt): |
|---|
| 27 |
"""Serialize the given datetime.datetime object to a JSON string.""" |
|---|
| 28 |
|
|---|
| 29 |
|
|---|
| 30 |
return ("%04d-%02d-%02d %02d:%02d:%02d" % |
|---|
| 31 |
(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)) |
|---|
| 32 |
|
|---|
| 33 |
def default(self, o): |
|---|
| 34 |
|
|---|
| 35 |
|
|---|
| 36 |
|
|---|
| 37 |
if isinstance(o, datetime.datetime): |
|---|
| 38 |
return {'__datetime__': True, 'value': self.push_datetime(o)} |
|---|
| 39 |
elif isinstance(o, datetime.date): |
|---|
| 40 |
return {'__date__': True, 'value': self.push_date(o)} |
|---|
| 41 |
elif isinstance(o, datetime.time): |
|---|
| 42 |
return {'__time__': True, 'value': self.push_time(o)} |
|---|
| 43 |
elif isinstance(o, decimal.Decimal): |
|---|
| 44 |
return {'__decimal__': True, 'value': unicode(o)} |
|---|
| 45 |
else: |
|---|
| 46 |
return JSONEncoder.default(self, o) |
|---|
| 47 |
|
|---|
| 48 |
|
|---|
| 49 |
class Decoder(JSONDecoder): |
|---|
| 50 |
"""Extends the base simplejson JSONDecoder for Dejavu.""" |
|---|
| 51 |
|
|---|
| 52 |
def __init__(self, encoding=None, object_hook=None): |
|---|
| 53 |
if object_hook is None: |
|---|
| 54 |
object_hook = self.json_to_python |
|---|
| 55 |
JSONDecoder.__init__(self, encoding, object_hook) |
|---|
| 56 |
|
|---|
| 57 |
def pull_date(self, value): |
|---|
| 58 |
"""Return a datetime.date object from the given JSON string.""" |
|---|
| 59 |
chunks = (value[0:4], value[5:7], value[8:10]) |
|---|
| 60 |
return datetime.date(*map(int, chunks)) |
|---|
| 61 |
|
|---|
| 62 |
def pull_time(self, value): |
|---|
| 63 |
"""Return a datetime.time object from the given JSON string.""" |
|---|
| 64 |
chunks = (value[0:2], value[3:5], value[6:8]) |
|---|
| 65 |
return datetime.time(*map(int, chunks)) |
|---|
| 66 |
|
|---|
| 67 |
def pull_datetime(self, value): |
|---|
| 68 |
"""Return a datetime.datetime object from the given JSON string.""" |
|---|
| 69 |
chunks = (value[0:4], value[5:7], value[8:10], |
|---|
| 70 |
value[11:13], value[14:16], value[17:19], |
|---|
| 71 |
value[20:26] or 0) |
|---|
| 72 |
return datetime.datetime(*map(int, chunks)) |
|---|
| 73 |
|
|---|
| 74 |
def json_to_python(self, d): |
|---|
| 75 |
if '__datetime__' in d: |
|---|
| 76 |
return self.pull_datetime(d['value']) |
|---|
| 77 |
if '__date__' in d: |
|---|
| 78 |
return self.pull_date(d['value']) |
|---|
| 79 |
if '__time__' in d: |
|---|
| 80 |
return self.pull_time(d['value']) |
|---|
| 81 |
if '__decimal__' in d: |
|---|
| 82 |
return decimal.Decimal(d['value']) |
|---|
| 83 |
return d |
|---|
| 84 |
|
|---|
| 85 |
|
|---|
| 86 |
class Converter(object): |
|---|
| 87 |
"""Provides two-way conversion of Units/JSON via loads and dumps methods. |
|---|
| 88 |
|
|---|
| 89 |
Also converts datetime.date, datetime.time, datetime.datetime and |
|---|
| 90 |
decimal.Decimal to/from JSON. |
|---|
| 91 |
|
|---|
| 92 |
This is accomplished by the Encoder and Decoder classes, which are |
|---|
| 93 |
subclasses of their counterparts in simplejson. If you wish to change |
|---|
| 94 |
the output of the converter at all, you should probably subclass the |
|---|
| 95 |
Encoder/Decoder and then make a cusom Converter subclass with your |
|---|
| 96 |
encoder/decoder as class attributes. |
|---|
| 97 |
""" |
|---|
| 98 |
|
|---|
| 99 |
encoder = Encoder |
|---|
| 100 |
decoder = Decoder |
|---|
| 101 |
|
|---|
| 102 |
def loads(self, s, encoding=None, **kw): |
|---|
| 103 |
return self.decoder(encoding=encoding, **kw).decode(s) |
|---|
| 104 |
|
|---|
| 105 |
def dumps(self, obj, skipkeys=False, ensure_ascii=False, |
|---|
| 106 |
check_circular=True, allow_nan=True, indent=None, **kw): |
|---|
| 107 |
return self.encoder(skipkeys, ensure_ascii, check_circular, |
|---|
| 108 |
allow_nan, indent, **kw).encode(obj) |
|---|
| 109 |
|
|---|
| 110 |
|
|---|
| 111 |
def unit_to_dict(unit): |
|---|
| 112 |
"""Return a JSON-able dict for the given Dejavu Unit instance.""" |
|---|
| 113 |
d = unit._properties.copy() |
|---|
| 114 |
d['__dejavu.class__'] = unit.__class__.__name__ |
|---|
| 115 |
return d |
|---|
| 116 |
|
|---|
| 117 |
|
|---|
| 118 |
def dict_to_unit(d, store=None, cls=None): |
|---|
| 119 |
"""Return a Unit instance from a dict of UnitProperty values.""" |
|---|
| 120 |
if cls is None: |
|---|
| 121 |
clsname = d['__dejavu.class__'] |
|---|
| 122 |
cls = store.class_by_name(clsname) |
|---|
| 123 |
|
|---|
| 124 |
unit = cls() |
|---|
| 125 |
|
|---|
| 126 |
|
|---|
| 127 |
params = {} |
|---|
| 128 |
for k in cls.properties: |
|---|
| 129 |
v = d.get(k, None) |
|---|
| 130 |
|
|---|
| 131 |
if v is not None and getattr(cls, k).type is tuple: |
|---|
| 132 |
v = tuple(v) |
|---|
| 133 |
params[k] = v |
|---|
| 134 |
unit._properties = params |
|---|
| 135 |
unit.cleanse() |
|---|
| 136 |
return unit |
|---|