goa/db.py
branchstable
changeset 6340 470d8e828fda
parent 6339 bdc3dc94d744
child 6341 ad5e08981153
--- a/goa/db.py	Fri Sep 24 18:20:57 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,469 +0,0 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of CubicWeb.
-#
-# CubicWeb is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""provide replacement classes for gae db module, so that a gae model can be
-used as base for a cubicweb application by simply replacing ::
-
-  from google.appengine.ext import db
-
-by
-
-  from cubicweb.goa import db
-
-The db.model api should be fully featured by replacement classes, with the
-following differences:
-
-* all methods returning `google.appengine.ext.db.Model` instance(s) will return
-  `cubicweb.goa.db.Model` instance instead (though you should see almost no
-  difference since those instances have the same api)
-
-* class methods returning model instance take a `req` as first argument, unless
-  they are called through an instance, representing the current request
-  (accessible through `self.req` on almost all objects)
-
-* XXX no instance.<modelname>_set attributes, use instance.reverse_<attr name>
-      instead
-* XXX reference property always return a list of objects, not the instance
-* XXX name/collection_name argument of properties constructor are ignored
-* XXX ListProperty
-
-"""
-__docformat__ = "restructuredtext en"
-
-from copy import deepcopy
-
-from logilab.common.decorators import cached, iclassmethod
-
-from cubicweb import Binary, entities
-from cubicweb.req import RequestSessionBase
-from cubicweb.rset import ResultSet
-from cubicweb.entity import metaentity
-from cubicweb.server.utils import crypt_password
-from cubicweb.goa import MODE
-from cubicweb.goa.dbinit import init_relations
-
-from google.appengine.api.datastore import Get, Put, Key, Entity, Query
-from google.appengine.api.datastore import NormalizeAndTypeCheck, RunInTransaction
-from google.appengine.api.datastore_types import Text, Blob
-from google.appengine.api.datastore_errors import BadKeyError
-
-# XXX remove this dependancy
-from google.appengine.ext import db
-
-
-def rset_from_objs(req, objs, attrs=('eid',), rql=None, args=None):
-    """return a ResultSet instance for list of objects"""
-    if objs is None:
-        objs = ()
-    elif isinstance(objs, Entity):
-        objs = (objs,)
-    if rql is None:
-        rql = 'Any X'
-    rows = []
-    description = []
-    rset = ResultSet(rows, rql, args, description=description)
-    vreg = req.vreg
-    for i, obj in enumerate(objs):
-        line = []
-        linedescr = []
-        eschema = vreg.schema.eschema(obj.kind())
-        for j, attr in enumerate(attrs):
-            if attr == 'eid':
-                value = obj.key()
-                obj.row, obj.col = i, j
-                descr = eschema.type
-                value = str(value)
-            else:
-                value = obj[attr]
-                descr = str(eschema.destination(attr))
-            line.append(value)
-            linedescr.append(descr)
-        rows.append(line)
-        description.append(linedescr)
-        for j, attr in enumerate(attrs):
-            if attr == 'eid':
-                entity = vreg.etype_class(eschema.type)(req, rset, i, j)
-                rset._get_entity_cache_ = {(i, j): entity}
-    rset.rowcount = len(rows)
-    rset.req = req
-    return rset
-
-
-def needrequest(wrapped):
-    def wrapper(cls, *args, **kwargs):
-        req = kwargs.pop('req', None)
-        if req is None and args and isinstance(args[0], RequestSessionBase):
-            args = list(args)
-            req = args.pop(0)
-        if req is None:
-            req = getattr(cls, 'req', None)
-            if req is None:
-                raise Exception('either call this method on an instance or '
-                                'specify the req argument')
-        return wrapped(cls, req, *args, **kwargs)
-    return iclassmethod(wrapper)
-
-
-class gaedbmetaentity(metaentity):
-    """metaclass for goa.db.Model classes: filter entity / db model part,
-    put aside the db model part for later creation of db model class.
-    """
-    def __new__(mcs, name, bases, classdict):
-        if not 'id' in classdict:
-            classdict['id'] = name
-        entitycls = super(gaedbmetaentity, mcs).__new__(mcs, name, bases, classdict)
-        return entitycls
-
-
-TEST_MODELS = {}
-
-def extract_dbmodel(entitycls):
-    if MODE == 'test' and entitycls in TEST_MODELS:
-        dbclassdict = TEST_MODELS[entitycls]
-    else:
-        dbclassdict = {}
-        for attr, value in entitycls.__dict__.items():
-            if isinstance(value, db.Property) or isinstance(value, ReferencePropertyStub):
-                dbclassdict[attr] = value
-                # don't remove attr from entitycls, this make tests fail, and it's anyway
-                # overwritten by descriptor at class initialization time
-                #delattr(entitycls, attr)
-    if MODE == 'test':
-        TEST_MODELS[entitycls] = dbclassdict
-        dbclassdict = deepcopy(dbclassdict)
-        for propname, prop in TEST_MODELS[entitycls].iteritems():
-            if getattr(prop, 'reference_class', None) is db._SELF_REFERENCE:
-                dbclassdict[propname].reference_class = db._SELF_REFERENCE
-    return dbclassdict
-
-
-class Model(entities.AnyEntity):
-    id = 'Any'
-    __metaclass__ = gaedbmetaentity
-
-    row = col = 0
-
-    @classmethod
-    def __initialize__(cls):
-        super(Model, cls).__initialize__()
-        cls._attributes = frozenset(rschema for rschema in cls.e_schema.subject_relations()
-                                    if rschema.final)
-
-    def __init__(self, *args, **kwargs):
-        # db.Model prototype:
-        #   __init__(self, parent=None, key_name=None, **kw)
-        #
-        # Entity prototype:
-        #   __init__(self, req, rset, row=None, col=0)
-        if args and isinstance(args[0], RequestSessionBase) or 'req' in kwargs:
-            super(Model, self).__init__(*args, **kwargs)
-            self._gaeinitargs = None
-        else:
-            super(Model, self).__init__(None, None)
-            # if Model instances are given in kwargs, turn them into db model
-            for key, val in kwargs.iteritems():
-                if key in self.e_schema.subject_relations() and not self.e_schema.schema[key].final:
-                    if isinstance(kwargs, (list, tuple)):
-                        val = [isinstance(x, Model) and x._dbmodel or x for x in val]
-                    elif isinstance(val, Model):
-                        val = val._dbmodel
-                    kwargs[key] = val.key()
-            self._gaeinitargs = (args, kwargs)
-
-    def __repr__(self):
-        return '<ModelEntity %s %s %s at %s>' % (
-            self.e_schema, self.eid, self.keys(), id(self))
-
-    def _cubicweb_to_datastore(self, attr, value):
-        attr = attr[2:] # remove 's_' / 'o_' prefix
-        if attr in self._attributes:
-            tschema = self.e_schema.destination(attr)
-            if tschema == 'String':
-                if len(value) > 500:
-                    value = Text(value)
-            elif tschema == 'Password':
-                # if value is a Binary instance, this mean we got it
-                # from a query result and so it is already encrypted
-                if isinstance(value, Binary):
-                    value = value.getvalue()
-                else:
-                    value = crypt_password(value)
-            elif tschema == 'Bytes':
-                if isinstance(value, Binary):
-                    value = value.getvalue()
-                value = Blob(value)
-        else:
-            value = Key(value)
-        return value
-
-    def _to_gae_dict(self, convert=True):
-        gaedict = {}
-        for attr, value in self.iteritems():
-            attr = 's_' + attr
-            if value is not None and convert:
-                value = self._cubicweb_to_datastore(attr, value)
-            gaedict[attr] = value
-        return gaedict
-
-    def to_gae_model(self):
-        dbmodel = self._dbmodel
-        dbmodel.update(self._to_gae_dict())
-        return dbmodel
-
-    @property
-    @cached
-    def _dbmodel(self):
-        if self.has_eid():
-            assert self._gaeinitargs is None
-            try:
-                return self.req.datastore_get(self.eid)
-            except AttributeError: # self.req is not a server session
-                return Get(self.eid)
-        self._cw_set_defaults()
-        values = self._to_gae_dict(convert=False)
-        parent = key_name = _app = None
-        if self._gaeinitargs is not None:
-            args, kwargs = self._gaeinitargs
-            args = list(args)
-            if args:
-                parent = args.pop(0)
-            if args:
-                key_name = args.pop(0)
-            if args:
-                _app = args.pop(0)
-            assert not args
-            if 'parent' in kwargs:
-                assert parent is None
-                parent = kwargs.pop('parent')
-            if 'key_name' in kwargs:
-                assert key_name is None
-                key_name = kwargs.pop('key_name')
-            if '_app' in kwargs:
-                assert _app is None
-                _app = kwargs.pop('_app')
-
-            for key, value in kwargs.iteritems():
-                if key in self._attributes:
-                    values['s_'+key] = value
-        else:
-            kwargs = None
-        if key_name is None:
-            key_name = self.db_key_name()
-            if key_name is not None:
-                key_name = 'key_' + key_name
-        for key, value in values.iteritems():
-            if value is None:
-                continue
-            values[key] = self._cubicweb_to_datastore(key, value)
-        entity = Entity(self.id, parent, _app, key_name)
-        entity.update(values)
-        init_relations(entity, self.e_schema)
-        return entity
-
-    def db_key_name(self):
-        """override this method to control datastore key name that should be
-        used at entity creation.
-
-        Note that if this function return something else than None, the returned
-        value will be prefixed by 'key_' to build the actual key name.
-        """
-        return None
-
-    def metainformation(self):
-        return {'type': self.id, 'source': {'uri': 'system'}, 'extid': None}
-
-    def view(self, vid, __registry='views', **kwargs):
-        """shortcut to apply a view on this entity"""
-        return self.vreg[__registry].render(vid, self.req, rset=self.rset,
-                                           row=self.row, col=self.col, **kwargs)
-
-    @classmethod
-    def _rest_attr_info(cls):
-        mainattr, needcheck = super(Model, cls)._rest_attr_info()
-        if needcheck:
-            return 'eid', False
-        return mainattr, needcheck
-
-    def get_value(self, name):
-        try:
-            value = self[name]
-        except KeyError:
-            if not self.has_eid():
-                return None
-            value = self._dbmodel.get('s_'+name)
-            if value is not None:
-                if isinstance(value, Text):
-                    value = unicode(value)
-                elif isinstance(value, Blob):
-                    value = Binary(str(value))
-            self[name] = value
-        return value
-
-    def has_eid(self):
-        if self.eid is None:
-            return False
-        try:
-            Key(self.eid)
-            return True
-        except BadKeyError:
-            return False
-
-    def complete(self, skip_bytes=True):
-        pass
-
-    def unrelated(self, rtype, targettype, role='subject', limit=None,
-                  ordermethod=None):
-        # XXX dumb implementation
-        if limit is not None:
-            objs = Query(str(targettype)).Get(limit)
-        else:
-            objs = Query(str(targettype)).Run()
-        return rset_from_objs(self.req, objs, ('eid',),
-                              'Any X WHERE X is %s' % targettype)
-
-    def key(self):
-        return Key(self.eid)
-
-    def put(self, req=None):
-        if req is not None and self.req is None:
-            self.req = req
-        dbmodel = self.to_gae_model()
-        key = Put(dbmodel)
-        self.eid = str(key)
-        if self.req is not None and self.rset is None:
-            self.rset = rset_from_objs(self.req, dbmodel, ('eid',),
-                                       'Any X WHERE X eid %(x)s', {'x': self.eid})
-            self.row = self.col = 0
-        return dbmodel
-
-    @needrequest
-    def get(cls, req, keys):
-        # if check if this is a dict.key call
-        if isinstance(cls, Model) and keys in cls._attributes:
-            return super(Model, cls).get(keys)
-        rset = rset_from_objs(req, Get(keys), ('eid',),
-                              'Any X WHERE X eid IN %(x)s', {'x': keys})
-        return list(rset.entities())
-
-    @needrequest
-    def get_by_id(cls, req, ids, parent=None):
-        if isinstance(parent, Model):
-            parent = parent.key()
-        ids, multiple = NormalizeAndTypeCheck(ids, (int, long))
-        keys = [Key.from_path(cls.kind(), id, parent=parent)
-                for id in ids]
-        rset = rset_from_objs(req, Get(keys))
-        return list(rset.entities())
-
-    @classmethod
-    def get_by_key_name(cls, req, key_names, parent=None):
-        if isinstance(parent, Model):
-            parent = parent.key()
-        key_names, multiple = NormalizeAndTypeCheck(key_names, basestring)
-        keys = [Key.from_path(cls.kind(), name, parent=parent)
-                for name in key_names]
-        rset = rset_from_objs(req, Get(keys))
-        return list(rset.entities())
-
-    @classmethod
-    def get_or_insert(cls, req, key_name, **kwds):
-        def txn():
-            entity = cls.get_by_key_name(key_name, parent=kwds.get('parent'))
-            if entity is None:
-                entity = cls(key_name=key_name, **kwds)
-                entity.put()
-            return entity
-        return RunInTransaction(txn)
-
-    @classmethod
-    def all(cls, req):
-        rset = rset_from_objs(req, Query(cls.id).Run())
-        return list(rset.entities())
-
-    @classmethod
-    def gql(cls, req, query_string, *args, **kwds):
-        raise NotImplementedError('use rql')
-
-    @classmethod
-    def kind(cls):
-        return cls.id
-
-    @classmethod
-    def properties(cls):
-        raise NotImplementedError('use eschema')
-
-    def dynamic_properties(self):
-        raise NotImplementedError('use eschema')
-
-    def cw_is_saved(self):
-        return self.has_eid()
-
-    def parent(self):
-        parent = self._dbmodel.parent()
-        if not parent is None:
-            rset = rset_from_objs(self.req, (parent,), ('eid',),
-                                  'Any X WHERE X eid %(x)s', {'x': parent.key()})
-            parent = rset.get_entity(0, 0)
-        return parent
-
-    def parent_key(self):
-        return self.parent().key()
-
-    def to_xml(self):
-        return self._dbmodel.ToXml()
-
-# hijack AnyEntity class
-entities.AnyEntity = Model
-
-BooleanProperty = db.BooleanProperty
-URLProperty = db.URLProperty
-DateProperty = db.DateProperty
-DateTimeProperty = db.DateTimeProperty
-TimeProperty = db.TimeProperty
-StringProperty = db.StringProperty
-TextProperty = db.TextProperty
-BlobProperty = db.BlobProperty
-IntegerProperty = db.IntegerProperty
-FloatProperty = db.FloatProperty
-ListProperty = db.ListProperty
-SelfReferenceProperty = db.SelfReferenceProperty
-UserProperty = db.UserProperty
-
-
-class ReferencePropertyStub(object):
-    def __init__(self, cls, args, kwargs):
-        self.cls = cls
-        self.args = args
-        self.kwargs = kwargs
-        self.required = False
-        self.__dict__.update(kwargs)
-        self.creation_counter = db.Property.creation_counter
-        db.Property.creation_counter += 1
-
-    @property
-    def data_type(self):
-        class FakeDataType(object):
-            @staticmethod
-            def kind():
-                return self.cls.__name__
-        return FakeDataType
-
-def ReferenceProperty(cls, *args, **kwargs):
-    if issubclass(cls, db.Model):
-        cls = db.class_for_kind(cls.__name__)
-        return db.ReferenceProperty(cls, *args, **kwargs)
-    return ReferencePropertyStub(cls, args, kwargs)