Source code for pouchdb.objectstorage
#	Copyright 2014, Marten de Vries
#
#	Licensed under the Apache License, Version 2.0 (the "License");
#	you may not use this file except in compliance with the License.
#	You may obtain a copy of the License at
#
#	http://www.apache.org/licenses/LICENSE-2.0
#
#	Unless required by applicable law or agreed to in writing, software
#	distributed under the License is distributed on an "AS IS" BASIS,
#	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#	See the License for the specific language governing permissions and
#	limitations under the License.
"""The objectstorage module allows you to save ordinary Python objects
directly into PouchDB. The (de)serializing is handled by the
`jsonpickle module <https://jsonpickle.github.io/>`_.
*******
Example
*******
>>> from pouchdb.objectstorage import load, store
>>> from jsonpickle._samples import Thing
>>> 
>>> env = setup()
>>> db = env.PouchDB("objectstorage_test")
>>> thingy = Thing("abc")
>>> 
>>> store(thingy, db, "the_id")
Thing("abc")
>>> new_thingy = load(db, "the_id")
>>> print new_thingy.name
abc
>>> 
>>> env.destroy("objectstorage_test")
"""
try:
	import jsonpickle.pickler
	import jsonpickle.unpickler
except ImportError: #pragma: no cover
	raise ImportError("pouchdb.objectstorage requires the jsonpickle module to be installed.")
import collections
import pouchdb.info
_KEY_PREFIX = pouchdb.info.name.lower() + "-"
[docs]def store(obj, db, id=None, rev=None, prefix=None):
	"""Stores `obj` into `db`.
	:param obj: Any Python object that ``jsonpickle`` can handle. (i.e.
		most objects.)
	:param pouchdb.SyncPouchDB db: A database as returned by
		``pouchdb.setup().PouchDB(db_name)``. This **can't** be a
		database as generated by the asynchronous API.
	:param str id: Used as the ``_id`` field in PouchDB. If this is
		``None`` and ``obj.id`` exists, that is used. The fallback is a
		UUID.
	:param str rev: Used as the ``_rev`` field in PouchDB. If this is
		``None`` and ``obj.rev`` exists, that is used. Otherwise a new
		document is assumed (with no ``_rev`` yet).
	:param str prefix: a serialization as returned by ``jsonpickle`` can
		have a field starting with '_' in a dictionary. That clashes
		with PouchDB, which can't. To work around that, every key is
		prefixed with this argument **if the situation occurs**. When
		no prefix is specified it'll be: '``%s``'.
	:returns: `obj` -- the same object as the parameter on which
		``obj.id`` and ``obj.rev`` will have been set.
	"""
	if not prefix:
		prefix = _KEY_PREFIX
	if hasattr(obj, "id"):
		id = id or obj.id
		del obj.id
	if hasattr(obj, "rev"):
		rev = rev or obj.rev
		del obj.rev
	doc = jsonpickle.pickler.Pickler().flatten(obj)
	if any(k.startswith("_") for k in doc):
		doc = {prefix + k:v for k, v in doc.iteritems()}
	if id:
		doc["_id"] = id
	if rev:
		doc["_rev"] = rev
	resp = db.post(doc)
	obj.id = resp.id
	obj.rev = resp.rev
	return obj
 
store.__doc__ = store.__doc__ % _KEY_PREFIX
[docs]def load(db, id, rev=None, prefix=None):
	"""Loads an earlier under `id` stored object from `db`.
	:param str id: The ``_id`` to look for in the database.
	:param str rev: The ``_rev`` to look for in the database. When not
		specified the latest revision is used.
	:param str prefix: see :func:`store`'s parameter with the same name.
		Should equal the one used to store the object, otherwise the
		result is **undefined**.
	:returns: the earlier stored object.
	"""
	if not prefix:
		prefix = _KEY_PREFIX
	doc = db.get(id, rev=rev)
	data = {}
	for key, value in doc.iteritems():
		if key in ["_id", "_rev"]:
			continue
		if key.startswith(prefix):
			key = key[len(prefix):]
		data[key] = _normalizeDicts(value)
	obj = jsonpickle.unpickler.Unpickler().restore(data)
	obj.id = doc["_id"]
	obj.rev = doc["_rev"]
	return obj
 
def _normalizeDicts(obj):
	if isinstance(obj, collections.Mapping):
		return {k:_normalizeDicts(v) for k, v in obj.iteritems()}
	if isinstance(obj, collections.MutableSequence):
		return [_normalizeDicts(x) for x in obj]
	return obj