Source code for couchbasekit.document

#! /usr/bin/env python
"""
couchbasekit.document
~~~~~~~~~~~~~~~~~~~~~

:website: http://github.com/kirpit/couchbasekit
:copyright: Copyright 2013, Roy Enjoy <kirpit *at* gmail.com>, see AUTHORS.txt.
:license: MIT, see LICENSE.txt for details.
"""
import datetime
import hashlib
import jsonpickle
from dateutil.tz import tzutc
from couchbase.exception import MemcachedError
from couchbasekit import Connection
from couchbasekit.schema import SchemaDocument
from couchbasekit.errors import DoesNotExist
from couchbasekit.fields import CustomField


[docs]class Document(SchemaDocument): """Couchbase document to be inherited by user-defined model documents that handles everything from validation to comparison with the help of :class:`couchbasekit.schema.SchemaDocument` parent class. :param key_or_map: Either the document id to be fetched or dictionary to initialize the first values of a new document. :type key_or_map: basestring or dict :param get_lock: True, if the document wanted to be locked for other processes, defaults to False. :type get_lock: bool :param kwargs: key=value arguments to be passed to the dictionary. :raises: :exc:`couchbasekit.errors.StructureError` or :exc:`couchbasekit.errors.DoesNotExist` """ DoesNotExist = DoesNotExist __bucket_name__ = None __view_name__ = None _hashed_key = None _view_design_doc = None _view_cache = None full_set = False cas_value = None def __init__(self, key_or_map=None, get_lock=False, **kwargs): # check document schema first super(Document, self).__init__(key_or_map, **kwargs) # fetch document by key? if isinstance(key_or_map, basestring): if self.__key_field__: self[self.__key_field__] = key_or_map else: # must be a hashed key then self._hashed_key = key_or_map # the document must be found if a key is given if self._fetch_data(get_lock) is False: raise self.DoesNotExist(self) def __eq__(self, other): if type(other) is type(self) and \ other.cas_value==self.cas_value and \ other.keys()==self.keys() and \ all([other[k]==self[k] for k in self.keys()]): return True return False def __ne__(self, other): return not self.__eq__(other) def __getattr__(self, item): if item in self: return self[item] elif item in self.structure: return None # raise AttributeError eventually: return super(Document, self).__getattribute__(item) def __setattr__(self, key, value): if key in self.structure: self[unicode(key)] = value else: super(Document, self).__setattr__(key, value) @property
[docs] def id(self): """Returns the document's key field value (sort of primary key if you defined it in your model, which is optional), object property. :returns: The document key if :attr:`__key_field__` was defined, or None. :rtype: unicode or None """ return self.get(self.__key_field__) if self.__key_field__ else None
@property
[docs] def doc_id(self): """Returns the couchbase document's id, object property. :returns: The document id (that is created from :attr:`doc_type` and :attr:`__key_field__` value, or auto-hashed document id at first saving). :rtype: unicode """ if self.id: return '%s_%s' % (self.doc_type, self.id.lower()) return self._hashed_key
@property
[docs] def bucket(self): """Returns the couchbase Bucket object for this instance, object property. :returns: See: :class:`couchbase.client.Bucket`. :rtype: :class:`couchbase.client.Bucket` """ return Connection.bucket(self.__bucket_name__)
[docs] def view(self, view_name=None): """Returns a couchbase view (or design document view with no view_name provided) if :func:`couchbasekit.viewsync.register_view` decorator was applied to model class. :param view_name: If provided returns the asked couchbase view object or design document otherwise. :type view_name: str :returns: couchbase design document, couchbase view or None :rtype: :class:`couchbase.client.View` or :class:`couchbase.client.DesignDoc` or None """ if self.__view_name__ is None: return None # cache the design document if self._view_design_doc is None: self._view_design_doc = self.bucket['_design/%s' % self.__view_name__] # return the design doc if view_name is None: return self._view_design_doc # cache the views if not self._view_cache: # patch is not necessary if not self._view_design_doc.name.startswith('dev_') or not self.full_set: self._view_cache = self._view_design_doc.views() # patch'em all else: def results(instance, params={}): if 'full_set' not in params: params['full_set'] = True return instance._results(params) self._view_cache = list() from couchbase.client import View for view in self._view_design_doc.views(): func_type = type(view.results) view._results = view.results view.results = func_type(results, view, View) self._view_cache.append(view) return next(iter([v for v in self._view_cache if v.name==view_name]), None)
def _fetch_data(self, get_lock=False): try: if get_lock is True: status, self.cas_value, data = self.bucket.getl(self.doc_id) else: status, self.cas_value, data = self.bucket.get(self.doc_id) except MemcachedError as why: # raise if other than "not found" if why.status!=1: raise why else: # found within couchbase self.is_new_record = False data = jsonpickle.decode(data) self.update(data) # return is_fetched in other words: return not self.is_new_record def _encode_item(self, value): # Document instance if isinstance(value, Document): if value.doc_id is None: raise self.StructureError( msg="Trying to relate an unsaved " "document; '%s'" % type(value).__name__ ) return value.doc_id # CustomField instance elif isinstance(value, CustomField): return value.value # datetime types elif isinstance(value, (datetime.datetime, datetime.date, datetime.time)): if hasattr(value, 'tzinfo') and value.tzinfo is None: # always timezone "aware" datetime and time value = value.replace(tzinfo=tzutc()) pickler = jsonpickle.Pickler(unpicklable=False) return pickler.flatten(value) # list elif isinstance(value, list): return [self._encode_item(v) for v in value] # dictionary, pass it to dict encoder elif isinstance(value, dict): return self._encode_dict(value) # no need to encode return value def _encode_dict(self, mapping): data = dict() for key, value in mapping.iteritems(): # None values will be stripped out if value is None: continue # Document instance key issue if isinstance(key, Document): # document instances are not hashable! # should raise an error here pass key = self._encode_item(key) data[key] = self._encode_item(value) return data
[docs] def save(self, expiration=0): """Saves the current instance after validating it. :param expiration: Expiration in seconds for the document to be removed by couchbase server, defaults to 0 - will never expire. :type expiration: int :returns: couchbase document CAS value :rtype: int :raises: :exc:`couchbasekit.errors.StructureError`, See :meth:`couchbasekit.schema.SchemaDocument.validate`. """ # set the default values first for key, value in self.default_values.iteritems(): if callable(value): value = value() self.setdefault(key, value) # validate self[u'doc_type'] = unicode(self.doc_type) self.validate() # json safe data json_safe = self._encode_dict(self) json_data = jsonpickle.encode(json_safe, unpicklable=False) # still no document id? create one.. if self.doc_id is None: self._hashed_key = hashlib.sha1(json_data).hexdigest()[0:12] # finally self.cas_value = self.bucket.set(self.doc_id, expiration, 0, json_data)[1] return self.cas_value
[docs] def delete(self): """Deletes the current document explicitly with CAS value. :returns: Response from CouchbaseClient. :rtype: unicode :raises: :exc:`couchbasekit.errors.DoesNotExist` or :exc:`couchbase.exception.MemcachedError` """ if not self.cas_value or not self.doc_id: raise self.DoesNotExist(self) return self.bucket.delete(self.doc_id, self.cas_value)
[docs] def touch(self, expiration): """Updates the current document's expiration value. :param expiration: Expiration in seconds for the document to be removed by couchbase server, defaults to 0 - will never expire. :type expiration: int :returns: Response from CouchbaseClient. :rtype: unicode :raises: :exc:`couchbasekit.errors.DoesNotExist` or :exc:`couchbase.exception.MemcachedError` """ if not self.cas_value or not self.doc_id: raise self.DoesNotExist(self) return self.bucket.touch(self.doc_id, expiration)

Project Versions

This Page