0001importatexit0002importinspect0003importsys0004importos0005importthreading0006importtypes0007try:0008fromurlparseimporturlparse,parse_qsl0009fromurllibimportunquote,quote,urlencode0010exceptImportError:0011fromurllib.parseimporturlparse,parse_qsl,unquote,quote,urlencode00120013importwarnings0014importweakref00150016from.cacheimportCacheSet0017from.importclassregistry0018from.importcol0019from.convertersimportsqlrepr0020from.importsqlbuilder0021from.util.threadinglocalimportlocalasthreading_local0022from.compatimportPY2,string_type,unicode_type00230024warnings.filterwarnings("ignore","DB-API extension cursor.lastrowid used")00250026_connections={}002700280029def_closeConnection(ref):0030conn=ref()0031ifconnisnotNone:0032conn.close()003300340035classConsoleWriter:0036def__init__(self,connection,loglevel):0037# loglevel: None or empty string for stdout; or 'stderr'0038self.loglevel=loglevelor"stdout"0039self.dbEncoding=getattr(connection,"dbEncoding",None)or"ascii"00400041defwrite(self,text):0042logfile=getattr(sys,self.loglevel)0043ifPY2andisinstance(text,unicode_type):0044try:0045text=text.encode(self.dbEncoding)0046exceptUnicodeEncodeError:0047text=repr(text)[2:-1]# Remove u'...' from the repr0048logfile.write(text+'\n')004900500051classLogWriter:0052def__init__(self,connection,logger,loglevel):0053self.logger=logger0054self.loglevel=loglevel0055self.logmethod=getattr(logger,loglevel)00560057defwrite(self,text):0058self.logmethod(text)005900600061defmakeDebugWriter(connection,loggerName,loglevel):0062ifnotloggerName:0063returnConsoleWriter(connection,loglevel)0064importlogging0065logger=logging.getLogger(loggerName)0066returnLogWriter(connection,logger,loglevel)006700680069classBoolean(object):0070"""A bool class that also understands some special string keywords00710072 Understands: yes/no, true/false, on/off, 1/0, case ignored.00730074 """0075_keywords={'1':True,'yes':True,'true':True,'on':True,0076'0':False,'no':False,'false':False,'off':False}00770078def__new__(cls,value):0079try:0080returnBoolean._keywords[value.lower()]0081except(AttributeError,KeyError):0082returnbool(value)008300840085classDBConnection:00860087def__init__(self,name=None,debug=False,debugOutput=False,0088cache=True,style=None,autoCommit=True,0089debugThreading=False,registry=None,0090logger=None,loglevel=None):0091self.name=name0092self.debug=Boolean(debug)0093self.debugOutput=Boolean(debugOutput)0094self.debugThreading=Boolean(debugThreading)0095self.debugWriter=makeDebugWriter(self,logger,loglevel)0096self.doCache=Boolean(cache)0097self.cache=CacheSet(cache=self.doCache)0098self.style=style0099self._connectionNumbers={}0100self._connectionCount=10101self.autoCommit=Boolean(autoCommit)0102self.registry=registryorNone0103classregistry.registry(self.registry).addCallback(self.soClassAdded)0104registerConnectionInstance(self)0105atexit.register(_closeConnection,weakref.ref(self))01060107defoldUri(self):0108auth=getattr(self,'user','')or''0109ifauth:0110ifself.password:0111auth=auth+':'+self.password0112auth=auth+'@'0113else:0114assertnotgetattr(self,'password',None),(0115'URIs cannot express passwords without usernames')0116uri='%s://%s'%(self.dbName,auth)0117ifself.host:0118uri+=self.host0119ifself.port:0120uri+=':%d'%self.port0121uri+='/'0122db=self.db0123ifdb.startswith('/'):0124db=db[1:]0125returnuri+db01260127defuri(self):0128auth=getattr(self,'user','')or''0129ifauth:0130auth=quote(auth)0131ifself.password:0132auth=auth+':'+quote(self.password)0133auth=auth+'@'0134else:0135assertnotgetattr(self,'password',None),(0136'URIs cannot express passwords without usernames')0137uri='%s://%s'%(self.dbName,auth)0138ifself.host:0139uri+=self.host0140ifself.port:0141uri+=':%d'%self.port0142uri+='/'0143db=self.db0144ifdb.startswith('/'):0145db=db[1:]0146returnuri+quote(db)01470148@classmethod0149defconnectionFromOldURI(cls,uri):0150returncls._connectionFromParams(*cls._parseOldURI(uri))01510152@classmethod0153defconnectionFromURI(cls,uri):0154returncls._connectionFromParams(*cls._parseURI(uri))01550156@staticmethod0157def_parseOldURI(uri):0158schema,rest=uri.split(':',1)0159assertrest.startswith('/'),"URIs must start with scheme:/ -- ""you did not include a / (in %r)"%rest0162ifrest.startswith('/')andnotrest.startswith('//'):0163host=None0164rest=rest[1:]0165elifrest.startswith('///'):0166host=None0167rest=rest[3:]0168else:0169rest=rest[2:]0170ifrest.find('/')==-1:0171host=rest0172rest=''0173else:0174host,rest=rest.split('/',1)0175ifhostandhost.find('@')!=-1:0176user,host=host.rsplit('@',1)0177ifuser.find(':')!=-1:0178user,password=user.split(':',1)0179else:0180password=None0181else:0182user=password=None0183ifhostandhost.find(':')!=-1:0184_host,port=host.split(':')0185try:0186port=int(port)0187exceptValueError:0188raiseValueError("port must be integer, "0189"got '%s' instead"%port)0190ifnot(1<=port<=65535):0191raiseValueError("port must be integer in the range 1-65535, "0192"got '%d' instead"%port)0193host=_host0194else:0195port=None0196path='/'+rest0197ifos.name=='nt':0198if(len(rest)>1)and(rest[1]=='|'):0199path="%s:%s"%(rest[0],rest[2:])0200args={}0201ifpath.find('?')!=-1:0202path,arglist=path.split('?',1)0203arglist=arglist.split('&')0204forsingleinarglist:0205argname,argvalue=single.split('=',1)0206argvalue=unquote(argvalue)0207args[argname]=argvalue0208returnuser,password,host,port,path,args02090210@staticmethod0211def_parseURI(uri):0212parsed=urlparse(uri)0213ifsys.version_info[0:2]==(2,6):0214# In python 2.6, urlparse only parses the uri completely0215# for certain schemes, so we force the scheme to0216# something that will be parsed correctly0217scheme=parsed.scheme0218parsed=urlparse(uri.replace(scheme,'http',1))0219host,path=parsed.hostname,parsed.path0220user,password,port=None,None,None0221ifparsed.username:0222user=unquote(parsed.username)0223ifparsed.password:0224password=unquote(parsed.password)0225ifparsed.port:0226port=int(parsed.port)02270228path=unquote(path)0229if(os.name=='nt')and(len(path)>2):0230# Preserve backward compatibility with URIs like /C|/path;0231# replace '|' by ':'0232ifpath[2]=='|':0233path="%s:%s"%(path[0:2],path[3:])0234# Remove leading slash0235if(path[0]=='/')and(path[2]==':'):0236path=path[1:]02370238query=parsed.query0239# hash-tag / fragment is ignored02400241args={}0242ifquery:0243forname,valueinparse_qsl(query):0244args[name]=value02450246returnuser,password,host,port,path,args02470248defsoClassAdded(self,soClass):0249"""0250 This is called for each new class; we use this opportunity0251 to create an instance method that is bound to the class0252 and this connection.0253 """0254name=soClass.__name__0255assertnothasattr(self,name),(0256"Connection %r already has an attribute with the name "0257"%r (and you just created the conflicting class %r)"0258%(self,name,soClass))0259setattr(self,name,ConnWrapper(soClass,self))02600261defexpireAll(self):0262"""0263 Expire all instances of objects for this connection.0264 """0265cache_set=self.cache0266cache_set.weakrefAll()0267foritemincache_set.getAll():0268item.expire()026902700271classConnWrapper(object):02720273"""0274 This represents a SQLObject class that is bound to a specific0275 connection (instances have a connection instance variable, but0276 classes are global, so this is binds the connection variable0277 lazily when a class method is accessed)0278 """0279# @@: methods that take connection arguments should be explicitly0280# marked up instead of the implicit use of a connection argument0281# and inspect.getargspec()02820283def__init__(self,soClass,connection):0284self._soClass=soClass0285self._connection=connection02860287def__call__(self,*args,**kw):0288kw['connection']=self._connection0289returnself._soClass(*args,**kw)02900291def__getattr__(self,attr):0292meth=getattr(self._soClass,attr)0293ifnotisinstance(meth,types.MethodType):0294# We don't need to wrap non-methods0295returnmeth0296try:0297takes_conn=meth.takes_connection0298exceptAttributeError:0299args,varargs,varkw,defaults=inspect.getargspec(meth)0300assertnotvarkwandnotvarargs,(0301"I cannot tell whether I must wrap this method, "0302"because it takes **kw: %r"0303%meth)0304takes_conn='connection'inargs0305meth.__func__.takes_connection=takes_conn0306ifnottakes_conn:0307returnmeth0308returnConnMethodWrapper(meth,self._connection)030903100311classConnMethodWrapper(object):03120313def__init__(self,method,connection):0314self._method=method0315self._connection=connection03160317def__getattr__(self,attr):0318returngetattr(self._method,attr)03190320def__call__(self,*args,**kw):0321kw['connection']=self._connection0322returnself._method(*args,**kw)03230324def__repr__(self):0325return'<Wrapped %r with connection %r>'%(0326self._method,self._connection)032703280329classDBAPI(DBConnection):03300331"""0332 Subclass must define a `makeConnection()` method, which0333 returns a newly-created connection object.03340335 ``queryInsertID`` must also be defined.0336 """0337dbName=None03380339def__init__(self,**kw):0340self._pool=[]0341self._poolLock=threading.Lock()0342DBConnection.__init__(self,**kw)0343self._binaryType=type(self.module.Binary(b''))03440345def_runWithConnection(self,meth,*args):0346conn=self.getConnection()0347try:0348val=meth(conn,*args)0349finally:0350self.releaseConnection(conn)0351returnval03520353defgetConnection(self):0354self._poolLock.acquire()0355try:0356ifnotself._pool:0357conn=self.makeConnection()0358self._connectionNumbers[id(conn)]=self._connectionCount0359self._connectionCount+=10360else:0361conn=self._pool.pop()0362ifself.debug:0363s='ACQUIRE'0364ifself._poolisnotNone:0365s+=' pool=[%s]'%', '.join(0366[str(self._connectionNumbers[id(v)])0367forvinself._pool])0368self.printDebug(conn,s,'Pool')0369returnconn0370finally:0371self._poolLock.release()03720373defreleaseConnection(self,conn,explicit=False):0374ifself.debug:0375ifexplicit:0376s='RELEASE (explicit)'0377else:0378s='RELEASE (implicit, autocommit=%s)'%self.autoCommit0379ifself._poolisNone:0380s+=' no pooling'0381else:0382s+=' pool=[%s]'%', '.join(0383[str(self._connectionNumbers[id(v)])forvinself._pool])0384self.printDebug(conn,s,'Pool')0385ifself.supportTransactionsandnotexplicit:0386ifself.autoCommit=='exception':0387ifself.debug:0388self.printDebug(conn,'auto/exception','ROLLBACK')0389conn.rollback()0390raiseException('Object used outside of a transaction; '0391'implicit COMMIT or ROLLBACK not allowed')0392elifself.autoCommit:0393ifself.debug:0394self.printDebug(conn,'auto','COMMIT')0395ifnotgetattr(conn,'autocommit',False):0396conn.commit()0397else:0398ifself.debug:0399self.printDebug(conn,'auto','ROLLBACK')0400conn.rollback()0401ifself._poolisnotNone:0402ifconnnotinself._pool:0403# @@: We can get duplicate releasing of connections with0404# the __del__ in Iteration (unfortunately, not sure why0405# it happens)0406self._pool.insert(0,conn)0407else:0408conn.close()04090410defprintDebug(self,conn,s,name,type='query'):0411ifname=='Pool'andself.debug!='Pool':0412return0413iftype=='query':0414sep=': '0415else:0416sep='->'0417s=repr(s)0418n=self._connectionNumbers[id(conn)]0419spaces=' '*(8-len(name))0420ifself.debugThreading:0421threadName=threading.currentThread().getName()0422threadName=(':'+threadName+' '*(8-len(threadName)))0423else:0424threadName=''0425msg='%(n)2i%(threadName)s/%(name)s%(spaces)s%(sep)s %(s)s'%locals()0426self.debugWriter.write(msg)04270428def_executeRetry(self,conn,cursor,query):0429ifself.debug:0430self.printDebug(conn,query,'QueryR')0431returncursor.execute(query)04320433def_query(self,conn,s):0434ifself.debug:0435self.printDebug(conn,s,'Query')0436self._executeRetry(conn,conn.cursor(),s)04370438defquery(self,s):0439returnself._runWithConnection(self._query,s)04400441def_queryAll(self,conn,s):0442ifself.debug:0443self.printDebug(conn,s,'QueryAll')0444c=conn.cursor()0445self._executeRetry(conn,c,s)0446value=c.fetchall()0447ifself.debugOutput:0448self.printDebug(conn,value,'QueryAll','result')0449returnvalue04500451defqueryAll(self,s):0452returnself._runWithConnection(self._queryAll,s)04530454def_queryAllDescription(self,conn,s):0455"""0456 Like queryAll, but returns (description, rows), where the0457 description is cursor.description (which gives row types)0458 """0459ifself.debug:0460self.printDebug(conn,s,'QueryAllDesc')0461c=conn.cursor()0462self._executeRetry(conn,c,s)0463value=c.fetchall()0464ifself.debugOutput:0465self.printDebug(conn,value,'QueryAll','result')0466returnc.description,value04670468defqueryAllDescription(self,s):0469returnself._runWithConnection(self._queryAllDescription,s)04700471def_queryOne(self,conn,s):0472ifself.debug:0473self.printDebug(conn,s,'QueryOne')0474c=conn.cursor()0475self._executeRetry(conn,c,s)0476value=c.fetchone()0477ifself.debugOutput:0478self.printDebug(conn,value,'QueryOne','result')0479returnvalue04800481defqueryOne(self,s):0482returnself._runWithConnection(self._queryOne,s)04830484def_insertSQL(self,table,names,values):0485return("INSERT INTO %s (%s) VALUES (%s)"%0486(table,', '.join(names),0487', '.join([self.sqlrepr(v)forvinvalues])))04880489deftransaction(self):0490returnTransaction(self)04910492defqueryInsertID(self,soInstance,id,names,values):0493returnself._runWithConnection(self._queryInsertID,soInstance,id,0494names,values)04950496defiterSelect(self,select):0497returnselect.IterationClass(self,self.getConnection(),0498select,keepConnection=False)04990500defaccumulateSelect(self,select,*expressions):0501""" Apply an accumulate function(s) (SUM, COUNT, MIN, AVG, MAX, etc...)0502 to the select object.0503 """0504q=select.queryForSelect().newItems(expressions).unlimited().orderBy(None)0506q=self.sqlrepr(q)0507val=self.queryOne(q)0508iflen(expressions)==1:0509val=val[0]0510returnval05110512defqueryForSelect(self,select):0513returnself.sqlrepr(select.queryForSelect())05140515def_SO_createJoinTable(self,join):0516self.query(self._SO_createJoinTableSQL(join))05170518def_SO_createJoinTableSQL(self,join):0519return('CREATE TABLE %s (\n%s %s,\n%s %s\n)'%0520(join.intermediateTable,0521join.joinColumn,0522self.joinSQLType(join),0523join.otherColumn,0524self.joinSQLType(join)))05250526def_SO_dropJoinTable(self,join):0527self.query("DROP TABLE %s"%join.intermediateTable)05280529def_SO_createIndex(self,soClass,index):0530self.query(self.createIndexSQL(soClass,index))05310532defcreateIndexSQL(self,soClass,index):0533assert0,'Implement in subclasses'05340535defcreateTable(self,soClass):0536createSql,constraints=self.createTableSQL(soClass)0537self.query(createSql)05380539returnconstraints05400541defcreateReferenceConstraints(self,soClass):0542refConstraints=[self.createReferenceConstraint(soClass,column)0543forcolumninsoClass.sqlmeta.columnList0544ifisinstance(column,col.SOForeignKey)]0545refConstraintDefs=[constraintforconstraintinrefConstraints0546ifconstraint]0547returnrefConstraintDefs05480549defcreateSQL(self,soClass):0550tableCreateSQLs=getattr(soClass.sqlmeta,'createSQL',None)0551iftableCreateSQLs:0552assertisinstance(tableCreateSQLs,(str,list,dict,tuple)),(0553'%s.sqlmeta.createSQL must be a str, list, dict or tuple.'%0554(soClass.__name__))0555ifisinstance(tableCreateSQLs,dict):0556tableCreateSQLs=tableCreateSQLs.get(0557soClass._connection.dbName,[])0558ifisinstance(tableCreateSQLs,str):0559tableCreateSQLs=[tableCreateSQLs]0560ifisinstance(tableCreateSQLs,tuple):0561tableCreateSQLs=list(tableCreateSQLs)0562assertisinstance(tableCreateSQLs,list),(0563'Unable to create a list from %s.sqlmeta.createSQL'%0564(soClass.__name__))0565returntableCreateSQLsor[]05660567defcreateTableSQL(self,soClass):0568constraints=self.createReferenceConstraints(soClass)0569extraSQL=self.createSQL(soClass)0570createSql=('CREATE TABLE %s (\n%s\n)'%0571(soClass.sqlmeta.table,self.createColumns(soClass)))0572returncreateSql,constraints+extraSQL05730574defcreateColumns(self,soClass):0575columnDefs=[self.createIDColumn(soClass)]+[self.createColumn(soClass,col)0577forcolinsoClass.sqlmeta.columnList]0578return",\n".join([" %s"%cforcincolumnDefs])05790580defcreateReferenceConstraint(self,soClass,col):0581assert0,"Implement in subclasses"05820583defcreateColumn(self,soClass,col):0584assert0,"Implement in subclasses"05850586defdropTable(self,tableName,cascade=False):0587self.query("DROP TABLE %s"%tableName)05880589defclearTable(self,tableName):0590# 3-03 @@: Should this have a WHERE 1 = 1 or similar0591# clause? In some configurations without the WHERE clause0592# the query won't go through, but maybe we shouldn't override0593# that.0594self.query("DELETE FROM %s"%tableName)05950596defcreateBinary(self,value):0597"""0598 Create a binary object wrapper for the given database.0599 """0600# Default is Binary() function from the connection driver.0601returnself.module.Binary(value)06020603# The _SO_* series of methods are sorts of "friend" methods0604# with SQLObject. They grab values from the SQLObject instances0605# or classes freely, but keep the SQLObject class from accessing0606# the database directly. This way no SQL is actually created0607# in the SQLObject class.06080609def_SO_update(self,so,values):0610self.query("UPDATE %s SET %s WHERE %s = (%s)"%0611(so.sqlmeta.table,0612", ".join(["%s = (%s)"%(dbName,self.sqlrepr(value))0613fordbName,valueinvalues]),0614so.sqlmeta.idName,0615self.sqlrepr(so.id)))06160617def_SO_selectOne(self,so,columnNames):0618returnself._SO_selectOneAlt(so,columnNames,so.q.id==so.id)06190620def_SO_selectOneAlt(self,so,columnNames,condition):0621ifcolumnNames:0622columns=[isinstance(x,string_type)and0623sqlbuilder.SQLConstant(x)or0624xforxincolumnNames]0625else:0626columns=None0627returnself.queryOne(self.sqlrepr(sqlbuilder.Select(0628columns,staticTables=[so.sqlmeta.table],clause=condition)))06290630def_SO_delete(self,so):0631self.query("DELETE FROM %s WHERE %s = (%s)"%0632(so.sqlmeta.table,0633so.sqlmeta.idName,0634self.sqlrepr(so.id)))06350636def_SO_selectJoin(self,soClass,column,value):0637returnself.queryAll("SELECT %s FROM %s WHERE %s = (%s)"%0638(soClass.sqlmeta.idName,0639soClass.sqlmeta.table,0640column,0641self.sqlrepr(value)))06420643def_SO_intermediateJoin(self,table,getColumn,joinColumn,value):0644returnself.queryAll("SELECT %s FROM %s WHERE %s = (%s)"%0645(getColumn,0646table,0647joinColumn,0648self.sqlrepr(value)))06490650def_SO_intermediateDelete(self,table,firstColumn,firstValue,0651secondColumn,secondValue):0652self.query("DELETE FROM %s WHERE %s = (%s) AND %s = (%s)"%0653(table,0654firstColumn,0655self.sqlrepr(firstValue),0656secondColumn,0657self.sqlrepr(secondValue)))06580659def_SO_intermediateInsert(self,table,firstColumn,firstValue,0660secondColumn,secondValue):0661self.query("INSERT INTO %s (%s, %s) VALUES (%s, %s)"%0662(table,0663firstColumn,0664secondColumn,0665self.sqlrepr(firstValue),0666self.sqlrepr(secondValue)))06670668def_SO_columnClause(self,soClass,kw):0669from.importmain0670ops={None:"IS"}0671data=[]0672if'id'inkw:0673data.append((soClass.sqlmeta.idName,kw.pop('id')))0674forsoColumninsoClass.sqlmeta.columnList:0675key=soColumn.name0676ifkeyinkw:0677val=kw.pop(key)0678ifsoColumn.from_python:0679val=soColumn.from_python(0680val,0681sqlbuilder.SQLObjectState(soClass,connection=self))0682data.append((soColumn.dbName,val))0683elifsoColumn.foreignNameinkw:0684obj=kw.pop(soColumn.foreignName)0685ifisinstance(obj,main.SQLObject):0686data.append((soColumn.dbName,obj.id))0687else:0688data.append((soColumn.dbName,obj))0689ifkw:0690# pick the first key from kw to use to raise the error,0691raiseTypeError("got an unexpected keyword argument(s): "0692"%r"%kw.keys())06930694ifnotdata:0695returnNone0696return' AND '.join(0697['%s %s %s'%0698(dbName,ops.get(value,"="),self.sqlrepr(value))0699fordbName,value0700indata])07010702defsqlrepr(self,v):0703returnsqlrepr(v,self.dbName)07040705def__del__(self):0706self.close()07070708defclose(self):0709ifnothasattr(self,'_pool'):0710# Probably there was an exception while creating this0711# instance, so it is incomplete.0712return0713ifnotself._pool:0714return0715self._poolLock.acquire()0716try:0717ifnotself._pool:# _pool could be filled in a different thread0718return0719conns=self._pool[:]0720self._pool[:]=[]0721forconninconns:0722try:0723conn.close()0724exceptself.module.Error:0725pass0726delconn0727delconns0728finally:0729self._poolLock.release()07300731defcreateEmptyDatabase(self):0732"""0733 Create an empty database.0734 """0735raiseNotImplementedError073607370738classIteration(object):07390740def__init__(self,dbconn,rawconn,select,keepConnection=False):0741self.dbconn=dbconn0742self.rawconn=rawconn0743self.select=select0744self.keepConnection=keepConnection0745self.cursor=rawconn.cursor()0746self.query=self.dbconn.queryForSelect(select)0747ifdbconn.debug:0748dbconn.printDebug(rawconn,self.query,'Select')0749self.dbconn._executeRetry(self.rawconn,self.cursor,self.query)07500751def__iter__(self):0752returnself07530754def__next__(self):0755returnself.next()07560757defnext(self):0758result=self.cursor.fetchone()0759ifresultisNone:0760self._cleanup()0761raiseStopIteration0762ifresult[0]isNone:0763returnNone0764ifself.select.ops.get('lazyColumns',0):0765obj=self.select.sourceClass.get(result[0],0766connection=self.dbconn)0767returnobj0768else:0769obj=self.select.sourceClass.get(result[0],0770selectResults=result[1:],0771connection=self.dbconn)0772returnobj07730774def_cleanup(self):0775ifgetattr(self,'query',None)isNone:0776# already cleaned up0777return0778self.query=None0779ifnotself.keepConnection:0780self.dbconn.releaseConnection(self.rawconn)0781self.dbconn=self.rawconn=self.select=self.cursor=None07820783def__del__(self):0784self._cleanup()078507860787classTransaction(object):07880789def__init__(self,dbConnection):0790# this is to skip __del__ in case of an exception in this __init__0791self._obsolete=True0792self._dbConnection=dbConnection0793self._connection=dbConnection.getConnection()0794self._dbConnection._setAutoCommit(self._connection,0)0795self.cache=CacheSet(cache=dbConnection.doCache)0796self._deletedCache={}0797self._obsolete=False07980799defassertActive(self):0800assertnotself._obsolete,"This transaction has already gone through ROLLBACK; ""begin another transaction"08030804defquery(self,s):0805self.assertActive()0806returnself._dbConnection._query(self._connection,s)08070808defqueryAll(self,s):0809self.assertActive()0810returnself._dbConnection._queryAll(self._connection,s)08110812defqueryOne(self,s):0813self.assertActive()0814returnself._dbConnection._queryOne(self._connection,s)08150816defqueryInsertID(self,soInstance,id,names,values):0817self.assertActive()0818returnself._dbConnection._queryInsertID(0819self._connection,soInstance,id,names,values)08200821defiterSelect(self,select):0822self.assertActive()0823# We can't keep the cursor open with results in a transaction,0824# because we might want to use the connection while we're0825# still iterating through the results.0826# @@: But would it be okay for psycopg, with threadsafety0827# level 2?0828returniter(list(select.IterationClass(self,self._connection,0829select,keepConnection=True)))08300831def_SO_delete(self,inst):0832cls=inst.__class__.__name__0833ifclsnotinself._deletedCache:0834self._deletedCache[cls]=[]0835self._deletedCache[cls].append(inst.id)0836ifPY2:0837meth=types.MethodType(self._dbConnection._SO_delete.__func__,0838self,self.__class__)0839else:0840meth=types.MethodType(self._dbConnection._SO_delete.__func__,0841self)0842returnmeth(inst)08430844defcommit(self,close=False):0845ifself._obsolete:0846# @@: is it okay to get extraneous commits?0847return0848ifself._dbConnection.debug:0849self._dbConnection.printDebug(self._connection,'','COMMIT')0850self._connection.commit()0851subCaches=[(sub[0],sub[1].allIDs())0852forsubinself.cache.allSubCachesByClassNames().items()]0853subCaches.extend([(x[0],x[1])forxinself._deletedCache.items()])0854forcls,idsinsubCaches:0855foridinlist(ids):0856inst=self._dbConnection.cache.tryGetByName(id,cls)0857ifinstisnotNone:0858inst.expire()0859ifclose:0860self._makeObsolete()08610862defrollback(self):0863ifself._obsolete:0864# @@: is it okay to get extraneous rollbacks?0865return0866ifself._dbConnection.debug:0867self._dbConnection.printDebug(self._connection,'','ROLLBACK')0868subCaches=[(sub,sub.allIDs())forsubinself.cache.allSubCaches()]0869self._connection.rollback()08700871forsubCache,idsinsubCaches:0872foridinlist(ids):0873inst=subCache.tryGet(id)0874ifinstisnotNone:0875inst.expire()0876self._makeObsolete()08770878def__getattr__(self,attr):0879"""0880 If nothing else works, let the parent connection handle it.0881 Except with this transaction as 'self'. Poor man's0882 acquisition? Bad programming? Okay, maybe.0883 """0884self.assertActive()0885attr=getattr(self._dbConnection,attr)0886try:0887func=attr.__func__0888exceptAttributeError:0889ifisinstance(attr,ConnWrapper):0890returnConnWrapper(attr._soClass,self)0891else:0892returnattr0893else:0894ifPY2:0895meth=types.MethodType(func,self,self.__class__)0896else:0897meth=types.MethodType(func,self)0898returnmeth08990900def_makeObsolete(self):0901self._obsolete=True0902ifself._dbConnection.autoCommit:0903self._dbConnection._setAutoCommit(self._connection,1)0904self._dbConnection.releaseConnection(self._connection,0905explicit=True)0906self._connection=None0907self._deletedCache={}09080909defbegin(self):0910# @@: Should we do this, or should begin() be a no-op when we're0911# not already obsolete?0912assertself._obsolete,"You cannot begin a new transaction session ""without rolling back this one"0915self._obsolete=False0916self._connection=self._dbConnection.getConnection()0917self._dbConnection._setAutoCommit(self._connection,0)09180919def__del__(self):0920ifself._obsolete:0921return0922self.rollback()09230924defclose(self):0925raiseTypeError('You cannot just close transaction - '0926'you should either call rollback(), commit() '0927'or commit(close=True) '0928'to close the underlying connection.')092909300931classConnectionHub(object):09320933"""0934 This object serves as a hub for connections, so that you can pass0935 in a ConnectionHub to a SQLObject subclass as though it was a0936 connection, but actually bind a real database connection later.0937 You can also bind connections on a per-thread basis.09380939 You must hang onto the original ConnectionHub instance, as you0940 cannot retrieve it again from the class or instance.09410942 To use the hub, do something like::09430944 hub = ConnectionHub()0945 class MyClass(SQLObject):0946 _connection = hub09470948 hub.threadConnection = connectionFromURI('...')09490950 """09510952def__init__(self):0953self.threadingLocal=threading_local()09540955def__get__(self,obj,type=None):0956# I'm a little surprised we have to do this, but apparently0957# the object's private dictionary of attributes doesn't0958# override this descriptor.0959if(objisnotNone)and'_connection'inobj.__dict__:0960returnobj.__dict__['_connection']0961returnself.getConnection()09620963def__set__(self,obj,value):0964obj.__dict__['_connection']=value09650966defgetConnection(self):0967try:0968returnself.threadingLocal.connection0969exceptAttributeError:0970try:0971returnself.processConnection0972exceptAttributeError:0973raiseAttributeError(0974"No connection has been defined for this thread "0975"or process")09760977defdoInTransaction(self,func,*args,**kw):0978"""0979 This routine can be used to run a function in a transaction,0980 rolling the transaction back if any exception is raised from0981 that function, and committing otherwise.09820983 Use like::09840985 sqlhub.doInTransaction(process_request, os.environ)09860987 This will run ``process_request(os.environ)``. The return0988 value will be preserved.0989 """0990# @@: In Python 2.5, something usable with with: should also0991# be added.0992try:0993old_conn=self.threadingLocal.connection0994old_conn_is_threading=True0995exceptAttributeError:0996old_conn=self.processConnection0997old_conn_is_threading=False0998conn=old_conn.transaction()0999ifold_conn_is_threading:1000self.threadConnection=conn1001else:1002self.processConnection=conn1003try:1004try:1005value=func(*args,**kw)1006except:1007conn.rollback()1008raise1009else:1010conn.commit(close=True)1011returnvalue1012finally:1013ifold_conn_is_threading:1014self.threadConnection=old_conn1015else:1016self.processConnection=old_conn10171018def_set_threadConnection(self,value):1019self.threadingLocal.connection=value10201021def_get_threadConnection(self):1022returnself.threadingLocal.connection10231024def_del_threadConnection(self):1025delself.threadingLocal.connection10261027threadConnection=property(_get_threadConnection,1028_set_threadConnection,1029_del_threadConnection)103010311032classConnectionURIOpener(object):10331034def__init__(self):1035self.schemeBuilders={}1036self.instanceNames={}1037self.cachedURIs={}10381039defregisterConnection(self,schemes,builder):1040foruriSchemeinschemes:1041asserturiSchemenotinself.schemeBuildersorself.schemeBuilders[uriScheme]isbuilder,"A driver has already been registered ""for the URI scheme %s"%uriScheme1045self.schemeBuilders[uriScheme]=builder10461047defregisterConnectionInstance(self,inst):1048ifinst.name:1049assert(inst.namenotinself.instanceNamesor1050self.instanceNames[inst.name]iscls# noqa1051),("A instance has already been registered "1052"with the name %s"%inst.name)1053assertinst.name.find(':')==-1,"You cannot include ':' ""in your class names (%r)"%cls.name# noqa1056self.instanceNames[inst.name]=inst10571058defconnectionForURI(self,uri,oldUri=False,**args):1059ifargs:1060if'?'notinuri:1061uri+='?'+urlencode(args)1062else:1063uri+='&'+urlencode(args)1064ifuriinself.cachedURIs:1065returnself.cachedURIs[uri]1066ifuri.find(':')!=-1:1067scheme,rest=uri.split(':',1)1068connCls=self.dbConnectionForScheme(scheme)1069ifoldUri:1070conn=connCls.connectionFromOldURI(uri)1071else:1072conn=connCls.connectionFromURI(uri)1073else:1074# We just have a name, not a URI1075asserturiinself.instanceNames,"No SQLObject driver exists under the name %s"%uri1077conn=self.instanceNames[uri]1078# @@: Do we care if we clobber another connection?1079self.cachedURIs[uri]=conn1080returnconn10811082defdbConnectionForScheme(self,scheme):1083assertschemeinself.schemeBuilders,(1084"No SQLObject driver exists for %s (only %s)"%(1085scheme,1086', '.join(self.schemeBuilders.keys())))1087returnself.schemeBuilders[scheme]()10881089TheURIOpener=ConnectionURIOpener()10901091registerConnection=TheURIOpener.registerConnection1092registerConnectionInstance=TheURIOpener.registerConnectionInstance1093connectionForURI=TheURIOpener.connectionForURI1094dbConnectionForScheme=TheURIOpener.dbConnectionForScheme10951096# Register DB URI schemas -- do import for side effects1097# noqa is a directive for flake8 to ignore seemingly unused imports1098from.importfirebird# noqa1099from.importmaxdb# noqa1100from.importmssql# noqa1101from.importmysql# noqa1102from.importpostgres# noqa1103from.importrdbhost# noqa1104from.importsqlite# noqa1105from.importsybase# noqa