goa/gaesource.py
branchtls-sprint
changeset 1802 d628defebc17
parent 1398 5fe84a5f7035
child 1977 606923dff11b
equal deleted inserted replaced
1801:672acc730ce5 1802:d628defebc17
    13 from cubicweb.goa.dbinit import set_user_groups
    13 from cubicweb.goa.dbinit import set_user_groups
    14 from cubicweb.goa.rqlinterpreter import RQLInterpreter
    14 from cubicweb.goa.rqlinterpreter import RQLInterpreter
    15 
    15 
    16 from google.appengine.api.datastore import Key, Entity, Put, Delete
    16 from google.appengine.api.datastore import Key, Entity, Put, Delete
    17 from google.appengine.api import datastore_errors, users
    17 from google.appengine.api import datastore_errors, users
    18     
    18 
    19 def _init_groups(guser, euser):
    19 def _init_groups(guser, euser):
    20     # set default groups
    20     # set default groups
    21     if guser is None:
    21     if guser is None:
    22         groups = ['guests']
    22         groups = ['guests']
    23     else:
    23     else:
    80         if related is not None:
    80         if related is not None:
    81             related = [key for key in related if not key == targetkey]
    81             related = [key for key in related if not key == targetkey]
    82             gaeentity[relation] = related or None
    82             gaeentity[relation] = related or None
    83     _mark_modified(session, gaeentity)
    83     _mark_modified(session, gaeentity)
    84 
    84 
    85     
    85 
    86 class DatastorePutOp(SingleOperation):
    86 class DatastorePutOp(SingleOperation):
    87     """delayed put of entities to have less datastore write api calls
    87     """delayed put of entities to have less datastore write api calls
    88 
    88 
    89     * save all modified entities at precommit (should be the first operation
    89     * save all modified entities at precommit (should be the first operation
    90       processed, hence the 0 returned by insert_index())
    90       processed, hence the 0 returned by insert_index())
    91       
    91 
    92     * in case others precommit operations modify some entities, resave modified
    92     * in case others precommit operations modify some entities, resave modified
    93       entities at commit. This suppose that no db changes will occurs during
    93       entities at commit. This suppose that no db changes will occurs during
    94       commit event but it should be the case.
    94       commit event but it should be the case.
    95     """
    95     """
    96     def insert_index(self):
    96     def insert_index(self):
   101         modified = self.session.query_data('modifiedentities', {})
   101         modified = self.session.query_data('modifiedentities', {})
   102         for eid, gaeentity in modified.iteritems():
   102         for eid, gaeentity in modified.iteritems():
   103             assert not eid in pending
   103             assert not eid in pending
   104             Put(gaeentity)
   104             Put(gaeentity)
   105         modified.clear()
   105         modified.clear()
   106         
   106 
   107     def commit_event(self):
   107     def commit_event(self):
   108         self._put_entities()
   108         self._put_entities()
   109         
   109 
   110     def precommit_event(self):
   110     def precommit_event(self):
   111         self._put_entities()
   111         self._put_entities()
   112 
   112 
   113 
   113 
   114 class GAESource(AbstractSource):
   114 class GAESource(AbstractSource):
   115     """adapter for a system source on top of google appengine datastore"""
   115     """adapter for a system source on top of google appengine datastore"""
   116 
   116 
   117     passwd_rql = "Any P WHERE X is CWUser, X login %(login)s, X upassword P"
   117     passwd_rql = "Any P WHERE X is CWUser, X login %(login)s, X upassword P"
   118     auth_rql = "Any X WHERE X is CWUser, X login %(login)s, X upassword %(pwd)s"
   118     auth_rql = "Any X WHERE X is CWUser, X login %(login)s, X upassword %(pwd)s"
   119     _sols = ({'X': 'CWUser', 'P': 'Password'},)
   119     _sols = ({'X': 'CWUser', 'P': 'Password'},)
   120     
   120 
   121     options = ()
   121     options = ()
   122     
   122 
   123     def __init__(self, repo, appschema, source_config, *args, **kwargs):
   123     def __init__(self, repo, appschema, source_config, *args, **kwargs):
   124         AbstractSource.__init__(self, repo, appschema, source_config,
   124         AbstractSource.__init__(self, repo, appschema, source_config,
   125                                 *args, **kwargs)
   125                                 *args, **kwargs)
   126         if repo.config['use-google-auth']:
   126         if repo.config['use-google-auth']:
   127             self.info('using google authentication service')
   127             self.info('using google authentication service')
   128             self.authenticate = self.authenticate_gauth
   128             self.authenticate = self.authenticate_gauth
   129         else:
   129         else:
   130             self.authenticate = self.authenticate_local
   130             self.authenticate = self.authenticate_local
   131             
   131 
   132     def reset_caches(self):
   132     def reset_caches(self):
   133         """method called during test to reset potential source caches"""
   133         """method called during test to reset potential source caches"""
   134         pass
   134         pass
   135     
   135 
   136     def init_creating(self):
   136     def init_creating(self):
   137         pass
   137         pass
   138 
   138 
   139     def init(self):
   139     def init(self):
   140         # XXX unregister unsupported hooks
   140         # XXX unregister unsupported hooks
   142         self.repo.hm.unregister_hook(sync_owner_after_add_composite_relation,
   142         self.repo.hm.unregister_hook(sync_owner_after_add_composite_relation,
   143                                      'after_add_relation', '')
   143                                      'after_add_relation', '')
   144 
   144 
   145     def get_connection(self):
   145     def get_connection(self):
   146         return ConnectionWrapper()
   146         return ConnectionWrapper()
   147     
   147 
   148     # ISource interface #######################################################
   148     # ISource interface #######################################################
   149 
   149 
   150     def compile_rql(self, rql):
   150     def compile_rql(self, rql):
   151         rqlst = self.repo.querier._rqlhelper.parse(rql)
   151         rqlst = self.repo.querier._rqlhelper.parse(rql)
   152         rqlst.restricted_vars = ()
   152         rqlst.restricted_vars = ()
   153         rqlst.children[0].solutions = self._sols
   153         rqlst.children[0].solutions = self._sols
   154         return rqlst
   154         return rqlst
   155     
   155 
   156     def set_schema(self, schema):
   156     def set_schema(self, schema):
   157         """set the application'schema"""
   157         """set the application'schema"""
   158         self.interpreter = RQLInterpreter(schema)
   158         self.interpreter = RQLInterpreter(schema)
   159         self.schema = schema
   159         self.schema = schema
   160         if 'CWUser' in schema and not self.repo.config['use-google-auth']:
   160         if 'CWUser' in schema and not self.repo.config['use-google-auth']:
   161             # rql syntax trees used to authenticate users
   161             # rql syntax trees used to authenticate users
   162             self._passwd_rqlst = self.compile_rql(self.passwd_rql)
   162             self._passwd_rqlst = self.compile_rql(self.passwd_rql)
   163             self._auth_rqlst = self.compile_rql(self.auth_rql)
   163             self._auth_rqlst = self.compile_rql(self.auth_rql)
   164                 
   164 
   165     def support_entity(self, etype, write=False):
   165     def support_entity(self, etype, write=False):
   166         """return true if the given entity's type is handled by this adapter
   166         """return true if the given entity's type is handled by this adapter
   167         if write is true, return true only if it's a RW support
   167         if write is true, return true only if it's a RW support
   168         """
   168         """
   169         return True
   169         return True
   170     
   170 
   171     def support_relation(self, rtype, write=False):
   171     def support_relation(self, rtype, write=False):
   172         """return true if the given relation's type is handled by this adapter
   172         """return true if the given relation's type is handled by this adapter
   173         if write is true, return true only if it's a RW support
   173         if write is true, return true only if it's a RW support
   174         """
   174         """
   175         return True
   175         return True
   198             euser = Entity('CWUser', name='key_' + login)
   198             euser = Entity('CWUser', name='key_' + login)
   199             euser['s_login'] = login
   199             euser['s_login'] = login
   200             _init_groups(guser, euser)
   200             _init_groups(guser, euser)
   201             Put(euser)
   201             Put(euser)
   202             return str(euser.key())
   202             return str(euser.key())
   203         
   203 
   204     def authenticate_local(self, session, login, password):
   204     def authenticate_local(self, session, login, password):
   205         """return CWUser eid for the given login/password if this account is
   205         """return CWUser eid for the given login/password if this account is
   206         defined in this source, else raise `AuthenticationError`
   206         defined in this source, else raise `AuthenticationError`
   207 
   207 
   208         two queries are needed since passwords are stored crypted, so we have
   208         two queries are needed since passwords are stored crypted, so we have
   222         rset = self.syntax_tree_search(session, self._auth_rqlst, args)
   222         rset = self.syntax_tree_search(session, self._auth_rqlst, args)
   223         try:
   223         try:
   224             return rset[0][0]
   224             return rset[0][0]
   225         except IndexError:
   225         except IndexError:
   226             raise AuthenticationError('bad password')
   226             raise AuthenticationError('bad password')
   227     
   227 
   228     def syntax_tree_search(self, session, union, args=None, cachekey=None, 
   228     def syntax_tree_search(self, session, union, args=None, cachekey=None,
   229                            varmap=None):
   229                            varmap=None):
   230         """return result from this source for a rql query (actually from a rql
   230         """return result from this source for a rql query (actually from a rql
   231         syntax tree and a solution dictionary mapping each used variable to a
   231         syntax tree and a solution dictionary mapping each used variable to a
   232         possible type). If cachekey is given, the query necessary to fetch the
   232         possible type). If cachekey is given, the query necessary to fetch the
   233         results (but not the results themselves) may be cached using this key.
   233         results (but not the results themselves) may be cached using this key.
   234         """
   234         """
   235         results, description = self.interpreter.interpret(union, args,
   235         results, description = self.interpreter.interpret(union, args,
   236                                                           session.datastore_get)
   236                                                           session.datastore_get)
   237         return results # XXX description
   237         return results # XXX description
   238                 
   238 
   239     def flying_insert(self, table, session, union, args=None, varmap=None):
   239     def flying_insert(self, table, session, union, args=None, varmap=None):
   240         raise NotImplementedError
   240         raise NotImplementedError
   241     
   241 
   242     def add_entity(self, session, entity):
   242     def add_entity(self, session, entity):
   243         """add a new entity to the source"""
   243         """add a new entity to the source"""
   244         # do not delay add_entity as other modifications, new created entity
   244         # do not delay add_entity as other modifications, new created entity
   245         # needs an eid
   245         # needs an eid
   246         entity.put()
   246         entity.put()
   247         
   247 
   248     def update_entity(self, session, entity):
   248     def update_entity(self, session, entity):
   249         """replace an entity in the source"""
   249         """replace an entity in the source"""
   250         gaeentity = entity.to_gae_model()
   250         gaeentity = entity.to_gae_model()
   251         _mark_modified(session, entity.to_gae_model())
   251         _mark_modified(session, entity.to_gae_model())
   252         if gaeentity.kind() == 'CWUser':
   252         if gaeentity.kind() == 'CWUser':
   253             for asession in self.repo._sessions.itervalues():
   253             for asession in self.repo._sessions.itervalues():
   254                 if asession.user.eid == entity.eid:
   254                 if asession.user.eid == entity.eid:
   255                     asession.user.update(dict(gaeentity))
   255                     asession.user.update(dict(gaeentity))
   256                 
   256 
   257     def delete_entity(self, session, etype, eid):
   257     def delete_entity(self, session, etype, eid):
   258         """delete an entity from the source"""
   258         """delete an entity from the source"""
   259         # do not delay delete_entity as other modifications to ensure
   259         # do not delay delete_entity as other modifications to ensure
   260         # consistency
   260         # consistency
   261         key = Key(eid)
   261         key = Key(eid)
   268         """add a relation to the source"""
   268         """add a relation to the source"""
   269         gaesubj, gaeobj, cards = _rinfo(session, subject, rtype, object)
   269         gaesubj, gaeobj, cards = _rinfo(session, subject, rtype, object)
   270         _radd(session, gaesubj, gaeobj.key(), 's_' + rtype, cards[0])
   270         _radd(session, gaesubj, gaeobj.key(), 's_' + rtype, cards[0])
   271         _radd(session, gaeobj, gaesubj.key(), 'o_' + rtype, cards[1])
   271         _radd(session, gaeobj, gaesubj.key(), 'o_' + rtype, cards[1])
   272         _clear_related_cache(session, gaesubj, rtype, gaeobj)
   272         _clear_related_cache(session, gaesubj, rtype, gaeobj)
   273             
   273 
   274     def delete_relation(self, session, subject, rtype, object):
   274     def delete_relation(self, session, subject, rtype, object):
   275         """delete a relation from the source"""
   275         """delete a relation from the source"""
   276         gaesubj, gaeobj, cards = _rinfo(session, subject, rtype, object)
   276         gaesubj, gaeobj, cards = _rinfo(session, subject, rtype, object)
   277         pending = session.query_data('pendingeids', set(), setdefault=True)
   277         pending = session.query_data('pendingeids', set(), setdefault=True)
   278         if not subject in pending:
   278         if not subject in pending:
   279             _rdel(session, gaesubj, gaeobj.key(), 's_' + rtype, cards[0])
   279             _rdel(session, gaesubj, gaeobj.key(), 's_' + rtype, cards[0])
   280         if not object in pending:
   280         if not object in pending:
   281             _rdel(session, gaeobj, gaesubj.key(), 'o_' + rtype, cards[1])
   281             _rdel(session, gaeobj, gaesubj.key(), 'o_' + rtype, cards[1])
   282         _clear_related_cache(session, gaesubj, rtype, gaeobj)
   282         _clear_related_cache(session, gaesubj, rtype, gaeobj)
   283         
   283 
   284     # system source interface #################################################
   284     # system source interface #################################################
   285 
   285 
   286     def eid_type_source(self, session, eid):
   286     def eid_type_source(self, session, eid):
   287         """return a tuple (type, source, extid) for the entity with id <eid>"""
   287         """return a tuple (type, source, extid) for the entity with id <eid>"""
   288         try:
   288         try:
   289             key = Key(eid)
   289             key = Key(eid)
   290         except datastore_errors.BadKeyError:
   290         except datastore_errors.BadKeyError:
   291             raise UnknownEid(eid)
   291             raise UnknownEid(eid)
   292         return key.kind(), 'system', None
   292         return key.kind(), 'system', None
   293     
   293 
   294     def create_eid(self, session):
   294     def create_eid(self, session):
   295         return None # let the datastore generating key
   295         return None # let the datastore generating key
   296 
   296 
   297     def add_info(self, session, entity, source, extid=None):
   297     def add_info(self, session, entity, source, extid=None):
   298         """add type and source info for an eid into the system table"""
   298         """add type and source info for an eid into the system table"""
   301     def delete_info(self, session, eid, etype, uri, extid):
   301     def delete_info(self, session, eid, etype, uri, extid):
   302         """delete system information on deletion of an entity by transfering
   302         """delete system information on deletion of an entity by transfering
   303         record from the entities table to the deleted_entities table
   303         record from the entities table to the deleted_entities table
   304         """
   304         """
   305         pass
   305         pass
   306         
   306 
   307     def fti_unindex_entity(self, session, eid):
   307     def fti_unindex_entity(self, session, eid):
   308         """remove text content for entity with the given eid from the full text
   308         """remove text content for entity with the given eid from the full text
   309         index
   309         index
   310         """
   310         """
   311         pass
   311         pass
   312         
   312 
   313     def fti_index_entity(self, session, entity):
   313     def fti_index_entity(self, session, entity):
   314         """add text content of a created/modified entity to the full text index
   314         """add text content of a created/modified entity to the full text index
   315         """
   315         """
   316         pass
   316         pass
   317