Skip to content

Commit

Permalink
Update to v3 serialization
Browse files Browse the repository at this point in the history
* v3 is lighter and now fully supported on the server side
* deserialization is backwards compatible with v2

[sc-74126]
  • Loading branch information
sharoonthomas committed May 7, 2024
1 parent 368565b commit 709d0f9
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 78 deletions.
167 changes: 89 additions & 78 deletions fulfil_client/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
from decimal import Decimal
from collections import namedtuple
from functools import partial
import isodate

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


CONTENT_TYPE = 'application/vnd.fulfil.v2+json'
CONTENT_TYPE = "application/vnd.fulfil.v3+json"


class JSONDecoder(object):
Expand All @@ -23,69 +25,90 @@ def register(cls, klass, decoder):
cls.decoders[klass] = decoder

def __call__(self, dct):
if dct.get('__class__') in self.decoders:
return self.decoders[dct['__class__']](dct)
if dct.get("__class__") in self.decoders:
return self.decoders[dct["__class__"]](dct)
return dct


JSONDecoder.register(
'datetime',
lambda dct: datetime.datetime(
dct['year'], dct['month'], dct['day'],
dct['hour'], dct['minute'], dct['second'], dct['microsecond']
def register_decoder(klass):
def decorator(decoder):
assert klass not in JSONDecoder.decoders
JSONDecoder.decoders[klass] = decoder

return decorator


@register_decoder("datetime")
def datetime_decoder(v):
if v.get("iso_string"):
return isodate.parse_datetime(v["iso_string"])
return datetime.datetime(
v["year"],
v["month"],
v["day"],
v["hour"],
v["minute"],
v["second"],
v["microsecond"],
)
)
JSONDecoder.register(
'date',
lambda dct: datetime.date(dct['year'], dct['month'], dct['day'])
)
JSONDecoder.register(
'time',
lambda dct: datetime.time(
dct['hour'], dct['minute'], dct['second'], dct['microsecond']


@register_decoder("date")
def date_decoder(v):
if v.get("iso_string"):
return isodate.parse_date(v["iso_string"])
return datetime.date(
v["year"],
v["month"],
v["day"],
)
)
JSONDecoder.register(
'timedelta',
lambda dct: datetime.timedelta(seconds=dct['seconds'])
)


def _bytes_decoder(dct):
@register_decoder("time")
def time_decoder(v):
if v.get("iso_string"):
return isodate.parse_time(v["iso_string"])
return datetime.time(v["hour"], v["minute"], v["second"], v["microsecond"])


@register_decoder("timedelta")
def timedelta_decoder(v):
if v.get("iso_string"):
return isodate.parse_duration(v["iso_string"])
return datetime.timedelta(seconds=v["seconds"])


@register_decoder("bytes")
def _bytes_decoder(v):
cast = bytearray if bytes == str else bytes
return cast(base64.b64decode(dct['base64'].encode('utf-8')))
return cast(base64.decodebytes(v["base64"].encode("utf-8")))


JSONDecoder.register('bytes', _bytes_decoder)
JSONDecoder.register(
'Decimal', lambda dct: Decimal(dct['decimal'])
)
@register_decoder("Decimal")
def decimal_decoder(v):
return Decimal(v["decimal"])


dummy_record = namedtuple('Record', ['model_name', 'id', 'rec_name'])
JSONDecoder.register(
'Model', lambda dct: dummy_record(
dct['model_name'], dct['id'], dct.get('rec_name')
)
)
dummy_record = namedtuple("Record", ["model_name", "id", "rec_name"])


@register_decoder("Model")
def model_decoder(dct):
return dummy_record(dct["model_name"], dct["id"], dct.get("rec_name"))


@register_decoder("AsyncResult")
def parse_async_result(dct):
from .client import AsyncResult
return AsyncResult(dct['task_id'], dct['token'], None)


JSONDecoder.register(
'AsyncResult', parse_async_result
)
return AsyncResult(dct["task_id"], dct["token"], None)


class JSONEncoder(json.JSONEncoder):

serializers = {}

def __init__(self, *args, **kwargs):
super(JSONEncoder, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
# Force to use our custom decimal with simplejson
self.use_decimal = False

Expand All @@ -95,63 +118,51 @@ def register(cls, klass, encoder):
cls.serializers[klass] = encoder

def default(self, obj):
marshaller = self.serializers.get(
type(obj),
super(JSONEncoder, self).default
)
marshaller = self.serializers.get(type(obj), super().default)
return marshaller(obj)


JSONEncoder.register(
datetime.datetime,
lambda o: {
'__class__': 'datetime',
'year': o.year,
'month': o.month,
'day': o.day,
'hour': o.hour,
'minute': o.minute,
'second': o.second,
'microsecond': o.microsecond,
'iso_string': o.isoformat(),
})
"__class__": "datetime",
"iso_string": o.isoformat(),
},
)
JSONEncoder.register(
datetime.date,
lambda o: {
'__class__': 'date',
'year': o.year,
'month': o.month,
'day': o.day,
'iso_string': o.isoformat(),
})
"__class__": "date",
"iso_string": o.isoformat(),
},
)
JSONEncoder.register(
datetime.time,
lambda o: {
'__class__': 'time',
'hour': o.hour,
'minute': o.minute,
'second': o.second,
'microsecond': o.microsecond,
})
"__class__": "time",
"iso_string": o.isoformat(),
},
)
JSONEncoder.register(
datetime.timedelta,
lambda o: {
'__class__': 'timedelta',
'seconds': o.total_seconds(),
})
_bytes_encoder = lambda o: {
'__class__': 'bytes',
'base64': base64.encodestring(o).decode('utf-8'),
}
"__class__": "timedelta",
"iso_string": isodate.duration_isoformat(o),
},
)
_bytes_encoder = lambda o: { # noqa
"__class__": "bytes",
"base64": base64.b64encode(o).decode("utf-8"),
}
JSONEncoder.register(bytes, _bytes_encoder)
JSONEncoder.register(bytearray, _bytes_encoder)
JSONEncoder.register(
Decimal,
lambda o: {
'__class__': 'Decimal',
'decimal': str(o),
})

"__class__": "Decimal",
"decimal": str(o),
},
)


dumps = partial(json.dumps, cls=JSONEncoder)
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
'babel',
'six',
'more-itertools',
'isodate',
]


Expand Down

0 comments on commit 709d0f9

Please sign in to comment.