sqlobject-cvs Mailing List for SQLObject (Page 180)
SQLObject is a Python ORM.
Brought to you by:
ianbicking,
phd
You can subscribe to this list here.
2003 |
Jan
|
Feb
|
Mar
(9) |
Apr
(74) |
May
(29) |
Jun
(16) |
Jul
(28) |
Aug
(10) |
Sep
(57) |
Oct
(9) |
Nov
(29) |
Dec
(12) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
2004 |
Jan
(7) |
Feb
(14) |
Mar
(6) |
Apr
(3) |
May
(12) |
Jun
(34) |
Jul
(9) |
Aug
(29) |
Sep
(22) |
Oct
(2) |
Nov
(15) |
Dec
(52) |
2005 |
Jan
(47) |
Feb
(78) |
Mar
(14) |
Apr
(35) |
May
(33) |
Jun
(16) |
Jul
(26) |
Aug
(63) |
Sep
(40) |
Oct
(96) |
Nov
(96) |
Dec
(123) |
2006 |
Jan
(159) |
Feb
(144) |
Mar
(64) |
Apr
(31) |
May
(88) |
Jun
(48) |
Jul
(16) |
Aug
(64) |
Sep
(87) |
Oct
(92) |
Nov
(56) |
Dec
(76) |
2007 |
Jan
(94) |
Feb
(103) |
Mar
(126) |
Apr
(123) |
May
(85) |
Jun
(11) |
Jul
(130) |
Aug
(47) |
Sep
(65) |
Oct
(70) |
Nov
(12) |
Dec
(11) |
2008 |
Jan
(30) |
Feb
(55) |
Mar
(88) |
Apr
(20) |
May
(50) |
Jun
|
Jul
(38) |
Aug
(1) |
Sep
(9) |
Oct
(5) |
Nov
(6) |
Dec
(39) |
2009 |
Jan
(8) |
Feb
(16) |
Mar
(3) |
Apr
(33) |
May
(44) |
Jun
(1) |
Jul
(10) |
Aug
(33) |
Sep
(74) |
Oct
(22) |
Nov
|
Dec
(15) |
2010 |
Jan
(28) |
Feb
(22) |
Mar
(46) |
Apr
(29) |
May
(1) |
Jun
(1) |
Jul
(27) |
Aug
(8) |
Sep
(5) |
Oct
(33) |
Nov
(24) |
Dec
(41) |
2011 |
Jan
(4) |
Feb
(12) |
Mar
(35) |
Apr
(29) |
May
(19) |
Jun
(16) |
Jul
(32) |
Aug
(25) |
Sep
(5) |
Oct
(11) |
Nov
(21) |
Dec
(12) |
2012 |
Jan
(3) |
Feb
(4) |
Mar
(20) |
Apr
(4) |
May
(25) |
Jun
(13) |
Jul
|
Aug
|
Sep
(2) |
Oct
(25) |
Nov
(9) |
Dec
(1) |
2013 |
Jan
(6) |
Feb
(8) |
Mar
|
Apr
(10) |
May
(31) |
Jun
(7) |
Jul
(18) |
Aug
(33) |
Sep
(4) |
Oct
(16) |
Nov
|
Dec
(27) |
2014 |
Jan
(2) |
Feb
|
Mar
|
Apr
(11) |
May
(39) |
Jun
(8) |
Jul
(11) |
Aug
(4) |
Sep
|
Oct
(27) |
Nov
|
Dec
(71) |
2015 |
Jan
(17) |
Feb
(47) |
Mar
(33) |
Apr
|
May
|
Jun
(9) |
Jul
(7) |
Aug
|
Sep
|
Oct
|
Nov
|
Dec
(8) |
2016 |
Jan
(4) |
Feb
(4) |
Mar
|
Apr
|
May
(12) |
Jun
(7) |
Jul
(9) |
Aug
(31) |
Sep
(8) |
Oct
(3) |
Nov
(15) |
Dec
(1) |
2017 |
Jan
(13) |
Feb
(7) |
Mar
(14) |
Apr
(8) |
May
(10) |
Jun
(4) |
Jul
(2) |
Aug
(1) |
Sep
|
Oct
(8) |
Nov
(4) |
Dec
(5) |
2018 |
Jan
(2) |
Feb
(8) |
Mar
|
Apr
(4) |
May
|
Jun
(6) |
Jul
|
Aug
(1) |
Sep
|
Oct
|
Nov
(1) |
Dec
|
2019 |
Jan
(1) |
Feb
(16) |
Mar
(1) |
Apr
(3) |
May
(5) |
Jun
(1) |
Jul
|
Aug
|
Sep
(2) |
Oct
|
Nov
(1) |
Dec
(3) |
2020 |
Jan
|
Feb
|
Mar
|
Apr
(1) |
May
(1) |
Jun
|
Jul
|
Aug
(1) |
Sep
|
Oct
(2) |
Nov
|
Dec
(2) |
2021 |
Jan
|
Feb
(2) |
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
(1) |
Nov
(1) |
Dec
|
2022 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
(6) |
Oct
(1) |
Nov
(1) |
Dec
(4) |
2023 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
(1) |
Aug
(3) |
Sep
(2) |
Oct
(2) |
Nov
(4) |
Dec
|
2024 |
Jan
|
Feb
(2) |
Mar
|
Apr
|
May
|
Jun
|
Jul
(1) |
Aug
|
Sep
(1) |
Oct
|
Nov
|
Dec
(9) |
2025 |
Jan
|
Feb
(4) |
Mar
(2) |
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
From: <sub...@co...> - 2004-05-27 10:00:57
|
Author: ahmedmo Date: 2004-05-27 02:00:07 -0400 (Thu, 27 May 2004) New Revision: 114 Added: trunk/SQLObject/SQLObject/ trunk/SQLObject/SQLObject/sqlobject/ Log: Initial Import |
From: <sub...@co...> - 2004-05-27 09:55:29
|
Author: ahmedmo Date: 2004-05-27 01:54:38 -0400 (Thu, 27 May 2004) New Revision: 112 Added: trunk/SQLObject/conf/ trunk/SQLObject/dav/ trunk/SQLObject/db/ trunk/SQLObject/hooks/ trunk/SQLObject/locks/ Log: Initial Import |
From: <sub...@co...> - 2004-05-27 09:50:25
|
Author: ahmedmo Date: 2004-05-27 01:49:33 -0400 (Thu, 27 May 2004) New Revision: 109 Added: trunk/SQLObject/SQLOBJECT/ Log: Initial Import |
From: <sub...@co...> - 2004-05-26 15:52:39
|
Author: ianb Date: 2004-05-26 07:51:52 -0400 (Wed, 26 May 2004) New Revision: 107 Modified: trunk/SQLObject/sqlobject/firebird/firebirdconnection.py Log: Translate '/' in the path in a Firebird connection URI to os.path.sep, i.e., '\' on Windows, since Firebird doesn't handle /'s appropriately on that platform. Modified: trunk/SQLObject/sqlobject/firebird/firebirdconnection.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- trunk/SQLObject/sqlobject/firebird/firebirdconnection.py 2004-05-25 1= 5:11:52 UTC (rev 106) +++ trunk/SQLObject/sqlobject/firebird/firebirdconnection.py 2004-05-26 1= 1:51:52 UTC (rev 107) @@ -1,6 +1,7 @@ from sqlobject.dbconnection import DBAPI kinterbasdb =3D None import re +import os =20 class FirebirdConnection(DBAPI): =20 @@ -33,6 +34,7 @@ password =3D 'masterkey' if not auth: auth=3D'sysdba' + path =3D path.replace('/', os.path.sep) return cls(host, db=3Dpath, user=3Dauth, passwd=3Dpassword, **ar= gs) connectionFromURI =3D classmethod(connectionFromURI) =20 |
From: <sub...@co...> - 2004-05-25 19:12:36
|
Author: iansparks Date: 2004-05-25 11:11:52 -0400 (Tue, 25 May 2004) New Revision: 106 Modified: trunk/SQLObject/sqlobject/col.py Log: Added support for firebird for Datetime and Date columns. Improved firebird support for enum columns. Modified: trunk/SQLObject/sqlobject/col.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- trunk/SQLObject/sqlobject/col.py 2004-04-25 18:02:09 UTC (rev 105) +++ trunk/SQLObject/sqlobject/col.py 2004-05-25 15:11:52 UTC (rev 106) @@ -221,8 +221,8 @@ # them differently for Enum columns. if not isinstance(self, SOEnumCol): return ' '.join([self.dbName, self._firebirdType()] + self._= extraSQL()) - else: - return ' '.join([self.dbName] + self._extraSQL() + [self._fi= rebirdType()]) + else: + return ' '.join([self.dbName] + [self._firebirdType()[0]] + = self._extraSQL() + [self._firebirdType()[1]]) =20 def __get__(self, obj, type=3DNone): if obj is None: @@ -472,7 +472,11 @@ return self._postgresType() =20 def _firebirdType(self): - return self._postgresType() + length =3D max(map(len, self.enumValues)) + enumValues =3D ', '.join([sqlbuilder.sqlrepr(v, 'firebird') for = v in self.enumValues]) + checkConstraint =3D "CHECK (%s in (%s))" % (self.dbName, enumVal= ues) + #NB. Return a tuple, not a string here + return "VARCHAR(%i)" % (length), checkConstraint =20 class EnumCol(Col): baseClass =3D SOEnumCol @@ -493,7 +497,10 @@ =20 def _sqliteType(self): return 'TIMESTAMP' - + + def _firebirdType(self): + return 'TIMESTAMP' + class DateTimeCol(Col): baseClass =3D SODateTimeCol =20 @@ -510,6 +517,9 @@ =20 def _sybaseType(self): return self._postgresType() + + def _firebirdType(self): + return 'DATE' =20 class DateCol(Col): baseClass =3D SODateCol |
From: <sub...@co...> - 2004-04-25 22:00:09
|
Author: sweafty Date: 2004-04-25 14:02:09 -0400 (Sun, 25 Apr 2004) New Revision: 105 Modified: trunk/SQLObject/sqlobject/col.py Log: Adding SQLite DateTime type support Modified: trunk/SQLObject/sqlobject/col.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- trunk/SQLObject/sqlobject/col.py 2004-04-23 18:04:25 UTC (rev 104) +++ trunk/SQLObject/sqlobject/col.py 2004-04-25 18:02:09 UTC (rev 105) @@ -491,6 +491,9 @@ def _sybaseType(self): return 'DATETIME' =20 + def _sqliteType(self): + return 'TIMESTAMP' + class DateTimeCol(Col): baseClass =3D SODateTimeCol =20 |
From: <sub...@co...> - 2004-04-23 22:02:09
|
Author: ianb Date: 2004-04-23 14:04:25 -0400 (Fri, 23 Apr 2004) New Revision: 104 Modified: trunk/SQLObject/sqlobject/main.py trunk/SQLObject/tests/test.py Log: Fixed bug related to orderBy=3D'-column_name' Modified: trunk/SQLObject/sqlobject/main.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- trunk/SQLObject/sqlobject/main.py 2004-04-14 16:38:33 UTC (rev 103) +++ trunk/SQLObject/sqlobject/main.py 2004-04-23 18:04:25 UTC (rev 104) @@ -1095,6 +1095,7 @@ orderBy =3D map(self._mungeOrderBy, orderBy) else: orderBy =3D self._mungeOrderBy(orderBy) + print "OUT: %r; in: %r" % (sourceClass.sqlrepr(orderBy), sourceC= lass.sqlrepr(self.ops['orderBy'])) self.ops['dbOrderBy'] =3D orderBy if ops.has_key('connection') and ops['connection'] is None: del ops['connection'] @@ -1105,17 +1106,20 @@ desc =3D True else: desc =3D False - if self.sourceClass._SO_columnDict.has_key(orderBy): - val =3D self.sourceClass._SO_columnDict[orderBy].dbName - if desc: - return '-' + val + if isinstance(orderBy, (str, unicode)): + if self.sourceClass._SO_columnDict.has_key(orderBy): + val =3D self.sourceClass._SO_columnDict[orderBy].dbName + if desc: + return '-' + val + else: + return val else: - return val + if desc: + return '-' + orderBy + else: + return orderBy else: - if desc: - return sqlbuilder.DESC(orderBy) - else: - return orderBy + return orderBy =20 def clone(self, **newOps): ops =3D self.ops.copy() Modified: trunk/SQLObject/tests/test.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- trunk/SQLObject/tests/test.py 2004-04-14 16:38:33 UTC (rev 103) +++ trunk/SQLObject/tests/test.py 2004-04-23 18:04:25 UTC (rev 104) @@ -259,33 +259,56 @@ =20 _table =3D 'names_table' =20 - fname =3D StringCol(length=3D30) - lname =3D StringCol(length=3D30) + firstName =3D StringCol(length=3D30) + lastName =3D StringCol(length=3D30) =20 - _defaultOrder =3D ['lname', 'fname'] + _defaultOrder =3D ['lastName', 'firstName'] =20 class NamesTest(SQLObjectTest): =20 classes =3D [Names] =20 def inserts(self): - for fname, lname in [('aj', 'baker'), ('joe', 'robbins'), - ('tim', 'jackson'), ('joe', 'baker'), - ('zoe', 'robbins')]: - Names(fname=3Dfname, lname=3Dlname) + for firstName, lastName in [('aj', 'baker'), ('joe', 'robbins'), + ('tim', 'jackson'), ('joe', 'baker')= , + ('zoe', 'robbins')]: + Names(firstName=3DfirstName, lastName=3DlastName) =20 def testDefaultOrder(self): - self.assertEqual([(n.fname, n.lname) for n in Names.select()], + self.assertEqual([(n.firstName, n.lastName) for n in Names.selec= t()], [('aj', 'baker'), ('joe', 'baker'), ('tim', 'jackson'), ('joe', 'robbins'), ('zoe', 'robbins')]) =20 def testOtherOrder(self): - self.assertEqual([(n.fname, n.lname) for n in Names.select().ord= erBy(['fname', 'lname'])], + self.assertEqual([(n.firstName, n.lastName) for n in Names.selec= t().orderBy(['firstName', 'lastName'])], [('aj', 'baker'), ('joe', 'baker'), ('joe', 'robbins'), ('tim', 'jackson'), ('zoe', 'robbins')]) =20 + def testUntranslatedColumnOrder(self): + self.assertEqual([(n.firstName, n.lastName) for n in Names.selec= t().orderBy(['first_name', 'last_name'])], + [('aj', 'baker'), ('joe', 'baker'), + ('joe', 'robbins'), ('tim', 'jackson'), + ('zoe', 'robbins')]) + + def testSingleUntranslatedColumnOrder(self): + self.assertEqual([n.firstName for n in + Names.select().orderBy('firstName')], + ['aj', 'joe', 'joe', 'tim', 'zoe']) + self.assertEqual([n.firstName for n in + Names.select().orderBy('first_name')], + ['aj', 'joe', 'joe', 'tim', 'zoe']) + self.assertEqual([n.firstName for n in + Names.select().orderBy('-firstName')], + ['zoe', 'tim', 'joe', 'joe', 'aj']) + self.assertEqual([n.firstName for n in + Names.select().orderBy('-first_name')], + ['zoe', 'tim', 'joe', 'joe', 'aj']) + self.assertEqual([n.firstName for n in + Names.select().orderBy(Names.q.firstName)], + ['aj', 'joe', 'joe', 'tim', 'zoe']) + ######################################## ## Select results ######################################## |
From: <sub...@co...> - 2004-04-14 20:35:22
|
Author: sweafty Date: 2004-04-14 12:38:33 -0400 (Wed, 14 Apr 2004) New Revision: 103 Modified: trunk/SQLObject/sqlobject/dbconnection.py trunk/SQLObject/sqlobject/main.py trunk/SQLObject/tests/test.py Log: Accumulate functions in SelectResults: sum(), count() Modified: trunk/SQLObject/sqlobject/dbconnection.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- trunk/SQLObject/sqlobject/dbconnection.py 2004-04-05 02:41:54 UTC (re= v 102) +++ trunk/SQLObject/sqlobject/dbconnection.py 2004-04-14 16:38:33 UTC (re= v 103) @@ -236,9 +236,14 @@ return Iteration(self, self.getConnection(), select, keepConnection=3DFalse) =20 - def countSelect(self, select): - q =3D "SELECT COUNT(*) FROM %s WHERE" % \ - ", ".join(select.tables) + def accumulateSelect(self, select, expression): + """ Apply an accumulate function (like SUM, COUNT, ..) + to the select object. + Return the value resulting from the SQL accumulate function + as an integer. + """ + q =3D "SELECT %s" % expression + q +=3D " FROM %s WHERE" % ", ".join(select.tables) q =3D self._addWhereClause(select, q, limit=3D0, order=3D0) val =3D int(self.queryOne(q)[0]) return val Modified: trunk/SQLObject/sqlobject/main.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- trunk/SQLObject/sqlobject/main.py 2004-04-05 02:41:54 UTC (rev 102) +++ trunk/SQLObject/sqlobject/main.py 2004-04-14 16:38:33 UTC (rev 103) @@ -1191,15 +1191,35 @@ conn =3D self.ops.get('connection', self.sourceClass._connection= ) return conn.iterSelect(self) =20 + def accumulate(self, expression): + """ Use an accumulate expression to select result + using another SQL select through current + connection. + Return the accumulate result + """ + conn =3D self.ops.get('connection', self.sourceClass._connection= ) + return conn.accumulateSelect(self,expression) + def count(self): - conn =3D self.ops.get('connection', self.sourceClass._connection= ) - count =3D conn.countSelect(self) + """ Counting elements of current select results """ + count =3D self.accumulate('COUNT(*)') if self.ops.get('start'): count -=3D self.ops['start'] if self.ops.get('end'): count =3D min(self.ops['end'] - self.ops.get('start', 0), co= unt) return count =20 + def sum(self, attribute): + """ Making the sum of a given select result attribute. + `attribute` can be a column name (like 'a_column') + or a dot-q attribute (like Table.q.aColumn) + """ + if type(attribute) =3D=3D type(''): + expression =3D 'SUM(%s)' % attribute + else: + expression =3D sqlbuilder.func.SUM(attribute) + return self.accumulate(expression) + class SQLObjectState(object): =20 def __init__(self, soObject): Modified: trunk/SQLObject/tests/test.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- trunk/SQLObject/tests/test.py 2004-04-05 02:41:54 UTC (rev 102) +++ trunk/SQLObject/tests/test.py 2004-04-14 16:38:33 UTC (rev 103) @@ -499,6 +499,17 @@ def counterEqual(self, counters, value): self.assertEquals([(c.n1, c.n2) for c in counters], value) =20 + def accumulateEqual(self, func, counters, value): + self.assertEqual(func([ c.n1 for c in counters]), value) + + def test1(self): + self.accumulateEqual(sum,Counter2.select('n1', orderBy=3D'n1'), + sum(range(10)) * 10) + + def test2(self): + self.accumulateEqual(len,Counter2.select('all'), 100) + + =20 ######################################## ## Dynamic column tests ######################################## |
From: <sub...@co...> - 2004-03-31 06:50:28
|
Author: ianb Date: 2004-03-30 21:55:04 -0500 (Tue, 30 Mar 2004) New Revision: 84 Modified: trunk/SQLObject/sqlobject/dbconnection.py trunk/SQLObject/sqlobject/dbm/dbmconnection.py trunk/SQLObject/sqlobject/firebird/firebirdconnection.py trunk/SQLObject/sqlobject/mysql/mysqlconnection.py trunk/SQLObject/sqlobject/postgres/pgconnection.py trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py trunk/SQLObject/sqlobject/sybase/sybaseconnection.py Log: * Fixed threading issue with Iterator, to match fix in 0.5 (connections were being released multiple times) * Expanded URIs to allow keyword (GET) arguments * Put in (optionally) more thorough debugging output Modified: trunk/SQLObject/sqlobject/dbconnection.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- trunk/SQLObject/sqlobject/dbconnection.py 2004-03-31 02:48:02 UTC (re= v 83) +++ trunk/SQLObject/sqlobject/dbconnection.py 2004-03-31 02:55:04 UTC (re= v 84) @@ -13,6 +13,7 @@ import col from joins import sorter from converters import sqlrepr +import urllib =20 warnings.filterwarnings("ignore", "DB-API extension cursor.lastrowid use= d") =20 @@ -21,10 +22,12 @@ class DBConnection: =20 def __init__(self, name=3DNone, debug=3DFalse, debugOutput=3DFalse, - cache=3DTrue, style=3DNone, autoCommit=3DTrue): + cache=3DTrue, style=3DNone, autoCommit=3DTrue, + debugThreading=3DFalse): self.name =3D name self.debug =3D debug self.debugOutput =3D debugOutput + self.debugThreading =3D debugThreading self.cache =3D CacheSet(cache=3Dcache) self.doCache =3D cache self.style =3D style @@ -81,7 +84,15 @@ else: user =3D password =3D None path =3D '/' + rest - return user, password, host, path + args =3D {} + if path.find('?') !=3D -1: + path, arglist =3D path.split('?', 1) + arglist =3D arglist.split('&') + for single in arglist: + argname, argvalue =3D single.split('=3D', 1) + argvalue =3D urllib.unquote(argvalue) + args[argname] =3D argvalue + return user, password, host, path, args _parseURI =3D staticmethod(_parseURI) =20 class DBAPI(DBConnection): @@ -117,11 +128,26 @@ self._connectionNumbers[id(newConn)] =3D self._connectio= nCount self._connectionCount +=3D 1 val =3D self._pool.pop() + if self.debug: + s =3D 'ACQUIRE' + if self._pool is not None: + s +=3D ' pool=3D[%s]' % ', '.join([str(self._connect= ionNumbers[id(v)]) for v in self._pool]) + self.printDebug(val, s, 'Pool') return val finally: self._poolLock.release() =20 def releaseConnection(self, conn, explicit=3DFalse): + if self.debug: + if explicit: + s =3D 'RELEASE (explicit)' + else: + s =3D 'RELEASE (implicit, autocommit=3D%s)' % self.autoC= ommit + if self._pool is None: + s +=3D ' no pooling' + else: + s +=3D ' pool=3D[%s]' % ', '.join([str(self._connectionN= umbers[id(v)]) for v in self._pool]) + self.printDebug(conn, s, 'Pool') if self.supportTransactions: if self.autoCommit =3D=3D 'exception': if self.debug: @@ -137,7 +163,11 @@ self.printDebug(conn, 'auto', 'ROLLBACK') conn.rollback() if self._pool is not None: - self._pool.append(conn) + if conn not in self._pool: + # @@: We can get duplicate releasing of connections with + # the __del__ in Iteration (unfortunately, not sure why + # it happens) + self._pool.append(conn) =20 def printDebug(self, conn, s, name, type=3D'query'): if type =3D=3D 'query': @@ -147,12 +177,20 @@ s =3D repr(s) n =3D self._connectionNumbers[id(conn)] spaces =3D ' '*(8-len(name)) - print '%(n)2i/%(name)s%(spaces)s%(sep)s %(s)s' % locals() + if self.debugThreading: + threadName =3D threading.currentThread().getName() + threadName =3D (':' + threadName + ' '*(8-len(threadName))) + else: + threadName =3D '' + print '%(n)2i%(threadName)s/%(name)s%(spaces)s%(sep)s %(s)s' % l= ocals() =20 + def _executeRetry(self, conn, cursor, query): + return cursor.execute(query) + def _query(self, conn, s): if self.debug: self.printDebug(conn, s, 'Query') - conn.cursor().execute(s) + self._executeRetry(conn, conn.cursor(), s) =20 def query(self, s): return self._runWithConnection(self._query, s) @@ -161,7 +199,7 @@ if self.debug: self.printDebug(conn, s, 'QueryAll') c =3D conn.cursor() - c.execute(s) + self._executeRetry(conn, c, s) value =3D c.fetchall() if self.debugOutput: self.printDebug(conn, value, 'QueryAll', 'result') @@ -174,7 +212,7 @@ if self.debug: self.printDebug(conn, s, 'QueryOne') c =3D conn.cursor() - c.execute(s) + self._executeRetry(conn, c, s) value =3D c.fetchone() if self.debugOutput: self.printDebug(conn, value, 'QueryOne', 'result') @@ -390,13 +428,12 @@ self.query =3D self.dbconn.queryForSelect(select) if dbconn.debug: dbconn.printDebug(rawconn, self.query, 'Select') - self.cursor.execute(self.query) + self.dbconn._executeRetry(self.rawconn, self.cursor, self.query) =20 def next(self): result =3D self.cursor.fetchone() if result is None: - if not self.keepConnection: - self.dbconn.releaseConnection(self.rawconn) + self._cleanup() raise StopIteration if self.select.ops.get('lazyColumns', 0): obj =3D self.select.sourceClass.get(result[0], connection=3D= self.dbconn) @@ -405,11 +442,20 @@ obj =3D self.select.sourceClass.get(result[0], selectResults= =3Dresult[1:], connection=3Dself.dbconn) return obj =20 - def __del__(self): + def _cleanup(self): + if self.query is None: + # already cleaned up + return + self.query =3D None if not self.keepConnection: self.dbconn.releaseConnection(self.rawconn) + self.dbconn =3D self.rawconn =3D self.select =3D self.cursor =3D= None =20 + def __del__(self): + self._cleanup() + =20 =20 + class Transaction(object): =20 def __init__(self, dbConnection): @@ -441,8 +487,13 @@ =20 def iterSelect(self, select): self.assertActive() - return Iteration(self, self._connection, - select, keepConnection=3DTrue) + # We can't keep the cursor open with results in a transaction, + # because we might want to use the connection while we're + # still iterating through the results. + # @@: But would it be okay for psycopg, with threadsafety + # level 2? + return list(Iteration(self, self._connection, + select, keepConnection=3DTrue)) =20 def commit(self): if self._obsolete: Modified: trunk/SQLObject/sqlobject/dbm/dbmconnection.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- trunk/SQLObject/sqlobject/dbm/dbmconnection.py 2004-03-31 02:48:02 UT= C (rev 83) +++ trunk/SQLObject/sqlobject/dbm/dbmconnection.py 2004-03-31 02:55:04 UT= C (rev 84) @@ -127,12 +127,12 @@ FileConnection.__init__(self, **kw) =20 def connectionFromURI(cls, uri): - user, password, host, path =3D self._parseURI(uri, expectHost=3D= False) + user, password, host, path, args =3D self._parseURI(uri, expectH= ost=3DFalse) assert host is None assert user is None and password is None, \ - "SQLite cannot accept usernames or passwords" + "DBM cannot accept usernames or passwords" path =3D '/' + path - return cls(path) + return cls(path, **args) connectionFromURI =3D classmethod(connectionFromURI) =20 def _newID(self, table): Modified: trunk/SQLObject/sqlobject/firebird/firebirdconnection.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- trunk/SQLObject/sqlobject/firebird/firebirdconnection.py 2004-03-31 0= 2:48:02 UTC (rev 83) +++ trunk/SQLObject/sqlobject/firebird/firebirdconnection.py 2004-03-31 0= 2:55:04 UTC (rev 84) @@ -28,12 +28,12 @@ DBAPI.__init__(self, **kw) =20 def connectionFromURI(cls, uri): - auth, password, host, path =3D cls._parseURI(uri) + auth, password, host, path, args =3D cls._parseURI(uri) if not password: password =3D 'masterkey' if not auth: auth=3D'sysdba' - return cls(host, db=3Dpath, user=3Dauth, passwd=3Dpassword) + return cls(host, db=3Dpath, user=3Dauth, passwd=3Dpassword, **ar= gs) connectionFromURI =3D classmethod(connectionFromURI) =20 def _runWithConnection(self, meth, *args): Modified: trunk/SQLObject/sqlobject/mysql/mysqlconnection.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- trunk/SQLObject/sqlobject/mysql/mysqlconnection.py 2004-03-31 02:48:0= 2 UTC (rev 83) +++ trunk/SQLObject/sqlobject/mysql/mysqlconnection.py 2004-03-31 02:55:0= 4 UTC (rev 84) @@ -19,15 +19,27 @@ DBAPI.__init__(self, **kw) =20 def connectionFromURI(cls, uri): - user, password, host, path =3D cls._parseURI(uri) + user, password, host, path, args =3D cls._parseURI(uri) return cls(db=3Dpath.strip('/'), user=3Duser or '', passwd=3Dpas= sword or '', - host=3Dhost or 'localhost') + host=3Dhost or 'localhost', **args) connectionFromURI =3D classmethod(connectionFromURI) =20 def makeConnection(self): return MySQLdb.connect(host=3Dself.host, db=3Dself.db, user=3Dself.user, passwd=3Dself.passwd) =20 + def _executeRetry(self, conn, cursor, query): + while 1: + try: + return cursor.execute(query) + except MySQLdb.OperationalError, e: + if e.args[0] =3D=3D 2013: + # This is a=20 + if self.debug: + self.printDebug(conn, str(e), 'ERROR') + else: + raise + def _queryInsertID(self, conn, table, idName, id, names, values): c =3D conn.cursor() if id is not None: @@ -36,7 +48,7 @@ q =3D self._insertSQL(table, names, values) if self.debug: self.printDebug(conn, q, 'QueryIns') - c.execute(q) + self._executeRetry(conn, c, q) if id is None: id =3D c.insert_id() if self.debugOutput: Modified: trunk/SQLObject/sqlobject/postgres/pgconnection.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- trunk/SQLObject/sqlobject/postgres/pgconnection.py 2004-03-31 02:48:0= 2 UTC (rev 83) +++ trunk/SQLObject/sqlobject/postgres/pgconnection.py 2004-03-31 02:55:0= 4 UTC (rev 84) @@ -42,16 +42,15 @@ DBAPI.__init__(self, **kw) =20 def connectionFromURI(cls, uri): - user, password, host, path =3D cls._parseURI(uri) + user, password, host, path, args =3D cls._parseURI(uri) path =3D path.strip('/') - return cls(host=3Dhost, db=3Dpath, user=3Duser, passwd=3Dpasswor= d) + return cls(host=3Dhost, db=3Dpath, user=3Duser, passwd=3Dpasswor= d, **args) connectionFromURI =3D classmethod(connectionFromURI) =20 def _setAutoCommit(self, conn, auto): conn.autocommit(auto) =20 def makeConnection(self): - print 'DSN', self.dsn conn =3D self.pgmodule.connect(self.dsn) if self.autoCommit: conn.autocommit(1) Modified: trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py 2004-03-31 02:48= :02 UTC (rev 83) +++ trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py 2004-03-31 02:55= :04 UTC (rev 84) @@ -21,10 +21,10 @@ DBAPI.__init__(self, **kw) =20 def connectionFromURI(cls, uri): - user, password, host, path =3D cls._parseURI(uri) + user, password, host, path, args =3D cls._parseURI(uri) assert host is None, "SQLite can only be used locally (with a UR= I like sqlite:///file or sql:/file, not %r)" % uri assert user is None and password is None, "You may not provide u= sernames or passwords for SQLite databases" - return cls(filename=3D'/' + path) + return cls(filename=3D'/' + path, **args) connectionFromURI =3D classmethod(connectionFromURI) =20 def _setAutoCommit(self, conn, auto): Modified: trunk/SQLObject/sqlobject/sybase/sybaseconnection.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- trunk/SQLObject/sqlobject/sybase/sybaseconnection.py 2004-03-31 02:48= :02 UTC (rev 83) +++ trunk/SQLObject/sqlobject/sybase/sybaseconnection.py 2004-03-31 02:55= :04 UTC (rev 84) @@ -26,9 +26,9 @@ DBAPI.__init__(self, **kw) =20 def connectionFromURI(cls, uri): - user, password, host, path =3D cls._parseURI(uri) + user, password, host, path, args =3D cls._parseURI(uri) return cls(user=3Duser, passwd=3Dpassword, host=3Dhost or 'local= host', - db=3Dpath) + db=3Dpath, **args) connectionFromURI =3D classmethod(connectionFromURI) =20 def insert_id(self, conn): |
From: <sub...@co...> - 2004-03-31 06:43:27
|
Author: ianb Date: 2004-03-30 21:48:02 -0500 (Tue, 30 Mar 2004) New Revision: 83 Modified: branches/SQLObject/0.5/SQLObject/DBConnection.py Log: Fixed another (blech) threading issue with iterators, where a=20 connection could be returned to the pool multiple times, and then be fetched from the pool by different threads. Modified: branches/SQLObject/0.5/SQLObject/DBConnection.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- branches/SQLObject/0.5/SQLObject/DBConnection.py 2004-03-31 02:47:19 = UTC (rev 82) +++ branches/SQLObject/0.5/SQLObject/DBConnection.py 2004-03-31 02:48:02 = UTC (rev 83) @@ -105,7 +105,11 @@ self.printDebug(conn, 'auto', 'ROLLBACK') conn.rollback() if self._pool is not None: - self._pool.append(conn) + if conn not in self._pool: + # @@: We can get duplicate releasing of connections with + # the __del__ in Iteration (unfortunately, not sure why + # it happens) + self._pool.append(conn) =20 def printDebug(self, conn, s, name, type=3D'query'): if type =3D=3D 'query': @@ -363,8 +367,7 @@ def next(self): result =3D self.cursor.fetchone() if result is None: - if not self.keepConnection: - self.dbconn.releaseConnection(self.rawconn) + self._cleanup() raise StopIteration if self.select.ops.get('lazyColumns', 0): obj =3D self.select.sourceClass(result[0], connection=3Dself= .dbconn) @@ -373,10 +376,18 @@ obj =3D self.select.sourceClass(result[0], selectResults=3Dr= esult[1:], connection=3Dself.dbconn) return obj =20 - def __del__(self): + def _cleanup(self): + if self.query is None: + # already cleaned up + return + self.query =3D None if not self.keepConnection: self.dbconn.releaseConnection(self.rawconn) + self.dbconn =3D self.rawconn =3D self.select =3D self.cursor =3D= None =20 + def __del__(self): + self._cleanup() + class Transaction(object): =20 def __init__(self, dbConnection): |
From: <sub...@co...> - 2004-03-31 06:42:44
|
Author: ianb Date: 2004-03-30 21:47:19 -0500 (Tue, 30 Mar 2004) New Revision: 82 Modified: branches/SQLObject/0.5/release Log: Simplified Modified: branches/SQLObject/0.5/release =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- branches/SQLObject/0.5/release 2004-03-27 19:44:31 UTC (rev 81) +++ branches/SQLObject/0.5/release 2004-03-31 02:47:19 UTC (rev 82) @@ -1,14 +1,14 @@ #!/bin/sh =20 -echo 'Installing...' -sudo python ./setup.py install -echo 'installation done.' +#echo 'Installing...' +#sudo python ./setup.py install +#echo 'installation done.' =20 -pushd tests > /dev/null -echo 'Testing...' -python ./test.py -echo 'testing done.' -popd +#pushd tests > /dev/null +#echo 'Testing...' +#python ./test.py +#echo 'testing done.' +#popd =20 echo 'Building...' python ./setup.py sdist |
From: <sub...@co...> - 2004-03-18 07:48:31
|
Author: ianb Date: 2004-03-17 22:54:19 -0500 (Wed, 17 Mar 2004) New Revision: 69 Removed: trunk/FormEncode/experimental/FormEncodeKit/Examples/address.py Log: removed dup file Deleted: trunk/FormEncode/experimental/FormEncodeKit/Examples/address.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- trunk/FormEncode/experimental/FormEncodeKit/Examples/address.py 2004-= 03-18 03:49:45 UTC (rev 68) +++ trunk/FormEncode/experimental/FormEncodeKit/Examples/address.py 2004-= 03-18 03:54:19 UTC (rev 69) @@ -1,61 +0,0 @@ -from Component.CPage import CPage - -from formencode import validators, htmlview, schema -from formencode.FormComponent import FormComponent - -class Address(CPage): - - components =3D [FormComponent()] - - class schema(schema.Schema): - - firstName =3D validators.String(notEmpty=3DTrue, htmlview=3Dhtml= view.Text) - lastName =3D validators.String(notEmpty=3DTrue, htmlview=3Dhtmlv= iew.Text) - - class phone(schema.Schema): - repeating =3D 1 - number =3D validators.PhoneNumber(ifEmpty=3DNone, htmlview=3D= htmlview.Text) - type =3D validators.OneOf(['home', 'work'], - htmlview=3Dhtmlview.Select(selections= =3D[('home', 'home'), ('work', 'work')])) - - repeats =3D validators.Int(htmlview=3Dhtmlview.Hidden) - - submit =3D validators.String(htmlview=3Dhtmlview.SubmitButton(de= scription=3D'')) - - def cleanup(cls, value): - value =3D value.copy() - value['phone'] =3D [p for p in value['phone'] if p['number']= is not None] - return value - cleanup =3D classmethod(cleanup) - - def render(cls, value): - s =3D 'Name: %s %s<br>\n' % (value.get('firstName'), - value.get('lastName')) - if value.get('phone', []): - s +=3D '<ul>\n' - for p in value.get('phone'): - s +=3D '<li>%s: %s\n' % (p['type'], p['number']) - s +=3D '</ul>\n' - return s - render =3D classmethod(render) - - def optionsAddRep(cls, value, add=3D1): - value['repeats'] =3D len(value.get('phone', [])) + add - return {'phone': {'repetitions': value['repeats']}} - optionsAddRep =3D classmethod(optionsAddRep) - - def writeContent(self): - ses =3D self.session() - - valid, address =3D self.processForm() - if valid: - address =3D self.schema.cleanup(address) - ses.setValue('address', address) - else: - address =3D ses.value('address', {}) - self.write('<h1>Address:</h1>\n') - self.write(self.schema.render(address)) - self.write('<hr noshade>\n') - options =3D self.schema.optionsAddRep(address) - self.writeForm(defaults=3Daddress, options=3Doptions) - |
From: <sub...@co...> - 2004-03-18 07:44:01
|
Author: ianb Date: 2004-03-17 22:49:45 -0500 (Wed, 17 Mar 2004) New Revision: 68 Modified: trunk/FormEncode/experimental/FormEncodeKit/ trunk/FormEncode/experimental/FormEncodeKit/Examples/ Log: Added svn:ignore's Property changes on: trunk/FormEncode/experimental/FormEncodeKit ___________________________________________________________________ Name: svn:ignore + *.pyc Property changes on: trunk/FormEncode/experimental/FormEncodeKit/Examples ___________________________________________________________________ Name: svn:ignore + *.pyc |
From: <ian...@us...> - 2004-03-09 07:10:09
|
Update of /cvsroot/sqlobject/SQLObject In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv1083 Added Files: DO_NOT_USE_CVS Log Message: Added note about not using CVS --- NEW FILE: DO_NOT_USE_CVS --- Do not use this CVS repository! This CVS repository is defunct! Use the Subversion repository: svn://colorstudy.com/trunk/SQLObject svn://colorstudy.com/branches/SQLObject |
From: <bbo...@us...> - 2004-02-11 16:29:54
|
Update of /cvsroot/sqlobject/SQLObject/tests In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv20327/tests Modified Files: SQLObjectTest.py test.py Log Message: I wrote a unit test to show that, when using a transaction to create a new object, its ForeignKey objects were not using that same connection (which they should have.) Daniel Savard implemented the fix in SQLObject. I'm also deliberately checking in a broken test that demonstrates how we want instance validation to work (confirmed with Ian), in the hopes that a broken test will annoy someone enough to go in and implement in lieu of me/us having the time to do so. :) Index: SQLObjectTest.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/tests/SQLObjectTest.py,v retrieving revision 1.21 retrieving revision 1.22 diff -C2 -d -r1.21 -r1.22 *** SQLObjectTest.py 4 Dec 2003 16:46:31 -0000 1.21 --- SQLObjectTest.py 11 Feb 2004 16:25:40 -0000 1.22 *************** *** 1,6 **** import unittest from SQLObject import * ! True, False = 1==1, 0==1 def mysqlConnection(): --- 1,8 ---- import unittest + from SQLObject import * ! True = (1 == 1) ! False = (0 == 1) def mysqlConnection(): Index: test.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/tests/test.py,v retrieving revision 1.38 retrieving revision 1.39 diff -C2 -d -r1.38 -r1.39 *** test.py 21 Jan 2004 22:21:33 -0000 1.38 --- test.py 11 Feb 2004 16:25:40 -0000 1.39 *************** *** 10,13 **** --- 10,14 ---- import sys + sys.path.insert(0, "..") if '--coverage' in sys.argv: import coverage *************** *** 369,395 **** ######################################## ! class TestSOTrans(SQLObject): ! #_cacheValues = False ! name = StringCol(length=10, alternateID=True, dbName='name_col') _defaultOrderBy = 'name' class TransactionTest(SQLObjectTest): ! classes = [TestSOTrans] def inserts(self): ! TestSOTrans.new(name='bob') ! TestSOTrans.new(name='tim') def testTransaction(self): ! if not self.supportTransactions: return ! trans = TestSOTrans._connection.transaction() try: ! TestSOTrans._connection.autoCommit = 'exception' ! TestSOTrans.new(name='joe', connection=trans) trans.rollback() ! self.assertEqual([n.name for n in TestSOTrans.select(connection=trans)], ! ['bob', 'tim']) ! b = TestSOTrans.byName('bob', connection=trans) b.name = 'robert' trans.commit() --- 370,402 ---- ######################################## ! class Man(SQLObject): ! name = StringCol(length = 10, alternateID = True) ! ! class Dog(SQLObject): _defaultOrderBy = 'name' + name = StringCol(length = 10, alternateID = True, dbName = 'name_col') + ownerID = ForeignKey('Man', notNone = True) class TransactionTest(SQLObjectTest): ! classes = [Man, Dog] def inserts(self): ! bob = Man.new(name = 'bob') ! Dog.new(name = 'fido', ownerID = bob.id) ! Dog.new(name = 'ralph', ownerID = bob.id) def testTransaction(self): ! if not self.supportTransactions: ! return ! ! trans = Man._connection.transaction() try: ! Man._connection.autoCommit = 'exception' ! Man.new(name = 'joe', connection = trans) trans.rollback() ! self.assertEqual([n.name for n in Man.select(connection = trans)], ['bob']) ! ! b = Man.byName('bob', connection = trans) b.name = 'robert' trans.commit() *************** *** 399,403 **** self.assertEqual(b.name, 'robert') finally: ! TestSOTrans._connection.autoCommit = True --- 406,427 ---- self.assertEqual(b.name, 'robert') finally: ! Man._connection.autoCommit = True ! Dog._connection.autoCommit = True ! ! def testForeignKeyObjConnectionWithinTransaction(self): ! if not self.supportTransactions: ! return ! ! trans = Man._connection.transaction() ! try: ! jim = Man.new(name = 'jim') ! jims_id = jim.id ! del jim ! benji = Dog.new(name = 'benji', ownerID = jims_id, connection = trans) ! self.assertEqual(benji._connection, trans) ! self.assertEqual(benji._connection, benji.owner._connection) ! finally: ! Man._connection.autoCommit = True ! Dog._connection.autoCommit = True *************** *** 782,790 **** --- 806,834 ---- ######################################## + class Age2GreaterThanAge1(Validator.FancyValidator): + def validatePython(self, values, state): + cur_obj = state.soObject + if values.has_key('age1'): + age1 = int(values['age1']) + else: + age1 = cur_obj.age1 + + if values.has_key('age2'): + age2 = int(values['age2']) + else: + age2 = cur_obj.age2 + + if not (age2 > age1): + raise Validator.InvalidField( + self.message('badAges', 'Age 2 must be greater than age1'), value, state) + class SOValidation(SQLObject): + _validator = Age2GreaterThanAge1() name = StringCol(validator=Validator.PlainText(), default='x', dbName='name_col') name2 = StringCol(validator=Validator.ConfirmType(str), default='y') name3 = IntCol(validator=Validator.Wrapper(fromPython=int), default=100) + age1 = IntCol(notNone = True, default = 1) + age2 = IntCol(notNone = True, default = 2) class ValidationTest(SQLObjectTest): *************** *** 815,818 **** --- 859,868 ---- self.assertEqual(t.name3, 0) + def testInstanceValidation(self): + obj = SOValidation.new(age1 = 5, age2 = 18) + self.assertRaises(Validator.InvalidField, setattr, obj, 'age2', 4) + self.assertRaises(Validator.InvalidField, obj.set, age1 = 20, age2 = 19) + self.assertRaises(Validator.InvalidField, SOValidation.new, age1 = 7, age2 = 3) + ######################################## |
From: <bbo...@us...> - 2004-02-11 16:29:54
|
Update of /cvsroot/sqlobject/SQLObject/SQLObject In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv20327/SQLObject Modified Files: SQLObject.py Log Message: I wrote a unit test to show that, when using a transaction to create a new object, its ForeignKey objects were not using that same connection (which they should have.) Daniel Savard implemented the fix in SQLObject. I'm also deliberately checking in a broken test that demonstrates how we want instance validation to work (confirmed with Ian), in the hopes that a broken test will annoy someone enough to go in and implement in lieu of me/us having the time to do so. :) Index: SQLObject.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/SQLObject.py,v retrieving revision 1.66 retrieving revision 1.67 diff -C2 -d -r1.66 -r1.67 *** SQLObject.py 4 Dec 2003 16:46:31 -0000 1.66 --- SQLObject.py 11 Feb 2004 16:25:38 -0000 1.67 *************** *** 838,843 **** # Passing None for the ID tells __new__ we want to create # a new object. if kw.has_key('connection'): ! inst = cls(CreateNewSQLObject, connection=kw['connection']) del kw['connection'] else: --- 838,845 ---- # Passing None for the ID tells __new__ we want to create # a new object. + connection = None if kw.has_key('connection'): ! connection = kw['connection'] ! inst = cls(CreateNewSQLObject, connection=connection) del kw['connection'] else: *************** *** 895,903 **** # Then we finalize the process: ! inst._SO_finishCreate(id) return inst new = classmethod(new) ! def _SO_finishCreate(self, id=None): # Here's where an INSERT is finalized. # These are all the column values that were supposed --- 897,905 ---- # Then we finalize the process: ! inst._SO_finishCreate(id, connection=connection) return inst new = classmethod(new) ! def _SO_finishCreate(self, id=None, connection=None): # Here's where an INSERT is finalized. # These are all the column values that were supposed *************** *** 920,924 **** cache = self._connection.cache cache.created(id, self.__class__, self) ! self._init(id) def _SO_getID(self, obj): --- 922,926 ---- cache = self._connection.cache cache.created(id, self.__class__, self) ! self._init(id, connection=connection) def _SO_getID(self, obj): |
From: <sub...@co...> - 2004-02-11 05:38:55
|
Author: ianb Date: Tue Feb 10 20:30:58 2004 New Revision: 14 Modified: trunk/SQLObject/sqlobject/__init__.py Log: Added some backward-compatible *Connection constructors Modified: trunk/SQLObject/sqlobject/__init__.py ============================================================================== --- trunk/SQLObject/sqlobject/__init__.py (original) +++ trunk/SQLObject/sqlobject/__init__.py Tue Feb 10 20:30:58 2004 @@ -7,16 +7,52 @@ from dbconnection import connectionForURI ## Each of these imports allows the driver to install itself +## Then we set up some backward compatibility + +def _warn(msg): + import warnings + warnings.warn(msg, warnings.DeprecationWarning, stacklevel=2) import firebird +_firebird = firebird del firebird +def FirebirdConnection(*args, **kw): + _warn('FirebirdConnection is deprecated; use connectionForURI("firebird://...") or "from sqlobject.firebird import builder; FirebirdConnection = builder()"') + _firebird.builder()(*args, **kw) + import mysql +_mysql = mysql del mysql +def MySQLConnection(*args, **kw): + _warn('MySQLConnection is deprecated; use connectionForURI("mysql://...") or "from sqlobject.mysql import builder; MySQLConnection = builder()"') + _mysql.builder()(*args, **kw) + import postgres +_postgres = postgres del postgres +def PostgresConnection(*args, **kw): + _warn('PostgresConnection is deprecated; use connectionForURI("postgres://...") or "from sqlobject.postgres import builder; PostgresConnection = builder()"') + _postgres.builder()(*args, **kw) + import sqlite +_sqlite = sqlite del sqlite +def SQLiteConnection(*args, **kw): + _warn('SQLiteConnection is deprecated; use connectionForURI("sqlite://...") or "from sqlobject.sqlite import builder; SQLiteConnection = builder()"') + _sqlite.builder()(*args, **kw) + import dbm +_dbm = dbm del dbm +def DBMConnection(*args, **kw): + _warn('DBMConnection is deprecated; use connectionForURI("dbm://...") or "from sqlobject.dbm import builder; DBMConnection = builder()"') + _dbm.builder()(*args, **kw) + import sybase +_sybase = sybase del sybase +def SybaseConnection(*args, **kw): + _warn('SybaseConnection is deprecated; use connectionForURI("sybase://...") or "from sqlobject.sybase import builder; SybaseConnection = builder()"') + _sybase.builder()(*args, **kw) + + |
From: <sub...@co...> - 2004-02-11 05:21:17
|
Author: ianb Date: Tue Feb 10 20:13:17 2004 New Revision: 13 Modified: trunk/SQLObject/docs/News.txt trunk/SQLObject/sqlobject/__init__.py trunk/SQLObject/sqlobject/col.py trunk/SQLObject/sqlobject/dbconnection.py trunk/SQLObject/sqlobject/dbm/__init__.py trunk/SQLObject/sqlobject/dbm/dbmconnection.py trunk/SQLObject/sqlobject/firebird/__init__.py trunk/SQLObject/sqlobject/firebird/firebirdconnection.py trunk/SQLObject/sqlobject/main.py trunk/SQLObject/sqlobject/mysql/__init__.py trunk/SQLObject/sqlobject/mysql/mysqlconnection.py trunk/SQLObject/sqlobject/postgres/__init__.py trunk/SQLObject/sqlobject/postgres/pgconnection.py trunk/SQLObject/sqlobject/sqlite/__init__.py trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py trunk/SQLObject/sqlobject/sybase/__init__.py trunk/SQLObject/sqlobject/sybase/sybaseconnection.py trunk/SQLObject/tests/SQLObjectTest.py Log: Bunch of fixes to database refactoring- made database modules load lazily (only when they are used), did actual regression testing on the databases Modified: trunk/SQLObject/docs/News.txt ============================================================================== --- trunk/SQLObject/docs/News.txt (original) +++ trunk/SQLObject/docs/News.txt Tue Feb 10 20:13:17 2004 @@ -18,6 +18,7 @@ ``obj.sync()`` (``sync`` also refetches the data from the database, which ``syncUpdate`` does not do). When enabled, instances have a property ``dirty``, which indicates if they have pending updates. + Inserts are still done immediately. * Separated database drivers (PostgresConnection, MySQLConnection, etc.) into separate packages. You can access the driver through URIs, like ``mysql://user:pass@host/dbname`` -- to set drivers after @@ -32,6 +33,11 @@ * We're now using a Subversion repository instead of CVS. It is located at svn://colorstudy.com/trunk/SQLObject +Bugs +---- + +* SQLite booleans fixed. + SQLObject 0.5.2 =============== Modified: trunk/SQLObject/sqlobject/__init__.py ============================================================================== --- trunk/SQLObject/sqlobject/__init__.py (original) +++ trunk/SQLObject/sqlobject/__init__.py Tue Feb 10 20:13:17 2004 @@ -4,9 +4,12 @@ from styles import * from joins import * from include import validators +from dbconnection import connectionForURI ## Each of these imports allows the driver to install itself +import firebird +del firebird import mysql del mysql import postgres Modified: trunk/SQLObject/sqlobject/col.py ============================================================================== --- trunk/SQLObject/sqlobject/col.py (original) +++ trunk/SQLObject/sqlobject/col.py Tue Feb 10 20:13:17 2004 @@ -352,6 +352,9 @@ def _sybaseType(self): return "BIT" + def _firebirdType(self): + return 'INT' + class BoolCol(Col): baseClass = SOBoolCol @@ -405,7 +408,7 @@ SOKeyCol.__init__(self, **kw) def postgresCreateSQL(self): - from SQLObject import findClass + from main import findClass sql = SOKeyCol.postgresCreateSQL(self) if self.cascade is not None: other = findClass(self.foreignKey) Modified: trunk/SQLObject/sqlobject/dbconnection.py ============================================================================== --- trunk/SQLObject/sqlobject/dbconnection.py (original) +++ trunk/SQLObject/sqlobject/dbconnection.py Tue Feb 10 20:13:17 2004 @@ -56,24 +56,30 @@ raise NotImplemented connectionFromURI = classmethod(connectionFromURI) - def _parseURI(uri, expectHost=True): + def _parseURI(uri): schema, rest = uri.split(':', 1) - rest = rest.strip('/') - if expectHost: + assert rest.startswith('/'), "URIs must start with scheme:/ -- you did not include a / (in %r)" % rest + if rest.startswith('/') and not rest.startswith('//'): + host = None + rest = rest[1:] + elif rest.startswith('///'): + host = None + rest = rest[3:] + else: + rest = rest[2:] if rest.find('/') == -1: - host, rest = rest, '' + host = rest + rest = '' else: host, rest = rest.split('/', 1) - if host.find('@') != -1: - user, host = host.split('@', 1) - if user.find(':') != -1: - user, password = user.split(':', 1) - else: - password = None + if host and host.find('@') != -1: + user, host = host.split('@', 1) + if user.find(':') != -1: + user, password = user.split(':', 1) else: - user = password = None + password = None else: - host = user = password = None + user = password = None path = '/' + rest return user, password, host, path _parseURI = staticmethod(_parseURI) @@ -462,18 +468,18 @@ class ConnectionURIOpener(object): def __init__(self): - self.allClasses = [] - self.classSchemes = {} + self.schemeBuilders = {} + self.schemeSupported = {} self.instanceNames = {} + self.cachedURIs = {} - def registerConnectionClass(self, cls): - if cls not in self.allClasses: - self.allClasses.append(cls) - for uriScheme in cls.schemes: - assert not self.classSchemes.has_key(uriScheme) \ - or self.classSchemes[uriScheme] is cls, \ - "A class has already been registered for the URI scheme %s" % uriScheme - self.classSchemes[uriScheme] = cls + def registerConnection(self, schemes, builder, isSupported): + for uriScheme in schemes: + assert not self.schemeBuilders.has_key(uriScheme) \ + or self.schemeBuilders[uriScheme] is builder, \ + "A driver has already been registered for the URI scheme %s" % uriScheme + self.schemeBuilders[uriScheme] = builder + self.schemeSupported = isSupported def registerConnectionInstance(self, inst): if inst.name: @@ -483,20 +489,25 @@ assert inst.name.find(':') == -1, "You cannot include ':' in your class names (%r)" % cls.name self.instanceNames[inst.name] = inst - def openURI(self, uri): + def connectionForURI(self, uri): + if self.cachedURIs.has_key(uri): + return self.cachedURIs[uri] if uri.find(':') != -1: scheme, rest = uri.split(':', 1) - assert self.classSchemes.has_key(scheme), \ + assert self.schemeBuilders.has_key(scheme), \ "No SQLObject driver exists for %s" % scheme - return self.classSchemes[scheme].connectionFromURI(uri) + conn = self.schemeBuilders[scheme]().connectionFromURI(uri) else: # We just have a name, not a URI assert self.instanceNames.has_key(uri), \ "No SQLObject driver exists under the name %s" % uri - return self.instanceNames[uri] + conn = self.instanceNames[uri] + # @@: Do we care if we clobber another connection? + self.cachedURIs[uri] = conn + return conn TheURIOpener = ConnectionURIOpener() -registerConnectionClass = TheURIOpener.registerConnectionClass +registerConnection = TheURIOpener.registerConnection registerConnectionInstance = TheURIOpener.registerConnectionInstance -openURI = TheURIOpener.openURI +connectionForURI = TheURIOpener.connectionForURI Modified: trunk/SQLObject/sqlobject/dbm/__init__.py ============================================================================== --- trunk/SQLObject/sqlobject/dbm/__init__.py (original) +++ trunk/SQLObject/sqlobject/dbm/__init__.py Tue Feb 10 20:13:17 2004 @@ -1,5 +1,14 @@ -from sqlobject import dbconnection -from dbmconnection import DBMConnection +from sqlobject.dbconnection import registerConnection -dbconnection.registerConnectionClass( - DBMConnection) +def builder(): + import dbmconnection + return dbmconnection.DBMConnection + +def isSupported(): + try: + import anydbm + except ImportError: + return False + return True + +registerConnection(['dbm'], builder, isSupported) Modified: trunk/SQLObject/sqlobject/dbm/dbmconnection.py ============================================================================== --- trunk/SQLObject/sqlobject/dbm/dbmconnection.py (original) +++ trunk/SQLObject/sqlobject/dbm/dbmconnection.py Tue Feb 10 20:13:17 2004 @@ -105,7 +105,6 @@ supportTransactions = False dbName = 'dbm' - schemes = [dbName] def __init__(self, path, **kw): global anydbm, pickle @@ -136,16 +135,6 @@ return cls(path) connectionFromURI = classmethod(connectionFromURI) - def isSupported(cls): - global anydbm - if anydbm is None: - try: - import anydbm - except ImportError: - return False - return True - isSupported = classmethod(isSupported) - def _newID(self, table): id = int(self._meta["%s.id" % table]) + 1 self._meta["%s.id" % table] = str(id) Modified: trunk/SQLObject/sqlobject/firebird/__init__.py ============================================================================== --- trunk/SQLObject/sqlobject/firebird/__init__.py (original) +++ trunk/SQLObject/sqlobject/firebird/__init__.py Tue Feb 10 20:13:17 2004 @@ -1,5 +1,13 @@ -from sqlobject import dbconnection -from firebirdconnection import FirebirdConnection +from sqlobject.dbconnection import registerConnection -dbconnection.registerConnectionClass( - FirebirdConnection) +def builder(): + import firebirdconnection + return firebirdconnection.FirebirdConnection + +def isSupported(): + try: + import kinterbasdb + except ImportError: + return False + +registerConnection(['firebird', 'interbase'], builder, isSupported) Modified: trunk/SQLObject/sqlobject/firebird/firebirdconnection.py ============================================================================== --- trunk/SQLObject/sqlobject/firebird/firebirdconnection.py (original) +++ trunk/SQLObject/sqlobject/firebird/firebirdconnection.py Tue Feb 10 20:13:17 2004 @@ -1,5 +1,6 @@ from sqlobject.dbconnection import DBAPI kinterbasdb = None +import re class FirebirdConnection(DBAPI): @@ -27,23 +28,13 @@ DBAPI.__init__(self, **kw) def connectionFromURI(cls, uri): - auth, password, host, path = self._parseURI(url) + auth, password, host, path = cls._parseURI(uri) if not password: password = 'masterkey' if not auth: auth='sysdba' - return cls(host, db=path, user=user, passwd=password) + return cls(host, db=path, user=auth, passwd=password) connectionFromURI = classmethod(connectionFromURI) - - def isSupported(cls): - global kinterbasdb - if kinterbasdb is None: - try: - import kinterbasdb - except ImportError: - return False - return True - isSupported = classmethod(isSupported) def _runWithConnection(self, meth, *args): conn = self.getConnection() Modified: trunk/SQLObject/sqlobject/main.py ============================================================================== --- trunk/SQLObject/sqlobject/main.py (original) +++ trunk/SQLObject/sqlobject/main.py Tue Feb 10 20:13:17 2004 @@ -242,7 +242,7 @@ def findClass(name, registry=None): #assert classRegistry.get(registry, {}).has_key(name), "No class by the name %s found (I have %s)" % (repr(name), ', '.join(map(str, classRegistry.keys()))) - return classRegistry[registry][name] + return classregistry.registry(registry).getClass(name) def findDependencies(name, registry=None): depends = [] @@ -652,7 +652,6 @@ def syncUpdate(self): if not self._SO_createValues: return - print 'UP:', self._SO_createValues self._SO_writeLock.acquire() try: if self._SO_columnDict: @@ -1035,7 +1034,7 @@ def setConnection(cls, value): if isinstance(value, (str, unicode)): - value = dbconnection.openURI(value) + value = dbconnection.connectionForURI(value) cls._connection = value setConnection = classmethod(setConnection) Modified: trunk/SQLObject/sqlobject/mysql/__init__.py ============================================================================== --- trunk/SQLObject/sqlobject/mysql/__init__.py (original) +++ trunk/SQLObject/sqlobject/mysql/__init__.py Tue Feb 10 20:13:17 2004 @@ -1,5 +1,14 @@ -from sqlobject import dbconnection -from mysqlconnection import MySQLConnection +from sqlobject.dbconnection import registerConnection -dbconnection.registerConnectionClass( - MySQLConnection) +def builder(): + import mysqlconnection + return mysqlconnection.MySQLConnection + +def isSupported(): + try: + import MySQLdb + except ImportError: + return False + return True + +registerConnection(['mysql'], builder, isSupported) Modified: trunk/SQLObject/sqlobject/mysql/mysqlconnection.py ============================================================================== --- trunk/SQLObject/sqlobject/mysql/mysqlconnection.py (original) +++ trunk/SQLObject/sqlobject/mysql/mysqlconnection.py Tue Feb 10 20:13:17 2004 @@ -1,4 +1,5 @@ from sqlobject.dbconnection import DBAPI +from sqlobject import col MySQLdb = None class MySQLConnection(DBAPI): @@ -23,16 +24,6 @@ host=host or 'localhost') connectionFromURI = classmethod(connectionFromURI) - def isSupported(cls): - global MySQLdb - if MySQLdb is None: - try: - import MySQLdb - except ImportError: - return False - return True - isSupported = classmethod(isSupported) - def makeConnection(self): return MySQLdb.connect(host=self.host, db=self.db, user=self.user, passwd=self.passwd) Modified: trunk/SQLObject/sqlobject/postgres/__init__.py ============================================================================== --- trunk/SQLObject/sqlobject/postgres/__init__.py (original) +++ trunk/SQLObject/sqlobject/postgres/__init__.py Tue Feb 10 20:13:17 2004 @@ -1,5 +1,15 @@ -from sqlobject import dbconnection -from pgconnection import PostgresConnection +from sqlobject.dbconnection import registerConnection -dbconnection.registerConnectionClass( - PostgresConnection) +def builder(): + import pgconnection + return pgconnection.PostgresConnection + +def isSupported(): + try: + import psycopg + except ImportError: + return False + return False + +registerConnection(['postgres', 'postgresql', 'psycopg'], + builder, isSupported) Modified: trunk/SQLObject/sqlobject/postgres/pgconnection.py ============================================================================== --- trunk/SQLObject/sqlobject/postgres/pgconnection.py (original) +++ trunk/SQLObject/sqlobject/postgres/pgconnection.py Tue Feb 10 20:13:17 2004 @@ -1,4 +1,7 @@ from sqlobject.dbconnection import DBAPI +import re +from sqlobject import col +from sqlobject import sqlbuilder psycopg = None pgdb = None @@ -42,24 +45,16 @@ DBAPI.__init__(self, **kw) def connectionFromURI(cls, uri): - user, password, host, path = self._parseURI(uri) + user, password, host, path = cls._parseURI(uri) + path = path.strip('/') return cls(host=host, db=path, user=user, passwd=password) connectionFromURI = classmethod(connectionFromURI) - def isSupported(cls): - global psycopg - if psycopg is None: - try: - import psycopg - except ImportError: - return False - return False - isSupported = classmethod(isSupported) - def _setAutoCommit(self, conn, auto): conn.autocommit(auto) def makeConnection(self): + print 'DSN', self.dsn conn = self.pgmodule.connect(self.dsn) if self.autoCommit: conn.autocommit(1) Modified: trunk/SQLObject/sqlobject/sqlite/__init__.py ============================================================================== --- trunk/SQLObject/sqlobject/sqlite/__init__.py (original) +++ trunk/SQLObject/sqlobject/sqlite/__init__.py Tue Feb 10 20:13:17 2004 @@ -1,5 +1,14 @@ -from sqlobject import dbconnection -from sqliteconnection import SQLiteConnection +from sqlobject.dbconnection import registerConnection -dbconnection.registerConnectionClass( - SQLiteConnection) +def builder(): + import sqliteconnection + return sqliteconnection.SQLiteConnection + +def isSupported(): + try: + import sqlite + except ImportError: + return False + return True + +registerConnection(['sqlite'], builder, isSupported) Modified: trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py ============================================================================== --- trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py (original) +++ trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py Tue Feb 10 20:13:17 2004 @@ -21,22 +21,12 @@ DBAPI.__init__(self, **kw) def connectionFromURI(cls, uri): - user, password, host, path = cls._parseURI(uri, expectHost=False) - assert host is None + user, password, host, path = cls._parseURI(uri) + assert host is None, "SQLite can only be used locally (with a URI like sqlite:///file or sql:/file, not %r)" % uri assert user is None and password is None, "You may not provide usernames or passwords for SQLite databases" return cls(filename='/' + path) connectionFromURI = classmethod(connectionFromURI) - def isSupported(cls): - global sqlite - if sqlite is None: - try: - import sqlite - except ImportError: - return False - return True - isSupported = classmethod(isSupported) - def _setAutoCommit(self, conn, auto): conn.autocommit = auto Modified: trunk/SQLObject/sqlobject/sybase/__init__.py ============================================================================== --- trunk/SQLObject/sqlobject/sybase/__init__.py (original) +++ trunk/SQLObject/sqlobject/sybase/__init__.py Tue Feb 10 20:13:17 2004 @@ -1,5 +1,14 @@ -from sqlobject import dbconnection -from sybaseconnection import SybaseConnection +from sqlobject.dbconnection import registerConnection -dbconnection.registerConnectionClass( - SybaseConnection) +def builder(): + import sybaseconnection + return sybaseconnection.SybaseConnection + +def isSupported(cls): + try: + import Sybase + except ImportError: + return False + return True + +registerConnection(['sybase'], builder, isSupported) Modified: trunk/SQLObject/sqlobject/sybase/sybaseconnection.py ============================================================================== --- trunk/SQLObject/sqlobject/sybase/sybaseconnection.py (original) +++ trunk/SQLObject/sqlobject/sybase/sybaseconnection.py Tue Feb 10 20:13:17 2004 @@ -31,16 +31,6 @@ db=path) connectionFromURI = classmethod(connectionFromURI) - def isSupported(cls): - global Sybase - if Sybase is None: - try: - import Sybase - except ImportError: - return False - return True - isSupported = classmethod(isSupported) - def insert_id(self, conn): """ Sybase adapter/cursor does not support the Modified: trunk/SQLObject/tests/SQLObjectTest.py ============================================================================== --- trunk/SQLObject/tests/SQLObjectTest.py (original) +++ trunk/SQLObject/tests/SQLObjectTest.py Tue Feb 10 20:13:17 2004 @@ -1,6 +1,5 @@ import unittest from sqlobject import * -from sqlobject.dbconnection import openURI import os True, False = 1==1, 0==1 @@ -27,7 +26,7 @@ SQLObjectTest.supportAuto = True SQLObjectTest.supportRestrictedEnum = True SQLObjectTest.supportTransactions = True - return 'postgres://localhost/test' + return 'postgres:///test' def pygresConnection(): SQLObjectTest.supportDynamic = True @@ -89,7 +88,7 @@ def setUp(self): global __connection__ if isinstance(__connection__, str): - __connection__ = openURI(__connection__) + __connection__ = connectionForURI(__connection__) if self.debugSQL: print print '#' * 70 |
From: <sub...@co...> - 2004-02-10 06:05:20
|
Author: ianb Date: Mon Feb 9 20:57:48 2004 New Revision: 12 Modified: trunk/SQLObject/tests/test.py Log: Added another boolean test Modified: trunk/SQLObject/tests/test.py ============================================================================== --- trunk/SQLObject/tests/test.py (original) +++ trunk/SQLObject/tests/test.py Mon Feb 9 20:57:48 2004 @@ -98,6 +98,8 @@ def testBoolCol(self): student = Student(is_smart=False) self.assertEqual(student.is_smart, False) + student2 = Student(is_smart='false') + self.assertEqual(student2.is_smart, True) class TestCase34(SQLObjectTest): |
From: <sub...@co...> - 2004-02-10 06:04:38
|
Author: ianb Date: Mon Feb 9 20:57:09 2004 New Revision: 11 Modified: trunk/SQLObject/sqlobject/col.py Log: Fixed converter for BoolValidator, which wasn't working with SQLite Modified: trunk/SQLObject/sqlobject/col.py ============================================================================== --- trunk/SQLObject/sqlobject/col.py (original) +++ trunk/SQLObject/sqlobject/col.py Mon Feb 9 20:57:09 2004 @@ -328,6 +328,12 @@ else: return sqlbuilder.FALSE + def toPython(self, value, state): + if int(value): + return sqlbuilder.TRUE + else: + return sqlbuilder.FALSE + class SOBoolCol(SOCol): def __init__(self, **kw): |
From: <sub...@co...> - 2004-02-10 05:13:56
|
Author: ianb Date: Mon Feb 9 20:06:27 2004 New Revision: 10 Modified: trunk/SQLObject/docs/News.txt Log: Added notes about URIs and lazy updates Modified: trunk/SQLObject/docs/News.txt ============================================================================== --- trunk/SQLObject/docs/News.txt (original) +++ trunk/SQLObject/docs/News.txt Mon Feb 9 20:06:27 2004 @@ -13,6 +13,15 @@ Interface Changes ----------------- +* Lazy updates. Add ``_lazyUpdate=True`` to your class, and updates + will only be written when you call ``obj.syncUpdate()`` or + ``obj.sync()`` (``sync`` also refetches the data from the database, + which ``syncUpdate`` does not do). When enabled, instances have a + property ``dirty``, which indicates if they have pending updates. +* Separated database drivers (PostgresConnection, MySQLConnection, + etc.) into separate packages. You can access the driver through + URIs, like ``mysql://user:pass@host/dbname`` -- to set drivers after + class creation you should use `sqlobject.dbconnection.openURI()`. * The ``SQLObject`` package has been renamed to ``sqlobject``. This makes it similar to several other packages, and emphasizes the distinction between the ``sqlobject`` package and the ``SQLObject`` |
From: <sub...@co...> - 2004-02-10 05:10:27
|
Author: ianb Date: Mon Feb 9 20:02:50 2004 New Revision: 9 Modified: trunk/SQLObject/sqlobject/ (props changed) trunk/SQLObject/sqlobject/include/ (props changed) trunk/SQLObject/sqlobject/main.py trunk/SQLObject/tests/SQLObjectTest.py trunk/SQLObject/tests/test.py Log: Added lazy updating Some URI-related fixes Modified: trunk/SQLObject/sqlobject/main.py ============================================================================== --- trunk/SQLObject/sqlobject/main.py (original) +++ trunk/SQLObject/sqlobject/main.py Mon Feb 9 20:02:50 2004 @@ -80,6 +80,16 @@ if not d.has_key('_table'): d['_table'] = None + if d.has_key('_connection'): + connection = d['_connection'] + del d['_connection'] + assert not d.has_key('connection') + elif d.has_key('connection'): + connection = d['connection'] + del d['connection'] + else: + connection = None + # We actually create the class. newClass = type.__new__(cls, className, bases, d) newClass._SO_finishedClassCreation = False @@ -96,19 +106,14 @@ ###################################################### # Set some attributes to their defaults, if necessary. # First we get the connection: - if not newClass._connection: - + if not connection and not getattr(newClass, '_connection', None): mod = sys.modules[newClass.__module__] # See if there's a __connection__ global in # the module, use it if there is. if hasattr(mod, '__connection__'): - newClass._connection = mod.__connection__ + connection = mod.__connection__ - # If the connection is named, we turn the name into - # a real connection. - if isinstance(newClass._connection, str): - newClass._connection = dbconnection.openURI( - newClass._connection) + newClass.setConnection(connection) # The style object tells how to map between Python # identifiers and Database identifiers: @@ -309,6 +314,8 @@ # when necessary: (bad clever? maybe) _expired = False + _lazyUpdate = False + def get(cls, id, connection=None, selectResults=None): assert id is not None, 'None is not a possible id for %s' % cls.__name @@ -597,6 +604,7 @@ if not selectResults: raise SQLObjectNotFound, "The object %s by the ID %s does not exist" % (self.__class__.__name__, self.id) self._SO_selectInit(selectResults) + self.dirty = 0 def _SO_loadValue(self, attrName): try: @@ -628,6 +636,8 @@ self._SO_writeLock.release() def sync(self): + if self._lazyUpdate and self._SO_createValues: + self.syncUpdate() self._SO_writeLock.acquire() try: dbNames = [col.dbName for col in self._SO_columns] @@ -639,6 +649,20 @@ finally: self._SO_writeLock.release() + def syncUpdate(self): + if not self._SO_createValues: + return + print 'UP:', self._SO_createValues + self._SO_writeLock.acquire() + try: + if self._SO_columnDict: + values = [(self._SO_columnDict[v[0]].dbName, v[1]) for v in self._SO_createValues.items()] + self._connection._SO_update(self, values) + self.dirty = False + self._SO_createValues = {} + finally: + self._SO_writeLock.release() + def expire(self): if self._expired: return @@ -663,8 +687,10 @@ # dictionary until later: if fromPython: value = fromPython(value, self._SO_validatorState) - if self._SO_creating: + if self._SO_creating or self._lazyUpdate: + self.dirty = True self._SO_createValues[name] = value + setattr(self, instanceName(name), value) return self._connection._SO_update(self, @@ -679,12 +705,14 @@ # potentially with one SQL statement if possible. # _SO_creating is special, see _SO_setValue - if self._SO_creating: + if self._SO_creating or self._lazyUpdate: for name, value in kw.items(): fromPython = getattr(self, '_SO_fromPython_%s' % name) if fromPython: kw[name] = fromPython(value, self._SO_validatorState) self._SO_createValues.update(kw) + self.dirty = True + setattr(self, instanceName(name), value) return self._SO_writeLock.acquire() @@ -753,7 +781,7 @@ return # Pass the connection object along if we were given one. - # Passing None for the ID tells __new__ we want to create + # Passing None for the ID tells __init__ we want to create # a new object. if kw.has_key('connection'): self._connection = kw['connection'] @@ -788,7 +816,7 @@ # If we don't get it, it's an error: if default is NoDefault: - raise TypeError, "%s did not get expected keyword argument %s" % (cls.__name__, repr(column.name)) + raise TypeError, "%s() did not get expected keyword argument %s" % (self.__class__.__name__, column.name) # Otherwise we put it in as though they did pass # that keyword: kw[column.name] = default @@ -829,7 +857,11 @@ # Get rid of _SO_create*, we aren't creating anymore. # Doesn't have to be threadsafe because we're still in # new(), which doesn't need to be threadsafe. - del self._SO_createValues + self.dirty = False + if not self._lazyUpdate: + del self._SO_createValues + else: + self._SO_createValues = {} del self._SO_creating # Do the insert -- most of the SQL in this case is left @@ -1001,6 +1033,11 @@ items.append((col.name, getattr(self, col.name))) return items + def setConnection(cls, value): + if isinstance(value, (str, unicode)): + value = dbconnection.openURI(value) + cls._connection = value + setConnection = classmethod(setConnection) def capitalize(name): return name[0].capitalize() + name[1:] @@ -1151,6 +1188,7 @@ self.soObject = soObject self.protocol = 'sql' + ######################################## ## Utility functions (for external consumption) Modified: trunk/SQLObject/tests/SQLObjectTest.py ============================================================================== --- trunk/SQLObject/tests/SQLObjectTest.py (original) +++ trunk/SQLObject/tests/SQLObjectTest.py Mon Feb 9 20:02:50 2004 @@ -1,5 +1,7 @@ import unittest from sqlobject import * +from sqlobject.dbconnection import openURI +import os True, False = 1==1, 0==1 @@ -11,60 +13,49 @@ SQLObjectTest.supportRestrictedEnum = False # Technically it does, but now how we're using it: SQLObjectTest.supportTransactions = False - return MySQLConnection(host='localhost', - db='test', - user='test', - passwd='', - debug=0) + return 'mysql://test@localhost/test' def dbmConnection(): SQLObjectTest.supportDynamic = True SQLObjectTest.supportAuto = False SQLObjectTest.supportRestrictedEnum = False SQLObjectTest.supportTransactions = False - return DBMConnection('data') + return 'dbm:///data' def postgresConnection(): SQLObjectTest.supportDynamic = True SQLObjectTest.supportAuto = True SQLObjectTest.supportRestrictedEnum = True SQLObjectTest.supportTransactions = True - return PostgresConnection(db='test') + return 'postgres://localhost/test' def pygresConnection(): SQLObjectTest.supportDynamic = True SQLObjectTest.supportAuto = True SQLObjectTest.supportRestrictedEnum = True SQLObjectTest.supportTransactions = True - return PostgresConnection(db='test', usePygresql=True) + return 'pygresql://localhost/test' def sqliteConnection(): SQLObjectTest.supportDynamic = False SQLObjectTest.supportAuto = False SQLObjectTest.supportRestrictedEnum = False SQLObjectTest.supportTransactions = True - return SQLiteConnection('data/sqlite.data') - + return 'sqlite:///%s/data/sqlite.data' % os.getcwd() def sybaseConnection(): SQLObjectTest.supportDynamic = False SQLObjectTest.supportAuto = False SQLObjectTest.supportRestrictedEnum = False SQLObjectTest.supportTransactions = True - return SybaseConnection(host='localhost', - db='test', - user='sa', - passwd='sybasesa', - autoCommit=1) + return 'sybase://sa:sybasesa@localhost/test' def firebirdConnection(): SQLObjectTest.supportDynamic = True SQLObjectTest.supportAuto = False SQLObjectTest.supportRestrictedEnum = True SQLObjectTest.supportTransactions = True - return FirebirdConnection('localhost', '/var/lib/firebird/data/test.gdb', - user='sysdba', passwd='masterkey') - + return 'firebird://sysdba:masterkey@localhost/var/lib/firebird/data/test.gdb' _supportedDatabases = { 'mysql': 'MySQLdb', @@ -96,6 +87,9 @@ databaseName = None def setUp(self): + global __connection__ + if isinstance(__connection__, str): + __connection__ = openURI(__connection__) if self.debugSQL: print print '#' * 70 Modified: trunk/SQLObject/tests/test.py ============================================================================== --- trunk/SQLObject/tests/test.py (original) +++ trunk/SQLObject/tests/test.py Mon Feb 9 20:02:50 2004 @@ -900,6 +900,97 @@ self.assertEqual(st1.st2, st2) ######################################## +## Lazy updates +######################################## + +class Lazy(SQLObject): + + _lazyUpdate = True + name = StringCol() + other = StringCol(default='nothing') + +class LazyTest(SQLObjectTest): + + classes = [Lazy] + + def setUp(self): + SQLObjectTest.setUp(self) + self.conn = Lazy._connection + self.conn.didUpdate = False + oldUpdate = self.conn._SO_update + newUpdate = lambda so, values, s=self, c=self.conn, o=oldUpdate: self._alternateUpdate(so, values, c, o) + self.conn._SO_update = newUpdate + + def _alternateUpdate(self, so, values, conn, oldUpdate): + conn.didUpdate = True + return oldUpdate(so, values) + + def test(self): + assert not self.conn.didUpdate + obj = Lazy(name='tim') + # We just did an insert, but not an update: + assert not self.conn.didUpdate + obj.set(name='joe') + assert obj.dirty + self.assertEqual(obj.name, 'joe') + assert not self.conn.didUpdate + obj.syncUpdate() + self.assertEqual(obj.name, 'joe') + assert self.conn.didUpdate + assert not obj.dirty + self.assertEqual(obj.name, 'joe') + self.conn.didUpdate = False + + obj = Lazy(name='frank') + obj.name = 'joe' + assert not self.conn.didUpdate + assert obj.dirty + self.assertEqual(obj.name, 'joe') + obj.name = 'joe2' + assert not self.conn.didUpdate + assert obj.dirty + self.assertEqual(obj.name, 'joe2') + obj.syncUpdate() + self.assertEqual(obj.name, 'joe2') + assert not obj.dirty + assert self.conn.didUpdate + self.conn.didUpdate = False + + obj = Lazy(name='loaded') + assert not obj.dirty + assert not self.conn.didUpdate + self.assertEqual(obj.name, 'loaded') + obj.name = 'unloaded' + assert obj.dirty + self.assertEqual(obj.name, 'unloaded') + assert not self.conn.didUpdate + obj.sync() + assert not obj.dirty + self.assertEqual(obj.name, 'unloaded') + assert self.conn.didUpdate + self.conn.didUpdate = False + obj.name = 'whatever' + assert obj.dirty + self.assertEqual(obj.name, 'whatever') + assert not self.conn.didUpdate + obj._SO_loadValue('name') + assert obj.dirty + self.assertEqual(obj.name, 'whatever') + assert not self.conn.didUpdate + obj._SO_loadValue('other') + self.assertEqual(obj.name, 'whatever') + assert not self.conn.didUpdate + obj.syncUpdate() + assert self.conn.didUpdate + self.conn.didUpdate = False + + obj = Lazy(name='last') + assert not obj.dirty + obj.syncUpdate() + assert not self.conn.didUpdate + assert not obj.dirty + +######################################## ## Run from command-line: ######################################## |
From: <sub...@co...> - 2004-02-08 09:47:21
|
Author: ianb Date: Sun Feb 8 00:39:59 2004 New Revision: 8 Added: trunk/SQLObject/sqlobject/dbm/ trunk/SQLObject/sqlobject/dbm/__init__.py trunk/SQLObject/sqlobject/dbm/dbmconnection.py trunk/SQLObject/sqlobject/firebird/ trunk/SQLObject/sqlobject/firebird/__init__.py trunk/SQLObject/sqlobject/firebird/firebirdconnection.py trunk/SQLObject/sqlobject/mysql/ trunk/SQLObject/sqlobject/mysql/__init__.py trunk/SQLObject/sqlobject/mysql/mysqlconnection.py trunk/SQLObject/sqlobject/postgres/ trunk/SQLObject/sqlobject/postgres/__init__.py trunk/SQLObject/sqlobject/postgres/pgconnection.py trunk/SQLObject/sqlobject/sqlite/ trunk/SQLObject/sqlobject/sqlite/__init__.py trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py trunk/SQLObject/sqlobject/sybase/ trunk/SQLObject/sqlobject/sybase/__init__.py trunk/SQLObject/sqlobject/sybase/sybaseconnection.py Modified: trunk/SQLObject/docs/SQLObject.txt trunk/SQLObject/examples/config.py trunk/SQLObject/examples/leftjoin.py trunk/SQLObject/setup.py trunk/SQLObject/sqlobject/__init__.py trunk/SQLObject/sqlobject/dbconnection.py trunk/SQLObject/sqlobject/main.py Log: Split database support into subpackages, one per driver Added URI support, which is not the most convenient way to specify the database. URIs can be used in lieu of a connection. Modified: trunk/SQLObject/docs/SQLObject.txt ============================================================================== --- trunk/SQLObject/docs/SQLObject.txt (original) +++ trunk/SQLObject/docs/SQLObject.txt Sun Feb 8 00:39:59 2004 @@ -1,5 +1,5 @@ ``````````````` -SQLObject 0.5 +SQLObject 0.6 ``````````````` .. contents:: Contents: Modified: trunk/SQLObject/examples/config.py ============================================================================== --- trunk/SQLObject/examples/config.py (original) +++ trunk/SQLObject/examples/config.py Sun Feb 8 00:39:59 2004 @@ -8,9 +8,14 @@ """ ## Snippet "connections" conn = MySQLConnection(user='test', db='testdb') +conn = 'mysql://test@localhost/testdb' conn = PostgresConnection('user=test dbname=testdb') +conn = 'postgres://test@localhost/testdb' conn = SQLiteConnect('database.db') +conn = 'sqlite://path/to/database.db' conn = DBMConnection('database/') +conn = 'dbm://path/to/database/' ## end snippet """ -conn = MySQLConnection(user='test', db='test') +#conn = MySQLConnection(user='test', db='test') +conn = 'mysql://test@localhost/test' Modified: trunk/SQLObject/examples/leftjoin.py ============================================================================== --- trunk/SQLObject/examples/leftjoin.py (original) +++ trunk/SQLObject/examples/leftjoin.py Sun Feb 8 00:39:59 2004 @@ -1,14 +1,5 @@ from sqlobject import * - -## Use one of these to define your connection: -""" -conn = MySQLConnection(user='test', db='testdb') -conn = PostgresConnection('user=test dbname=testdb') -conn = SQLiteConnect('database.db') -conn = DBMConnection('database/') -""" -__connection__ = MySQLConnection(user='test', db='test') - +from setup import * class Customer(SQLObject): Modified: trunk/SQLObject/setup.py ============================================================================== --- trunk/SQLObject/setup.py (original) +++ trunk/SQLObject/setup.py Sun Feb 8 00:39:59 2004 @@ -2,6 +2,9 @@ import warnings warnings.filterwarnings("ignore", "Unknown distribution option") +subpackages = ['dbm', 'firebird', 'include', 'mysql', 'postgres', + 'sqlite', 'sybase'] + import sys # patch distutils if it can't cope with the "classifiers" keyword if sys.version < '2.2.3': @@ -30,7 +33,7 @@ author_email="ia...@co...", url="http://sqlobject.org", license="LGPL", - packages=["sqlobject", "sqlobject.include"], + packages=["sqlobject"] + ['sqlobject.%s' % package for package in subpackages], download_url="http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.6.tar.gz?download") # Send announce to: Modified: trunk/SQLObject/sqlobject/__init__.py ============================================================================== --- trunk/SQLObject/sqlobject/__init__.py (original) +++ trunk/SQLObject/sqlobject/__init__.py Sun Feb 8 00:39:59 2004 @@ -1,7 +1,19 @@ from main import * from col import * from sqlbuilder import AND, OR, NOT, IN, LIKE, CONTAINSSTRING, const, func -from dbconnection import * from styles import * from joins import * from include import validators + +## Each of these imports allows the driver to install itself + +import mysql +del mysql +import postgres +del postgres +import sqlite +del sqlite +import dbm +del dbm +import sybase +del sybase Modified: trunk/SQLObject/sqlobject/dbconnection.py ============================================================================== --- trunk/SQLObject/sqlobject/dbconnection.py (original) +++ trunk/SQLObject/sqlobject/dbconnection.py Sun Feb 8 00:39:59 2004 @@ -14,32 +14,15 @@ from joins import sorter from converters import sqlrepr -# We set these up as globals, which will be set if we end up -# needing the drivers: -anydbm = None -pickle = None -MySQLdb = None -psycopg = None -pgdb = None -sqlite = None -kinterbasdb = None -Sybase = None - warnings.filterwarnings("ignore", "DB-API extension cursor.lastrowid used") -__all__ = ['MySQLConnection', 'PostgresConnection', 'SQLiteConnection', - 'DBMConnection', 'FirebirdConnection', 'SybaseConnection'] - _connections = {} class DBConnection: def __init__(self, name=None, debug=False, debugOutput=False, cache=True, style=None, autoCommit=True): - if name: - assert not _connections.has_key(name), 'A database by the name %s has already been created: %s' % (name, _connections[name]) - _connections[name] = self - self.name = name + self.name = name self.debug = debug self.debugOutput = debugOutput self.cache = CacheSet(cache=cache) @@ -48,9 +31,52 @@ self._connectionNumbers = {} self._connectionCount = 1 self.autoCommit = autoCommit + registerConnectionInstance(self) -def connectionForName(name): - return _connections[name] + def uri(self): + auth = self.user or '' + if auth: + if self.password: + auth = auth + '@' + self.password + auth = auth + ':' + else: + assert not password, 'URIs cannot express passwords without usernames' + uri = '%s://%s' % (self.dbName, auth) + if self.host: + uri += self.host + '/' + if path.startswith('/'): + path = path[1:] + return uri + path + + def isSupported(cls): + raise NotImplemented + isSupported = classmethod(isSupported) + + def connectionFromURI(cls, uri): + raise NotImplemented + connectionFromURI = classmethod(connectionFromURI) + + def _parseURI(uri, expectHost=True): + schema, rest = uri.split(':', 1) + rest = rest.strip('/') + if expectHost: + if rest.find('/') == -1: + host, rest = rest, '' + else: + host, rest = rest.split('/', 1) + if host.find('@') != -1: + user, host = host.split('@', 1) + if user.find(':') != -1: + user, password = user.split(':', 1) + else: + password = None + else: + user = password = None + else: + host = user = password = None + path = '/' + rest + return user, password, host, path + _parseURI = staticmethod(_parseURI) class DBAPI(DBConnection): @@ -433,951 +459,44 @@ self.rollback() self._dbConnection.releaseConnection(self._connection) -######################################## -## MySQL connection -######################################## - -class MySQLConnection(DBAPI): - - supportTransactions = False - dbName = 'mysql' - - def __init__(self, db, user, passwd='', host='localhost', **kw): - global MySQLdb - if MySQLdb is None: - import MySQLdb - self.host = host - self.db = db - self.user = user - self.passwd = passwd - DBAPI.__init__(self, **kw) - - def makeConnection(self): - return MySQLdb.connect(host=self.host, db=self.db, - user=self.user, passwd=self.passwd) - - def _queryInsertID(self, conn, table, idName, id, names, values): - c = conn.cursor() - if id is not None: - names = [idName] + names - values = [id] + values - q = self._insertSQL(table, names, values) - if self.debug: - self.printDebug(conn, q, 'QueryIns') - c.execute(q) - if id is None: - id = c.insert_id() - if self.debugOutput: - self.printDebug(conn, id, 'QueryIns', 'result') - return id - - def _queryAddLimitOffset(self, query, start, end): - if not start: - return "%s LIMIT %i" % (query, end) - if not end: - return "%s LIMIT %i, -1" % (query, start) - return "%s LIMIT %i, %i" % (query, start, end-start) - - def createColumn(self, soClass, col): - return col.mysqlCreateSQL() - - def createIDColumn(self, soClass): - return '%s INT PRIMARY KEY AUTO_INCREMENT' % soClass._idName - - def joinSQLType(self, join): - return 'INT NOT NULL' - - def tableExists(self, tableName): - for (table,) in self.queryAll('SHOW TABLES'): - if table.lower() == tableName.lower(): - return True - return False - - def addColumn(self, tableName, column): - self.query('ALTER TABLE %s ADD COLUMN %s' % - (tableName, - column.mysqlCreateSQL())) - - def delColumn(self, tableName, column): - self.query('ALTER TABLE %s DROP COLUMN %s' % - (tableName, - column.dbName)) - - def columnsFromSchema(self, tableName, soClass): - colData = self.queryAll("SHOW COLUMNS FROM %s" - % tableName) - results = [] - for field, t, nullAllowed, key, default, extra in colData: - if field == 'id': - continue - colClass, kw = self.guessClass(t) - kw['name'] = soClass._style.dbColumnToPythonAttr(field) - kw['notNone'] = not nullAllowed - kw['default'] = default - # @@ skip key... - # @@ skip extra... - results.append(colClass(**kw)) - return results - - def guessClass(self, t): - if t.startswith('int'): - return col.IntCol, {} - elif t.startswith('varchar'): - return col.StringCol, {'length': int(t[8:-1])} - elif t.startswith('char'): - return col.StringCol, {'length': int(t[5:-1]), - 'varchar': False} - elif t.startswith('datetime'): - return col.DateTimeCol, {} - elif t.startswith('bool'): - return col.BoolCol, {} - else: - return col.Col, {} - -######################################## -## Postgres connection -######################################## - -class PostgresConnection(DBAPI): - - supportTransactions = True - dbName = 'postgres' - - def __init__(self, dsn=None, host=None, db=None, - user=None, passwd=None, autoCommit=1, - usePygresql=False, - **kw): - global psycopg, pgdb - if usePygresql: - if pgdb is None: - import pgdb - self.pgmodule = pgdb - else: - if psycopg is None: - import psycopg - self.pgmodule = psycopg - - self.autoCommit = autoCommit - if not autoCommit and not kw.has_key('pool'): - # Pooling doesn't work with transactions... - kw['pool'] = 0 - if dsn is None: - dsn = [] - if db: - dsn.append('dbname=%s' % db) - if user: - dsn.append('user=%s' % user) - if passwd: - dsn.append('password=%s' % passwd) - if host: - # @@: right format? - dsn.append('host=%s' % host) - dsn = ' '.join(dsn) - self.dsn = dsn - DBAPI.__init__(self, **kw) - - def _setAutoCommit(self, conn, auto): - conn.autocommit(auto) - - def makeConnection(self): - conn = self.pgmodule.connect(self.dsn) - if self.autoCommit: - conn.autocommit(1) - return conn - - def _queryInsertID(self, conn, table, idName, id, names, values): - c = conn.cursor() - if id is not None: - names = [idName] + names - values = [id] + values - q = self._insertSQL(table, names, values) - if self.debug: - self.printDebug(conn, q, 'QueryIns') - c.execute(q) - if id is None: - c.execute('SELECT %s FROM %s WHERE oid = %s' - % (idName, table, c.lastoid())) - id = c.fetchone()[0] - if self.debugOutput: - self.printDebug(conn, id, 'QueryIns', 'result') - return id - - def _queryAddLimitOffset(self, query, start, end): - if not start: - return "%s LIMIT %i" % (query, end) - if not end: - return "%s OFFSET %i" % (query, start) - return "%s LIMIT %i OFFSET %i" % (query, end-start, start) - - def createColumn(self, soClass, col): - return col.postgresCreateSQL() - - def createIDColumn(self, soClass): - return '%s SERIAL PRIMARY KEY' % soClass._idName - - def dropTable(self, tableName, cascade=False): - self.query("DROP TABLE %s %s" % (tableName, - cascade and 'CASCADE' or '')) - - def joinSQLType(self, join): - return 'INT NOT NULL' - - def tableExists(self, tableName): - # @@: obviously broken - result = self.queryOne("SELECT COUNT(relname) FROM pg_class WHERE relname = '%s'" - % tableName) - return result[0] - - def addColumn(self, tableName, column): - self.query('ALTER TABLE %s ADD COLUMN %s' % - (tableName, - column.postgresCreateSQL())) - - def delColumn(self, tableName, column): - self.query('ALTER TABLE %s DROP COLUMN %s' % - (tableName, - column.dbName)) - - def columnsFromSchema(self, tableName, soClass): - - keyQuery = """ - SELECT pg_catalog.pg_get_constraintdef(oid) as condef - FROM pg_catalog.pg_constraint r - WHERE r.conrelid = '%s'::regclass AND r.contype = 'f'""" - - colQuery = """ - SELECT a.attname, - pg_catalog.format_type(a.atttypid, a.atttypmod), a.attnotnull, - (SELECT substring(d.adsrc for 128) FROM pg_catalog.pg_attrdef d - WHERE d.adrelid=a.attrelid AND d.adnum = a.attnum) - FROM pg_catalog.pg_attribute a - WHERE a.attrelid ='%s'::regclass - AND a.attnum > 0 AND NOT a.attisdropped - ORDER BY a.attnum""" - - keyData = self.queryAll(keyQuery % tableName) - keyRE = re.compile("\((.+)\) REFERENCES (.+)\(") - keymap = {} - for (condef,) in keyData: - match = keyRE.search(condef) - if match: - field, reftable = match.groups() - keymap[field] = reftable.capitalize() - colData = self.queryAll(colQuery % tableName) - results = [] - for field, t, notnull, defaultstr in colData: - if field == 'id': - continue - colClass, kw = self.guessClass(t) - kw['name'] = soClass._style.dbColumnToPythonAttr(field) - kw['notNone'] = notnull - if defaultstr is not None: - kw['default'] = getattr(sqlbuilder.const, defaultstr) - if keymap.has_key(field): - kw['foreignKey'] = keymap[field] - results.append(colClass(**kw)) - return results - - def guessClass(self, t): - if t.count('int'): - return col.IntCol, {} - elif t.count('varying'): - return col.StringCol, {'length': int(t[t.index('(')+1:-1])} - elif t.startswith('character('): - return col.StringCol, {'length': int(t[t.index('(')+1:-1]), - 'varchar': False} - elif t == 'text': - return col.StringCol, {} - elif t.startswith('datetime'): - return col.DateTimeCol, {} - elif t.startswith('bool'): - return col.BoolCol, {} - else: - return col.Col, {} - - -######################################## -## SQLite connection -######################################## - -class SQLiteConnection(DBAPI): - - supportTransactions = True - dbName = 'sqlite' - - def __init__(self, filename, autoCommit=1, **kw): - global sqlite - if sqlite is None: - import sqlite - self.filename = filename # full path to sqlite-db-file - if not autoCommit and not kw.has_key('pool'): - # Pooling doesn't work with transactions... - kw['pool'] = 0 - # use only one connection for sqlite - supports multiple - # cursors per connection - self._conn = sqlite.connect(self.filename) - DBAPI.__init__(self, **kw) - - def _setAutoCommit(self, conn, auto): - conn.autocommit = auto - - def makeConnection(self): - return self._conn - - def _queryInsertID(self, conn, table, idName, id, names, values): - c = conn.cursor() - if id is not None: - names = [idName] + names - values = [id] + values - q = self._insertSQL(table, names, values) - if self.debug: - self.printDebug(conn, q, 'QueryIns') - c.execute(q) - # lastrowid is a DB-API extension from "PEP 0249": - if id is None: - id = int(c.lastrowid) - if self.debugOutput: - self.printDebug(conn, id, 'QueryIns', 'result') - return id - - def _queryAddLimitOffset(self, query, start, end): - if not start: - return "%s LIMIT %i" % (query, end) - if not end: - return "%s LIMIT 0 OFFSET %i" % (query, start) - return "%s LIMIT %i OFFSET %i" % (query, end-start, start) - - def createColumn(self, soClass, col): - return col.sqliteCreateSQL() - - def createIDColumn(self, soClass): - return '%s INTEGER PRIMARY KEY' % soClass._idName - - def joinSQLType(self, join): - return 'INT NOT NULL' - - def tableExists(self, tableName): - result = self.queryOne("SELECT tbl_name FROM sqlite_master WHERE type='table' AND tbl_name = '%s'" % tableName) - # turn it into a boolean: - return not not result - -######################################## -## Sybase connection -######################################## - -class SybaseConnection(DBAPI): - - supportTransactions = True - dbName = 'sybase' - - def __init__(self, db, user, passwd='', host='localhost', - autoCommit=0, **kw): - global Sybase - if Sybase is None: - import Sybase - from Sybase import NumericType - from Converters import registerConverter, IntConverter - registerConverter(NumericType, IntConverter) - if not autoCommit and not kw.has_key('pool'): - # Pooling doesn't work with transactions... - kw['pool'] = 0 - self.autoCommit=autoCommit - self.host = host - self.db = db - self.user = user - self.passwd = passwd - DBAPI.__init__(self, **kw) - - def insert_id(self, conn): - """ - Sybase adapter/cursor does not support the - insert_id method. - """ - c = conn.cursor() - c.execute('SELECT @@IDENTITY') - return c.fetchone()[0] - - def makeConnection(self): - return Sybase.connect(self.host, self.user, self.passwd, - database=self.db, auto_commit=self.autoCommit) - - def _queryInsertID(self, conn, table, idName, id, names, values): - c = conn.cursor() - if id is not None: - names = [idName] + names - values = [id] + values - c.execute('SET IDENTITY_INSERT %s ON' % table) - else: - c.execute('SET IDENTITY_INSERT %s OFF' % table) - q = self._insertSQL(table, names, values) - if self.debug: - print 'QueryIns: %s' % q - c.execute(q) - if id is None: - id = self.insert_id(conn) - if self.debugOutput: - self.printDebug(conn, id, 'QueryIns', 'result') - return id - - def _queryAddLimitOffset(self, query, start, end): - # XXX Sybase doesn't support LIMIT - return query - - def createColumn(self, soClass, col): - return col.sybaseCreateSQL() - - def createIDColumn(self, soClass): - return '%s NUMERIC(18,0) IDENTITY' % soClass._idName - - def joinSQLType(self, join): - return 'NUMERIC(18,0) NOT NULL' - - SHOW_TABLES="SELECT name FROM sysobjects WHERE type='U'" - def tableExists(self, tableName): - for (table,) in self.queryAll(self.SHOW_TABLES): - if table.lower() == tableName.lower(): - return True - return False - - def addColumn(self, tableName, column): - self.query('ALTER TABLE %s ADD COLUMN %s' % - (tableName, - column.sybaseCreateSQL())) - - def delColumn(self, tableName, column): - self.query('ALTER TABLE %s DROP COLUMN %s' % - (tableName, - column.dbName)) - - SHOW_COLUMNS=("select 'column' = COL_NAME(id, colid) " - "from syscolumns where id = OBJECT_ID(%s)") - def columnsFromSchema(self, tableName, soClass): - colData = self.queryAll(self.SHOW_COLUMNS - % tableName) - results = [] - for field, t, nullAllowed, key, default, extra in colData: - if field == 'id': - continue - colClass, kw = self.guessClass(t) - kw['name'] = soClass._style.dbColumnToPythonAttr(field) - kw['notNone'] = not nullAllowed - kw['default'] = default - # @@ skip key... - # @@ skip extra... - results.append(colClass(**kw)) - return results - - def guessClass(self, t): - if t.startswith('int'): - return col.IntCol, {} - elif t.startswith('varchar'): - return col.StringCol, {'length': int(t[8:-1])} - elif t.startswith('char'): - return col.StringCol, {'length': int(t[5:-1]), - 'varchar': False} - elif t.startswith('datetime'): - return col.DateTimeCol, {} - else: - return col.Col, {} - -######################################## -## Firebird connection -######################################## - -class FirebirdConnection(DBAPI): - - supportTransactions = False - dbName = 'firebird' - - def __init__(self, host, db, user='sysdba', - passwd='masterkey', autoCommit=1, **kw): - global kinterbasdb - if kinterbasdb is None: - import kinterbasdb - - self.limit_re = re.compile('^\s*(select )(.*)', re.IGNORECASE) - - if not autoCommit and not kw.has_key('pool'): - # Pooling doesn't work with transactions... - kw['pool'] = 0 - - self.host = host - self.db = db - self.user = user - self.passwd = passwd - - DBAPI.__init__(self, **kw) - - def _runWithConnection(self, meth, *args): - conn = self.getConnection() - # @@: Horrible auto-commit implementation. Just horrible! - try: - conn.begin() - except kinterbasdb.ProgrammingError: - pass - try: - val = meth(conn, *args) - try: - conn.commit() - except kinterbasdb.ProgrammingError: - pass - finally: - self.releaseConnection(conn) - return val - - def _setAutoCommit(self, conn, auto): - # Only _runWithConnection does "autocommit", so we don't - # need to worry about that. - pass - - def makeConnection(self): - return kinterbasdb.connect( - host = self.host, database = self.db, - user = self.user, password = self.passwd - ) - - def _queryInsertID(self, conn, table, idName, id, names, values): - """Firebird uses 'generators' to create new ids for a table. - The users needs to create a generator named GEN_<tablename> - for each table this method to work.""" - - if id is None: - row = self.queryOne('SELECT gen_id(GEN_%s,1) FROM rdb$database' - % table) - id = row[0] - names = [idName] + names - values = [id] + values - q = self._insertSQL(table, names, values) - if self.debug: - self.printDebug(conn, q, 'QueryIns') - self.query(q) - if self.debugOutput: - self.printDebug(conn, id, 'QueryIns', 'result') - return id - - def _queryAddLimitOffset(self, query, start, end): - """Firebird slaps the limit and offset (actually 'first' and - 'skip', respectively) statement right after the select.""" - if not start: - limit_str = "SELECT FIRST %i" % end - if not end: - limit_str = "SELECT SKIP %i" % start - else: - limit_str = "SELECT FIRST %i SKIP %i" % (end-start, start) - - match = self.limit_re.match(query) - if match and len(match.groups()) == 2: - return ' '.join([limit_str, match.group(2)]) - else: - return query - - def createTable(self, soClass): - self.query('CREATE TABLE %s (\n%s\n)' % \ - (soClass._table, self.createColumns(soClass))) - self.query("CREATE GENERATOR GEN_%s" % soClass._table) - - def createColumn(self, soClass, col): - return col.firebirdCreateSQL() - - def createIDColumn(self, soClass): - return '%s INT NOT NULL PRIMARY KEY' % soClass._idName - - def joinSQLType(self, join): - return 'INT NOT NULL' - - def tableExists(self, tableName): - # there's something in the database by this name...let's - # assume it's a table. By default, fb 1.0 stores EVERYTHING - # it cares about in uppercase. - result = self.queryOne("SELECT COUNT(rdb$relation_name) FROM rdb$relations WHERE rdb$relation_name = '%s'" - % tableName.upper()) - return result[0] - - def addColumn(self, tableName, column): - self.query('ALTER TABLE %s ADD %s' % - (tableName, - column.firebirdCreateSQL())) - - def dropTable(self, tableName, cascade=False): - self.query("DROP TABLE %s" % tableName) - self.query("DROP GENERATOR GEN_%s" % tableName) - - def delColumn(self, tableName, column): - self.query('ALTER TABLE %s DROP %s' % - (tableName, - column.dbName)) - - def columnsFromSchema(self, tableName, soClass): - """ - Look at the given table and create Col instances (or - subclasses of Col) for the fields it finds in that table. - """ - - fieldqry = """\ - SELECT RDB$RELATION_FIELDS.RDB$FIELD_NAME as field, - RDB$TYPES.RDB$TYPE_NAME as t, - RDB$FIELDS.RDB$FIELD_LENGTH as flength, - RDB$FIELDS.RDB$FIELD_SCALE as fscale, - RDB$RELATION_FIELDS.RDB$NULL_FLAG as nullAllowed, - RDB$RELATION_FIELDS.RDB$DEFAULT_VALUE as thedefault, - RDB$FIELDS.RDB$FIELD_SUB_TYPE as blobtype - FROM RDB$RELATION_FIELDS - INNER JOIN RDB$FIELDS ON - (RDB$RELATION_FIELDS.RDB$FIELD_SOURCE = RDB$FIELDS.RDB$FIELD_NAME) - INNER JOIN RDB$TYPES ON (RDB$FIELDS.RDB$FIELD_TYPE = - RDB$TYPES.RDB$TYPE) - WHERE - (RDB$RELATION_FIELDS.RDB$RELATION_NAME = '%s') - AND (RDB$TYPES.RDB$FIELD_NAME = 'RDB$FIELD_TYPE')""" - - colData = self.queryAll(fieldqry % tableName.upper()) - results = [] - for field, t, flength, fscale, nullAllowed, thedefault, blobType in colData: - if field == 'id': - continue - colClass, kw = self.guessClass(t, flength, fscale) - kw['name'] = soClass._style.dbColumnToPythonAttr(field) - kw['notNone'] = not nullAllowed - kw['default'] = thedefault - results.append(colClass(**kw)) - return results - - _intTypes=['INT64', 'SHORT','LONG'] - _dateTypes=['DATE','TIME','TIMESTAMP'] - - def guessClass(self, t, flength, fscale=None): - """ - An internal method that tries to figure out what Col subclass - is appropriate given whatever introspective information is - available -- both very database-specific. - """ - - if t in self._intTypes: - return col.IntCol, {} - elif t == 'VARYING': - return col.StringCol, {'length': flength} - elif t == 'TEXT': - return col.StringCol, {'length': flength, - 'varchar': False} - elif t in self._dateTypes: - return col.DateTimeCol, {} - else: - return col.Col, {} - -######################################## -## File-based connections -######################################## - -class FileConnection(DBConnection): - - """ - Files connections should deal with setup, and define the - methods: - - * ``_fetchDict(self, table, id)`` - * ``_saveDict(self, table, id, d)`` - * ``_newID(table)`` - * ``tableExists(table)`` - * ``createTable(soClass)`` - * ``dropTable(table)`` - * ``clearTable(table)`` - * ``_SO_delete(so)`` - * ``_allIDs()`` - * ``_SO_createJoinTable(join)`` - """ - - def queryInsertID(self, table, idName, id, names, values): - if id is None: - id = self._newID(table) - self._saveDict(table, id, dict(zip(names, values))) - return id - - def createColumns(self, soClass): - pass - - def _SO_update(self, so, values): - d = self._fetchDict(so._table, so.id) - for dbName, value in values: - d[dbName] = value - self._saveDict(so._table, so.id, d) - - def _SO_selectOne(self, so, columnNames): - d = self._fetchDict(so._table, so.id) - return [d[name] for name in columnNames] - - def _SO_selectOneAlt(self, cls, columnNames, column, value): - for id in self._allIDs(cls._table): - d = self._fetchDict(cls._table, id) - if d[column] == value: - d['id'] = id - return [d[name] for name in columnNames] - - _createRE = re.compile('CREATE TABLE\s+(IF NOT EXISTS\s+)?([^ ]*)', re.I) - _dropRE = re.compile('DROP TABLE\s+(IF EXISTS\s+)?([^ ]*)', re.I) - - def query(self, q): - match = self._createRE.search(q) - if match: - if match.group(1) and self.tableExists(match.group(2)): - return - class X: pass - x = X() - x._table = match.group(2) - return self.createTable(x) - match = self._dropRE.search(q) - if match: - if match.group(1) and not self.tableExists(match.group(2)): - return - return self.dropTable(match.group(2)) - - def addColumn(self, tableName, column): - for id in self._allIDs(tableName): - d = self._fetchDict(tableName, id) - d[column.dbName] = None - self._saveDict(tableName, id, d) - - def delColumn(self, tableName, column): - for id in self._allIDs(tableName): - d = self._fetchDict(tableName, id) - del d[column.dbName] - self._saveDict(tableName, id, d) - - def _SO_columnClause(self, soClass, kw): - clauses = [] - for name, value in kw.items(): - clauses.append(getattr(soClass.q, name) == value) - return sqlbuilder.AND(*clauses) - - def _SO_selectJoin(self, soClass, column, value): - results = [] - # @@: seems lame I need to do this... - value = int(value) - for id in self._allIDs(soClass._table): - d = self._fetchDict(soClass._table, id) - if d[column] == value: - results.append((id,)) - return results - -######################################## -## DBM connection -######################################## - -class DBMConnection(FileConnection): - - supportTransactions = False - dbName = 'dbm' - - def __init__(self, path, **kw): - global anydbm, pickle - if anydbm is None: - import anydbm - if pickle is None: - try: - import cPickle as pickle - except ImportError: - import pickle - self.path = path - try: - self._meta = anydbm.open(os.path.join(path, "meta.db"), "w") - except anydbm.error: - self._meta = anydbm.open(os.path.join(path, "meta.db"), "c") - self._tables = {} - atexit.register(self.close) - self._closed = 0 - FileConnection.__init__(self, **kw) - - def _newID(self, table): - id = int(self._meta["%s.id" % table]) + 1 - self._meta["%s.id" % table] = str(id) - return id - - def _saveDict(self, table, id, d): - db = self._getDB(table) - db[str(id)] = pickle.dumps(d) - - def _fetchDict(self, table, id): - return pickle.loads(self._getDB(table)[str(id)]) - - def _getDB(self, table): - try: - return self._tables[table] - except KeyError: - db = self._openTable(table) - self._tables[table] = db - return db - - def close(self): - if self._closed: - return - self._closed = 1 - self._meta.close() - del self._meta - for table in self._tables.values(): - table.close() - del self._tables - - def __del__(self): - FileConnection.__del__(self) - self.close() - - def _openTable(self, table): - try: - db = anydbm.open(os.path.join(self.path, "%s.db" % table), "w") - except anydbm.error: - db = anydbm.open(os.path.join(self.path, "%s.db" % table), "c") - return db - - def tableExists(self, table): - return self._meta.has_key("%s.id" % table) \ - or os.path.exists(os.path.join(self.path, table + ".db")) - - def createTable(self, soClass): - self._meta["%s.id" % soClass._table] = "1" - - def dropTable(self, tableName, cascade=False): - try: - del self._meta["%s.id" % tableName] - except KeyError: - pass - self.clearTable(tableName) - - def clearTable(self, tableName): - if self._tables.has_key(tableName): - del self._tables[tableName] - filename = os.path.join(self.path, "%s.db" % tableName) - if os.path.exists(filename): - os.unlink(filename) - - def _SO_delete(self, so): - db = self._getDB(so._table) - del db[str(so.id)] - - def iterSelect(self, select): - return DBMSelectResults(self, select) - - def _allIDs(self, tableName): - return self._getDB(tableName).keys() - - def _SO_createJoinTable(self, join): - pass - - def _SO_dropJoinTable(self, join): - os.unlink(os.path.join(self.path, join.intermediateTable + ".db")) - - def _SO_intermediateJoin(self, table, get, join1, id1): - db = self._openTable(table) - try: - results = db[join1 + str(id1)] - except KeyError: - return [] - if not results: - return [] - return [(int(id),) for id in results.split(',')] - - def _SO_intermediateInsert(self, table, join1, id1, join2, id2): - db = self._openTable(table) - try: - results = db[join1 + str(id1)] - except KeyError: - results = "" - if results: - db[join1 + str(id1)] = results + "," + str(id2) - else: - db[join1 + str(id1)] = str(id2) - - try: - results = db[join2 + str(id2)] - except KeyError: - results = "" - if results: - db[join2 + str(id2)] = results + "," + str(id1) - else: - db[join2 + str(id2)] = str(id1) - - def _SO_intermediateDelete(self, table, join1, id1, join2, id2): - db = self._openTable(table) - try: - results = db[join1 + str(id1)] - except KeyError: - results = "" - results = map(int, results.split(",")) - results.remove(int(id2)) - db[join1 + str(id1)] = ",".join(map(str, results)) - try: - results = db[join2 + str(id2)] - except KeyError: - results = "" - results = map(int, results.split(",")) - results.remove(int(id1)) - db[join2 + str(id2)] = ",".join(map(str, results)) - -class DBMSelectResults(object): - - def __init__(self, conn, select): - self.select = select - self.conn = conn - self.tables = select.tables - self.tableDict = {} - self._results = None - for i in range(len(self.tables)): - self.tableDict[self.tables[i]] = i - self.comboIter = _iterAllCombinations( - [self.conn._getDB(table).keys() for table in self.tables]) - if select.ops.get('orderBy'): - self._maxNext = -1 - results = self.allResults() - results.sort(sorter(select.ops['orderBy'])) - self._results = results - if select.ops.get('start'): - if select.ops.get('end'): - self._results = self._results[select.ops['start']:select.ops['end']] - else: - self._results = self._results[select.ops['start']:] - elif select.ops.get('end'): - self._results = self._results[:select.ops['end']] - elif select.ops.get('start'): - for i in range(select.ops.get('start')): - self.next() - if select.ops.get('end'): - self._maxNext = select.ops['end'] - select.ops['start'] - elif select.ops.get('end'): - self._maxNext = select.ops['end'] - else: - self._maxNext = -1 - - def next(self): - if self._results is not None: - try: - return self._results.pop(0) - except IndexError: - raise StopIteration - - for idList in self.comboIter: - self.idList = idList - if sqlbuilder.execute(self.select.clause, self): - if not self._maxNext: - raise StopIteration - self._maxNext -= 1 - return self.select.sourceClass.get(int(idList[self.tableDict[self.select.sourceClass._table]])) - raise StopIteration - - def field(self, table, field): - return self.conn._fetchDict(table, self.idList[self.tableDict[table]])[field] - - def allResults(self): - results = [] - while 1: - try: - results.append(self.next()) - except StopIteration: - return results - +class ConnectionURIOpener(object): -def _iterAllCombinations(l): - if len(l) == 1: - for id in l[0]: - yield [id] - else: - for id in l[0]: - for idList in _iterAllCombinations(l[1:]): - yield [id] + idList + def __init__(self): + self.allClasses = [] + self.classSchemes = {} + self.instanceNames = {} + + def registerConnectionClass(self, cls): + if cls not in self.allClasses: + self.allClasses.append(cls) + for uriScheme in cls.schemes: + assert not self.classSchemes.has_key(uriScheme) \ + or self.classSchemes[uriScheme] is cls, \ + "A class has already been registered for the URI scheme %s" % uriScheme + self.classSchemes[uriScheme] = cls + + def registerConnectionInstance(self, inst): + if inst.name: + assert not self.instanceNames.has_key(inst.name) \ + or self.instanceNames[inst.name] is cls, \ + "A instance has already been registered with the name %s" % inst.name + assert inst.name.find(':') == -1, "You cannot include ':' in your class names (%r)" % cls.name + self.instanceNames[inst.name] = inst + + def openURI(self, uri): + if uri.find(':') != -1: + scheme, rest = uri.split(':', 1) + assert self.classSchemes.has_key(scheme), \ + "No SQLObject driver exists for %s" % scheme + return self.classSchemes[scheme].connectionFromURI(uri) + else: + # We just have a name, not a URI + assert self.instanceNames.has_key(uri), \ + "No SQLObject driver exists under the name %s" % uri + return self.instanceNames[uri] + +TheURIOpener = ConnectionURIOpener() + +registerConnectionClass = TheURIOpener.registerConnectionClass +registerConnectionInstance = TheURIOpener.registerConnectionInstance +openURI = TheURIOpener.openURI Added: trunk/SQLObject/sqlobject/dbm/__init__.py ============================================================================== --- (empty file) +++ trunk/SQLObject/sqlobject/dbm/__init__.py Sun Feb 8 00:39:59 2004 @@ -0,0 +1,5 @@ +from sqlobject import dbconnection +from dbmconnection import DBMConnection + +dbconnection.registerConnectionClass( + DBMConnection) Added: trunk/SQLObject/sqlobject/dbm/dbmconnection.py ============================================================================== --- (empty file) +++ trunk/SQLObject/sqlobject/dbm/dbmconnection.py Sun Feb 8 00:39:59 2004 @@ -0,0 +1,343 @@ +from sqlobject.dbconnection import DBConnection +import re +anydbm = None +pickle = None + +######################################## +## File-based connections +######################################## + +class FileConnection(DBConnection): + + """ + Files connections should deal with setup, and define the + methods: + + * ``_fetchDict(self, table, id)`` + * ``_saveDict(self, table, id, d)`` + * ``_newID(table)`` + * ``tableExists(table)`` + * ``createTable(soClass)`` + * ``dropTable(table)`` + * ``clearTable(table)`` + * ``_SO_delete(so)`` + * ``_allIDs()`` + * ``_SO_createJoinTable(join)`` + """ + + def queryInsertID(self, table, idName, id, names, values): + if id is None: + id = self._newID(table) + self._saveDict(table, id, dict(zip(names, values))) + return id + + def createColumns(self, soClass): + pass + + def _SO_update(self, so, values): + d = self._fetchDict(so._table, so.id) + for dbName, value in values: + d[dbName] = value + self._saveDict(so._table, so.id, d) + + def _SO_selectOne(self, so, columnNames): + d = self._fetchDict(so._table, so.id) + return [d[name] for name in columnNames] + + def _SO_selectOneAlt(self, cls, columnNames, column, value): + for id in self._allIDs(cls._table): + d = self._fetchDict(cls._table, id) + if d[column] == value: + d['id'] = id + return [d[name] for name in columnNames] + + _createRE = re.compile('CREATE TABLE\s+(IF NOT EXISTS\s+)?([^ ]*)', re.I) + _dropRE = re.compile('DROP TABLE\s+(IF EXISTS\s+)?([^ ]*)', re.I) + + def query(self, q): + match = self._createRE.search(q) + if match: + if match.group(1) and self.tableExists(match.group(2)): + return + class X: pass + x = X() + x._table = match.group(2) + return self.createTable(x) + match = self._dropRE.search(q) + if match: + if match.group(1) and not self.tableExists(match.group(2)): + return + return self.dropTable(match.group(2)) + + def addColumn(self, tableName, column): + for id in self._allIDs(tableName): + d = self._fetchDict(tableName, id) + d[column.dbName] = None + self._saveDict(tableName, id, d) + + def delColumn(self, tableName, column): + for id in self._allIDs(tableName): + d = self._fetchDict(tableName, id) + del d[column.dbName] + self._saveDict(tableName, id, d) + + def _SO_columnClause(self, soClass, kw): + clauses = [] + for name, value in kw.items(): + clauses.append(getattr(soClass.q, name) == value) + return sqlbuilder.AND(*clauses) + + def _SO_selectJoin(self, soClass, column, value): + results = [] + # @@: seems lame I need to do this... + value = int(value) + for id in self._allIDs(soClass._table): + d = self._fetchDict(soClass._table, id) + if d[column] == value: + results.append((id,)) + return results + +######################################## +## DBM connection +######################################## + +class DBMConnection(FileConnection): + + supportTransactions = False + dbName = 'dbm' + schemes = [dbName] + + def __init__(self, path, **kw): + global anydbm, pickle + if anydbm is None: + import anydbm + if pickle is None: + try: + import cPickle as pickle + except ImportError: + import pickle + self.path = path + try: + self._meta = anydbm.open(os.path.join(path, "meta.db"), "w") + except anydbm.error: + self._meta = anydbm.open(os.path.join(path, "meta.db"), "c") + self._tables = {} + atexit.register(self.close) + self._closed = 0 + self.host = self.user = self.password = None + FileConnection.__init__(self, **kw) + + def connectionFromURI(cls, uri): + user, password, host, path = self._parseURI(uri, expectHost=False) + assert host is None + assert user is None and password is None, \ + "SQLite cannot accept usernames or passwords" + path = '/' + path + return cls(path) + connectionFromURI = classmethod(connectionFromURI) + + def isSupported(cls): + global anydbm + if anydbm is None: + try: + import anydbm + except ImportError: + return False + return True + isSupported = classmethod(isSupported) + + def _newID(self, table): + id = int(self._meta["%s.id" % table]) + 1 + self._meta["%s.id" % table] = str(id) + return id + + def _saveDict(self, table, id, d): + db = self._getDB(table) + db[str(id)] = pickle.dumps(d) + + def _fetchDict(self, table, id): + return pickle.loads(self._getDB(table)[str(id)]) + + def _getDB(self, table): + try: + return self._tables[table] + except KeyError: + db = self._openTable(table) + self._tables[table] = db + return db + + def close(self): + if self._closed: + return + self._closed = 1 + self._meta.close() + del self._meta + for table in self._tables.values(): + table.close() + del self._tables + + def __del__(self): + FileConnection.__del__(self) + self.close() + + def _openTable(self, table): + try: + db = anydbm.open(os.path.join(self.path, "%s.db" % table), "w") + except anydbm.error: + db = anydbm.open(os.path.join(self.path, "%s.db" % table), "c") + return db + + def tableExists(self, table): + return self._meta.has_key("%s.id" % table) \ + or os.path.exists(os.path.join(self.path, table + ".db")) + + def createTable(self, soClass): + self._meta["%s.id" % soClass._table] = "1" + + def dropTable(self, tableName, cascade=False): + try: + del self._meta["%s.id" % tableName] + except KeyError: + pass + self.clearTable(tableName) + + def clearTable(self, tableName): + if self._tables.has_key(tableName): + del self._tables[tableName] + filename = os.path.join(self.path, "%s.db" % tableName) + if os.path.exists(filename): + os.unlink(filename) + + def _SO_delete(self, so): + db = self._getDB(so._table) + del db[str(so.id)] + + def iterSelect(self, select): + return DBMSelectResults(self, select) + + def _allIDs(self, tableName): + return self._getDB(tableName).keys() + + def _SO_createJoinTable(self, join): + pass + + def _SO_dropJoinTable(self, join): + os.unlink(os.path.join(self.path, join.intermediateTable + ".db")) + + def _SO_intermediateJoin(self, table, get, join1, id1): + db = self._openTable(table) + try: + results = db[join1 + str(id1)] + except KeyError: + return [] + if not results: + return [] + return [(int(id),) for id in results.split(',')] + + def _SO_intermediateInsert(self, table, join1, id1, join2, id2): + db = self._openTable(table) + try: + results = db[join1 + str(id1)] + except KeyError: + results = "" + if results: + db[join1 + str(id1)] = results + "," + str(id2) + else: + db[join1 + str(id1)] = str(id2) + + try: + results = db[join2 + str(id2)] + except KeyError: + results = "" + if results: + db[join2 + str(id2)] = results + "," + str(id1) + else: + db[join2 + str(id2)] = str(id1) + + def _SO_intermediateDelete(self, table, join1, id1, join2, id2): + db = self._openTable(table) + try: + results = db[join1 + str(id1)] + except KeyError: + results = "" + results = map(int, results.split(",")) + results.remove(int(id2)) + db[join1 + str(id1)] = ",".join(map(str, results)) + try: + results = db[join2 + str(id2)] + except KeyError: + results = "" + results = map(int, results.split(",")) + results.remove(int(id1)) + db[join2 + str(id2)] = ",".join(map(str, results)) + +class DBMSelectResults(object): + + def __init__(self, conn, select): + self.select = select + self.conn = conn + self.tables = select.tables + self.tableDict = {} + self._results = None + for i in range(len(self.tables)): + self.tableDict[self.tables[i]] = i + self.comboIter = _iterAllCombinations( + [self.conn._getDB(table).keys() for table in self.tables]) + if select.ops.get('orderBy'): + self._maxNext = -1 + results = self.allResults() + results.sort(sorter(select.ops['orderBy'])) + self._results = results + if select.ops.get('start'): + if select.ops.get('end'): + self._results = self._results[select.ops['start']:select.ops['end']] + else: + self._results = self._results[select.ops['start']:] + elif select.ops.get('end'): + self._results = self._results[:select.ops['end']] + elif select.ops.get('start'): + for i in range(select.ops.get('start')): + self.next() + if select.ops.get('end'): + self._maxNext = select.ops['end'] - select.ops['start'] + elif select.ops.get('end'): + self._maxNext = select.ops['end'] + else: + self._maxNext = -1 + + def next(self): + if self._results is not None: + try: + return self._results.pop(0) + except IndexError: + raise StopIteration + + for idList in self.comboIter: + self.idList = idList + if sqlbuilder.execute(self.select.clause, self): + if not self._maxNext: + raise StopIteration + self._maxNext -= 1 + return self.select.sourceClass.get(int(idList[self.tableDict[self.select.sourceClass._table]])) + raise StopIteration + + def field(self, table, field): + return self.conn._fetchDict(table, self.idList[self.tableDict[table]])[field] + + def allResults(self): + results = [] + while 1: + try: + results.append(self.next()) + except StopIteration: + return results + + +def _iterAllCombinations(l): + if len(l) == 1: + for id in l[0]: + yield [id] + else: + for id in l[0]: + for idList in _iterAllCombinations(l[1:]): + yield [id] + idList Added: trunk/SQLObject/sqlobject/firebird/__init__.py ============================================================================== --- (empty file) +++ trunk/SQLObject/sqlobject/firebird/__init__.py Sun Feb 8 00:39:59 2004 @@ -0,0 +1,5 @@ +from sqlobject import dbconnection +from firebirdconnection import FirebirdConnection + +dbconnection.registerConnectionClass( + FirebirdConnection) Added: trunk/SQLObject/sqlobject/firebird/firebirdconnection.py ============================================================================== --- (empty file) +++ trunk/SQLObject/sqlobject/firebird/firebirdconnection.py Sun Feb 8 00:39:59 2004 @@ -0,0 +1,202 @@ +from sqlobject.dbconnection import DBAPI +kinterbasdb = None + +class FirebirdConnection(DBAPI): + + supportTransactions = False + dbName = 'firebird' + schemes = [dbName] + + def __init__(self, host, db, user='sysdba', + passwd='masterkey', autoCommit=1, **kw): + global kinterbasdb + if kinterbasdb is None: + import kinterbasdb + + self.limit_re = re.compile('^\s*(select )(.*)', re.IGNORECASE) + + if not autoCommit and not kw.has_key('pool'): + # Pooling doesn't work with transactions... + kw['pool'] = 0 + + self.host = host + self.db = db + self.user = user + self.passwd = passwd + + DBAPI.__init__(self, **kw) + + def connectionFromURI(cls, uri): + auth, password, host, path = self._parseURI(url) + if not password: + password = 'masterkey' + if not auth: + auth='sysdba' + return cls(host, db=path, user=user, passwd=password) + connectionFromURI = classmethod(connectionFromURI) + + def isSupported(cls): + global kinterbasdb + if kinterbasdb is None: + try: + import kinterbasdb + except ImportError: + return False + return True + isSupported = classmethod(isSupported) + + def _runWithConnection(self, meth, *args): + conn = self.getConnection() + # @@: Horrible auto-commit implementation. Just horrible! + try: + conn.begin() + except kinterbasdb.ProgrammingError: + pass + try: + val = meth(conn, *args) + try: + conn.commit() + except kinterbasdb.ProgrammingError: + pass + finally: + self.releaseConnection(conn) + return val + + def _setAutoCommit(self, conn, auto): + # Only _runWithConnection does "autocommit", so we don't + # need to worry about that. + pass + + def makeConnection(self): + return kinterbasdb.connect( + host = self.host, database = self.db, + user = self.user, password = self.passwd + ) + + def _queryInsertID(self, conn, table, idName, id, names, values): + """Firebird uses 'generators' to create new ids for a table. + The users needs to create a generator named GEN_<tablename> + for each table this method to work.""" + + if id is None: + row = self.queryOne('SELECT gen_id(GEN_%s,1) FROM rdb$database' + % table) + id = row[0] + names = [idName] + names + values = [id] + values + q = self._insertSQL(table, names, values) + if self.debug: + self.printDebug(conn, q, 'QueryIns') + self.query(q) + if self.debugOutput: + self.printDebug(conn, id, 'QueryIns', 'result') + return id + + def _queryAddLimitOffset(self, query, start, end): + """Firebird slaps the limit and offset (actually 'first' and + 'skip', respectively) statement right after the select.""" + if not start: + limit_str = "SELECT FIRST %i" % end + if not end: + limit_str = "SELECT SKIP %i" % start + else: + limit_str = "SELECT FIRST %i SKIP %i" % (end-start, start) + + match = self.limit_re.match(query) + if match and len(match.groups()) == 2: + return ' '.join([limit_str, match.group(2)]) + else: + return query + + def createTable(self, soClass): + self.query('CREATE TABLE %s (\n%s\n)' % \ + (soClass._table, self.createColumns(soClass))) + self.query("CREATE GENERATOR GEN_%s" % soClass._table) + + def createColumn(self, soClass, col): + return col.firebirdCreateSQL() + + def createIDColumn(self, soClass): + return '%s INT NOT NULL PRIMARY KEY' % soClass._idName + + def joinSQLType(self, join): + return 'INT NOT NULL' + + def tableExists(self, tableName): + # there's something in the database by this name...let's + # assume it's a table. By default, fb 1.0 stores EVERYTHING + # it cares about in uppercase. + result = self.queryOne("SELECT COUNT(rdb$relation_name) FROM rdb$relations WHERE rdb$relation_name = '%s'" + % tableName.upper()) + return result[0] + + def addColumn(self, tableName, column): + self.query('ALTER TABLE %s ADD %s' % + (tableName, + column.firebirdCreateSQL())) + + def dropTable(self, tableName, cascade=False): + self.query("DROP TABLE %s" % tableName) + self.query("DROP GENERATOR GEN_%s" % tableName) + + def delColumn(self, tableName, column): + self.query('ALTER TABLE %s DROP %s' % + (tableName, + column.dbName)) + + def columnsFromSchema(self, tableName, soClass): + """ + Look at the given table and create Col instances (or + subclasses of Col) for the fields it finds in that table. + """ + + fieldqry = """\ + SELECT RDB$RELATION_FIELDS.RDB$FIELD_NAME as field, + RDB$TYPES.RDB$TYPE_NAME as t, + RDB$FIELDS.RDB$FIELD_LENGTH as flength, + RDB$FIELDS.RDB$FIELD_SCALE as fscale, + RDB$RELATION_FIELDS.RDB$NULL_FLAG as nullAllowed, + RDB$RELATION_FIELDS.RDB$DEFAULT_VALUE as thedefault, + RDB$FIELDS.RDB$FIELD_SUB_TYPE as blobtype + FROM RDB$RELATION_FIELDS + INNER JOIN RDB$FIELDS ON + (RDB$RELATION_FIELDS.RDB$FIELD_SOURCE = RDB$FIELDS.RDB$FIELD_NAME) + INNER JOIN RDB$TYPES ON (RDB$FIELDS.RDB$FIELD_TYPE = + RDB$TYPES.RDB$TYPE) + WHERE + (RDB$RELATION_FIELDS.RDB$RELATION_NAME = '%s') + AND (RDB$TYPES.RDB$FIELD_NAME = 'RDB$FIELD_TYPE')""" + + colData = self.queryAll(fieldqry % tableName.upp... [truncated message content] |
From: <sub...@co...> - 2004-02-07 23:31:50
|
Author: ianb Date: Sat Feb 7 14:24:34 2004 New Revision: 7 Modified: trunk/SQLObject/docs/News.txt Log: Added some news for 0.6 Modified: trunk/SQLObject/docs/News.txt ============================================================================== --- trunk/SQLObject/docs/News.txt (original) +++ trunk/SQLObject/docs/News.txt Sat Feb 7 14:24:34 2004 @@ -7,6 +7,22 @@ .. _start: +SQLObject 0.6 +============= + +Interface Changes +----------------- + +* The ``SQLObject`` package has been renamed to ``sqlobject``. This + makes it similar to several other packages, and emphasizes the + distinction between the ``sqlobject`` package and the ``SQLObject`` + class. +* Class instantiation now creates new rows (like `.new()` used to + do), and the `.get()` method now retrieves objects that already have + rows (like class instantiation used to do). +* We're now using a Subversion repository instead of CVS. It is + located at svn://colorstudy.com/trunk/SQLObject + SQLObject 0.5.2 =============== |
From: <sub...@co...> - 2004-02-07 23:29:15
|
Author: ianb Date: Sat Feb 7 14:21:53 2004 New Revision: 6 Modified: trunk/SQLObject/docs/SQLObject.txt trunk/SQLObject/examples/ (props changed) trunk/SQLObject/examples/codebits.py trunk/SQLObject/examples/config.py trunk/SQLObject/examples/examplestripper.py trunk/SQLObject/examples/leftjoin.py trunk/SQLObject/examples/people.py trunk/SQLObject/examples/personaddress.py trunk/SQLObject/examples/setup.py trunk/SQLObject/examples/simpleperson.py trunk/SQLObject/examples/styles.py trunk/SQLObject/examples/userrole.py Log: Fixed examples and documentation for .new/.get change, and the new sqlobject (vs. SQLObject) package Modified: trunk/SQLObject/docs/SQLObject.txt ============================================================================== --- trunk/SQLObject/docs/SQLObject.txt (original) +++ trunk/SQLObject/docs/SQLObject.txt Sat Feb 7 14:21:53 2004 @@ -65,7 +65,7 @@ effects (it changes the database), and defining classes has side effects (through the use of metaclasses). Attributes are generally exposed, not marked private, knowing that they can be made dynamic -later. +or write-only later. SQLObject creates objects that feel similar to normal Python objects (with the semantics of new-style classes). An attribute attached to a @@ -96,10 +96,10 @@ .. _Webware: http://webware.sourceforge.net SQLObject provides a strong database abstraction, allowing -cross-database compatibility (so long as you don't specifically go -around SQLObject). This compatibility extends not just to several -databases, but also to currently one non-SQL, non-relational backend -(based on the `dbm` module). +cross-database compatibility (so long as you don't sidestep +SQLObject). This compatibility extends not just to several databases, +but also to currently one non-SQL, non-relational backend (based on +the `dbm` module). SQLObject has joins, one-to-many, and many-to-many, something which many ORMs do not have. The join system is also intended to be @@ -109,7 +109,7 @@ names; often these two won't match, or the database style would be inappropriate for a Python attribute. This way your database schema does not have to be designed with SQLObject in mind, and the resulting -classes +classes do not have to inherit the database's naming schemes. Future ====== @@ -130,6 +130,10 @@ * More kinds of joins, and more powerful join results (closer to how `select` works). +See also the `Plan for 0.6`__. + +.. __: Plan06.html + Using SQLObject: An Introduction ================================ @@ -164,6 +168,11 @@ use (you can also set a module-level variable `__connection__` which would automatically be picked up if you don't specify `_connection`). +.. warning:: + The `__connection__` magic variable can be a little fragile -- it + has to be defined before the class is defined. This means it + *must* be assigned above the ``class ...:`` line. + `firstName`, `middleInitial`, and `lastName` are all columns in the database. The general schema implied by this class definition is: @@ -191,7 +200,7 @@ you want to create the tables yourself), you can just use the vague `Col` class. SQLObject doesn't do much type checking, allowing the database and the adapter to handle most of the type conversion. -Databases usually do their own type coercion anyway. +Databases generally do their own type coercion on inputs. You'll note that the ``id`` column is not given in the class definition, it is implied. For MySQL databases it should be defined @@ -209,16 +218,23 @@ Now that you have a class, how will you use it? We'll be considering the class defined above. -You can use the standard constructor to fetch instances that *already -exist*. So if you wanted to fetch the Person by id 10, you'd call -``Person(10)``. - -To create a new object (and row), use the `.new()` class method. In -this case you might call ``Person.new(firstName="John", -lastName="Doe")``. If you had left out ``firstName`` or ``lastName`` -you would have gotten an error, as no default was given for these -columns (``middleInitial`` has a default, so it will be set to -``NULL``, the SQL equivalent of ``None``). +You can use the class method `.get()` to fetch instances that +already exist. So if you wanted to fetch the Person by id 10, you'd +call ``Person.get(10)``. + +.. warning:: + This is a change from SQLObject 0.5 -- before the standard + constructor fetched rows from the database, and the `.new()` + method created new rows. Now SQLObject is more like Python, where + the class constructor creates a new object/row, and the `.get()` + method fetches a row. + +To create a new object (and row), use class instantiation. In this +case you might call ``Person.new(firstName="John", lastName="Doe")``. +If you had left out ``firstName`` or ``lastName`` you would have +gotten an error, as no default was given for these columns +(``middleInitial`` has a default, so it will be set to ``NULL``, the +SQL equivalent of ``None``). When you create an object, it is immediately inserted into the database. SQLObject generally uses the database as immediate storage. @@ -236,7 +252,7 @@ person by a particular ID, you'll get back the same instance. This way you can be sure of a certain amount of consistency if you have multiple threads accessing the same data (though of course across -processes there can be no sharing of an instance). This changes if +processes there can be no sharing of an instance). This isn't true if you're using transactions_. To get an idea of what's happening behind the surface, I'll give the @@ -259,15 +275,15 @@ :file: ../examples/snippets/simpleaddress-person1-use-set.html This will send only one ``UPDATE`` statement. You can also use `set` -with non-database properties (there's no benefit, but the distinction -between database columns and other attributes thus remains somewhat -hidden). +with non-database properties (there's no benefit, but it helps hide +the difference between database and non-database attributes). + One-to-Many Relationships ------------------------- -Well, a real address book should have people, but also addresses. -These examples are in ``personaddress.py`` +A real address book should have people, but also addresses. These +examples are in ``personaddress.py`` First, let's define the new address table. People can have multiple addresses, of course: @@ -275,7 +291,7 @@ .. raw:: html :file: ../examples/snippets/address-address.html -Note the column ``person = ForeignKey('Person')``. This is a +Note the column ``person = ForeignKey("Person")``. This is a reference to a `Person` object. We refer to other classes by name (with a string) to avoid circular dependencies. In the database there will be a ``person_id`` column, type ``INT``, which points to @@ -286,10 +302,10 @@ .. raw:: html :file: ../examples/snippets/address-person.html -We get the backreference with ``addresses = -MultipleJoin('Address')``. When we access a person's `addresses` -attribute, we will get back a (dynamic) list of all the `Address` -objects associated with that person. An example: +We get the backreference with ``addresses = MultipleJoin('Address')``. +When we access a person's `addresses` attribute, we will get back a +list of all the `Address` objects associated with that person. An +example: .. raw:: html :file: ../examples/snippets/address-use1.html @@ -320,6 +336,17 @@ identifier is in addition to the primary key (``id``), which is always present. +.. note:: + SQLObject has a strong requirement that the primary key be unique + and *immutable*. You cannot change the primary key through + SQLObject, and if you change it through another mechanism you can + cause inconsistency in any running SQLObject program (and in your + data). For this reason meaningless integer IDs are encouraged -- + something like a username that could change in the future may + uniquely identify a row, but it may be changed in the future. So + long as it is not used to reference the row internally, it is also + *safe* to change it in the future. + A alternateID column creates a class method, like ``byUsername`` for a column named ``username`` (or you can use the `alternateMethodName` keyword argument to override this). Its use: @@ -356,8 +383,8 @@ .. raw:: html :file: ../examples/snippets/person-select3.html -You may wish to use `MyClass.sqlrepr` to quote any values you use -if you use this technique (quoting is automatic if you use ``q``). +You may wish to use `MyClass.sqlrepr` to quote any values you use if +you create SQL manually (quoting is automatic if you use ``q``). Tables given in `clauseTables` will be added to the ``FROM`` portion (again, they are automatically picked up when using ``q``). The table you're selecting is always assumed to be included, of course. @@ -367,9 +394,8 @@ You can use the keyword arguments `orderBy` to create ``ORDER BY`` in the select statements: `orderBy` takes a string, which should be the *database* name of the column, or a column in the form -``Person.q.firstName``; `groupBy` is similar. Both accept lists or -tuples of arguments. You can use ``"-colname"`` to specify descending -order, or call ``MyClass.select().reversed()``. +``Person.q.firstName``. You can use ``"-colname"`` to specify +descending order, or call ``MyClass.select().reversed()``. You can use the special class variable `_defaultOrder` to give a default ordering for all selects. To get an unordered result when @@ -380,7 +406,9 @@ use ``list()`` to force the result to be executed. When you iterate over the select results, rows are fetched one at a time. This way you can iterate over large results without keeping the entire result set -in memory. +in memory. You can also do things like ``.reversed()`` without +fetching and reversing the entire result -- instead, SQLObject can +change the SQL that is sent so you get equivalent results. You can also slice select results. The results are used in the SQL query, so ``peeps[:10]`` will result in ``LIMIT 10`` being added to @@ -398,6 +426,29 @@ .. raw:: html :file: ../examples/snippets/slicing-batch.html +.. note:: + + There are several factors when considering the efficiency of this + kind of batching, and it depends very much how the batching is + being used. Consider a web application where you are showing an + average of 100 results, 10 at a time, and the results are ordered + by the date they were added to the database. While slicing will + keep the database from returning all the results (and so save some + communication time), the database will still have to scan through + the entire result set to sort the items (so it knows which the + first ten are), and depending on your query may need to scan + through the entire table (depending on your use of indexes). + Indexes are probably the most important way to improve importance + in a case like this, and you may find caching to be more effective + than slicing. + + In this case, caching would mean retrieving the *complete* results. + You can use ``list(MyClass.select(...))`` to do this. You can save + these results for some limited period of time, as the user looks + through the results page by page. This means the first page in a + search result will be slightly more expensive, but all later pages + will be very cheap. + For more information on the where clause in the queries, see the `SQLBuilder documentation`_. @@ -414,29 +465,36 @@ Initializing the Objects ~~~~~~~~~~~~~~~~~~~~~~~~ -With new-style classes, `__init__` is called everytime the class is -called. That means it's called when an object is just fetched from -the cache. That's useless in most cases, so instead we use a `_init` -method, which is only called once in an object's life (with one -argument -- the object's ID). +There are two ways SQLObject instances can come into existance: they +can be fetched from the database, or they can be inserted into the +database. In both cases a new Python object is created. This makes +the place of `__init__` a little confusing. + +In general, you should not touch `__init__`. Instead use the `_init` +method, which is called after an object is fetched or inserted. This +method has the signature ``_init(self, id, connection=None, +selectResults=None)``, though you may just want to use ``_init(self, +*args, **kw)``. Adding Magic Attributes (properties) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can use all the normal techniques for defining this new-style -class, including `classmethod`, `staticmethod`, and `property`, but -you can use a shortcut. If you have a method that's name starts with -``_set_``, ``_get_``, ``_del_``, or ``_doc_``, it will be used to -create a property. So, for instance, say you have images stored under -the ID of the person in the ``/var/people/images`` directory: +You can use all the normal techniques for defining methods in this +new-style class, including `classmethod`, `staticmethod`, and +`property`, but you can also use a shortcut. If you have a method +that's name starts with ``_set_``, ``_get_``, ``_del_``, or ``_doc_``, +it will be used to create a property. So, for instance, say you have +images stored under the ID of the person in the ``/var/people/images`` +directory: .. raw:: html :file: ../examples/snippets/person_magicmethod.html Later, you can use the ``.image`` property just like an attribute, and the changes will be reflected in the filesystem by calling these -methods. I use this particular technique frequently for information -that is better to keep in files as opposed to the database. +methods. This is a good technique for information that is better to +keep in files as opposed to the database (such as large, opaque data +like images). You can also pass an ``image`` keyword argument to the `new` class method or the `set` method, like ``Person.new(..., image=imageText)``. @@ -455,12 +513,13 @@ code you want to run whenever someone's name changes -- you could make a subclass, and then use ``Person.__setattr__(self, 'lastName', value)`` to actually do the deed, but that's obviously very awkward -- -you have to create subclasses without an real inheritance -relationship, and the whole thing feels architecturally fragile. -SQLObject creates methods like ``_set_lastName`` for each of your -columns, but again you can't use this, since there's no superclass to -reference (and you can't write ``SQLObject._set_lastName(...)``). You -want to override that ``_set_lastName`` method yourself. +you have to create subclasses without a real inheritance relationship, +and the whole thing feels architecturally fragile. SQLObject creates +methods like ``_set_lastName`` for each of your columns, but again you +can't use this, since there's no superclass to reference (and you +can't write ``SQLObject._set_lastName(...)``, because the SQLObject +class doesn't know about your class's columns). You want to override +that ``_set_lastName`` method yourself. To deal with this, SQLObject creates two methods for each getter and setter, for example: ``_set_lastName`` and ``_SO_set_lastName``. So @@ -475,12 +534,15 @@ .. raw:: html :file: ../examples/snippets/phonenumber_magicoverride.html -Of course, the user may be surprised if the value they set the -attribute to is not the same value they get back -- in this case we -removed some of the characters before putting it in the database, and -then formatted the number into a nice string on the way out. This is -one disadvantage of making actual work look like simple attribute -access. +.. note:: + + You should be a little cautious when modifying data that gets set + in an attribute. Generally someone using your class will expect + that the value they set the attribute to will be the same value + they get back. In this example we removed some of the characters + before putting it in the database, and reformatted it on the way + out. One advantage of methods (as opposed to attribute access) is + that the programmer is more likely to expect this disconnect. Reference ========= Modified: trunk/SQLObject/examples/codebits.py ============================================================================== --- trunk/SQLObject/examples/codebits.py (original) +++ trunk/SQLObject/examples/codebits.py Sat Feb 7 14:21:53 2004 @@ -67,7 +67,7 @@ ## Snippet "transactions1" conn = DBConnection.PostgresConnection('yada') trans = conn.transaction() -p = Person(1, trans) +p = Person.get(1, trans) p.firstName = 'Bob' trans.commit() p.firstName = 'Billy' Modified: trunk/SQLObject/examples/config.py ============================================================================== --- trunk/SQLObject/examples/config.py (original) +++ trunk/SQLObject/examples/config.py Sat Feb 7 14:21:53 2004 @@ -1,4 +1,4 @@ -from SQLObject import * +from sqlobject import * """ This contains basic configuration for all the examples. Since they all require a connection, you can configure that just in this file. @@ -6,10 +6,11 @@ ## Use one of these to define your connection: """ +## Snippet "connections" conn = MySQLConnection(user='test', db='testdb') conn = PostgresConnection('user=test dbname=testdb') conn = SQLiteConnect('database.db') conn = DBMConnection('database/') +## end snippet """ -conn = DBMConnection('database/') conn = MySQLConnection(user='test', db='test') Modified: trunk/SQLObject/examples/examplestripper.py ============================================================================== --- trunk/SQLObject/examples/examplestripper.py (original) +++ trunk/SQLObject/examples/examplestripper.py Sat Feb 7 14:21:53 2004 @@ -10,6 +10,10 @@ ## End Snippet Then a file snippets/snippetname.html is created. + +This isn't an example, but it's a tool for merging the examples and +the documentation. This requires the presence of the source-highlight +program: http://www.gnu.org/software/src-highlite/source-highlight.html """ import re, os, sys Modified: trunk/SQLObject/examples/leftjoin.py ============================================================================== --- trunk/SQLObject/examples/leftjoin.py (original) +++ trunk/SQLObject/examples/leftjoin.py Sat Feb 7 14:21:53 2004 @@ -1,4 +1,4 @@ -from SQLObject import * +from sqlobject import * ## Use one of these to define your connection: """ @@ -34,9 +34,9 @@ for insert in data: firstName, lastName = insert[0].split(' ', 1) - customer = Customer.new(firstName=firstName, lastName=lastName) + customer = Customer(firstName=firstName, lastName=lastName) for number in insert[1:]: - contact = Contact.new(customer=customer, phoneNumber=number) + contact = Contact(customer=customer, phoneNumber=number) ## Snippet "leftjoin-simple" for customer in Customer.select(): Modified: trunk/SQLObject/examples/people.py ============================================================================== --- trunk/SQLObject/examples/people.py (original) +++ trunk/SQLObject/examples/people.py Sat Feb 7 14:21:53 2004 @@ -1,31 +1,9 @@ #!/usr/bin/env python -from SQLObject import * +from sqlobject import * import os -############################################################ -## Configuration parameters: -############################################################ - -user = os.environ.get('SQLOBJECT_USER', 'sqlobject_test') -passwd = os.environ.get('SQLOBJECT_PASSWORD', '') -database = os.environ.get('SQLOBJECT_DATABASE', 'sqlobject_test') -debug = 1 - -############################################################ -## Setup connections: -############################################################ - -print 'Accessing with user %s and password %s' % (user, passwd) - -if 1: - # do MySQL test - __connection__ = MySQLConnection('localhost', database, - user, passwd, debug=debug) -else: - # do Postgres test - __connection__ = PostgresConnection('dbname=%s user=%s' % - (database, user), debug=debug) - +from setup import * +__connection__ = conn ############################################################ ## Define classes: @@ -93,7 +71,7 @@ if 'create' in args: for table in tableClasses: - table.createTable(ifExists=True) + table.createTable(ifNotExists=True) if 'clear' in args: for table in tableClasses: @@ -105,7 +83,7 @@ ############################################################ test1 = """ ->>> p = Person.new(firstName="John", lastName="Doe", username="johnd") +>>> p = Person(firstName="John", lastName="Doe", username="johnd") >>> print p <Person 1 firstName='John' middleInitial=None lastName='Doe'> >>> print p.firstName @@ -113,7 +91,7 @@ >>> p.middleInitial = 'Q' >>> print p.middleInitial Q ->>> p2 = Person(p.id) +>>> p2 = Person.get(p.id) >>> print p2 <Person 1 firstName='John' middleInitial='Q' lastName='Doe'> >>> print p is p2 @@ -124,7 +102,7 @@ """ test2 = """ ->>> r = Role.new(name="editor") +>>> r = Role(name="editor") >>> p = list(Person.select('all'))[-1] >>> p.addRole(r) >>> print p.roles @@ -134,7 +112,7 @@ >>> r.removePerson(p) >>> print p.roles [] ->>> phone = PhoneNumber.new(person=p, phoneNumber='773-555-1023', phoneType='home') +>>> phone = PhoneNumber(person=p, phoneNumber='773-555-1023', phoneType='home') >>> print p.phoneNumbers """ Modified: trunk/SQLObject/examples/personaddress.py ============================================================================== --- trunk/SQLObject/examples/personaddress.py (original) +++ trunk/SQLObject/examples/personaddress.py Sat Feb 7 14:21:53 2004 @@ -1,4 +1,4 @@ -from SQLObject import * +from sqlobject import * from config import conn __connection__ = conn @@ -32,21 +32,21 @@ reset() ## Snippet "address-use1" -p = Person.new(firstName='John', lastName='Doe') +p = Person(firstName='John', lastName='Doe') print p.addresses #>> [] -a1 = Address.new(street='123', city='Smallsville', +a1 = Address(street='123', city='Smallsville', state='IL', zip='50484', person=p) print [a.street for a in p.addresses] #>> ['123'] ## end snippet # We'll add some more data to make the results more interesting: -add1 = Person.new(firstName='Jane', lastName='Doe') -add2 = Person.new(firstName='Tom', lastName='Brown') -Address.new(street='5839', city='Eckersville', +add1 = Person(firstName='Jane', lastName='Doe') +add2 = Person(firstName='Tom', lastName='Brown') +Address(street='5839', city='Eckersville', state='IL', zip='50482', person=add1) -Address.new(street='4', city='Whinging', +Address(street='4', city='Whinging', state='AZ', zip='49378', person=add2) ## Snippet "person-select1" Modified: trunk/SQLObject/examples/setup.py ============================================================================== --- trunk/SQLObject/examples/setup.py (original) +++ trunk/SQLObject/examples/setup.py Sat Feb 7 14:21:53 2004 @@ -1,6 +1,6 @@ import sys from config import conn -import SQLObject +import sqlobject main = sys.modules['__main__'] @@ -12,7 +12,7 @@ for name in dir(main): value = getattr(main, name) if isinstance(value, type) \ - and issubclass(value, SQLObject.SQLObject)\ - and value is not SQLObject.SQLObject: + and issubclass(value, sqlobject.SQLObject)\ + and value is not sqlobject.SQLObject: value.dropTable(ifExists=True) value.createTable() Modified: trunk/SQLObject/examples/simpleperson.py ============================================================================== --- trunk/SQLObject/examples/simpleperson.py (original) +++ trunk/SQLObject/examples/simpleperson.py Sat Feb 7 14:21:53 2004 @@ -1,16 +1,7 @@ -from SQLObject import * +from sqlobject import * -## Use one of these to define your connection: -""" -## Snippet "connections" -conn = MySQLConnection(user='test', db='testdb') -conn = PostgresConnection('user=test dbname=testdb') -conn = SQLiteConnect('database.db') -conn = DBMConnection('database/') -## End snippet -""" -conn = DBMConnection('database/') -conn = MySQLConnection(user='test', db='test') +from setup import * +__connection__ = conn ## Snippet "simpleaddress-person1" class Person(SQLObject): @@ -47,7 +38,7 @@ ## End snippet ## Snippet "simpleaddress-person1-use" -p = Person.new(firstName="John", lastName="Doe") +p = Person(firstName="John", lastName="Doe") print p #>> <Person 1 firstName='John' middleInitial=None lastName='Doe'> print p.firstName @@ -55,7 +46,7 @@ p.middleInitial = 'Q' print p.middleInitial #>> 'Q' -p2 = Person(1) +p2 = Person.get(1) print p2 #>> <Person 1 firstName='John' middleInitial='Q' lastName='Doe'> print p is p2 @@ -67,7 +58,7 @@ conn.debug = 1 ## Snippet "simpleaddress-person1-use-debug" -p = Person.new(firstName="John", lastName="Doe") +p = Person(firstName="John", lastName="Doe") #>> QueryIns: # INSERT INTO person (last_name, middle_initial, first_name) # VALUES ('Doe', NULL, 'John') @@ -91,7 +82,7 @@ # WHERE id = 1 print p.middleInitial #>> 'Q' -p2 = Person(1) +p2 = Person.get(1) #-- Again, no database access, since we're just grabbing the same #-- instance we already had. print p2 Modified: trunk/SQLObject/examples/styles.py ============================================================================== --- trunk/SQLObject/examples/styles.py (original) +++ trunk/SQLObject/examples/styles.py Sat Feb 7 14:21:53 2004 @@ -1,4 +1,4 @@ -from SQLObject import * +from sqlobject import * from config import conn __connection__ = conn Modified: trunk/SQLObject/examples/userrole.py ============================================================================== --- trunk/SQLObject/examples/userrole.py (original) +++ trunk/SQLObject/examples/userrole.py Sat Feb 7 14:21:53 2004 @@ -1,4 +1,4 @@ -from SQLObject import * +from sqlobject import * import setup __connection__ = setup.conn @@ -34,12 +34,12 @@ setup.reset() ## Snippet "userrole-use" -bob = User.new(username='bob') -tim = User.new(username='tim') -jay = User.new(username='jay') +bob = User(username='bob') +tim = User(username='tim') +jay = User(username='jay') -admin = Role.new(name='admin') -editor = Role.new(name='editor') +admin = Role(name='admin') +editor = Role(name='editor') bob.addRole(admin) bob.addRole(editor) |