diff --git a/session_redis/json_encoding.py b/session_redis/json_encoding.py new file mode 100644 index 0000000..f535a8f --- /dev/null +++ b/session_redis/json_encoding.py @@ -0,0 +1,43 @@ +# Copyright 2016-2020 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +import json + +from datetime import date, datetime + +import dateutil + + +class SessionEncoder(json.JSONEncoder): + """Encode date/datetime objects + + So that we can later recompose them if they were stored in the session + """ + + def default(self, obj): + if isinstance(obj, datetime): + return {"_type": "datetime_isoformat", "value": obj.isoformat()} + elif isinstance(obj, date): + return {"_type": "date_isoformat", "value": obj.isoformat()} + elif isinstance(obj, set): + return {"_type": "set", "value": tuple(obj)} + return json.JSONEncoder.default(self, obj) + + +class SessionDecoder(json.JSONDecoder): + """Decode json, recomposing recordsets and date/datetime""" + + def __init__(self, *args, **kwargs): + super().__init__(object_hook=self.object_hook, *args, **kwargs) + + def object_hook(self, obj): + if "_type" not in obj: + return obj + type_ = obj["_type"] + if type_ == "datetime_isoformat": + return dateutil.parser.parse(obj["value"]) + elif type_ == "date_isoformat": + return dateutil.parser.parse(obj["value"]).date() + elif type_ == "set": + return set(obj["value"]) + return obj diff --git a/session_redis/session.py b/session_redis/session.py index 8b9e5af..0d73b0a 100644 --- a/session_redis/session.py +++ b/session_redis/session.py @@ -6,6 +6,8 @@ import logging from werkzeug.contrib.sessions import SessionStore +from . import json_encoding + # this is equal to the duration of the session garbage collector in # odoo.http.session_gc() DEFAULT_SESSION_TIMEOUT = 60 * 60 * 24 * 7 # 7 days in seconds @@ -57,7 +59,9 @@ class RedisSessionStore(SessionStore): "expiration of %s seconds for %s", key, expiration, user_msg) - data = json.dumps(dict(session)).encode('utf-8') + data = json.dumps( + dict(session), cls=json_encoding.SessionEncoder + ).encode('utf-8') if self.redis.set(key, data): return self.redis.expire(key, expiration) @@ -79,7 +83,9 @@ class RedisSessionStore(SessionStore): "returning a new one", key) return self.new() try: - data = json.loads(saved.decode('utf-8')) + data = json.loads( + saved.decode('utf-8'), cls=json_encoding.SessionDecoder + ) except ValueError: _logger.debug("session for key '%s' has been asked but its json " "content could not be read, it has been reset", key)