Skip to content

Commit 4ad8177

Browse files
committed
Add support for custom serializers
See #48 for rationale
1 parent 8977d5a commit 4ad8177

File tree

4 files changed

+92
-4
lines changed

4 files changed

+92
-4
lines changed

docs/extend.rst

+32
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,38 @@
11
How to Extend TinyDB
22
====================
33

4+
Write a Serializer
5+
------------------
6+
7+
TinyDB's default storage is fairly limited when it comes to supported data types.
8+
If you need more flexibility, you can implement a Serializer. This allows TinyDB
9+
to handle classes it otherwise couldn't serialize. Let's see how a Serializer
10+
for ``datetime`` objects could look like:
11+
12+
.. code-block:: python
13+
14+
from datetime import datetime
15+
16+
class DateTimeSerializer(Serializer):
17+
NAME = 'TinyDate' # A unique name
18+
OBJ_CLASS = datetime # The class this serializer handles
19+
20+
def encode(self, obj):
21+
return obj.strftime('%Y-%m-%dT%H:%M:%S')
22+
23+
def decode(self, s):
24+
return datetime.strptime(s, '%Y-%m-%dT%H:%M:%S')
25+
26+
We can use this Serializer like this:
27+
28+
.. code-block:: python
29+
30+
>>> db = TinyDB('db.json')
31+
>>> db.register_serializer(DateTimeSerializer())
32+
>>> db.insert({'date': datetime(2000, 1, 1, 12, 0, 0)})
33+
>>> db.all()
34+
[{'date': datetime.datetime(2000, 1, 1, 12, 0)}]
35+
436
Write a custom Storage
537
----------------------
638

docs/usage.rst

+3-2
Original file line numberDiff line numberDiff line change
@@ -427,8 +427,9 @@ What's next
427427
Congratulations, you've made through the user guide! Now go and build something
428428
awesome or dive deeper into TinyDB with these ressources:
429429

430-
- Want to learn how to customize TinyDB and what extensions exist?
431-
Check out :doc:`extend`.
430+
- Want to learn how to customize TinyDB (custom serializers, storages,
431+
middlewares) and what extensions exist? Check out :doc:`extend` and
432+
:doc:`extensions`.
432433
- Want to study the API in detail? Read :doc:`api`.
433434
- Interested in contributing to the TinyDB development guide? Go on to the
434435
:doc:`contribute`.

tinydb/database.py

+41-2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def __init__(self, *args, **kwargs):
4343
storage = kwargs.pop('storage', JSONStorage)
4444
#: :type: Storage
4545
self._storage = storage(*args, **kwargs)
46+
self._serializers = []
4647

4748
self._table_cache = {}
4849
self._table = self.table('_default')
@@ -101,6 +102,19 @@ def purge_tables(self):
101102
self._write({})
102103
self._table_cache.clear()
103104

105+
def register_serializer(self, serializer):
106+
"""
107+
Register a new Serializer.
108+
109+
When reading from/writing to the underlying storage, TinyDB
110+
will run all objects through the list of registered serializers
111+
allowing each one to handle objects it recognizes.
112+
113+
:param serializer: an instance of the serializer
114+
:type serializer: tinydb.serialize.Serializer
115+
"""
116+
self._serializers.append(serializer)
117+
104118
def _read(self):
105119
"""
106120
Reading access to the backend.
@@ -123,7 +137,22 @@ def _read_table(self, table):
123137
"""
124138

125139
try:
126-
return self._read()[table]
140+
data = self._read()[table]
141+
142+
# Run deserialization
143+
for serializer in self._serializers:
144+
tag = '{{{}}}:'.format(serializer.NAME)
145+
146+
for eid in data:
147+
for field in data[eid]:
148+
try:
149+
if data[eid][field].startswith(tag):
150+
encoded = data[eid][field][len(tag):]
151+
data[eid][field] = serializer.decode(encoded)
152+
except AttributeError:
153+
pass # Not a string
154+
155+
return data
127156
except (KeyError, TypeError):
128157
return {}
129158

@@ -152,8 +181,18 @@ def _write_table(self, values, table):
152181
data = self._read()
153182
data[table] = values
154183

184+
# Run serialization
185+
for serializer in self._serializers:
186+
for eid in data:
187+
for field in data[eid]:
188+
if isinstance(data[eid][field], serializer.OBJ_CLASS):
189+
data[eid][field] = '{{{}}}:{}'.format(
190+
serializer.NAME,
191+
serializer.encode(data[eid][field])
192+
)
193+
194+
# Finally, store the data
155195
self._write(data)
156-
return
157196

158197
# Methods that are executed on the default table
159198
# Because magic methods are not handlet by __getattr__ we need to forward

tinydb/utils.py

+16
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,19 @@ def catch_warning(warning_cls):
103103
warnings.filterwarnings('error', category=warning_cls)
104104

105105
yield
106+
107+
108+
class AbstractAttribute(object):
109+
"""An abstract class attribute.
110+
111+
Use this instead of an abstract property when you don't expect the
112+
attribute to be implemented by a property.
113+
"""
114+
115+
__isabstractmethod__ = True
116+
117+
def __init__(self, doc=""):
118+
self.__doc__ = doc
119+
120+
def __get__(self, obj, cls):
121+
return self

0 commit comments

Comments
 (0)