goa/db.py
branchtls-sprint
changeset 1802 d628defebc17
parent 1131 544609e83317
child 1977 606923dff11b
equal deleted inserted replaced
1801:672acc730ce5 1802:d628defebc17
    11 following differences:
    11 following differences:
    12 
    12 
    13 * all methods returning `google.appengine.ext.db.Model` instance(s) will return
    13 * all methods returning `google.appengine.ext.db.Model` instance(s) will return
    14   `cubicweb.goa.db.Model` instance instead (though you should see almost no
    14   `cubicweb.goa.db.Model` instance instead (though you should see almost no
    15   difference since those instances have the same api)
    15   difference since those instances have the same api)
    16   
    16 
    17 * class methods returning model instance take a `req` as first argument, unless
    17 * class methods returning model instance take a `req` as first argument, unless
    18   they are called through an instance, representing the current request
    18   they are called through an instance, representing the current request
    19   (accessible through `self.req` on almost all objects)
    19   (accessible through `self.req` on almost all objects)
    20   
    20 
    21 * XXX no instance.<modelname>_set attributes, use instance.reverse_<attr name>
    21 * XXX no instance.<modelname>_set attributes, use instance.reverse_<attr name>
    22       instead
    22       instead
    23 * XXX reference property always return a list of objects, not the instance
    23 * XXX reference property always return a list of objects, not the instance
    24 * XXX name/collection_name argument of properties constructor are ignored
    24 * XXX name/collection_name argument of properties constructor are ignored
    25 * XXX ListProperty
    25 * XXX ListProperty
    45 from google.appengine.api.datastore import NormalizeAndTypeCheck, RunInTransaction
    45 from google.appengine.api.datastore import NormalizeAndTypeCheck, RunInTransaction
    46 from google.appengine.api.datastore_types import Text, Blob
    46 from google.appengine.api.datastore_types import Text, Blob
    47 from google.appengine.api.datastore_errors import BadKeyError
    47 from google.appengine.api.datastore_errors import BadKeyError
    48 
    48 
    49 # XXX remove this dependancy
    49 # XXX remove this dependancy
    50 from google.appengine.ext import db 
    50 from google.appengine.ext import db
    51 
    51 
    52 
    52 
    53 def rset_from_objs(req, objs, attrs=('eid',), rql=None, args=None):
    53 def rset_from_objs(req, objs, attrs=('eid',), rql=None, args=None):
    54     """return a ResultSet instance for list of objects"""
    54     """return a ResultSet instance for list of objects"""
    55     if objs is None:
    55     if objs is None:
    74                 value = str(value)
    74                 value = str(value)
    75             else:
    75             else:
    76                 value = obj[attr]
    76                 value = obj[attr]
    77                 descr = str(eschema.destination(attr))
    77                 descr = str(eschema.destination(attr))
    78             line.append(value)
    78             line.append(value)
    79             linedescr.append(descr)            
    79             linedescr.append(descr)
    80         rows.append(line)
    80         rows.append(line)
    81         description.append(linedescr)
    81         description.append(linedescr)
    82         for j, attr in enumerate(attrs):
    82         for j, attr in enumerate(attrs):
    83             if attr == 'eid':
    83             if attr == 'eid':
    84                 entity = vreg.etype_class(eschema.type)(req, rset, i, j)
    84                 entity = vreg.etype_class(eschema.type)(req, rset, i, j)
    85                 rset._get_entity_cache_ = {(i, j): entity}        
    85                 rset._get_entity_cache_ = {(i, j): entity}
    86     rset.rowcount = len(rows)
    86     rset.rowcount = len(rows)
    87     req.decorate_rset(rset)    
    87     req.decorate_rset(rset)
    88     return rset
    88     return rset
    89 
    89 
    90 
    90 
    91 def needrequest(wrapped):
    91 def needrequest(wrapped):
    92     def wrapper(cls, *args, **kwargs):
    92     def wrapper(cls, *args, **kwargs):
   100                 raise Exception('either call this method on an instance or '
   100                 raise Exception('either call this method on an instance or '
   101                                 'specify the req argument')
   101                                 'specify the req argument')
   102         return wrapped(cls, req, *args, **kwargs)
   102         return wrapped(cls, req, *args, **kwargs)
   103     return iclassmethod(wrapper)
   103     return iclassmethod(wrapper)
   104 
   104 
   105     
   105 
   106 class gaedbmetaentity(metaentity):
   106 class gaedbmetaentity(metaentity):
   107     """metaclass for goa.db.Model classes: filter entity / db model part,
   107     """metaclass for goa.db.Model classes: filter entity / db model part,
   108     put aside the db model part for later creation of db model class.
   108     put aside the db model part for later creation of db model class.
   109     """
   109     """
   110     def __new__(mcs, name, bases, classdict):
   110     def __new__(mcs, name, bases, classdict):
   137 
   137 
   138 
   138 
   139 class Model(entities.AnyEntity):
   139 class Model(entities.AnyEntity):
   140     id = 'Any'
   140     id = 'Any'
   141     __metaclass__ = gaedbmetaentity
   141     __metaclass__ = gaedbmetaentity
   142     
   142 
   143     row = col = 0
   143     row = col = 0
   144     
   144 
   145     @classmethod
   145     @classmethod
   146     def __initialize__(cls):
   146     def __initialize__(cls):
   147         super(Model, cls).__initialize__()
   147         super(Model, cls).__initialize__()
   148         cls._attributes = frozenset(rschema for rschema in cls.e_schema.subject_relations()
   148         cls._attributes = frozenset(rschema for rschema in cls.e_schema.subject_relations()
   149                                     if rschema.is_final())
   149                                     if rschema.is_final())
   150     
   150 
   151     def __init__(self, *args, **kwargs):
   151     def __init__(self, *args, **kwargs):
   152         # db.Model prototype:
   152         # db.Model prototype:
   153         #   __init__(self, parent=None, key_name=None, **kw)
   153         #   __init__(self, parent=None, key_name=None, **kw)
   154         #
   154         #
   155         # Entity prototype:
   155         # Entity prototype:
   166                         val = [isinstance(x, Model) and x._dbmodel or x for x in val]
   166                         val = [isinstance(x, Model) and x._dbmodel or x for x in val]
   167                     elif isinstance(val, Model):
   167                     elif isinstance(val, Model):
   168                         val = val._dbmodel
   168                         val = val._dbmodel
   169                     kwargs[key] = val.key()
   169                     kwargs[key] = val.key()
   170             self._gaeinitargs = (args, kwargs)
   170             self._gaeinitargs = (args, kwargs)
   171             
   171 
   172     def __repr__(self):
   172     def __repr__(self):
   173         return '<ModelEntity %s %s %s at %s>' % (
   173         return '<ModelEntity %s %s %s at %s>' % (
   174             self.e_schema, self.eid, self.keys(), id(self))
   174             self.e_schema, self.eid, self.keys(), id(self))
   175 
   175 
   176     def _cubicweb_to_datastore(self, attr, value):
   176     def _cubicweb_to_datastore(self, attr, value):
   177         attr = attr[2:] # remove 's_' / 'o_' prefix
   177         attr = attr[2:] # remove 's_' / 'o_' prefix
   178         if attr in self._attributes:
   178         if attr in self._attributes:
   179             tschema = self.e_schema.destination(attr)
   179             tschema = self.e_schema.destination(attr)
   180             if tschema == 'String':
   180             if tschema == 'String':
   181                 if len(value) > 500:
   181                 if len(value) > 500:
   182                     value = Text(value)                
   182                     value = Text(value)
   183             elif tschema == 'Password':
   183             elif tschema == 'Password':
   184                 # if value is a Binary instance, this mean we got it
   184                 # if value is a Binary instance, this mean we got it
   185                 # from a query result and so it is already encrypted
   185                 # from a query result and so it is already encrypted
   186                 if isinstance(value, Binary):
   186                 if isinstance(value, Binary):
   187                     value = value.getvalue()
   187                     value = value.getvalue()
   201             attr = 's_' + attr
   201             attr = 's_' + attr
   202             if value is not None and convert:
   202             if value is not None and convert:
   203                 value = self._cubicweb_to_datastore(attr, value)
   203                 value = self._cubicweb_to_datastore(attr, value)
   204             gaedict[attr] = value
   204             gaedict[attr] = value
   205         return gaedict
   205         return gaedict
   206     
   206 
   207     def to_gae_model(self):
   207     def to_gae_model(self):
   208         dbmodel = self._dbmodel
   208         dbmodel = self._dbmodel
   209         dbmodel.update(self._to_gae_dict())
   209         dbmodel.update(self._to_gae_dict())
   210         return dbmodel
   210         return dbmodel
   211 
   211 
   212     @property
   212     @property
   213     @cached
   213     @cached
   214     def _dbmodel(self): 
   214     def _dbmodel(self):
   215         if self.has_eid():
   215         if self.has_eid():
   216             assert self._gaeinitargs is None
   216             assert self._gaeinitargs is None
   217             try:
   217             try:
   218                 return self.req.datastore_get(self.eid)
   218                 return self.req.datastore_get(self.eid)
   219             except AttributeError: # self.req is not a server session
   219             except AttributeError: # self.req is not a server session
   238                 assert key_name is None
   238                 assert key_name is None
   239                 key_name = kwargs.pop('key_name')
   239                 key_name = kwargs.pop('key_name')
   240             if '_app' in kwargs:
   240             if '_app' in kwargs:
   241                 assert _app is None
   241                 assert _app is None
   242                 _app = kwargs.pop('_app')
   242                 _app = kwargs.pop('_app')
   243             
   243 
   244             for key, value in kwargs.iteritems():
   244             for key, value in kwargs.iteritems():
   245                 if key in self._attributes:
   245                 if key in self._attributes:
   246                     values['s_'+key] = value
   246                     values['s_'+key] = value
   247         else:
   247         else:
   248             kwargs = None
   248             kwargs = None
   265 
   265 
   266         Note that if this function return something else than None, the returned
   266         Note that if this function return something else than None, the returned
   267         value will be prefixed by 'key_' to build the actual key name.
   267         value will be prefixed by 'key_' to build the actual key name.
   268         """
   268         """
   269         return None
   269         return None
   270     
   270 
   271     def metainformation(self):
   271     def metainformation(self):
   272         return {'type': self.id, 'source': {'uri': 'system'}, 'extid': None}
   272         return {'type': self.id, 'source': {'uri': 'system'}, 'extid': None}
   273        
   273 
   274     def view(self, vid, __registry='views', **kwargs):
   274     def view(self, vid, __registry='views', **kwargs):
   275         """shortcut to apply a view on this entity"""
   275         """shortcut to apply a view on this entity"""
   276         return self.vreg.render(__registry, vid, self.req, rset=self.rset,
   276         return self.vreg.render(__registry, vid, self.req, rset=self.rset,
   277                                 row=self.row, col=self.col, **kwargs)
   277                                 row=self.row, col=self.col, **kwargs)
   278 
   278 
   280     def _rest_attr_info(cls):
   280     def _rest_attr_info(cls):
   281         mainattr, needcheck = super(Model, cls)._rest_attr_info()
   281         mainattr, needcheck = super(Model, cls)._rest_attr_info()
   282         if needcheck:
   282         if needcheck:
   283             return 'eid', False
   283             return 'eid', False
   284         return mainattr, needcheck
   284         return mainattr, needcheck
   285     
   285 
   286     def get_value(self, name):
   286     def get_value(self, name):
   287         try:
   287         try:
   288             value = self[name]
   288             value = self[name]
   289         except KeyError:
   289         except KeyError:
   290             if not self.has_eid():
   290             if not self.has_eid():
   304         try:
   304         try:
   305             Key(self.eid)
   305             Key(self.eid)
   306             return True
   306             return True
   307         except BadKeyError:
   307         except BadKeyError:
   308             return False
   308             return False
   309         
   309 
   310     def complete(self, skip_bytes=True):
   310     def complete(self, skip_bytes=True):
   311         pass
   311         pass
   312 
   312 
   313     def unrelated(self, rtype, targettype, role='subject', limit=None,
   313     def unrelated(self, rtype, targettype, role='subject', limit=None,
   314                   ordermethod=None):
   314                   ordermethod=None):
   317             objs = Query(str(targettype)).Get(limit)
   317             objs = Query(str(targettype)).Get(limit)
   318         else:
   318         else:
   319             objs = Query(str(targettype)).Run()
   319             objs = Query(str(targettype)).Run()
   320         return rset_from_objs(self.req, objs, ('eid',),
   320         return rset_from_objs(self.req, objs, ('eid',),
   321                               'Any X WHERE X is %s' % targettype)
   321                               'Any X WHERE X is %s' % targettype)
   322     
   322 
   323     def key(self):
   323     def key(self):
   324         return Key(self.eid)
   324         return Key(self.eid)
   325 
   325 
   326     def put(self, req=None):
   326     def put(self, req=None):
   327         if req is not None and self.req is None:
   327         if req is not None and self.req is None:
   332         if self.req is not None and self.rset is None:
   332         if self.req is not None and self.rset is None:
   333             self.rset = rset_from_objs(self.req, dbmodel, ('eid',),
   333             self.rset = rset_from_objs(self.req, dbmodel, ('eid',),
   334                                        'Any X WHERE X eid %(x)s', {'x': self.eid})
   334                                        'Any X WHERE X eid %(x)s', {'x': self.eid})
   335             self.row = self.col = 0
   335             self.row = self.col = 0
   336         return dbmodel
   336         return dbmodel
   337     
   337 
   338     @needrequest
   338     @needrequest
   339     def get(cls, req, keys):
   339     def get(cls, req, keys):
   340         # if check if this is a dict.key call
   340         # if check if this is a dict.key call
   341         if isinstance(cls, Model) and keys in cls._attributes:
   341         if isinstance(cls, Model) and keys in cls._attributes:
   342             return super(Model, cls).get(keys)
   342             return super(Model, cls).get(keys)
   391     def properties(cls):
   391     def properties(cls):
   392         raise NotImplementedError('use eschema')
   392         raise NotImplementedError('use eschema')
   393 
   393 
   394     def dynamic_properties(self):
   394     def dynamic_properties(self):
   395         raise NotImplementedError('use eschema')
   395         raise NotImplementedError('use eschema')
   396         
   396 
   397     def is_saved(self):
   397     def is_saved(self):
   398         return self.has_eid()
   398         return self.has_eid()
   399 
   399 
   400     def parent(self):
   400     def parent(self):
   401         parent = self._dbmodel.parent()
   401         parent = self._dbmodel.parent()
   423 TextProperty = db.TextProperty
   423 TextProperty = db.TextProperty
   424 BlobProperty = db.BlobProperty
   424 BlobProperty = db.BlobProperty
   425 IntegerProperty = db.IntegerProperty
   425 IntegerProperty = db.IntegerProperty
   426 FloatProperty = db.FloatProperty
   426 FloatProperty = db.FloatProperty
   427 ListProperty = db.ListProperty
   427 ListProperty = db.ListProperty
   428 SelfReferenceProperty = db.SelfReferenceProperty 
   428 SelfReferenceProperty = db.SelfReferenceProperty
   429 UserProperty = db.UserProperty
   429 UserProperty = db.UserProperty
   430 
   430 
   431 
   431 
   432 class ReferencePropertyStub(object):
   432 class ReferencePropertyStub(object):
   433     def __init__(self, cls, args, kwargs):
   433     def __init__(self, cls, args, kwargs):