Skip to content

Commit

Permalink
Move serialization to a middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
msiemens committed Apr 7, 2015
1 parent a63628b commit c36f25c
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 77 deletions.
11 changes: 8 additions & 3 deletions docs/extend.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,17 @@ for ``datetime`` objects could look like:
def decode(self, s):
return datetime.strptime(s, '%Y-%m-%dT%H:%M:%S')
We can use this Serializer like this:
To use the new serializer, we need to use the serialization middleware:

.. code-block:: python
>>> db = TinyDB('db.json')
>>> db.register_serializer(DateTimeSerializer(), 'TinyDate')
>>> from tinydb.storages import JSONStorage
>>> from tinydb.middlewares import SerializationMiddleware
>>>
>>> serialization = SerializationMiddleware()
>>> serialization.register_serializer(DateTimeSerializer(), 'TinyDate')
>>>
>>> db = TinyDB('db.json', storage=serialization)
>>> db.insert({'date': datetime(2000, 1, 1, 12, 0, 0)})
>>> db.all()
[{'date': datetime.datetime(2000, 1, 1, 12, 0)}]
Expand Down
78 changes: 6 additions & 72 deletions tinydb/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class TinyDB(object):
and getting tables.
"""

DEFAULT_STORAGE = JSONStorage

def __init__(self, *args, **kwargs):
"""
Create a new instance of TinyDB.
Expand All @@ -40,7 +42,7 @@ def __init__(self, *args, **kwargs):
:param storage: The class of the storage to use. Will be initialized
with ``args`` and ``kwargs``.
"""
storage = kwargs.pop('storage', JSONStorage)
storage = kwargs.pop('storage', TinyDB.DEFAULT_STORAGE)
#: :type: Storage
self._storage = storage(*args, **kwargs)
self._serializers = {}
Expand Down Expand Up @@ -102,24 +104,6 @@ def purge_tables(self):
self._write({})
self._table_cache.clear()

def register_serializer(self, serializer, name):
"""
Register a new Serializer.
When reading from/writing to the underlying storage, TinyDB
will run all objects through the list of registered serializers
allowing each one to handle objects it recognizes.
.. note:: The name has to be unique among this database instance.
Re-using the same name will overwrite the old serializer.
Also, registering a serializer will be reflected in all
tables when reading/writing them.
:param serializer: an instance of the serializer
:type serializer: tinydb.serialize.Serializer
"""
self._serializers[name] = serializer

def _read(self):
"""
Reading access to the backend.
Expand All @@ -142,32 +126,10 @@ def _read_table(self, table):
"""

try:
return self._deserialize(self._read()[table])
return self._read()[table]
except (KeyError, TypeError):
return {}

def _deserialize(self, data):
"""
Deserialize the data passed in with all registered serializers
:param data: the data set to deserialize
:return: the data set with deserialized values as needed
"""
for serializer_name in self._serializers:
serializer = self._serializers[serializer_name]
tag = '{{{}}}:'.format(serializer_name) # E.g: '{TinyDate}:'

for eid in data:
for field in data[eid]:
try:
if data[eid][field].startswith(tag):
encoded = data[eid][field][len(tag):]
data[eid][field] = serializer.decode(encoded)
except AttributeError:
pass # Not a string

return data

def _write(self, tables):
"""
Writing access to the backend.
Expand All @@ -193,36 +155,8 @@ def _write_table(self, values, table):
data = self._read()
data[table] = values

# Finally, serialize & store the data
self._write(self._serialize(data))

def _serialize(self, data):
"""
Serialize the data passed in with all registered serializers
:param data: the data set to serialize
:return: the data set with serialized values as needed
"""

for serializer_name in self._serializers:
# If no serializers are registered, this code will just look up
# the serializer list and continue. But if there are serializers,
# the inner loop will run very often.
# For that reason, the lookup of the serialized class is pulled
# out into the outer loop:

serializer = self._serializers[serializer_name]
serializer_class = serializer.OBJ_CLASS

for eid in data:
for field in data[eid]:
if isinstance(data[eid][field], serializer_class):
encoded = serializer.encode(data[eid][field])
tagged = '{{{}}}:{}'.format(serializer_name, encoded)

data[eid][field] = tagged

return data
# Finally, store the data
self._write(data)

# Methods that are executed on the default table
# Because magic methods are not handlet by __getattr__ we need to forward
Expand Down
78 changes: 76 additions & 2 deletions tinydb/middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
Contains the :class:`base class <tinydb.middlewares.Middleware>` for
middlewares and implementations.
"""
from tinydb import TinyDB
from tinydb.storages import JSONStorage


class Middleware(object):
Expand All @@ -17,7 +19,7 @@ class Middleware(object):
example).
"""

def __init__(self, storage_cls):
def __init__(self, storage_cls=TinyDB.DEFAULT_STORAGE):
self._storage_cls = storage_cls
self.storage = None

Expand Down Expand Up @@ -84,7 +86,7 @@ class CachingMiddleware(Middleware):
#: The number of write operations to cache before writing to disc
WRITE_CACHE_SIZE = 1000

def __init__(self, storage_cls):
def __init__(self, storage_cls=TinyDB.DEFAULT_STORAGE):
super(CachingMiddleware, self).__init__(storage_cls)

self.cache = None
Expand Down Expand Up @@ -116,3 +118,75 @@ def flush(self):
def close(self):
self.flush()
self.storage.close()


class SerializationMiddleware(Middleware):
"""
Provide custom serialization for TinyDB.
This middleware allows users of TinyDB to register custom serializations.
The serialized data will be passed to the wrapped storage and data that
is read from the storage will be deserialized.
"""

def __init__(self, storage_cls=TinyDB.DEFAULT_STORAGE):
super(SerializationMiddleware, self).__init__(storage_cls)

self._serializers = {}

def register_serializer(self, serializer, name):
"""
Register a new Serializer.
When reading from/writing to the underlying storage, TinyDB
will run all objects through the list of registered serializers
allowing each one to handle objects it recognizes.
.. note:: The name has to be unique among this database instance.
Re-using the same name will overwrite the old serializer.
Also, registering a serializer will be reflected in all
tables when reading/writing them.
:param serializer: an instance of the serializer
:type serializer: tinydb.serialize.Serializer
"""
self._serializers[name] = serializer

def read(self):
data = self.storage.read()

for serializer_name in self._serializers:
serializer = self._serializers[serializer_name]
tag = '{{{}}}:'.format(serializer_name) # E.g: '{TinyDate}:'

for eid in data:
for field in data[eid]:
try:
if data[eid][field].startswith(tag):
encoded = data[eid][field][len(tag):]
data[eid][field] = serializer.decode(encoded)
except AttributeError:
pass # Not a string

return data

def write(self, data):
for serializer_name in self._serializers:
# If no serializers are registered, this code will just look up
# the serializer list and continue. But if there are serializers,
# the inner loop will run very often.
# For that reason, the lookup of the serialized class is pulled
# out into the outer loop:

serializer = self._serializers[serializer_name]
serializer_class = serializer.OBJ_CLASS

for eid in data:
for field in data[eid]:
if isinstance(data[eid][field], serializer_class):
encoded = serializer.encode(data[eid][field])
tagged = '{{{}}}:{}'.format(serializer_name, encoded)

data[eid][field] = tagged

self.storage.write(data)

0 comments on commit c36f25c

Please sign in to comment.