sqlobject-cvs Mailing List for SQLObject (Page 165)
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...> - 2005-05-22 19:32:11
|
Author: ianb Date: 2005-05-22 19:32:05 +0000 (Sun, 22 May 2005) New Revision: 796 Modified: trunk/SQLObject/sqlobject/manager/command.py Log: Give a proper error message when using -f and paste.pyconfig cannot be found Modified: trunk/SQLObject/sqlobject/manager/command.py =================================================================== --- trunk/SQLObject/sqlobject/manager/command.py 2005-05-20 23:21:26 UTC (rev 795) +++ trunk/SQLObject/sqlobject/manager/command.py 2005-05-22 19:32:05 UTC (rev 796) @@ -9,7 +9,7 @@ try: from paste import pyconfig from paste import CONFIG -except ImportError: +except ImportError, e: pyconfig = None CONFIG = {} import time @@ -131,11 +131,10 @@ help="The database connection URI", metavar='URI', dest='connection_uri') - if pyconfig: - parser.add_option('-f', '--config-file', - help="The Paste config file that contains the database URI (in the database key)", - metavar="FILE", - dest="config_file") + parser.add_option('-f', '--config-file', + help="The Paste config file that contains the database URI (in the database key)", + metavar="FILE", + dest="config_file") if find_modules: parser.add_option('-m', '--module', help="Module in which to find SQLObject classes", @@ -287,8 +286,10 @@ def config(self): if getattr(self.options, 'config_file', None): - assert pyconfig, ( - "The --config-file option should not be available without paste.pyconfig installed") + if not pyconfig: + print "You must have Python Paste installed (and on your $PYTHONPATH)" + print "to use the -f/--config-file option." + sys.exit(2) config = pyconfig.Config(with_default=True) config.load(self.options.config_file) CONFIG.push_process_config(config) |
From: <sub...@co...> - 2005-05-20 23:21:33
|
Author: ianb Date: 2005-05-20 23:21:26 +0000 (Fri, 20 May 2005) New Revision: 795 Modified: trunk/SQLObject/sqlobject/manager/command.py Log: Added --edit option to record, so you can immediately create an upgrade script Modified: trunk/SQLObject/sqlobject/manager/command.py =================================================================== --- trunk/SQLObject/sqlobject/manager/command.py 2005-05-20 22:54:07 UTC (rev 794) +++ trunk/SQLObject/sqlobject/manager/command.py 2005-05-20 23:21:26 UTC (rev 795) @@ -5,6 +5,7 @@ import os import sys import textwrap +import warnings try: from paste import pyconfig from paste import CONFIG @@ -18,6 +19,14 @@ from sqlobject.util import moduleloader from sqlobject.declarative import DeclarativeMeta +# It's not very unsafe to use tempnam like we are doing: +warnings.filterwarnings( + 'ignore', 'tempnam is a potential security risk.*', + RuntimeWarning, '.*command', 28) + +def nowarning_tempnam(*args, **kw): + return os.tempnam(*args, **kw) + class SQLObjectVersionTable(sqlobject.SQLObject): """ This table is used to store information about the database and @@ -321,6 +330,28 @@ fn = fn[len(os.getcwd())+1:] return fn + def open_editor(self, pretext, breaker=None, extension='.txt'): + """ + Open an editor with the given text. Return the new text, + or None if no edits were made. If given, everything after + `breaker` will be ignored. + """ + fn = nowarning_tempnam() + extension + f = open(fn, 'w') + f.write(pretext) + f.close() + print '$EDITOR %s' % fn + os.system('$EDITOR %s' % fn) + f = open(fn, 'r') + content = f.read() + f.close() + if breaker: + content = content.split(breaker)[0] + pretext = pretext.split(breaker)[0] + if content == pretext or not content.strip(): + return None + return content + class CommandSQL(Command): name = 'sql' @@ -617,6 +648,11 @@ "this tool, to create a 'beginning' revision.", metavar="VERSION_NAME", dest="force_db_version") + parser.add_option('--edit', + help="Open an editor for the upgrader in the last " + "version (using $EDITOR).", + action="store_true", + dest="open_editor") version_regex = re.compile(r'^\d\d\d\d-\d\d-\d\d') @@ -721,6 +757,24 @@ elif self.options.db_record: for conn in conns: self.update_db(version, conn) + if self.options.open_editor: + if not last_version_dir: + print ("Cannot edit upgrader because there is no " + "previous version") + else: + breaker = ('-'*20 + ' lines below this will be ignored ' + + '-'*20) + pre_text = breaker + '\n' + '\n'.join(all_diffs) + text = self.open_editor('\n\n' + pre_text, breaker=breaker, + extension='.sql') + if text is not None: + fn = os.path.join(last_version_dir, + 'upgrade_%s_%s.sql' % + (dbName, version)) + f = open(fn, 'w') + f.write(text) + f.close() + print 'Wrote to %s' % fn def update_db(self, version, conn): v = self.options.verbose @@ -910,7 +964,7 @@ dbname, target_dbname) continue if version > dest: - if self.verbose > 1: + if self.options.verbose > 1: print 'Version too new: %s (only want %s)' % ( version, dest) upgraders.append((version, os.path.join(current_dir, fn))) |
From: <sub...@co...> - 2005-05-20 22:54:15
|
Author: ianb Date: 2005-05-20 22:54:07 +0000 (Fri, 20 May 2005) New Revision: 794 Modified: trunk/SQLObject/sqlobject/manager/command.py Log: Fixed typo Modified: trunk/SQLObject/sqlobject/manager/command.py =================================================================== --- trunk/SQLObject/sqlobject/manager/command.py 2005-05-20 21:25:57 UTC (rev 793) +++ trunk/SQLObject/sqlobject/manager/command.py 2005-05-20 22:54:07 UTC (rev 794) @@ -905,7 +905,7 @@ dbname = match.group(1) version = match.group(2) if dbname != target_dbname: - if self.verbose > 1: + if self.options.verbose > 1: print 'Not for this database: %s (want %s)' % ( dbname, target_dbname) continue |
From: <sub...@co...> - 2005-05-20 21:26:04
|
Author: ianb Date: 2005-05-20 21:25:57 +0000 (Fri, 20 May 2005) New Revision: 793 Modified: trunk/SQLObject/sqlobject/manager/command.py Log: Fixed typo Modified: trunk/SQLObject/sqlobject/manager/command.py =================================================================== --- trunk/SQLObject/sqlobject/manager/command.py 2005-05-18 20:32:19 UTC (rev 792) +++ trunk/SQLObject/sqlobject/manager/command.py 2005-05-20 21:25:57 UTC (rev 793) @@ -899,7 +899,7 @@ for fn in os.listdir(current_dir): match = self.upgrade_regex.search(fn) if not match: - if self.verbose > 1: + if self.options.verbose > 1: print 'Not an upgrade script: %s' % fn continue dbname = match.group(1) |
From: <sub...@co...> - 2005-05-13 18:20:39
|
Author: ianb Date: 2005-05-13 18:20:29 +0000 (Fri, 13 May 2005) New Revision: 790 Modified: trunk/SQLObject/sqlobject/tests/test_basic.py Log: Added some more tests of selectBy and stuff (that already passed anyway) Modified: trunk/SQLObject/sqlobject/tests/test_basic.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_basic.py 2005-05-10 19:27:15 UTC (rev 789) +++ trunk/SQLObject/sqlobject/tests/test_basic.py 2005-05-13 18:20:29 UTC (rev 790) @@ -23,6 +23,8 @@ bob = TestSO1.selectBy(name='bob')[0] assert bob.name == 'bob' assert bob.passwd == 'god'.encode('rot13') + bobs = TestSO1.selectBy(name='bob')[:10] + assert len(list(bobs)) == 1 def test_newline(): setupGetters(TestSO1) @@ -107,6 +109,19 @@ tcc2 = TestSO3(name='c', other=tc4a.id) assert tcc2.other == tc4a +def test_selectBy(): + setupClass([TestSO4, TestSO3]) + tc4 = TestSO4(me='another') + tc3 = TestSO3(name='sel', other=tc4) + anothertc3 = TestSO3(name='not joined') + assert tc3.other == tc4 + assert list(TestSO3.selectBy(other=tc4)) == [tc3] + assert list(TestSO3.selectBy(otherID=tc4.id)) == [tc3] + assert TestSO3.selectBy(otherID=tc4.id)[0] == tc3 + assert list(TestSO3.selectBy(otherID=tc4.id)[:10]) == [tc3] + assert list(TestSO3.selectBy(other=tc4)[:10]) == [tc3] + assert 0 + class TestSO5(SQLObject): name = StringCol(length=10, dbName='name_col') other = ForeignKey('TestSO6', default=None, cascade=True) |
From: <sub...@co...> - 2005-05-10 19:27:26
|
Author: ianb Date: 2005-05-10 19:27:15 +0000 (Tue, 10 May 2005) New Revision: 789 Modified: trunk/SQLObject/sqlobject/dbconnection.py Log: New method to retrieve db description with rows Modified: trunk/SQLObject/sqlobject/dbconnection.py =================================================================== --- trunk/SQLObject/sqlobject/dbconnection.py 2005-05-10 17:38:24 UTC (rev 788) +++ trunk/SQLObject/sqlobject/dbconnection.py 2005-05-10 19:27:15 UTC (rev 789) @@ -312,6 +312,23 @@ def queryAll(self, s): return self._runWithConnection(self._queryAll, s) + def _queryAllDescription(self, conn, s): + """ + Like queryAll, but returns (description, rows), where the + description is cursor.description (which gives row types) + """ + if self.debug: + self.printDebug(conn, s, 'QueryAllDesc') + c = conn.cursor() + self._executeRetry(conn, c, s) + value = c.fetchall() + if self.debugOutput: + self.printDebug(conn, value, 'QueryAll', 'result') + return c.description, value + + def queryAllDescription(self, s): + return self._runWithConnection(self._queryAllDescription, s) + def _queryOne(self, conn, s): if self.debug: self.printDebug(conn, s, 'QueryOne') |
From: <sub...@co...> - 2005-05-10 17:38:30
|
Author: ianb Date: 2005-05-10 17:38:24 +0000 (Tue, 10 May 2005) New Revision: 788 Modified: trunk/SQLObject/sqlobject/tests/test_selectBy_foreignKey.py Log: Fixed (another) duplicate name Modified: trunk/SQLObject/sqlobject/tests/test_selectBy_foreignKey.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_selectBy_foreignKey.py 2005-05-10 17:37:43 UTC (rev 787) +++ trunk/SQLObject/sqlobject/tests/test_selectBy_foreignKey.py 2005-05-10 17:38:24 UTC (rev 788) @@ -4,30 +4,30 @@ class TestComposerKey(SQLObject): name = StringCol() -class TestWork(SQLObject): +class TestWorkKey(SQLObject): class sqlmeta: idName = "work_id" - composer = ForeignKey('TestComposer') + composer = ForeignKey('TestComposerKey') title = StringCol() def test1(): setupClass([TestComposerKey, - TestWork]) + TestWorkKey]) c = TestComposerKey(name='Mahler, Gustav') - w1 = TestWork(composer=c, title='Symphony No. 9') - w2 = TestWork(composer=None, title=None) + w1 = TestWorkKey(composer=c, title='Symphony No. 9') + w2 = TestWorkKey(composer=None, title=None) # Select by usual way - s = TestWork.selectBy(composerID=c.id, title='Symphony No. 9') + s = TestWorkKey.selectBy(composerID=c.id, title='Symphony No. 9') assert s[0]==w1 # selectBy object - s = TestWork.selectBy(composer=c, title='Symphony No. 9') + s = TestWorkKey.selectBy(composer=c, title='Symphony No. 9') assert s[0]==w1 # selectBy id - s = TestWork.selectBy(id=w1.id) + s = TestWorkKey.selectBy(id=w1.id) assert s[0]==w1 # is None handled correctly? - s = TestWork.selectBy(composer=None, title=None) + s = TestWorkKey.selectBy(composer=None, title=None) assert s[0]==w2 |
From: <sub...@co...> - 2005-05-10 17:37:49
|
Author: ianb Date: 2005-05-10 17:37:43 +0000 (Tue, 10 May 2005) New Revision: 787 Modified: trunk/SQLObject/sqlobject/tests/test_selectBy_foreignKey.py Log: Fixed duplicate name Modified: trunk/SQLObject/sqlobject/tests/test_selectBy_foreignKey.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_selectBy_foreignKey.py 2005-05-10 17:36:37 UTC (rev 786) +++ trunk/SQLObject/sqlobject/tests/test_selectBy_foreignKey.py 2005-05-10 17:37:43 UTC (rev 787) @@ -1,7 +1,7 @@ from sqlobject import * from sqlobject.tests.dbtest import * -class TestComposer(SQLObject): +class TestComposerKey(SQLObject): name = StringCol() class TestWork(SQLObject): @@ -12,10 +12,10 @@ title = StringCol() def test1(): - setupClass(TestComposer) - setupClass(TestWork) + setupClass([TestComposerKey, + TestWork]) - c = TestComposer(name='Mahler, Gustav') + c = TestComposerKey(name='Mahler, Gustav') w1 = TestWork(composer=c, title='Symphony No. 9') w2 = TestWork(composer=None, title=None) |
From: <sub...@co...> - 2005-05-10 17:36:44
|
Author: ianb Date: 2005-05-10 17:36:37 +0000 (Tue, 10 May 2005) New Revision: 786 Modified: trunk/SQLObject/sqlobject/tests/test_enum.py Log: Postgres raises a different exception than MySQL Modified: trunk/SQLObject/sqlobject/tests/test_enum.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_enum.py 2005-05-10 17:35:09 UTC (rev 785) +++ trunk/SQLObject/sqlobject/tests/test_enum.py 2005-05-10 17:36:37 UTC (rev 786) @@ -16,4 +16,7 @@ for l in ['a', 'bcd', 'a', 'e']: Enum1(l=l) if supports('restrictedEnum'): - raises(Enum1._connection.module.IntegrityError, Enum1, l='b') + raises( + (Enum1._connection.module.IntegrityError, + Enum1._connection.module.ProgrammingError), + Enum1, l='b') |
From: <sub...@co...> - 2005-05-10 17:35:17
|
Author: ianb Date: 2005-05-10 17:35:09 +0000 (Tue, 10 May 2005) New Revision: 785 Modified: trunk/SQLObject/sqlobject/tests/dbtest.py Log: Make sure not to clear or drop a table that doesn't exist Modified: trunk/SQLObject/sqlobject/tests/dbtest.py =================================================================== --- trunk/SQLObject/sqlobject/tests/dbtest.py 2005-05-10 17:31:29 UTC (rev 784) +++ trunk/SQLObject/sqlobject/tests/dbtest.py 2005-05-10 17:35:09 UTC (rev 785) @@ -127,10 +127,11 @@ any_drops = True break for soClass in reversed: - if any_drops: - cls.drop(soClass) - else: - cls.clear(soClass) + if soClass._connection.tableExists(soClass.sqlmeta.table): + if any_drops: + cls.drop(soClass) + else: + cls.clear(soClass) for soClass in soClasses: table = soClass.sqlmeta.table if not soClass._connection.tableExists(table): |
From: <sub...@co...> - 2005-05-10 17:31:34
|
Author: ianb Date: 2005-05-10 17:31:29 +0000 (Tue, 10 May 2005) New Revision: 784 Modified: trunk/SQLObject/sqlobject/conftest.py trunk/SQLObject/sqlobject/tests/dbtest.py Log: Added option to show query output Modified: trunk/SQLObject/sqlobject/conftest.py =================================================================== --- trunk/SQLObject/sqlobject/conftest.py 2005-05-10 17:25:13 UTC (rev 783) +++ trunk/SQLObject/sqlobject/conftest.py 2005-05-10 17:31:29 UTC (rev 784) @@ -33,7 +33,11 @@ Option('-S', '--SQL', action="store_true", dest="show_sql", default=False, help="Show SQL from statements (when capturing stdout the " - "SQL is only displayed when a test fails)")) + "SQL is only displayed when a test fails)"), + Option('-O', '--SQL-output', + action="store_true", dest="show_sql_output", default=False, + help="Show output from SQL statements (when capturing " + "stdout the output is only displayed when a test fails)")) class SQLObjectClass(py.test.collect.Class): def run(self): Modified: trunk/SQLObject/sqlobject/tests/dbtest.py =================================================================== --- trunk/SQLObject/sqlobject/tests/dbtest.py 2005-05-10 17:25:13 UTC (rev 783) +++ trunk/SQLObject/sqlobject/tests/dbtest.py 2005-05-10 17:31:29 UTC (rev 784) @@ -83,6 +83,8 @@ conn = sqlobject.connectionForURI(name, **kw) if conftest.option.show_sql: conn.debug = True + if conftest.option.show_sql_output: + conn.debugOutput = True return conn connection = getConnection() |
From: <sub...@co...> - 2005-05-10 17:25:20
|
Author: ianb Date: 2005-05-10 17:25:13 +0000 (Tue, 10 May 2005) New Revision: 783 Modified: trunk/SQLObject/sqlobject/tests/test_NoneValuedResultItem.py trunk/SQLObject/sqlobject/tests/test_SQLMultipleJoin.py Log: Make sure all of a set of related classes are created/dropped in the right order Modified: trunk/SQLObject/sqlobject/tests/test_NoneValuedResultItem.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_NoneValuedResultItem.py 2005-05-10 17:24:54 UTC (rev 782) +++ trunk/SQLObject/sqlobject/tests/test_NoneValuedResultItem.py 2005-05-10 17:25:13 UTC (rev 783) @@ -14,8 +14,8 @@ title = StringCol() def test1(): - setupClass(TestComposer) - setupClass(TestWork) + setupClass([TestComposer, + TestWork]) c = TestComposer(name='Mahler, Gustav') w = TestWork(composer=c, title='Symphony No. 9') Modified: trunk/SQLObject/sqlobject/tests/test_SQLMultipleJoin.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_SQLMultipleJoin.py 2005-05-10 17:24:54 UTC (rev 782) +++ trunk/SQLObject/sqlobject/tests/test_SQLMultipleJoin.py 2005-05-10 17:25:13 UTC (rev 783) @@ -12,8 +12,7 @@ power = IntCol() def createAllTables(): - setupClass(RFighter) - setupClass(Race) + setupClass([Race, RFighter]) def test_1(): createAllTables() |
From: <sub...@co...> - 2005-05-10 17:25:00
|
Author: ianb Date: 2005-05-10 17:24:54 +0000 (Tue, 10 May 2005) New Revision: 782 Modified: trunk/SQLObject/sqlobject/tests/dbtest.py Log: If any classes must be dropped, they all must be dropped Modified: trunk/SQLObject/sqlobject/tests/dbtest.py =================================================================== --- trunk/SQLObject/sqlobject/tests/dbtest.py 2005-05-10 17:20:20 UTC (rev 781) +++ trunk/SQLObject/sqlobject/tests/dbtest.py 2005-05-10 17:24:54 UTC (rev 782) @@ -104,6 +104,8 @@ cls.setup() reversed = list(soClasses)[:] reversed.reverse() + # If anything needs to be dropped, they all must be dropped + any_drops = False for soClass in reversed: table = soClass.sqlmeta.table if not soClass._connection.tableExists(table): @@ -120,6 +122,10 @@ if sql != newSQL: if sql is not None: instance.destroySelf() + any_drops = True + break + for soClass in reversed: + if any_drops: cls.drop(soClass) else: cls.clear(soClass) |
From: <sub...@co...> - 2005-05-10 17:20:34
|
Author: ianb Date: 2005-05-10 17:20:20 +0000 (Tue, 10 May 2005) New Revision: 781 Modified: trunk/SQLObject/sqlobject/conftest.py trunk/SQLObject/sqlobject/tests/dbtest.py Log: Added test option to show SQL Modified: trunk/SQLObject/sqlobject/conftest.py =================================================================== --- trunk/SQLObject/sqlobject/conftest.py 2005-05-10 17:10:27 UTC (rev 780) +++ trunk/SQLObject/sqlobject/conftest.py 2005-05-10 17:20:20 UTC (rev 781) @@ -29,7 +29,11 @@ action="store", dest="Database", default='sqlite', help="The database to run the tests under (default sqlite). " "Can also use an alias from: %s" - % (', '.join(connectionShortcuts.keys())))) + % (', '.join(connectionShortcuts.keys()))), + Option('-S', '--SQL', + action="store_true", dest="show_sql", default=False, + help="Show SQL from statements (when capturing stdout the " + "SQL is only displayed when a test fails)")) class SQLObjectClass(py.test.collect.Class): def run(self): Modified: trunk/SQLObject/sqlobject/tests/dbtest.py =================================================================== --- trunk/SQLObject/sqlobject/tests/dbtest.py 2005-05-10 17:10:27 UTC (rev 780) +++ trunk/SQLObject/sqlobject/tests/dbtest.py 2005-05-10 17:20:20 UTC (rev 781) @@ -80,7 +80,10 @@ name = conftest.option.Database if conftest.connectionShortcuts.has_key(name): name = conftest.connectionShortcuts[name] - return sqlobject.connectionForURI(name, **kw) + conn = sqlobject.connectionForURI(name, **kw) + if conftest.option.show_sql: + conn.debug = True + return conn connection = getConnection() |
From: <sub...@co...> - 2005-05-10 17:10:45
|
Author: ianb Date: 2005-05-10 17:10:27 +0000 (Tue, 10 May 2005) New Revision: 780 Modified: trunk/SQLObject/sqlobject/conftest.py Log: Don't collect SQLObject Test* classes Modified: trunk/SQLObject/sqlobject/conftest.py =================================================================== --- trunk/SQLObject/sqlobject/conftest.py 2005-05-10 17:02:35 UTC (rev 779) +++ trunk/SQLObject/sqlobject/conftest.py 2005-05-10 17:10:27 UTC (rev 780) @@ -9,6 +9,7 @@ import py import os +import sqlobject connectionShortcuts = { 'mysql': 'mysql://test@localhost/test', @@ -29,3 +30,12 @@ help="The database to run the tests under (default sqlite). " "Can also use an alias from: %s" % (', '.join(connectionShortcuts.keys())))) + +class SQLObjectClass(py.test.collect.Class): + def run(self): + if (isinstance(self.obj, type) + and issubclass(self.obj, sqlobject.SQLObject)): + return [] + return super(SQLObjectClass, self).run() + +Class = SQLObjectClass |
From: <sub...@co...> - 2005-05-10 17:02:44
|
Author: ianb Date: 2005-05-10 17:02:35 +0000 (Tue, 10 May 2005) New Revision: 779 Added: trunk/SQLObject/sqlobject/conftest.py Modified: trunk/SQLObject/sqlobject/tests/dbtest.py Log: Use py.test conftest file to add a --Database option (instead of postgres)... more customization upcoming Added: trunk/SQLObject/sqlobject/conftest.py =================================================================== --- trunk/SQLObject/sqlobject/conftest.py 2005-05-07 05:54:12 UTC (rev 778) +++ trunk/SQLObject/sqlobject/conftest.py 2005-05-10 17:02:35 UTC (rev 779) @@ -0,0 +1,31 @@ +""" +This module is used by py.test to configure testing for this +application. +""" + +# Override some options (doesn't override command line): +verbose = 0 +exitfirst = True + +import py +import os + +connectionShortcuts = { + 'mysql': 'mysql://test@localhost/test', + 'dbm': 'dbm:///data', + 'postgres': 'postgres:///test', + 'postgresql': 'postgres:///test', + 'pygresql': 'pygresql://localhost/test', + 'sqlite': 'sqlite:///%s/data/sqlite.data' % os.getcwd(), + 'sybase': 'sybase://test:test123@sybase/test?autoCommit=0', + 'firebird': 'firebird://sysdba:masterkey@localhost/var/lib/firebird/data/test.gdb', + } + +Option = py.test.Config.Option +option = py.test.Config.addoptions( + "SQLObject options", + Option('-D', '--Database', + action="store", dest="Database", default='sqlite', + help="The database to run the tests under (default sqlite). " + "Can also use an alias from: %s" + % (', '.join(connectionShortcuts.keys())))) Modified: trunk/SQLObject/sqlobject/tests/dbtest.py =================================================================== --- trunk/SQLObject/sqlobject/tests/dbtest.py 2005-05-07 05:54:12 UTC (rev 778) +++ trunk/SQLObject/sqlobject/tests/dbtest.py 2005-05-10 17:02:35 UTC (rev 779) @@ -5,8 +5,10 @@ import sys import os import re -import sqlobject from py.test import raises +import py +import sqlobject +import sqlobject.conftest as conftest if sys.platform[:3] == "win": def getcwd(): @@ -14,17 +16,6 @@ else: getcwd = os.getcwd -connectionShortcuts = { - 'mysql': 'mysql://test@localhost/test', - 'dbm': 'dbm:///data', - 'postgres': 'postgres:///test', - 'postgresql': 'postgres:///test', - 'pygresql': 'pygresql://localhost/test', - 'sqlite': 'sqlite:///%s/data/sqlite.data' % getcwd(), - 'sybase': 'sybase://test:test123@sybase/test?autoCommit=0', - 'firebird': 'firebird://sysdba:masterkey@localhost/var/lib/firebird/data/test.gdb', - } - """ supportsMatrix defines what database backends support what features. Each feature has a name, if you see a key like '+featureName' then @@ -86,17 +77,16 @@ 'sqlite:///' + installedDBFilename) def getConnection(**kw): - name = os.environ.get('TESTDB') - assert name, 'You must set $TESTDB to do database operations' - if connectionShortcuts.has_key(name): - name = connectionShortcuts[name] + name = conftest.option.Database + if conftest.connectionShortcuts.has_key(name): + name = conftest.connectionShortcuts[name] return sqlobject.connectionForURI(name, **kw) connection = getConnection() class InstalledTestDatabase(sqlobject.SQLObject): """ - This table is set up in SQLite (always, regardless of $TESTDB) and + This table is set up in SQLite (always, regardless of --Database) and tracks what tables have been set up in the 'real' database. This way we don't keep recreating the tables over and over when there are multiple tests that use a table. |
From: <sub...@co...> - 2005-05-07 05:54:17
|
Author: daverz Date: 2005-05-07 05:54:12 +0000 (Sat, 07 May 2005) New Revision: 778 Modified: trunk/SQLObject/sqlobject/dbconnection.py Log: In Iteration.next(), handle the case where the id is None by returning None. With outer joins this is a common case, e.g. Contact.select(join=LEFTJOINOn(Customer, Contact, Customer.q.id==Contact.q.customerID)) where some customers have no contacts. Modified: trunk/SQLObject/sqlobject/dbconnection.py =================================================================== --- trunk/SQLObject/sqlobject/dbconnection.py 2005-05-07 05:38:05 UTC (rev 777) +++ trunk/SQLObject/sqlobject/dbconnection.py 2005-05-07 05:54:12 UTC (rev 778) @@ -665,6 +665,8 @@ if result is None: self._cleanup() raise StopIteration + if result[0] is None: + return None if self.select.ops.get('lazyColumns', 0): obj = self.select.sourceClass.get(result[0], connection=self.dbconn) return obj |
From: <sub...@co...> - 2005-05-07 05:38:11
|
Author: daverz Date: 2005-05-07 05:38:05 +0000 (Sat, 07 May 2005) New Revision: 777 Added: trunk/SQLObject/sqlobject/tests/test_NoneValuedResultItem.py Log: Test that query results with None IDs (e.g. some outer join cases) are handled correctly, i.e. return None for that object. Added: trunk/SQLObject/sqlobject/tests/test_NoneValuedResultItem.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_NoneValuedResultItem.py 2005-05-06 17:07:38 UTC (rev 776) +++ trunk/SQLObject/sqlobject/tests/test_NoneValuedResultItem.py 2005-05-07 05:38:05 UTC (rev 777) @@ -0,0 +1,28 @@ +'''Test that selectResults handle NULL values +from, for example, outer joins.''' +from sqlobject import * +from sqlobject.tests.dbtest import * + +class TestComposer(SQLObject): + name = StringCol() + +class TestWork(SQLObject): + class sqlmeta: + idName = "work_id" + + composer = ForeignKey('TestComposer') + title = StringCol() + +def test1(): + setupClass(TestComposer) + setupClass(TestWork) + + c = TestComposer(name='Mahler, Gustav') + w = TestWork(composer=c, title='Symphony No. 9') + c2 = TestComposer(name='Bruckner, Anton') + # but don't add any works for Bruckner + + # do a left join, a common use case that often involves NULL results + s = TestWork.select(join=sqlbuilder.LEFTJOINOn(TestComposer, TestWork, + TestComposer.q.id==TestWork.q.composerID)) + assert tuple(s)==(w, None) |
From: <sub...@co...> - 2005-05-06 17:07:52
|
Author: ianb Date: 2005-05-06 17:07:38 +0000 (Fri, 06 May 2005) New Revision: 776 Modified: trunk/SQLObject/sqlobject/tests/test_aliases.py Log: py.test was catching class named Test* Modified: trunk/SQLObject/sqlobject/tests/test_aliases.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_aliases.py 2005-05-06 17:02:42 UTC (rev 775) +++ trunk/SQLObject/sqlobject/tests/test_aliases.py 2005-05-06 17:07:38 UTC (rev 776) @@ -6,22 +6,22 @@ ## Table aliases and self-joins ######################################## -class TestJoinAlias(SQLObject): +class JoinAlias(SQLObject): name = StringCol() parent = StringCol() def test_1syntax(): - setupClass(TestJoinAlias) - alias = Alias(TestJoinAlias) - select = TestJoinAlias.select(TestJoinAlias.q.parent == alias.q.name) + setupClass(JoinAlias) + alias = Alias(JoinAlias) + select = JoinAlias.select(JoinAlias.q.parent == alias.q.name) assert str(select) == \ - "SELECT test_join_alias.id, test_join_alias.name, test_join_alias.parent FROM test_join_alias, test_join_alias AS test_join_alias_alias1 WHERE (test_join_alias.parent = test_join_alias_alias1.name)" + "SELECT join_alias.id, join_alias.name, join_alias.parent FROM join_alias AS join_alias_alias1, join_alias WHERE (join_alias.parent = join_alias_alias1.name)" def test_2perform_join(): - setupClass(TestJoinAlias) - TestJoinAlias(name="grandparent", parent=None) - TestJoinAlias(name="parent", parent="grandparent") - TestJoinAlias(name="child", parent="parent") - alias = Alias(TestJoinAlias) - select = TestJoinAlias.select(TestJoinAlias.q.parent == alias.q.name) + setupClass(JoinAlias) + JoinAlias(name="grandparent", parent=None) + JoinAlias(name="parent", parent="grandparent") + JoinAlias(name="child", parent="parent") + alias = Alias(JoinAlias) + select = JoinAlias.select(JoinAlias.q.parent == alias.q.name) assert select.count() == 2 |
From: <sub...@co...> - 2005-05-06 17:02:50
|
Author: ianb Date: 2005-05-06 17:02:42 +0000 (Fri, 06 May 2005) New Revision: 775 Modified: trunk/SQLObject/sqlobject/tests/test_SQLMultipleJoin.py Log: Create database in wrong order Modified: trunk/SQLObject/sqlobject/tests/test_SQLMultipleJoin.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_SQLMultipleJoin.py 2005-05-06 16:29:30 UTC (rev 774) +++ trunk/SQLObject/sqlobject/tests/test_SQLMultipleJoin.py 2005-05-06 17:02:42 UTC (rev 775) @@ -12,8 +12,8 @@ power = IntCol() def createAllTables(): + setupClass(RFighter) setupClass(Race) - setupClass(RFighter) def test_1(): createAllTables() |
From: <sub...@co...> - 2005-05-06 16:32:11
|
Author: ianb Date: 2005-05-06 16:29:30 +0000 (Fri, 06 May 2005) New Revision: 774 Modified: trunk/SQLObject/sqlobject/manager/command.py Log: Added better help messages; added upgrade command Modified: trunk/SQLObject/sqlobject/manager/command.py =================================================================== --- trunk/SQLObject/sqlobject/manager/command.py 2005-05-06 16:14:45 UTC (rev 773) +++ trunk/SQLObject/sqlobject/manager/command.py 2005-05-06 16:29:30 UTC (rev 774) @@ -4,6 +4,7 @@ import re import os import sys +import textwrap try: from paste import pyconfig from paste import CONFIG @@ -165,6 +166,8 @@ required_args = [] description = None + help = '' + def __classinit__(cls, new_args): if cls.__bases__ == (object,): # This abstract base class @@ -179,6 +182,10 @@ def run(self): self.parser.usage = "%%prog [options]\n%s" % self.summary + if self.help: + help = textwrap.fill( + self.help, int(os.environ.get('COLUMNS', 80))-4) + self.parser.usage += '\n' + help self.parser.prog = '%s %s' % ( os.path.basename(self.invoked_as), self.command_name) @@ -420,6 +427,11 @@ name = 'status' summary = 'Show status of classes vs. database' + help = ('This command checks the SQLObject definition and checks if ' + 'the tables in the database match. It can always test for ' + 'missing tables, and on some databases can test for the ' + 'existance of other tables. Column types are not currently ' + 'checked.') parser = standard_parser(simulate=False) @@ -512,6 +524,9 @@ name = 'execute' summary = 'Execute SQL statements' + help = ('Runs SQL statements directly in the database, with no ' + 'intervention. Useful when used with a configuration file. ' + 'Each argument is executed as an individual statement.') parser = standard_parser(find_modules=False) parser.add_option('--stdin', @@ -563,7 +578,15 @@ class CommandRecord(Command): name = 'record' - summary = 'Record state of table definitions' + summary = 'Record historical information about the database status' + help = ('Record state of table definitions. The state of each ' + 'table is written out to a separate file in a directory, ' + 'and that directory forms a "version". A table is also ' + 'added to you datebase (%s) that reflects the version the ' + 'database is currently at. Use the upgrade command to ' + 'sync databases with code.' + % SQLObjectVersionTable.sqlmeta.table) + parser = standard_parser() parser.add_option('--output-dir', help="Base directory for recorded definitions", @@ -778,15 +801,135 @@ if self.options.db_record: self.update_db(version, self.connection()) +class CommandUpgrade(CommandRecord): + + name = 'upgrade' + summary = 'Update the database to a new version (as created by record)' + help = ('This command runs scripts (that you write by hand) to ' + 'upgrade a database. The database\'s current version is in ' + 'the sqlobject_version table (use record --force-db-version ' + 'if a database does not have a sqlobject_version table), ' + 'and upgrade scripts are in the version directory you are ' + 'upgrading FROM, named upgrade_DBNAME_VERSION.sql, like ' + '"upgrade_mysql_2004-12-01b.sql".') + + parser = standard_parser(find_modules=False) + parser.add_option('--upgrade-to', + help="Upgrade to the given version (default: newest version)", + dest="upgrade_to", + metavar="VERSION") + parser.add_option('--output-dir', + help="Base directory for recorded definitions", + dest="output_dir", + metavar="DIR", + default=None) + + upgrade_regex = re.compile(r'^upgrade_([a-z]*)_([^.]*)\.sql$', re.I) + + def command(self): + v = self.options.verbose + sim = self.options.simulate + if self.options.upgrade_to: + version_to = self.options.upgrade_to + else: + version_to = os.path.basename(self.find_last_version()) + current = self.current_version() + if v: + print 'Current version: %s' % current + version_list = self.make_plan(current, version_to) + if not version_list: + print 'Database up to date' + return + if v: + print 'Plan:' + for next_version, upgrader in version_list: + print ' Use %s to upgrade to %s' % ( + self.shorten_filename(upgrader), next_version) + conn = self.connection() + for next_version, upgrader in version_list: + f = open(upgrader) + sql = f.read() + f.close() + if v: + print "Running:" + print sql + print '-'*60 + if not sim: + conn.query(sql) + self.update_db(next_version, conn) + print 'Done.' + + + def current_version(self): + conn = self.connection() + if not conn.tableExists(SQLObjectVersionTable.sqlmeta.table): + print 'No sqlobject_version table!' + sys.exit(1) + versions = list(SQLObjectVersionTable.select(connection=conn)) + if not versions: + print 'No rows in sqlobject_version!' + sys.exit(1) + if len(versions) > 1: + print 'Ambiguous sqlobject_version_table' + sys.exit(1) + return versions[0].version + + def make_plan(self, current, dest): + if current == dest: + return [] + dbname = self.connection().dbName + next_version, upgrader = self.best_upgrade(current, dest, dbname) + if not upgrader: + print 'No way to upgrade from %s to %s' % (current, dest) + print ('(you need a %s/upgrade_%s_%s.sql script)' + % (current, dbname, dest)) + sys.exit(1) + plan = [(next_version, upgrader)] + if next_version == dest: + return plan + else: + return plan + self.make_plan(next_version, dest) + + def best_upgrade(self, current, dest, target_dbname): + current_dir = os.path.join(self.base_dir(), current) + if self.options.verbose > 1: + print ('Looking in %s for upgraders' + % self.shorten_filename(current_dir)) + upgraders = [] + for fn in os.listdir(current_dir): + match = self.upgrade_regex.search(fn) + if not match: + if self.verbose > 1: + print 'Not an upgrade script: %s' % fn + continue + dbname = match.group(1) + version = match.group(2) + if dbname != target_dbname: + if self.verbose > 1: + print 'Not for this database: %s (want %s)' % ( + dbname, target_dbname) + continue + if version > dest: + if self.verbose > 1: + print 'Version too new: %s (only want %s)' % ( + version, dest) + upgraders.append((version, os.path.join(current_dir, fn))) + if not upgraders: + if self.options.verbose > 1: + print 'No upgraders found in %s' % current_dir + return None, None + upgraders.sort() + return upgraders[-1] + def update_sys_path(paths, verbose): if isinstance(paths, (str, unicode)): paths = [paths] for path in paths: path = os.path.abspath(path) if path not in sys.path: - if verbose: + if verbose > 1: print 'Adding %s to path' % path - sys.path.append(path) + sys.path.insert(0, path) if __name__ == '__main__': the_runner.run(sys.argv) |
From: <sub...@co...> - 2005-05-06 16:15:24
|
Author: ianb Date: 2005-05-06 16:14:45 +0000 (Fri, 06 May 2005) New Revision: 773 Modified: trunk/SQLObject/sqlobject/sresults.py Log: doh, typo Modified: trunk/SQLObject/sqlobject/sresults.py =================================================================== --- trunk/SQLObject/sqlobject/sresults.py 2005-05-06 16:11:25 UTC (rev 772) +++ trunk/SQLObject/sqlobject/sresults.py 2005-05-06 16:14:45 UTC (rev 773) @@ -141,7 +141,7 @@ def __iter__(self): if self.ops.get('connection'): - conn = self.opts.get('connection') + conn = self.ops.get('connection') else: conn = self.sourceClass._connection return conn.iterSelect(self) |
From: <sub...@co...> - 2005-05-06 16:11:35
|
Author: ianb Date: 2005-05-06 16:11:25 +0000 (Fri, 06 May 2005) New Revision: 772 Modified: trunk/SQLObject/sqlobject/dbconnection.py Log: Handle access from class (where obj is None, type is class) Modified: trunk/SQLObject/sqlobject/dbconnection.py =================================================================== --- trunk/SQLObject/sqlobject/dbconnection.py 2005-05-06 16:10:41 UTC (rev 771) +++ trunk/SQLObject/sqlobject/dbconnection.py 2005-05-06 16:11:25 UTC (rev 772) @@ -812,7 +812,7 @@ # I'm a little surprised we have to do this, but apparently # the object's private dictionary of attributes doesn't # override this descriptor. - if obj.__dict__.has_key('_connection'): + if obj and obj.__dict__.has_key('_connection'): return obj.__dict__['_connection'] return self.getConnection() |
From: <sub...@co...> - 2005-05-06 16:10:56
|
Author: ianb Date: 2005-05-06 16:10:41 +0000 (Fri, 06 May 2005) New Revision: 771 Modified: trunk/SQLObject/sqlobject/sresults.py Log: Better handling of case where no explicit ConnectionHub connection has been provided Modified: trunk/SQLObject/sqlobject/sresults.py =================================================================== --- trunk/SQLObject/sqlobject/sresults.py 2005-05-05 22:59:56 UTC (rev 770) +++ trunk/SQLObject/sqlobject/sresults.py 2005-05-06 16:10:41 UTC (rev 771) @@ -140,7 +140,10 @@ return list(self.clone(start=start, end=start+1))[0] def __iter__(self): - conn = self.ops.get('connection', self.sourceClass._connection) + if self.ops.get('connection'): + conn = self.opts.get('connection') + else: + conn = self.sourceClass._connection return conn.iterSelect(self) def accumulate(self, *expressions): |
From: <sub...@co...> - 2005-05-05 23:00:05
|
Author: ianb Date: 2005-05-05 22:59:56 +0000 (Thu, 05 May 2005) New Revision: 770 Modified: trunk/SQLObject/sqlobject/manager/command.py Log: Added record command, to record the database status at a certain point in time Modified: trunk/SQLObject/sqlobject/manager/command.py =================================================================== --- trunk/SQLObject/sqlobject/manager/command.py 2005-05-05 22:58:15 UTC (rev 769) +++ trunk/SQLObject/sqlobject/manager/command.py 2005-05-05 22:59:56 UTC (rev 770) @@ -1,6 +1,7 @@ #!/usr/bin/env python import optparse import fnmatch +import re import os import sys try: @@ -8,11 +9,61 @@ from paste import CONFIG except ImportError: pyconfig = None + CONFIG = {} +import time import sqlobject +from sqlobject import col from sqlobject.util import moduleloader from sqlobject.declarative import DeclarativeMeta +class SQLObjectVersionTable(sqlobject.SQLObject): + """ + This table is used to store information about the database and + its version (used with record and update commands). + """ + class sqlmeta: + table = 'sqlobject_db_version' + version = col.StringCol() + updated = col.DateTimeCol(default=col.DateTimeCol.now) + +def db_differences(soClass, conn): + """ + Returns the differences between a class and the table in a + connection. Returns [] if no differences are found. This + function does the best it can; it can miss many differences. + """ + # @@: Repeats a lot from CommandStatus.command, but it's hard + # to actually factor out the display logic. Or I'm too lazy + # to do so. + diffs = [] + if not conn.tableExists(soClass.sqlmeta.table): + diffs.append('Does not exist in database') + else: + try: + columns = conn.columnsFromSchema(soClass.sqlmeta.table, + soClass) + except AttributeError: + # Database does not support reading columns + pass + else: + existing = {} + for col in columns: + col = col.withClass(soClass) + existing[col.dbName] = col + missing = {} + for col in soClass.sqlmeta._columns: + if existing.has_key(col.dbName): + del existing[col.dbName] + else: + missing[col.dbName] = col + for col in existing.values(): + diffs.append('Database has extra column: %s' + % col.dbName) + for col in missing.values(): + diffs.append('Database missing column: %s' % col.dbName) + return diffs + class CommandRunner(object): def __init__(self): @@ -254,6 +305,15 @@ return response[0].lower() == 'y' print 'Y or N please' + def shorten_filename(self, fn): + """ + Shortens a filename to make it relative to the current + directory (if it can). For display purposes. + """ + if fn.startswith(os.getcwd() + '/'): + fn = fn[len(os.getcwd())+1:] + return fn + class CommandSQL(Command): name = 'sql' @@ -499,7 +559,225 @@ sys.stdout.write("%r\t" % col) sys.stdout.write("\n") print - + +class CommandRecord(Command): + + name = 'record' + summary = 'Record state of table definitions' + parser = standard_parser() + parser.add_option('--output-dir', + help="Base directory for recorded definitions", + dest="output_dir", + metavar="DIR", + default=None) + parser.add_option('--no-db-record', + help="Don't record version to database", + dest="db_record", + action="store_false", + default=True) + parser.add_option('--force-create', + help="Create a new version even if appears to be " + "identical to the last version", + action="store_true", + dest="force_create") + parser.add_option('--name', + help="The name to append to the version. The " + "version should sort after previous versions (so " + "any versions from the same day should come " + "alphabetically before this version).", + dest="version_name", + metavar="NAME") + parser.add_option('--force-db-version', + help="Update the database version, and include no " + "database information. This is for databases that " + "were developed without any interaction with " + "this tool, to create a 'beginning' revision.", + metavar="VERSION_NAME", + dest="force_db_version") + + version_regex = re.compile(r'^\d\d\d\d-\d\d-\d\d') + + def command(self): + if self.options.force_db_version: + self.command_force_db_version() + return + + v = self.options.verbose + sim = self.options.simulate + classes = self.classes() + if not classes: + print "No classes found!" + return + + output_dir = self.find_output_dir() + version = os.path.basename(output_dir) + print "Creating version %s" % version + conns = [] + files = {} + for cls in self.classes(): + dbName = cls._connection.dbName + if cls._connection not in conns: + conns.append(cls._connection) + fn = os.path.join(cls.__name__ + + '_' + dbName + '.sql') + if sim: + continue + files[fn] = ''.join([ + '-- Exported definition from %s\n' + % time.strftime('%Y-%m-%dT%H:%M:%S'), + '-- Class %s.%s\n' + % (cls.__module__, cls.__name__), + '-- Database: %s\n' + % dbName, + cls.createTableSQL().strip(), + '\n']) + last_version_dir = self.find_last_version() + if last_version_dir and not self.options.force_create: + if v > 1: + print "Checking %s to see if it is current" % last_version_dir + files_copy = files.copy() + for fn in os.listdir(last_version_dir): + if not fn.endswith('.sql'): + continue + if not files_copy.has_key(fn): + if v > 1: + print "Missing file %s" % fn + break + f = open(os.path.join(last_version_dir, fn), 'r') + content = f.read() + f.close() + if (self.strip_comments(files_copy[fn]) + != self.strip_comments(content)): + if v > 1: + print "Content does not match: %s" % fn + break + del files_copy[fn] + else: + # No differences so far + if not files_copy: + # Used up all files + print ("Current status matches version %s" + % os.path.basename(last_version_dir)) + return + if v > 1: + print "Extra files: %s" % ', '.join(files_copy.keys()) + if v: + print ("Current state does not match %s" + % os.path.basename(last_version_dir)) + if v > 1 and not last_version_dir: + print "No last version to check" + if not sim: + os.mkdir(output_dir) + if v: + print 'Making directory %s' % self.shorten_filename(output_dir) + files = files.items() + files.sort() + for fn, content in files: + if v: + print ' Writing %s' % self.shorten_filename(fn) + if not sim: + f = open(os.path.join(output_dir, fn), 'w') + f.write(content) + f.close() + all_diffs = [] + for cls in self.classes(): + for conn in conns: + diffs = db_differences(cls, conn) + for diff in diffs: + if len(conns) > 1: + diff = ' (%s).%s: %s' % ( + conn.uri(), cls.sqlmeta.table, diff) + else: + diff = ' %s: %s' % (cls.sqlmeta.table, diff) + all_diffs.append(diff) + if all_diffs: + print 'Database does not match schema:' + print '\n'.join(all_diffs) + if self.options.db_record: + print '(Not updating database version)' + elif self.options.db_record: + for conn in conns: + self.update_db(version, conn) + + def update_db(self, version, conn): + v = self.options.verbose + if not conn.tableExists(SQLObjectVersionTable.sqlmeta.table): + if v: + print ('Creating table %s' + % SQLObjectVersionTable.sqlmeta.table) + sql = SQLObjectVersionTable.createTableSQL(connection=conn) + if v > 1: + print sql + if not self.options.simulate: + SQLObjectVersionTable.createTable(connection=conn) + if not self.options.simulate: + SQLObjectVersionTable.clearTable(connection=conn) + SQLObjectVersionTable( + version=version, + connection=conn) + + def strip_comments(self, sql): + lines = [l for l in sql.splitlines() + if not l.strip().startswith('--')] + return '\n'.join(lines) + + def base_dir(self): + base = self.options.output_dir + if base is None: + base = CONFIG.get('sqlobject_history_dir', '.') + return base + + def find_output_dir(self): + today = time.strftime('%Y-%m-%d', time.localtime()) + if self.options.version_name: + dir = os.path.join(self.base_dir(), today + '-' + + self.options.version_name) + if os.path.exists(dir): + print ("Error, directory already exists: %s" + % dir) + sys.exit(1) + return dir + extra = '' + while 1: + dir = os.path.join(self.base_dir(), today + extra) + if not os.path.exists(dir): + return dir + if not extra: + extra = 'a' + else: + extra = chr(ord(extra)+1) + + def find_last_version(self): + names = [] + for fn in os.listdir(self.base_dir()): + if not self.version_regex.search(fn): + continue + names.append(fn) + if not names: + return None + names.sort() + return os.path.join(self.base_dir(), names[-1]) + + def command_force_db_version(self): + v = self.options.verbose + sim = self.options.simulate + version = self.options.force_db_version + if not self.version_regex.search(version): + print "Versions must be in the format YYYY-MM-DD..." + print "You version %s does not fit this" % version + return + version_dir = os.path.join(self.base_dir(), version) + if not os.path.exists(version_dir): + if v: + print 'Creating %s' % self.shorten_filename(version_dir) + if not sim: + os.mkdir(version_dir) + elif v: + print ('Directory %s exists' + % self.shorten_filename(version_dir)) + if self.options.db_record: + self.update_db(version, self.connection()) + def update_sys_path(paths, verbose): if isinstance(paths, (str, unicode)): paths = [paths] |