From 82017411beb12717780d036f7308086d8fe37ef5 Mon Sep 17 00:00:00 2001 From: Sam Smith Date: Mon, 9 Jul 2018 16:00:46 +0100 Subject: [PATCH] Add custom JSON encoder/decoder option to `Document` constructor --- CHANGES.md | 1 + src/cloudant/document.py | 9 +++++--- tests/unit/document_tests.py | 44 ++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1ca11aa0..3c3e925a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,6 @@ # Unreleased +- [NEW] Add custom JSON encoder/decoder option to `Document` constructor. - [NEW] Add new view parameters, `stable` and `update`, as keyword arguments to `get_view_result`. - [FIXED] Case where an exception was raised after successful retry when using `doc.update_field`. diff --git a/src/cloudant/document.py b/src/cloudant/document.py index 768ecca5..7feda18a 100644 --- a/src/cloudant/document.py +++ b/src/cloudant/document.py @@ -53,8 +53,10 @@ class Document(dict): :param database: A database instance used by the Document. Can be either a ``CouchDatabase`` or ``CloudantDatabase`` instance. :param str document_id: Optional document id used to identify the document. + :param str encoder: Optional JSON encoder object. + :param str decoder: Optional JSON decoder object. """ - def __init__(self, database, document_id=None): + def __init__(self, database, document_id=None, **kwargs): super(Document, self).__init__() self._client = database.client self._database = database @@ -63,7 +65,8 @@ def __init__(self, database, document_id=None): self._document_id = document_id if self._document_id is not None: self['_id'] = self._document_id - self.encoder = self._client.encoder + self.encoder = kwargs.get('encoder') or self._client.encoder + self.decoder = kwargs.get('decoder') or json.JSONDecoder @property def r_session(self): @@ -165,7 +168,7 @@ def fetch(self): resp = self.r_session.get(self.document_url) resp.raise_for_status() self.clear() - self.update(resp.json()) + self.update(resp.json(cls=self.decoder)) def save(self): """ diff --git a/tests/unit/document_tests.py b/tests/unit/document_tests.py index dd443ccf..3c7b5afc 100644 --- a/tests/unit/document_tests.py +++ b/tests/unit/document_tests.py @@ -30,6 +30,8 @@ import uuid import inspect +from datetime import datetime + from cloudant.document import Document from cloudant.error import CloudantDocumentException @@ -859,5 +861,47 @@ def test_document_request_fails_after_client_disconnects(self): finally: self.client.connect() + def test_document_custom_json_encoder_and_decoder(self): + dt_format = '%Y-%m-%dT%H:%M:%S' + + class DTEncoder(json.JSONEncoder): + + def default(self, obj): + if isinstance(obj, datetime): + return { + '_type': 'datetime', + 'value': obj.strftime(dt_format) + } + return super(DTEncoder, self).default(obj) + + class DTDecoder(json.JSONDecoder): + + def __init__(self, *args, **kwargs): + json.JSONDecoder.__init__(self, object_hook=self.object_hook, + *args, **kwargs) + + def object_hook(self, obj): + if '_type' not in obj: + return obj + if obj['_type'] == 'datetime': + return datetime.strptime(obj['value'], dt_format) + return obj + + doc = Document(self.db, encoder=DTEncoder) + doc['name'] = 'julia' + doc['dt'] = datetime(2018, 7, 9, 15, 11, 10, 0) + doc.save() + + raw_doc = self.db.all_docs(include_docs=True)['rows'][0]['doc'] + + self.assertEquals(raw_doc['name'], 'julia') + self.assertEquals(raw_doc['dt']['_type'], 'datetime') + self.assertEquals(raw_doc['dt']['value'], '2018-07-09T15:11:10') + + doc2 = Document(self.db, doc['_id'], decoder=DTDecoder) + doc2.fetch() + + self.assertEquals(doc2['dt'], doc['dt']) + if __name__ == '__main__': unittest.main()