dbapi.py
branchstable
changeset 6252 d33509b79efe
parent 6061 7cb29eab1c9d
child 6257 7eb5f1aed728
equal deleted inserted replaced
6251:afc757568492 6252:d33509b79efe
   457         rset = Cursor.execute(self, operation, parameters, build_descr=build_descr)
   457         rset = Cursor.execute(self, operation, parameters, build_descr=build_descr)
   458         self.connection.executed_queries.append((operation, parameters,
   458         self.connection.executed_queries.append((operation, parameters,
   459                                                  time() - tstart, clock() - cstart))
   459                                                  time() - tstart, clock() - cstart))
   460         return rset
   460         return rset
   461 
   461 
       
   462 def check_not_closed(func):
       
   463     def decorator(self, *args, **kwargs):
       
   464         if self._closed is not None:
       
   465             raise ProgrammingError('Closed connection')
       
   466         return func(self, *args, **kwargs)
       
   467     return decorator
   462 
   468 
   463 class Connection(object):
   469 class Connection(object):
   464     """DB-API 2.0 compatible Connection object for CubicWeb
   470     """DB-API 2.0 compatible Connection object for CubicWeb
   465     """
   471     """
   466     # make exceptions available through the connection object
   472     # make exceptions available through the connection object
   497             self.commit()
   503             self.commit()
   498         else:
   504         else:
   499             self.rollback()
   505             self.rollback()
   500             return False #propagate the exception
   506             return False #propagate the exception
   501 
   507 
   502     def _txid(self, cursor=None): # XXX could now handle various isolation level!
   508     def __del__(self):
   503         # return a dict as bw compat trick
   509         """close the remote connection if necessary"""
   504         return {'txid': currentThread().getName()}
   510         if self._closed is None and self._close_on_del:
   505 
   511             try:
   506     def request(self):
   512                 self.close()
   507         return DBAPIRequest(self.vreg, DBAPISession(self))
   513             except:
   508 
   514                 pass
   509     def check(self):
   515 
   510         """raise `BadConnectionId` if the connection is no more valid"""
   516     # connection initialization methods ########################################
   511         if self._closed is not None:
       
   512             raise ProgrammingError('Closed connection')
       
   513         self._repo.check_session(self.sessionid)
       
   514 
       
   515     def set_session_props(self, **props):
       
   516         """raise `BadConnectionId` if the connection is no more valid"""
       
   517         if self._closed is not None:
       
   518             raise ProgrammingError('Closed connection')
       
   519         self._repo.set_session_props(self.sessionid, props)
       
   520 
       
   521     def get_shared_data(self, key, default=None, pop=False):
       
   522         """return value associated to `key` in shared data"""
       
   523         if self._closed is not None:
       
   524             raise ProgrammingError('Closed connection')
       
   525         return self._repo.get_shared_data(self.sessionid, key, default, pop)
       
   526 
       
   527     def set_shared_data(self, key, value, querydata=False):
       
   528         """set value associated to `key` in shared data
       
   529 
       
   530         if `querydata` is true, the value will be added to the repository
       
   531         session's query data which are cleared on commit/rollback of the current
       
   532         transaction, and won't be available through the connexion, only on the
       
   533         repository side.
       
   534         """
       
   535         if self._closed is not None:
       
   536             raise ProgrammingError('Closed connection')
       
   537         return self._repo.set_shared_data(self.sessionid, key, value, querydata)
       
   538 
       
   539     def get_schema(self):
       
   540         """Return the schema currently used by the repository.
       
   541 
       
   542         This is NOT part of the DB-API.
       
   543         """
       
   544         if self._closed is not None:
       
   545             raise ProgrammingError('Closed connection')
       
   546         return self._repo.get_schema()
       
   547 
   517 
   548     def load_appobjects(self, cubes=_MARKER, subpath=None, expand=True):
   518     def load_appobjects(self, cubes=_MARKER, subpath=None, expand=True):
   549         config = self.vreg.config
   519         config = self.vreg.config
   550         if cubes is _MARKER:
   520         if cubes is _MARKER:
   551             cubes = self._repo.get_cubes()
   521             cubes = self._repo.get_cubes()
   597         self.vreg.config.set_option('base-url', baseurl)
   567         self.vreg.config.set_option('base-url', baseurl)
   598         # XXX why is this needed? if really needed, could be fetched by a query
   568         # XXX why is this needed? if really needed, could be fetched by a query
   599         if sitetitle is not None:
   569         if sitetitle is not None:
   600             self.vreg['propertydefs']['ui.site-title'] = {'default': sitetitle}
   570             self.vreg['propertydefs']['ui.site-title'] = {'default': sitetitle}
   601 
   571 
       
   572     @check_not_closed
   602     def source_defs(self):
   573     def source_defs(self):
   603         """Return the definition of sources used by the repository.
   574         """Return the definition of sources used by the repository.
   604 
   575 
   605         This is NOT part of the DB-API.
   576         This is NOT part of the DB-API.
   606         """
   577         """
   607         if self._closed is not None:
       
   608             raise ProgrammingError('Closed connection')
       
   609         return self._repo.source_defs()
   578         return self._repo.source_defs()
   610 
   579 
       
   580     @check_not_closed
   611     def user(self, req=None, props=None):
   581     def user(self, req=None, props=None):
   612         """return the User object associated to this connection"""
   582         """return the User object associated to this connection"""
   613         # cnx validity is checked by the call to .user_info
   583         # cnx validity is checked by the call to .user_info
   614         if self._closed is not None:
       
   615             raise ProgrammingError('Closed connection')
       
   616         eid, login, groups, properties = self._repo.user_info(self.sessionid,
   584         eid, login, groups, properties = self._repo.user_info(self.sessionid,
   617                                                               props)
   585                                                               props)
   618         if req is None:
   586         if req is None:
   619             req = self.request()
   587             req = self.request()
   620         rset = req.eid_rset(eid, 'CWUser')
   588         rset = req.eid_rset(eid, 'CWUser')
   626             from cubicweb.entity import Entity
   594             from cubicweb.entity import Entity
   627             user = Entity(req, rset, row=0)
   595             user = Entity(req, rset, row=0)
   628         user['login'] = login # cache login
   596         user['login'] = login # cache login
   629         return user
   597         return user
   630 
   598 
   631     def __del__(self):
   599     @check_not_closed
   632         """close the remote connection if necessary"""
   600     def check(self):
   633         if self._closed is None and self._close_on_del:
   601         """raise `BadConnectionId` if the connection is no more valid"""
   634             try:
   602         self._repo.check_session(self.sessionid)
   635                 self.close()
   603 
   636             except:
   604     def _txid(self, cursor=None): # XXX could now handle various isolation level!
   637                 pass
   605         # return a dict as bw compat trick
   638 
   606         return {'txid': currentThread().getName()}
       
   607 
       
   608     def request(self):
       
   609         return DBAPIRequest(self.vreg, DBAPISession(self))
       
   610 
       
   611     # session data methods #####################################################
       
   612 
       
   613     @check_not_closed
       
   614     def set_session_props(self, **props):
       
   615         """raise `BadConnectionId` if the connection is no more valid"""
       
   616         self._repo.set_session_props(self.sessionid, props)
       
   617 
       
   618     @check_not_closed
       
   619     def get_shared_data(self, key, default=None, pop=False):
       
   620         """return value associated to `key` in shared data"""
       
   621         return self._repo.get_shared_data(self.sessionid, key, default, pop)
       
   622 
       
   623     @check_not_closed
       
   624     def set_shared_data(self, key, value, querydata=False):
       
   625         """set value associated to `key` in shared data
       
   626 
       
   627         if `querydata` is true, the value will be added to the repository
       
   628         session's query data which are cleared on commit/rollback of the current
       
   629         transaction, and won't be available through the connexion, only on the
       
   630         repository side.
       
   631         """
       
   632         return self._repo.set_shared_data(self.sessionid, key, value, querydata)
       
   633 
       
   634     # meta-data accessors ######################################################
       
   635 
       
   636     @check_not_closed
       
   637     def get_schema(self):
       
   638         """Return the schema currently used by the repository."""
       
   639         return self._repo.get_schema()
       
   640 
       
   641     @check_not_closed
   639     def describe(self, eid):
   642     def describe(self, eid):
   640         if self._closed is not None:
       
   641             raise ProgrammingError('Closed connection')
       
   642         return self._repo.describe(self.sessionid, eid, **self._txid())
   643         return self._repo.describe(self.sessionid, eid, **self._txid())
   643 
   644 
       
   645     # db-api like interface ####################################################
       
   646 
       
   647     @check_not_closed
       
   648     def commit(self):
       
   649         """Commit pending transaction for this connection to the repository.
       
   650 
       
   651         may raises `Unauthorized` or `ValidationError` if we attempted to do
       
   652         something we're not allowed to for security or integrity reason.
       
   653 
       
   654         If the transaction is undoable, a transaction id will be returned.
       
   655         """
       
   656         return self._repo.commit(self.sessionid, **self._txid())
       
   657 
       
   658     @check_not_closed
       
   659     def rollback(self):
       
   660         """This method is optional since not all databases provide transaction
       
   661         support.
       
   662 
       
   663         In case a database does provide transactions this method causes the the
       
   664         database to roll back to the start of any pending transaction.  Closing
       
   665         a connection without committing the changes first will cause an implicit
       
   666         rollback to be performed.
       
   667         """
       
   668         self._repo.rollback(self.sessionid, **self._txid())
       
   669 
       
   670     @check_not_closed
       
   671     def cursor(self, req=None):
       
   672         """Return a new Cursor Object using the connection.
       
   673 
       
   674         On pyro connection, you should get cursor after calling if
       
   675         load_appobjects method if desired (which you should call if you intend
       
   676         to use ORM abilities).
       
   677         """
       
   678         if req is None:
       
   679             req = self.request()
       
   680         return self.cursor_class(self, self._repo, req=req)
       
   681 
       
   682     @check_not_closed
   644     def close(self):
   683     def close(self):
   645         """Close the connection now (rather than whenever __del__ is called).
   684         """Close the connection now (rather than whenever __del__ is called).
   646 
   685 
   647         The connection will be unusable from this point forward; an Error (or
   686         The connection will be unusable from this point forward; an Error (or
   648         subclass) exception will be raised if any operation is attempted with
   687         subclass) exception will be raised if any operation is attempted with
   649         the connection. The same applies to all cursor objects trying to use the
   688         the connection. The same applies to all cursor objects trying to use the
   650         connection.  Note that closing a connection without committing the
   689         connection.  Note that closing a connection without committing the
   651         changes first will cause an implicit rollback to be performed.
   690         changes first will cause an implicit rollback to be performed.
   652         """
   691         """
   653         if self._closed:
       
   654             raise ProgrammingError('Connection is already closed')
       
   655         self._repo.close(self.sessionid, **self._txid())
   692         self._repo.close(self.sessionid, **self._txid())
   656         del self._repo # necessary for proper garbage collection
   693         del self._repo # necessary for proper garbage collection
   657         self._closed = 1
   694         self._closed = 1
   658 
   695 
   659     def commit(self):
       
   660         """Commit pending transaction for this connection to the repository.
       
   661 
       
   662         may raises `Unauthorized` or `ValidationError` if we attempted to do
       
   663         something we're not allowed to for security or integrity reason.
       
   664 
       
   665         If the transaction is undoable, a transaction id will be returned.
       
   666         """
       
   667         if not self._closed is None:
       
   668             raise ProgrammingError('Connection is already closed')
       
   669         return self._repo.commit(self.sessionid, **self._txid())
       
   670 
       
   671     def rollback(self):
       
   672         """This method is optional since not all databases provide transaction
       
   673         support.
       
   674 
       
   675         In case a database does provide transactions this method causes the the
       
   676         database to roll back to the start of any pending transaction.  Closing
       
   677         a connection without committing the changes first will cause an implicit
       
   678         rollback to be performed.
       
   679         """
       
   680         if not self._closed is None:
       
   681             raise ProgrammingError('Connection is already closed')
       
   682         self._repo.rollback(self.sessionid, **self._txid())
       
   683 
       
   684     def cursor(self, req=None):
       
   685         """Return a new Cursor Object using the connection.
       
   686 
       
   687         On pyro connection, you should get cursor after calling if
       
   688         load_appobjects method if desired (which you should call if you intend
       
   689         to use ORM abilities).
       
   690         """
       
   691         if self._closed is not None:
       
   692             raise ProgrammingError('Can\'t get cursor on closed connection')
       
   693         if req is None:
       
   694             req = self.request()
       
   695         return self.cursor_class(self, self._repo, req=req)
       
   696 
       
   697     # undo support ############################################################
   696     # undo support ############################################################
   698 
   697 
       
   698     @check_not_closed
   699     def undoable_transactions(self, ueid=None, req=None, **actionfilters):
   699     def undoable_transactions(self, ueid=None, req=None, **actionfilters):
   700         """Return a list of undoable transaction objects by the connection's
   700         """Return a list of undoable transaction objects by the connection's
   701         user, ordered by descendant transaction time.
   701         user, ordered by descendant transaction time.
   702 
   702 
   703         Managers may filter according to user (eid) who has done the transaction
   703         Managers may filter according to user (eid) who has done the transaction
   726             req = self.request()
   726             req = self.request()
   727         for txinfo in txinfos:
   727         for txinfo in txinfos:
   728             txinfo.req = req
   728             txinfo.req = req
   729         return txinfos
   729         return txinfos
   730 
   730 
       
   731     @check_not_closed
   731     def transaction_info(self, txuuid, req=None):
   732     def transaction_info(self, txuuid, req=None):
   732         """Return transaction object for the given uid.
   733         """Return transaction object for the given uid.
   733 
   734 
   734         raise `NoSuchTransaction` if not found or if session's user is not
   735         raise `NoSuchTransaction` if not found or if session's user is not
   735         allowed (eg not in managers group and the transaction doesn't belong to
   736         allowed (eg not in managers group and the transaction doesn't belong to
   740         if req is None:
   741         if req is None:
   741             req = self.request()
   742             req = self.request()
   742         txinfo.req = req
   743         txinfo.req = req
   743         return txinfo
   744         return txinfo
   744 
   745 
       
   746     @check_not_closed
   745     def transaction_actions(self, txuuid, public=True):
   747     def transaction_actions(self, txuuid, public=True):
   746         """Return an ordered list of action effectued during that transaction.
   748         """Return an ordered list of action effectued during that transaction.
   747 
   749 
   748         If public is true, return only 'public' actions, eg not ones triggered
   750         If public is true, return only 'public' actions, eg not ones triggered
   749         under the cover by hooks, else return all actions.
   751         under the cover by hooks, else return all actions.
   753         transaction doesn't belong to him).
   755         transaction doesn't belong to him).
   754         """
   756         """
   755         return self._repo.transaction_actions(self.sessionid, txuuid, public,
   757         return self._repo.transaction_actions(self.sessionid, txuuid, public,
   756                                               **self._txid())
   758                                               **self._txid())
   757 
   759 
       
   760     @check_not_closed
   758     def undo_transaction(self, txuuid):
   761     def undo_transaction(self, txuuid):
   759         """Undo the given transaction. Return potential restoration errors.
   762         """Undo the given transaction. Return potential restoration errors.
   760 
   763 
   761         raise `NoSuchTransaction` if not found or if session's user is not
   764         raise `NoSuchTransaction` if not found or if session's user is not
   762         allowed (eg not in managers group and the transaction doesn't belong to
   765         allowed (eg not in managers group and the transaction doesn't belong to