sqlobject 3.13.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlobject/__init__.py +15 -0
- sqlobject/__version__.py +8 -0
- sqlobject/boundattributes.py +124 -0
- sqlobject/cache.py +376 -0
- sqlobject/classregistry.py +138 -0
- sqlobject/col.py +2187 -0
- sqlobject/compat.py +59 -0
- sqlobject/conftest.py +64 -0
- sqlobject/constraints.py +77 -0
- sqlobject/converters.py +278 -0
- sqlobject/dbconnection.py +1137 -0
- sqlobject/dberrors.py +54 -0
- sqlobject/declarative.py +211 -0
- sqlobject/events.py +365 -0
- sqlobject/firebird/__init__.py +8 -0
- sqlobject/firebird/firebirdconnection.py +463 -0
- sqlobject/include/__init__.py +1 -0
- sqlobject/include/hashcol.py +109 -0
- sqlobject/include/tests/__init__.py +1 -0
- sqlobject/include/tests/test_hashcol.py +84 -0
- sqlobject/index.py +189 -0
- sqlobject/inheritance/__init__.py +551 -0
- sqlobject/inheritance/iteration.py +106 -0
- sqlobject/inheritance/tests/__init__.py +1 -0
- sqlobject/inheritance/tests/test_aggregates.py +23 -0
- sqlobject/inheritance/tests/test_asdict.py +48 -0
- sqlobject/inheritance/tests/test_deep_inheritance.py +114 -0
- sqlobject/inheritance/tests/test_destroy_cascade.py +23 -0
- sqlobject/inheritance/tests/test_foreignKey.py +94 -0
- sqlobject/inheritance/tests/test_indexes.py +42 -0
- sqlobject/inheritance/tests/test_inheritance.py +164 -0
- sqlobject/inheritance/tests/test_inheritance_tree.py +50 -0
- sqlobject/joins.py +656 -0
- sqlobject/main.py +1880 -0
- sqlobject/manager/__init__.py +1 -0
- sqlobject/manager/command.py +1309 -0
- sqlobject/maxdb/__init__.py +8 -0
- sqlobject/maxdb/maxdbconnection.py +320 -0
- sqlobject/maxdb/readme.txt +27 -0
- sqlobject/mssql/__init__.py +8 -0
- sqlobject/mssql/mssqlconnection.py +417 -0
- sqlobject/mysql/__init__.py +8 -0
- sqlobject/mysql/mysqlconnection.py +562 -0
- sqlobject/postgres/__init__.py +8 -0
- sqlobject/postgres/pgconnection.py +619 -0
- sqlobject/sqlbuilder.py +1655 -0
- sqlobject/sqlite/__init__.py +8 -0
- sqlobject/sqlite/sqliteconnection.py +418 -0
- sqlobject/sresults.py +377 -0
- sqlobject/styles.py +174 -0
- sqlobject/sybase/__init__.py +8 -0
- sqlobject/sybase/sybaseconnection.py +183 -0
- sqlobject/tests/__init__.py +1 -0
- sqlobject/tests/dbtest.py +363 -0
- sqlobject/tests/test_ForeignKey.py +133 -0
- sqlobject/tests/test_ForeignKey_cascade.py +150 -0
- sqlobject/tests/test_NoneValuedResultItem.py +33 -0
- sqlobject/tests/test_SQLMultipleJoin.py +65 -0
- sqlobject/tests/test_SQLRelatedJoin.py +114 -0
- sqlobject/tests/test_SingleJoin.py +30 -0
- sqlobject/tests/test_aggregates.py +75 -0
- sqlobject/tests/test_aliases.py +49 -0
- sqlobject/tests/test_asdict.py +18 -0
- sqlobject/tests/test_auto.py +218 -0
- sqlobject/tests/test_basic.py +359 -0
- sqlobject/tests/test_blob.py +34 -0
- sqlobject/tests/test_boundattributes.py +67 -0
- sqlobject/tests/test_cache.py +57 -0
- sqlobject/tests/test_class_hash.py +22 -0
- sqlobject/tests/test_columns_order.py +18 -0
- sqlobject/tests/test_combining_joins.py +47 -0
- sqlobject/tests/test_comparison.py +24 -0
- sqlobject/tests/test_compat.py +10 -0
- sqlobject/tests/test_complex_sorting.py +77 -0
- sqlobject/tests/test_constraints.py +43 -0
- sqlobject/tests/test_converters.py +276 -0
- sqlobject/tests/test_create_drop.py +29 -0
- sqlobject/tests/test_csvexport.py +101 -0
- sqlobject/tests/test_csvimport.py +21 -0
- sqlobject/tests/test_cyclic_reference.py +55 -0
- sqlobject/tests/test_datetime.py +171 -0
- sqlobject/tests/test_dbconnection.py +17 -0
- sqlobject/tests/test_decimal.py +63 -0
- sqlobject/tests/test_declarative.py +83 -0
- sqlobject/tests/test_default_style.py +65 -0
- sqlobject/tests/test_delete.py +74 -0
- sqlobject/tests/test_distinct.py +36 -0
- sqlobject/tests/test_empty.py +22 -0
- sqlobject/tests/test_enum.py +75 -0
- sqlobject/tests/test_events.py +142 -0
- sqlobject/tests/test_exceptions.py +40 -0
- sqlobject/tests/test_expire.py +29 -0
- sqlobject/tests/test_groupBy.py +45 -0
- sqlobject/tests/test_identity.py +29 -0
- sqlobject/tests/test_indexes.py +100 -0
- sqlobject/tests/test_inheritance.py +35 -0
- sqlobject/tests/test_joins.py +160 -0
- sqlobject/tests/test_joins_conditional.py +143 -0
- sqlobject/tests/test_jsonbcol.py +53 -0
- sqlobject/tests/test_jsoncol.py +56 -0
- sqlobject/tests/test_lazy.py +165 -0
- sqlobject/tests/test_md5.py +13 -0
- sqlobject/tests/test_mysql.py +50 -0
- sqlobject/tests/test_new_joins.py +165 -0
- sqlobject/tests/test_parse_uri.py +147 -0
- sqlobject/tests/test_paste.py +92 -0
- sqlobject/tests/test_perConnection.py +20 -0
- sqlobject/tests/test_pickle.py +42 -0
- sqlobject/tests/test_picklecol.py +44 -0
- sqlobject/tests/test_postgres.py +82 -0
- sqlobject/tests/test_reparent_sqlmeta.py +33 -0
- sqlobject/tests/test_schema.py +30 -0
- sqlobject/tests/test_select.py +218 -0
- sqlobject/tests/test_select_through.py +63 -0
- sqlobject/tests/test_setters.py +23 -0
- sqlobject/tests/test_slice.py +59 -0
- sqlobject/tests/test_sorting.py +80 -0
- sqlobject/tests/test_sqlbuilder.py +134 -0
- sqlobject/tests/test_sqlbuilder_dbspecific.py +66 -0
- sqlobject/tests/test_sqlbuilder_importproxy.py +55 -0
- sqlobject/tests/test_sqlbuilder_joins_instances.py +90 -0
- sqlobject/tests/test_sqlite.py +160 -0
- sqlobject/tests/test_sqlmeta_idName.py +52 -0
- sqlobject/tests/test_sqlobject_admin.py +38 -0
- sqlobject/tests/test_string_id.py +79 -0
- sqlobject/tests/test_style.py +36 -0
- sqlobject/tests/test_subqueries.py +142 -0
- sqlobject/tests/test_transactions.py +122 -0
- sqlobject/tests/test_unicode.py +84 -0
- sqlobject/tests/test_uuidcol.py +40 -0
- sqlobject/tests/test_validation.py +125 -0
- sqlobject/tests/test_views.py +159 -0
- sqlobject/util/__init__.py +1 -0
- sqlobject/util/csvexport.py +210 -0
- sqlobject/util/csvimport.py +366 -0
- sqlobject/util/moduleloader.py +35 -0
- sqlobject/util/threadinglocal.py +6 -0
- sqlobject/versioning/__init__.py +120 -0
- sqlobject/versioning/test/__init__.py +0 -0
- sqlobject/versioning/test/test_version.py +183 -0
- sqlobject/views.py +151 -0
- sqlobject/wsgi_middleware.py +109 -0
- sqlobject-3.13.1.data/scripts/sqlobject-admin +36 -0
- sqlobject-3.13.1.data/scripts/sqlobject-convertOldURI +19 -0
- sqlobject-3.13.1.dist-info/METADATA +177 -0
- sqlobject-3.13.1.dist-info/RECORD +149 -0
- sqlobject-3.13.1.dist-info/WHEEL +6 -0
- sqlobject-3.13.1.dist-info/entry_points.txt +4 -0
- sqlobject-3.13.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1309 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from __future__ import print_function
|
|
3
|
+
|
|
4
|
+
import fnmatch
|
|
5
|
+
import optparse
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
import textwrap
|
|
10
|
+
import time
|
|
11
|
+
import warnings
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from paste.deploy import appconfig
|
|
15
|
+
except ImportError:
|
|
16
|
+
appconfig = None
|
|
17
|
+
|
|
18
|
+
import sqlobject
|
|
19
|
+
from sqlobject import col
|
|
20
|
+
from sqlobject.classregistry import findClass
|
|
21
|
+
from sqlobject.declarative import DeclarativeMeta
|
|
22
|
+
from sqlobject.util import moduleloader
|
|
23
|
+
from sqlobject.compat import PY2, with_metaclass, string_type
|
|
24
|
+
|
|
25
|
+
# It's not very unsafe to use tempnam like we are doing:
|
|
26
|
+
warnings.filterwarnings(
|
|
27
|
+
'ignore', 'tempnam is a potential security risk.*',
|
|
28
|
+
RuntimeWarning, '.*command', 28)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if PY2:
|
|
32
|
+
# noqa for flake8 and python 3
|
|
33
|
+
input = raw_input # noqa
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def nowarning_tempnam(*args, **kw):
|
|
37
|
+
return os.tempnam(*args, **kw)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SQLObjectVersionTable(sqlobject.SQLObject):
|
|
41
|
+
"""
|
|
42
|
+
This table is used to store information about the database and
|
|
43
|
+
its version (used with record and update commands).
|
|
44
|
+
"""
|
|
45
|
+
class sqlmeta:
|
|
46
|
+
table = 'sqlobject_db_version'
|
|
47
|
+
version = col.StringCol()
|
|
48
|
+
updated = col.DateTimeCol(default=col.DateTimeCol.now)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def db_differences(soClass, conn):
|
|
52
|
+
"""
|
|
53
|
+
Returns the differences between a class and the table in a
|
|
54
|
+
connection. Returns [] if no differences are found. This
|
|
55
|
+
function does the best it can; it can miss many differences.
|
|
56
|
+
"""
|
|
57
|
+
# @@: Repeats a lot from CommandStatus.command, but it's hard
|
|
58
|
+
# to actually factor out the display logic. Or I'm too lazy
|
|
59
|
+
# to do so.
|
|
60
|
+
diffs = []
|
|
61
|
+
if not conn.tableExists(soClass.sqlmeta.table):
|
|
62
|
+
if soClass.sqlmeta.columns:
|
|
63
|
+
diffs.append('Does not exist in database')
|
|
64
|
+
else:
|
|
65
|
+
try:
|
|
66
|
+
columns = conn.columnsFromSchema(soClass.sqlmeta.table,
|
|
67
|
+
soClass)
|
|
68
|
+
except AttributeError:
|
|
69
|
+
# Database does not support reading columns
|
|
70
|
+
pass
|
|
71
|
+
else:
|
|
72
|
+
existing = {}
|
|
73
|
+
for _col in columns:
|
|
74
|
+
_col = _col.withClass(soClass)
|
|
75
|
+
existing[_col.dbName] = _col
|
|
76
|
+
missing = {}
|
|
77
|
+
for _col in soClass.sqlmeta.columnList:
|
|
78
|
+
if _col.dbName in existing:
|
|
79
|
+
del existing[_col.dbName]
|
|
80
|
+
else:
|
|
81
|
+
missing[_col.dbName] = _col
|
|
82
|
+
for _col in existing.values():
|
|
83
|
+
diffs.append('Database has extra column: %s' % _col.dbName)
|
|
84
|
+
for _col in missing.values():
|
|
85
|
+
diffs.append('Database missing column: %s' % _col.dbName)
|
|
86
|
+
return diffs
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class CommandRunner(object):
|
|
90
|
+
|
|
91
|
+
def __init__(self):
|
|
92
|
+
self.commands = {}
|
|
93
|
+
self.command_aliases = {}
|
|
94
|
+
|
|
95
|
+
def run(self, argv):
|
|
96
|
+
invoked_as = argv[0]
|
|
97
|
+
args = argv[1:]
|
|
98
|
+
for i in range(len(args)):
|
|
99
|
+
if not args[i].startswith('-'):
|
|
100
|
+
# this must be a command
|
|
101
|
+
command = args[i].lower()
|
|
102
|
+
del args[i]
|
|
103
|
+
break
|
|
104
|
+
else:
|
|
105
|
+
# no command found
|
|
106
|
+
self.invalid('No COMMAND given (try "%s help")'
|
|
107
|
+
% os.path.basename(invoked_as))
|
|
108
|
+
real_command = self.command_aliases.get(command, command)
|
|
109
|
+
if real_command not in self.commands.keys():
|
|
110
|
+
self.invalid('COMMAND %s unknown' % command)
|
|
111
|
+
runner = self.commands[real_command](
|
|
112
|
+
invoked_as, command, args, self)
|
|
113
|
+
runner.run()
|
|
114
|
+
|
|
115
|
+
def register(self, command):
|
|
116
|
+
name = command.name
|
|
117
|
+
self.commands[name] = command
|
|
118
|
+
for alias in command.aliases:
|
|
119
|
+
self.command_aliases[alias] = name
|
|
120
|
+
|
|
121
|
+
def invalid(self, msg, code=2):
|
|
122
|
+
print(msg)
|
|
123
|
+
sys.exit(code)
|
|
124
|
+
|
|
125
|
+
the_runner = CommandRunner()
|
|
126
|
+
register = the_runner.register
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def standard_parser(connection=True, simulate=True,
|
|
130
|
+
interactive=False, find_modules=True):
|
|
131
|
+
parser = optparse.OptionParser()
|
|
132
|
+
parser.add_option('-v', '--verbose',
|
|
133
|
+
help='Be verbose (multiple times for more verbosity)',
|
|
134
|
+
action='count',
|
|
135
|
+
dest='verbose',
|
|
136
|
+
default=0)
|
|
137
|
+
if simulate:
|
|
138
|
+
parser.add_option('-n', '--simulate',
|
|
139
|
+
help="Don't actually do anything (implies -v)",
|
|
140
|
+
action='store_true',
|
|
141
|
+
dest='simulate')
|
|
142
|
+
if connection:
|
|
143
|
+
parser.add_option('-c', '--connection',
|
|
144
|
+
help="The database connection URI",
|
|
145
|
+
metavar='URI',
|
|
146
|
+
dest='connection_uri')
|
|
147
|
+
parser.add_option('-f', '--config-file',
|
|
148
|
+
help="The Paste config file "
|
|
149
|
+
"that contains the database URI (in the database key)",
|
|
150
|
+
metavar="FILE",
|
|
151
|
+
dest="config_file")
|
|
152
|
+
if find_modules:
|
|
153
|
+
parser.add_option('-m', '--module',
|
|
154
|
+
help="Module in which to find SQLObject classes",
|
|
155
|
+
action='append',
|
|
156
|
+
metavar='MODULE',
|
|
157
|
+
dest='modules',
|
|
158
|
+
default=[])
|
|
159
|
+
parser.add_option('-p', '--package',
|
|
160
|
+
help="Package to search for SQLObject classes",
|
|
161
|
+
action="append",
|
|
162
|
+
metavar="PACKAGE",
|
|
163
|
+
dest="packages",
|
|
164
|
+
default=[])
|
|
165
|
+
parser.add_option('--class',
|
|
166
|
+
help="Select only named classes (wildcards allowed)",
|
|
167
|
+
action="append",
|
|
168
|
+
metavar="NAME",
|
|
169
|
+
dest="class_matchers",
|
|
170
|
+
default=[])
|
|
171
|
+
if interactive:
|
|
172
|
+
parser.add_option('-i', '--interactive',
|
|
173
|
+
help="Ask before doing anything "
|
|
174
|
+
"(use twice to be more careful)",
|
|
175
|
+
action="count",
|
|
176
|
+
dest="interactive",
|
|
177
|
+
default=0)
|
|
178
|
+
parser.add_option('--egg',
|
|
179
|
+
help="Select modules from the given Egg, "
|
|
180
|
+
"using sqlobject.txt",
|
|
181
|
+
action="append",
|
|
182
|
+
metavar="EGG_SPEC",
|
|
183
|
+
dest="eggs",
|
|
184
|
+
default=[])
|
|
185
|
+
return parser
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class Command(with_metaclass(DeclarativeMeta, object)):
|
|
189
|
+
|
|
190
|
+
min_args = 0
|
|
191
|
+
min_args_error = 'You must provide at least %(min_args)s arguments'
|
|
192
|
+
max_args = 0
|
|
193
|
+
max_args_error = 'You must provide no more than %(max_args)s arguments'
|
|
194
|
+
aliases = ()
|
|
195
|
+
required_args = []
|
|
196
|
+
description = None
|
|
197
|
+
|
|
198
|
+
help = ''
|
|
199
|
+
|
|
200
|
+
def orderClassesByDependencyLevel(self, classes):
|
|
201
|
+
"""
|
|
202
|
+
Return classes ordered by their depth in the class dependency
|
|
203
|
+
tree (this is *not* the inheritance tree), from the
|
|
204
|
+
top level (independant) classes to the deepest level.
|
|
205
|
+
The dependency tree is defined by the foreign key relations.
|
|
206
|
+
"""
|
|
207
|
+
# @@: written as a self-contained function for now, to prevent
|
|
208
|
+
# having to modify any core SQLObject component and namespace
|
|
209
|
+
# contamination.
|
|
210
|
+
# yemartin - 2006-08-08
|
|
211
|
+
|
|
212
|
+
class SQLObjectCircularReferenceError(Exception):
|
|
213
|
+
pass
|
|
214
|
+
|
|
215
|
+
def findReverseDependencies(cls):
|
|
216
|
+
"""
|
|
217
|
+
Return a list of classes that cls depends on. Note that
|
|
218
|
+
"depends on" here mean "has a foreign key pointing to".
|
|
219
|
+
"""
|
|
220
|
+
depended = []
|
|
221
|
+
for _col in cls.sqlmeta.columnList:
|
|
222
|
+
if _col.foreignKey:
|
|
223
|
+
other = findClass(_col.foreignKey,
|
|
224
|
+
_col.soClass.sqlmeta.registry)
|
|
225
|
+
if (other is not cls) and (other not in depended):
|
|
226
|
+
depended.append(other)
|
|
227
|
+
return depended
|
|
228
|
+
|
|
229
|
+
# Cache to save already calculated dependency levels.
|
|
230
|
+
dependency_levels = {}
|
|
231
|
+
|
|
232
|
+
def calculateDependencyLevel(cls, dependency_stack=[]):
|
|
233
|
+
"""
|
|
234
|
+
Recursively calculate the dependency level of cls, while
|
|
235
|
+
using the dependency_stack to detect any circular reference.
|
|
236
|
+
"""
|
|
237
|
+
# Return value from the cache if already calculated
|
|
238
|
+
if cls in dependency_levels:
|
|
239
|
+
return dependency_levels[cls]
|
|
240
|
+
# Check for circular references
|
|
241
|
+
if cls in dependency_stack:
|
|
242
|
+
dependency_stack.append(cls)
|
|
243
|
+
raise SQLObjectCircularReferenceError(
|
|
244
|
+
"Found a circular reference: %s " %
|
|
245
|
+
(' --> '.join([x.__name__ for x in dependency_stack])))
|
|
246
|
+
dependency_stack.append(cls)
|
|
247
|
+
# Recursively inspect dependent classes.
|
|
248
|
+
depended = findReverseDependencies(cls)
|
|
249
|
+
if depended:
|
|
250
|
+
level = max([calculateDependencyLevel(x, dependency_stack)
|
|
251
|
+
for x in depended]) + 1
|
|
252
|
+
else:
|
|
253
|
+
level = 0
|
|
254
|
+
dependency_levels[cls] = level
|
|
255
|
+
return level
|
|
256
|
+
|
|
257
|
+
# Now simply calculate and sort by dependency levels:
|
|
258
|
+
try:
|
|
259
|
+
sorter = []
|
|
260
|
+
for cls in classes:
|
|
261
|
+
level = calculateDependencyLevel(cls)
|
|
262
|
+
sorter.append((level, cls))
|
|
263
|
+
sorter.sort(key=lambda x: x[0])
|
|
264
|
+
ordered_classes = [cls for _, cls in sorter]
|
|
265
|
+
except SQLObjectCircularReferenceError as msg:
|
|
266
|
+
# Failsafe: return the classes as-is if a circular reference
|
|
267
|
+
# prevented the dependency levels to be calculated.
|
|
268
|
+
print("Warning: a circular reference was detected in the "
|
|
269
|
+
"model. Unable to sort the classes by dependency: they "
|
|
270
|
+
"will be treated in alphabetic order. This may or may "
|
|
271
|
+
"not work depending on your database backend. "
|
|
272
|
+
"The error was:\n%s" % msg)
|
|
273
|
+
return classes
|
|
274
|
+
return ordered_classes
|
|
275
|
+
|
|
276
|
+
def __classinit__(cls, new_args):
|
|
277
|
+
if cls.__bases__ == (object,):
|
|
278
|
+
# This abstract base class
|
|
279
|
+
return
|
|
280
|
+
register(cls)
|
|
281
|
+
|
|
282
|
+
def __init__(self, invoked_as, command_name, args, runner):
|
|
283
|
+
self.invoked_as = invoked_as
|
|
284
|
+
self.command_name = command_name
|
|
285
|
+
self.raw_args = args
|
|
286
|
+
self.runner = runner
|
|
287
|
+
|
|
288
|
+
def run(self):
|
|
289
|
+
self.parser.usage = "%%prog [options]\n%s" % self.summary
|
|
290
|
+
if self.help:
|
|
291
|
+
help = textwrap.fill(
|
|
292
|
+
self.help, int(os.environ.get('COLUMNS', 80)) - 4)
|
|
293
|
+
self.parser.usage += '\n' + help
|
|
294
|
+
self.parser.prog = '%s %s' % (
|
|
295
|
+
os.path.basename(self.invoked_as),
|
|
296
|
+
self.command_name)
|
|
297
|
+
if self.description:
|
|
298
|
+
self.parser.description = self.description
|
|
299
|
+
self.options, self.args = self.parser.parse_args(self.raw_args)
|
|
300
|
+
if (getattr(self.options, 'simulate', False)
|
|
301
|
+
and not self.options.verbose):
|
|
302
|
+
self.options.verbose = 1
|
|
303
|
+
if self.min_args is not None and len(self.args) < self.min_args:
|
|
304
|
+
self.runner.invalid(
|
|
305
|
+
self.min_args_error % {'min_args': self.min_args,
|
|
306
|
+
'actual_args': len(self.args)})
|
|
307
|
+
if self.max_args is not None and len(self.args) > self.max_args:
|
|
308
|
+
self.runner.invalid(
|
|
309
|
+
self.max_args_error % {'max_args': self.max_args,
|
|
310
|
+
'actual_args': len(self.args)})
|
|
311
|
+
for var_name, option_name in self.required_args:
|
|
312
|
+
if not getattr(self.options, var_name, None):
|
|
313
|
+
self.runner.invalid(
|
|
314
|
+
'You must provide the option %s' % option_name)
|
|
315
|
+
conf = self.config()
|
|
316
|
+
if conf and conf.get('sys_path'):
|
|
317
|
+
update_sys_path(conf['sys_path'], self.options.verbose)
|
|
318
|
+
if conf and conf.get('database'):
|
|
319
|
+
conn = sqlobject.connectionForURI(conf['database'])
|
|
320
|
+
sqlobject.sqlhub.processConnection = conn
|
|
321
|
+
for egg_spec in getattr(self.options, 'eggs', []):
|
|
322
|
+
self.load_options_from_egg(egg_spec)
|
|
323
|
+
self.command()
|
|
324
|
+
|
|
325
|
+
def classes(self, require_connection=True,
|
|
326
|
+
require_some=False):
|
|
327
|
+
all = []
|
|
328
|
+
for module_name in self.options.modules:
|
|
329
|
+
all.extend(self.classes_from_module(
|
|
330
|
+
moduleloader.load_module(module_name)))
|
|
331
|
+
for package_name in self.options.packages:
|
|
332
|
+
all.extend(self.classes_from_package(package_name))
|
|
333
|
+
for egg_spec in self.options.eggs:
|
|
334
|
+
all.extend(self.classes_from_egg(egg_spec))
|
|
335
|
+
if self.options.class_matchers:
|
|
336
|
+
filtered = []
|
|
337
|
+
for soClass in all:
|
|
338
|
+
name = soClass.__name__
|
|
339
|
+
for matcher in self.options.class_matchers:
|
|
340
|
+
if fnmatch.fnmatch(name, matcher):
|
|
341
|
+
filtered.append(soClass)
|
|
342
|
+
break
|
|
343
|
+
all = filtered
|
|
344
|
+
conn = self.connection()
|
|
345
|
+
if conn:
|
|
346
|
+
for soClass in all:
|
|
347
|
+
soClass._connection = conn
|
|
348
|
+
else:
|
|
349
|
+
missing = []
|
|
350
|
+
for soClass in all:
|
|
351
|
+
try:
|
|
352
|
+
if not soClass._connection:
|
|
353
|
+
missing.append(soClass)
|
|
354
|
+
except AttributeError:
|
|
355
|
+
missing.append(soClass)
|
|
356
|
+
if missing and require_connection:
|
|
357
|
+
self.runner.invalid(
|
|
358
|
+
'These classes do not have connections set:\n * %s\n'
|
|
359
|
+
'You must indicate --connection=URI'
|
|
360
|
+
% '\n * '.join([soClass.__name__
|
|
361
|
+
for soClass in missing]))
|
|
362
|
+
if require_some and not all:
|
|
363
|
+
print('No classes found!')
|
|
364
|
+
if self.options.modules:
|
|
365
|
+
print('Looked in modules: %s' %
|
|
366
|
+
', '.join(self.options.modules))
|
|
367
|
+
else:
|
|
368
|
+
print('No modules specified')
|
|
369
|
+
if self.options.packages:
|
|
370
|
+
print('Looked in packages: %s' %
|
|
371
|
+
', '.join(self.options.packages))
|
|
372
|
+
else:
|
|
373
|
+
print('No packages specified')
|
|
374
|
+
if self.options.class_matchers:
|
|
375
|
+
print('Matching class pattern: %s' %
|
|
376
|
+
self.options.class_matches)
|
|
377
|
+
if self.options.eggs:
|
|
378
|
+
print('Looked in eggs: %s' % ', '.join(self.options.eggs))
|
|
379
|
+
else:
|
|
380
|
+
print('No eggs specified')
|
|
381
|
+
sys.exit(1)
|
|
382
|
+
return self.orderClassesByDependencyLevel(all)
|
|
383
|
+
|
|
384
|
+
def classes_from_module(self, module):
|
|
385
|
+
all = []
|
|
386
|
+
if hasattr(module, 'soClasses'):
|
|
387
|
+
for name_or_class in module.soClasses:
|
|
388
|
+
if isinstance(name_or_class, str):
|
|
389
|
+
name_or_class = getattr(module, name_or_class)
|
|
390
|
+
all.append(name_or_class)
|
|
391
|
+
else:
|
|
392
|
+
for name in dir(module):
|
|
393
|
+
value = getattr(module, name)
|
|
394
|
+
if (isinstance(value, type)
|
|
395
|
+
and issubclass(value, sqlobject.SQLObject)
|
|
396
|
+
and value.__module__ == module.__name__):
|
|
397
|
+
all.append(value)
|
|
398
|
+
return all
|
|
399
|
+
|
|
400
|
+
def connection(self):
|
|
401
|
+
config = self.config()
|
|
402
|
+
if config is not None:
|
|
403
|
+
assert config.get('database'), (
|
|
404
|
+
"No database variable found in config file %s"
|
|
405
|
+
% self.options.config_file)
|
|
406
|
+
return sqlobject.connectionForURI(config['database'])
|
|
407
|
+
elif getattr(self.options, 'connection_uri', None):
|
|
408
|
+
return sqlobject.connectionForURI(self.options.connection_uri)
|
|
409
|
+
else:
|
|
410
|
+
return None
|
|
411
|
+
|
|
412
|
+
def config(self):
|
|
413
|
+
if not getattr(self.options, 'config_file', None):
|
|
414
|
+
return None
|
|
415
|
+
config_file = self.options.config_file
|
|
416
|
+
if appconfig:
|
|
417
|
+
if (not config_file.startswith('egg:')
|
|
418
|
+
and not config_file.startswith('config:')):
|
|
419
|
+
config_file = 'config:' + config_file
|
|
420
|
+
return appconfig(config_file,
|
|
421
|
+
relative_to=os.getcwd())
|
|
422
|
+
else:
|
|
423
|
+
return self.ini_config(config_file)
|
|
424
|
+
|
|
425
|
+
def ini_config(self, conf_fn):
|
|
426
|
+
conf_section = 'main'
|
|
427
|
+
if '#' in conf_fn:
|
|
428
|
+
conf_fn, conf_section = conf_fn.split('#', 1)
|
|
429
|
+
|
|
430
|
+
try:
|
|
431
|
+
from ConfigParser import ConfigParser
|
|
432
|
+
except ImportError:
|
|
433
|
+
from configparser import ConfigParser
|
|
434
|
+
p = ConfigParser()
|
|
435
|
+
# Case-sensitive:
|
|
436
|
+
p.optionxform = str
|
|
437
|
+
if not os.path.exists(conf_fn):
|
|
438
|
+
# Stupid RawConfigParser doesn't give an error for
|
|
439
|
+
# non-existant files:
|
|
440
|
+
raise OSError(
|
|
441
|
+
"Config file %s does not exist" % self.options.config_file)
|
|
442
|
+
p.read([conf_fn])
|
|
443
|
+
p._defaults.setdefault(
|
|
444
|
+
'here', os.path.dirname(os.path.abspath(conf_fn)))
|
|
445
|
+
|
|
446
|
+
possible_sections = []
|
|
447
|
+
for section in p.sections():
|
|
448
|
+
name = section.strip().lower()
|
|
449
|
+
if (conf_section == name
|
|
450
|
+
or (conf_section == name.split(':')[-1]
|
|
451
|
+
and name.split(':')[0] in ('app', 'application'))):
|
|
452
|
+
possible_sections.append(section)
|
|
453
|
+
|
|
454
|
+
if not possible_sections:
|
|
455
|
+
raise OSError(
|
|
456
|
+
"Config file %s does not have a section [%s] or [*:%s]"
|
|
457
|
+
% (conf_fn, conf_section, conf_section))
|
|
458
|
+
if len(possible_sections) > 1:
|
|
459
|
+
raise OSError(
|
|
460
|
+
"Config file %s has multiple sections matching %s: %s"
|
|
461
|
+
% (conf_fn, conf_section, ', '.join(possible_sections)))
|
|
462
|
+
|
|
463
|
+
config = {}
|
|
464
|
+
for op in p.options(possible_sections[0]):
|
|
465
|
+
config[op] = p.get(possible_sections[0], op)
|
|
466
|
+
return config
|
|
467
|
+
|
|
468
|
+
def classes_from_package(self, package_name):
|
|
469
|
+
all = []
|
|
470
|
+
package = moduleloader.load_module(package_name)
|
|
471
|
+
package_dir = os.path.dirname(package.__file__)
|
|
472
|
+
|
|
473
|
+
def find_classes_in_file(arg, dir_name, filenames):
|
|
474
|
+
if dir_name.startswith('.svn'):
|
|
475
|
+
return
|
|
476
|
+
filenames = filter(
|
|
477
|
+
lambda fname: fname.endswith('.py') and fname != '__init__.py',
|
|
478
|
+
filenames)
|
|
479
|
+
for fname in filenames:
|
|
480
|
+
module_name = os.path.join(dir_name, fname)
|
|
481
|
+
module_name = module_name[module_name.find(package_name):]
|
|
482
|
+
module_name = module_name.replace(os.path.sep, '.')[:-3]
|
|
483
|
+
try:
|
|
484
|
+
module = moduleloader.load_module(module_name)
|
|
485
|
+
except ImportError as err:
|
|
486
|
+
if self.options.verbose:
|
|
487
|
+
print('Could not import module "%s". '
|
|
488
|
+
'Error was : "%s"' % (module_name, err))
|
|
489
|
+
continue
|
|
490
|
+
except Exception as exc:
|
|
491
|
+
if self.options.verbose:
|
|
492
|
+
print('Unknown exception while processing module '
|
|
493
|
+
'"%s" : "%s"' % (module_name, exc))
|
|
494
|
+
continue
|
|
495
|
+
classes = self.classes_from_module(module)
|
|
496
|
+
all.extend(classes)
|
|
497
|
+
|
|
498
|
+
for dirpath, dirnames, filenames in os.walk(package_dir):
|
|
499
|
+
find_classes_in_file(None, dirpath, dirnames + filenames)
|
|
500
|
+
return all
|
|
501
|
+
|
|
502
|
+
def classes_from_egg(self, egg_spec):
|
|
503
|
+
modules = []
|
|
504
|
+
dist, conf = self.config_from_egg(egg_spec, warn_no_sqlobject=True)
|
|
505
|
+
for mod in conf.get('db_module', '').split(','):
|
|
506
|
+
mod = mod.strip()
|
|
507
|
+
if not mod:
|
|
508
|
+
continue
|
|
509
|
+
if self.options.verbose:
|
|
510
|
+
print('Looking in module %s' % mod)
|
|
511
|
+
modules.extend(self.classes_from_module(
|
|
512
|
+
moduleloader.load_module(mod)))
|
|
513
|
+
return modules
|
|
514
|
+
|
|
515
|
+
def load_options_from_egg(self, egg_spec):
|
|
516
|
+
dist, conf = self.config_from_egg(egg_spec)
|
|
517
|
+
if (hasattr(self.options, 'output_dir')
|
|
518
|
+
and not self.options.output_dir and conf.get('history_dir')):
|
|
519
|
+
dir = conf['history_dir']
|
|
520
|
+
dir = dir.replace('$base', dist.location)
|
|
521
|
+
self.options.output_dir = dir
|
|
522
|
+
|
|
523
|
+
def config_from_egg(self, egg_spec, warn_no_sqlobject=True):
|
|
524
|
+
import pkg_resources
|
|
525
|
+
dist = pkg_resources.get_distribution(egg_spec)
|
|
526
|
+
if not dist.has_metadata('sqlobject.txt'):
|
|
527
|
+
if warn_no_sqlobject:
|
|
528
|
+
print('No sqlobject.txt in %s egg info' % egg_spec)
|
|
529
|
+
return None, {}
|
|
530
|
+
result = {}
|
|
531
|
+
for line in dist.get_metadata_lines('sqlobject.txt'):
|
|
532
|
+
line = line.strip()
|
|
533
|
+
if not line or line.startswith('#'):
|
|
534
|
+
continue
|
|
535
|
+
name, value = line.split('=', 1)
|
|
536
|
+
name = name.strip().lower()
|
|
537
|
+
if name in result:
|
|
538
|
+
print('Warning: %s appears more than once '
|
|
539
|
+
'in sqlobject.txt' % name)
|
|
540
|
+
result[name.strip().lower()] = value.strip()
|
|
541
|
+
return dist, result
|
|
542
|
+
|
|
543
|
+
def command(self):
|
|
544
|
+
raise NotImplementedError
|
|
545
|
+
|
|
546
|
+
def _get_prog_name(self):
|
|
547
|
+
return os.path.basename(self.invoked_as)
|
|
548
|
+
prog_name = property(_get_prog_name)
|
|
549
|
+
|
|
550
|
+
def ask(self, prompt, safe=False, default=True):
|
|
551
|
+
if self.options.interactive >= 2:
|
|
552
|
+
default = safe
|
|
553
|
+
if default:
|
|
554
|
+
prompt += ' [Y/n]? '
|
|
555
|
+
else:
|
|
556
|
+
prompt += ' [y/N]? '
|
|
557
|
+
while 1:
|
|
558
|
+
response = input(prompt).strip()
|
|
559
|
+
if not response.strip():
|
|
560
|
+
return default
|
|
561
|
+
if response and response[0].lower() in ('y', 'n'):
|
|
562
|
+
return response[0].lower() == 'y'
|
|
563
|
+
print('Y or N please')
|
|
564
|
+
|
|
565
|
+
def shorten_filename(self, fn):
|
|
566
|
+
"""
|
|
567
|
+
Shortens a filename to make it relative to the current
|
|
568
|
+
directory (if it can). For display purposes.
|
|
569
|
+
"""
|
|
570
|
+
if fn.startswith(os.getcwd() + '/'):
|
|
571
|
+
fn = fn[len(os.getcwd()) + 1:]
|
|
572
|
+
return fn
|
|
573
|
+
|
|
574
|
+
def open_editor(self, pretext, breaker=None, extension='.txt'):
|
|
575
|
+
"""
|
|
576
|
+
Open an editor with the given text. Return the new text,
|
|
577
|
+
or None if no edits were made. If given, everything after
|
|
578
|
+
`breaker` will be ignored.
|
|
579
|
+
"""
|
|
580
|
+
fn = nowarning_tempnam() + extension
|
|
581
|
+
f = open(fn, 'w')
|
|
582
|
+
f.write(pretext)
|
|
583
|
+
f.close()
|
|
584
|
+
print('$EDITOR %s' % fn)
|
|
585
|
+
os.system('$EDITOR %s' % fn)
|
|
586
|
+
f = open(fn, 'r')
|
|
587
|
+
content = f.read()
|
|
588
|
+
f.close()
|
|
589
|
+
if breaker:
|
|
590
|
+
content = content.split(breaker)[0]
|
|
591
|
+
pretext = pretext.split(breaker)[0]
|
|
592
|
+
if content == pretext or not content.strip():
|
|
593
|
+
return None
|
|
594
|
+
return content
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
class CommandSQL(Command):
|
|
598
|
+
|
|
599
|
+
name = 'sql'
|
|
600
|
+
summary = 'Show SQL CREATE statements'
|
|
601
|
+
|
|
602
|
+
parser = standard_parser(simulate=False)
|
|
603
|
+
|
|
604
|
+
def command(self):
|
|
605
|
+
classes = self.classes()
|
|
606
|
+
allConstraints = []
|
|
607
|
+
for cls in classes:
|
|
608
|
+
if self.options.verbose >= 1:
|
|
609
|
+
print('-- %s from %s' % (
|
|
610
|
+
cls.__name__, cls.__module__))
|
|
611
|
+
createSql, constraints = cls.createTableSQL()
|
|
612
|
+
print(createSql.strip() + ';\n')
|
|
613
|
+
allConstraints.append(constraints)
|
|
614
|
+
for constraints in allConstraints:
|
|
615
|
+
if constraints:
|
|
616
|
+
for constraint in constraints:
|
|
617
|
+
if constraint:
|
|
618
|
+
print(constraint.strip() + ';\n')
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
class CommandList(Command):
|
|
622
|
+
|
|
623
|
+
name = 'list'
|
|
624
|
+
summary = 'Show all SQLObject classes found'
|
|
625
|
+
|
|
626
|
+
parser = standard_parser(simulate=False, connection=False)
|
|
627
|
+
|
|
628
|
+
def command(self):
|
|
629
|
+
if self.options.verbose >= 1:
|
|
630
|
+
print('Classes found:')
|
|
631
|
+
classes = self.classes(require_connection=False)
|
|
632
|
+
for soClass in classes:
|
|
633
|
+
print('%s.%s' % (soClass.__module__, soClass.__name__))
|
|
634
|
+
if self.options.verbose >= 1:
|
|
635
|
+
print(' Table: %s' % soClass.sqlmeta.table)
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
class CommandCreate(Command):
|
|
639
|
+
|
|
640
|
+
name = 'create'
|
|
641
|
+
summary = 'Create tables'
|
|
642
|
+
|
|
643
|
+
parser = standard_parser(interactive=True)
|
|
644
|
+
parser.add_option('--create-db',
|
|
645
|
+
action='store_true',
|
|
646
|
+
dest='create_db',
|
|
647
|
+
help="Create the database")
|
|
648
|
+
|
|
649
|
+
def command(self):
|
|
650
|
+
v = self.options.verbose
|
|
651
|
+
created = 0
|
|
652
|
+
existing = 0
|
|
653
|
+
dbs_created = []
|
|
654
|
+
constraints = {}
|
|
655
|
+
for soClass in self.classes(require_some=True):
|
|
656
|
+
if (self.options.create_db
|
|
657
|
+
and soClass._connection not in dbs_created):
|
|
658
|
+
if not self.options.simulate:
|
|
659
|
+
try:
|
|
660
|
+
soClass._connection.createEmptyDatabase()
|
|
661
|
+
except soClass._connection.module.ProgrammingError as e:
|
|
662
|
+
if str(e).find('already exists') != -1:
|
|
663
|
+
print('Database already exists')
|
|
664
|
+
else:
|
|
665
|
+
raise
|
|
666
|
+
else:
|
|
667
|
+
print('(simulating; cannot create database)')
|
|
668
|
+
dbs_created.append(soClass._connection)
|
|
669
|
+
if soClass._connection not in constraints.keys():
|
|
670
|
+
constraints[soClass._connection] = []
|
|
671
|
+
exists = soClass._connection.tableExists(soClass.sqlmeta.table)
|
|
672
|
+
if v >= 1:
|
|
673
|
+
if exists:
|
|
674
|
+
existing += 1
|
|
675
|
+
print('%s already exists.' % soClass.__name__)
|
|
676
|
+
else:
|
|
677
|
+
print('Creating %s' % soClass.__name__)
|
|
678
|
+
if v >= 2:
|
|
679
|
+
sql, extra = soClass.createTableSQL()
|
|
680
|
+
print(sql)
|
|
681
|
+
if (not self.options.simulate and not exists):
|
|
682
|
+
if self.options.interactive:
|
|
683
|
+
if self.ask('Create %s' % soClass.__name__):
|
|
684
|
+
created += 1
|
|
685
|
+
tableConstraints = soClass.createTable(
|
|
686
|
+
applyConstraints=False)
|
|
687
|
+
if tableConstraints:
|
|
688
|
+
constraints[soClass._connection].append(
|
|
689
|
+
tableConstraints)
|
|
690
|
+
else:
|
|
691
|
+
print('Cancelled')
|
|
692
|
+
else:
|
|
693
|
+
created += 1
|
|
694
|
+
tableConstraints = soClass.createTable(
|
|
695
|
+
applyConstraints=False)
|
|
696
|
+
if tableConstraints:
|
|
697
|
+
constraints[soClass._connection].append(
|
|
698
|
+
tableConstraints)
|
|
699
|
+
for connection in constraints.keys():
|
|
700
|
+
if v >= 2:
|
|
701
|
+
print('Creating constraints')
|
|
702
|
+
for constraintList in constraints[connection]:
|
|
703
|
+
for constraint in constraintList:
|
|
704
|
+
if constraint:
|
|
705
|
+
connection.query(constraint)
|
|
706
|
+
if v >= 1:
|
|
707
|
+
print('%i tables created (%i already exist)' % (
|
|
708
|
+
created, existing))
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
class CommandDrop(Command):
|
|
712
|
+
|
|
713
|
+
name = 'drop'
|
|
714
|
+
summary = 'Drop tables'
|
|
715
|
+
|
|
716
|
+
parser = standard_parser(interactive=True)
|
|
717
|
+
|
|
718
|
+
def command(self):
|
|
719
|
+
v = self.options.verbose
|
|
720
|
+
dropped = 0
|
|
721
|
+
not_existing = 0
|
|
722
|
+
for soClass in reversed(self.classes()):
|
|
723
|
+
exists = soClass._connection.tableExists(soClass.sqlmeta.table)
|
|
724
|
+
if v >= 1:
|
|
725
|
+
if exists:
|
|
726
|
+
print('Dropping %s' % soClass.__name__)
|
|
727
|
+
else:
|
|
728
|
+
not_existing += 1
|
|
729
|
+
print('%s does not exist.' % soClass.__name__)
|
|
730
|
+
if (not self.options.simulate and exists):
|
|
731
|
+
if self.options.interactive:
|
|
732
|
+
if self.ask('Drop %s' % soClass.__name__):
|
|
733
|
+
dropped += 1
|
|
734
|
+
soClass.dropTable()
|
|
735
|
+
else:
|
|
736
|
+
print('Cancelled')
|
|
737
|
+
else:
|
|
738
|
+
dropped += 1
|
|
739
|
+
soClass.dropTable()
|
|
740
|
+
if v >= 1:
|
|
741
|
+
print('%i tables dropped (%i didn\'t exist)' % (
|
|
742
|
+
dropped, not_existing))
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
class CommandStatus(Command):
|
|
746
|
+
|
|
747
|
+
name = 'status'
|
|
748
|
+
summary = 'Show status of classes vs. database'
|
|
749
|
+
help = ('This command checks the SQLObject definition and checks if '
|
|
750
|
+
'the tables in the database match. It can always test for '
|
|
751
|
+
'missing tables, and on some databases can test for the '
|
|
752
|
+
'existance of other tables. Column types are not currently '
|
|
753
|
+
'checked.')
|
|
754
|
+
|
|
755
|
+
parser = standard_parser(simulate=False)
|
|
756
|
+
|
|
757
|
+
def print_class(self, soClass):
|
|
758
|
+
if self.printed:
|
|
759
|
+
return
|
|
760
|
+
self.printed = True
|
|
761
|
+
print('Checking %s...' % soClass.__name__)
|
|
762
|
+
|
|
763
|
+
def command(self):
|
|
764
|
+
good = 0
|
|
765
|
+
bad = 0
|
|
766
|
+
missing_tables = 0
|
|
767
|
+
columnsFromSchema_warning = False
|
|
768
|
+
for soClass in self.classes(require_some=True):
|
|
769
|
+
conn = soClass._connection
|
|
770
|
+
self.printed = False
|
|
771
|
+
if self.options.verbose:
|
|
772
|
+
self.print_class(soClass)
|
|
773
|
+
if not conn.tableExists(soClass.sqlmeta.table):
|
|
774
|
+
self.print_class(soClass)
|
|
775
|
+
print(' Does not exist in database')
|
|
776
|
+
missing_tables += 1
|
|
777
|
+
continue
|
|
778
|
+
try:
|
|
779
|
+
columns = conn.columnsFromSchema(soClass.sqlmeta.table,
|
|
780
|
+
soClass)
|
|
781
|
+
except AttributeError:
|
|
782
|
+
if not columnsFromSchema_warning:
|
|
783
|
+
print('Database does not support reading columns')
|
|
784
|
+
columnsFromSchema_warning = True
|
|
785
|
+
good += 1
|
|
786
|
+
continue
|
|
787
|
+
except AssertionError as e:
|
|
788
|
+
print('Cannot read db table %s: %s' % (
|
|
789
|
+
soClass.sqlmeta.table, e))
|
|
790
|
+
continue
|
|
791
|
+
existing = {}
|
|
792
|
+
for _col in columns:
|
|
793
|
+
_col = _col.withClass(soClass)
|
|
794
|
+
existing[_col.dbName] = _col
|
|
795
|
+
missing = {}
|
|
796
|
+
for _col in soClass.sqlmeta.columnList:
|
|
797
|
+
if _col.dbName in existing:
|
|
798
|
+
del existing[_col.dbName]
|
|
799
|
+
else:
|
|
800
|
+
missing[_col.dbName] = _col
|
|
801
|
+
if existing:
|
|
802
|
+
self.print_class(soClass)
|
|
803
|
+
for _col in existing.values():
|
|
804
|
+
print(' Database has extra column: %s' % _col.dbName)
|
|
805
|
+
if missing:
|
|
806
|
+
self.print_class(soClass)
|
|
807
|
+
for _col in missing.values():
|
|
808
|
+
print(' Database missing column: %s' % _col.dbName)
|
|
809
|
+
if existing or missing:
|
|
810
|
+
bad += 1
|
|
811
|
+
else:
|
|
812
|
+
good += 1
|
|
813
|
+
if self.options.verbose:
|
|
814
|
+
print('%i in sync; %i out of sync; %i not in database' % (
|
|
815
|
+
good, bad, missing_tables))
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
class CommandHelp(Command):
|
|
819
|
+
|
|
820
|
+
name = 'help'
|
|
821
|
+
summary = 'Show help'
|
|
822
|
+
|
|
823
|
+
parser = optparse.OptionParser()
|
|
824
|
+
|
|
825
|
+
max_args = 1
|
|
826
|
+
|
|
827
|
+
def command(self):
|
|
828
|
+
if self.args:
|
|
829
|
+
the_runner.run([self.invoked_as, self.args[0], '-h'])
|
|
830
|
+
else:
|
|
831
|
+
print('Available commands:')
|
|
832
|
+
print(' (use "%s help COMMAND" or "%s COMMAND -h" ' % (
|
|
833
|
+
self.prog_name, self.prog_name))
|
|
834
|
+
print(' for more information)')
|
|
835
|
+
items = sorted(the_runner.commands.items())
|
|
836
|
+
max_len = max([len(cn) for cn, c in items])
|
|
837
|
+
for command_name, command in items:
|
|
838
|
+
print('%s:%s %s' % (command_name,
|
|
839
|
+
' ' * (max_len - len(command_name)),
|
|
840
|
+
command.summary))
|
|
841
|
+
if command.aliases:
|
|
842
|
+
print('%s (Aliases: %s)' % (
|
|
843
|
+
' ' * max_len, ', '.join(command.aliases)))
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
class CommandExecute(Command):
|
|
847
|
+
|
|
848
|
+
name = 'execute'
|
|
849
|
+
summary = 'Execute SQL statements'
|
|
850
|
+
help = ('Runs SQL statements directly in the database, with no '
|
|
851
|
+
'intervention. Useful when used with a configuration file. '
|
|
852
|
+
'Each argument is executed as an individual statement.')
|
|
853
|
+
|
|
854
|
+
parser = standard_parser(find_modules=False)
|
|
855
|
+
parser.add_option('--stdin',
|
|
856
|
+
help="Read SQL from stdin "
|
|
857
|
+
"(normally takes SQL from the command line)",
|
|
858
|
+
dest="use_stdin",
|
|
859
|
+
action="store_true")
|
|
860
|
+
|
|
861
|
+
max_args = None
|
|
862
|
+
|
|
863
|
+
def command(self):
|
|
864
|
+
args = self.args
|
|
865
|
+
if self.options.use_stdin:
|
|
866
|
+
if self.options.verbose:
|
|
867
|
+
print("Reading additional SQL from stdin "
|
|
868
|
+
"(Ctrl-D or Ctrl-Z to finish)...")
|
|
869
|
+
args.append(sys.stdin.read())
|
|
870
|
+
self.conn = self.connection().getConnection()
|
|
871
|
+
self.cursor = self.conn.cursor()
|
|
872
|
+
for sql in args:
|
|
873
|
+
self.execute_sql(sql)
|
|
874
|
+
|
|
875
|
+
def execute_sql(self, sql):
|
|
876
|
+
if self.options.verbose:
|
|
877
|
+
print(sql)
|
|
878
|
+
try:
|
|
879
|
+
self.cursor.execute(sql)
|
|
880
|
+
except Exception as e:
|
|
881
|
+
if not self.options.verbose:
|
|
882
|
+
print(sql)
|
|
883
|
+
print("****Error:")
|
|
884
|
+
print(' ', e)
|
|
885
|
+
return
|
|
886
|
+
desc = self.cursor.description
|
|
887
|
+
rows = self.cursor.fetchall()
|
|
888
|
+
if self.options.verbose:
|
|
889
|
+
if not self.cursor.rowcount:
|
|
890
|
+
print("No rows accessed")
|
|
891
|
+
else:
|
|
892
|
+
print("%i rows accessed" % self.cursor.rowcount)
|
|
893
|
+
if desc:
|
|
894
|
+
for (name, type_code, display_size, internal_size,
|
|
895
|
+
precision, scale, null_ok) in desc:
|
|
896
|
+
sys.stdout.write("%s\t" % name)
|
|
897
|
+
sys.stdout.write("\n")
|
|
898
|
+
for row in rows:
|
|
899
|
+
for _col in row:
|
|
900
|
+
sys.stdout.write("%r\t" % _col)
|
|
901
|
+
sys.stdout.write("\n")
|
|
902
|
+
print()
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
class CommandRecord(Command):
|
|
906
|
+
|
|
907
|
+
name = 'record'
|
|
908
|
+
summary = 'Record historical information about the database status'
|
|
909
|
+
help = ('Record state of table definitions. The state of each '
|
|
910
|
+
'table is written out to a separate file in a directory, '
|
|
911
|
+
'and that directory forms a "version". A table is also '
|
|
912
|
+
'added to your database (%s) that reflects the version the '
|
|
913
|
+
'database is currently at. Use the upgrade command to '
|
|
914
|
+
'sync databases with code.'
|
|
915
|
+
% SQLObjectVersionTable.sqlmeta.table)
|
|
916
|
+
|
|
917
|
+
parser = standard_parser()
|
|
918
|
+
parser.add_option('--output-dir',
|
|
919
|
+
help="Base directory for recorded definitions",
|
|
920
|
+
dest="output_dir",
|
|
921
|
+
metavar="DIR",
|
|
922
|
+
default=None)
|
|
923
|
+
parser.add_option('--no-db-record',
|
|
924
|
+
help="Don't record version to database",
|
|
925
|
+
dest="db_record",
|
|
926
|
+
action="store_false",
|
|
927
|
+
default=True)
|
|
928
|
+
parser.add_option('--force-create',
|
|
929
|
+
help="Create a new version even if appears to be "
|
|
930
|
+
"identical to the last version",
|
|
931
|
+
action="store_true",
|
|
932
|
+
dest="force_create")
|
|
933
|
+
parser.add_option('--name',
|
|
934
|
+
help="The name to append to the version. The "
|
|
935
|
+
"version should sort after previous versions (so "
|
|
936
|
+
"any versions from the same day should come "
|
|
937
|
+
"alphabetically before this version).",
|
|
938
|
+
dest="version_name",
|
|
939
|
+
metavar="NAME")
|
|
940
|
+
parser.add_option('--force-db-version',
|
|
941
|
+
help="Update the database version, and include no "
|
|
942
|
+
"database information. This is for databases that "
|
|
943
|
+
"were developed without any interaction with "
|
|
944
|
+
"this tool, to create a 'beginning' revision.",
|
|
945
|
+
metavar="VERSION_NAME",
|
|
946
|
+
dest="force_db_version")
|
|
947
|
+
parser.add_option('--edit',
|
|
948
|
+
help="Open an editor for the upgrader in the last "
|
|
949
|
+
"version (using $EDITOR).",
|
|
950
|
+
action="store_true",
|
|
951
|
+
dest="open_editor")
|
|
952
|
+
|
|
953
|
+
version_regex = re.compile(r'^\d\d\d\d-\d\d-\d\d')
|
|
954
|
+
|
|
955
|
+
def command(self):
|
|
956
|
+
if self.options.force_db_version:
|
|
957
|
+
self.command_force_db_version()
|
|
958
|
+
return
|
|
959
|
+
|
|
960
|
+
v = self.options.verbose
|
|
961
|
+
sim = self.options.simulate
|
|
962
|
+
classes = self.classes()
|
|
963
|
+
if not classes:
|
|
964
|
+
print("No classes found!")
|
|
965
|
+
return
|
|
966
|
+
|
|
967
|
+
output_dir = self.find_output_dir()
|
|
968
|
+
version = os.path.basename(output_dir)
|
|
969
|
+
print("Creating version %s" % version)
|
|
970
|
+
conns = []
|
|
971
|
+
files = {}
|
|
972
|
+
for cls in self.classes():
|
|
973
|
+
dbName = cls._connection.dbName
|
|
974
|
+
if cls._connection not in conns:
|
|
975
|
+
conns.append(cls._connection)
|
|
976
|
+
fn = os.path.join(cls.__name__ + '_' + dbName + '.sql')
|
|
977
|
+
if sim:
|
|
978
|
+
continue
|
|
979
|
+
create, constraints = cls.createTableSQL()
|
|
980
|
+
if constraints:
|
|
981
|
+
constraints = '\n-- Constraints:\n%s\n' % (
|
|
982
|
+
'\n'.join(constraints))
|
|
983
|
+
else:
|
|
984
|
+
constraints = ''
|
|
985
|
+
files[fn] = ''.join([
|
|
986
|
+
'-- Exported definition from %s\n'
|
|
987
|
+
% time.strftime('%Y-%m-%dT%H:%M:%S'),
|
|
988
|
+
'-- Class %s.%s\n'
|
|
989
|
+
% (cls.__module__, cls.__name__),
|
|
990
|
+
'-- Database: %s\n'
|
|
991
|
+
% dbName,
|
|
992
|
+
create.strip(),
|
|
993
|
+
'\n',
|
|
994
|
+
constraints])
|
|
995
|
+
last_version_dir = self.find_last_version()
|
|
996
|
+
if last_version_dir and not self.options.force_create:
|
|
997
|
+
if v > 1:
|
|
998
|
+
print("Checking %s to see if it is current" % last_version_dir)
|
|
999
|
+
files_copy = files.copy()
|
|
1000
|
+
for fn in os.listdir(last_version_dir):
|
|
1001
|
+
if not fn.endswith('.sql'):
|
|
1002
|
+
continue
|
|
1003
|
+
if fn not in files_copy:
|
|
1004
|
+
if v > 1:
|
|
1005
|
+
print("Missing file %s" % fn)
|
|
1006
|
+
break
|
|
1007
|
+
f = open(os.path.join(last_version_dir, fn), 'r')
|
|
1008
|
+
content = f.read()
|
|
1009
|
+
f.close()
|
|
1010
|
+
if (self.strip_comments(files_copy[fn])
|
|
1011
|
+
!= self.strip_comments(content)):
|
|
1012
|
+
if v > 1:
|
|
1013
|
+
print("Content does not match: %s" % fn)
|
|
1014
|
+
break
|
|
1015
|
+
del files_copy[fn]
|
|
1016
|
+
else:
|
|
1017
|
+
# No differences so far
|
|
1018
|
+
if not files_copy:
|
|
1019
|
+
# Used up all files
|
|
1020
|
+
print("Current status matches version %s"
|
|
1021
|
+
% os.path.basename(last_version_dir))
|
|
1022
|
+
return
|
|
1023
|
+
if v > 1:
|
|
1024
|
+
print("Extra files: %s" % ', '.join(files_copy.keys()))
|
|
1025
|
+
if v:
|
|
1026
|
+
print("Current state does not match %s"
|
|
1027
|
+
% os.path.basename(last_version_dir))
|
|
1028
|
+
if v > 1 and not last_version_dir:
|
|
1029
|
+
print("No last version to check")
|
|
1030
|
+
if not sim:
|
|
1031
|
+
os.mkdir(output_dir)
|
|
1032
|
+
if v:
|
|
1033
|
+
print('Making directory %s' % self.shorten_filename(output_dir))
|
|
1034
|
+
files = sorted(files.items())
|
|
1035
|
+
for fn, content in files:
|
|
1036
|
+
if v:
|
|
1037
|
+
print(' Writing %s' % self.shorten_filename(fn))
|
|
1038
|
+
if not sim:
|
|
1039
|
+
f = open(os.path.join(output_dir, fn), 'w')
|
|
1040
|
+
f.write(content)
|
|
1041
|
+
f.close()
|
|
1042
|
+
if self.options.db_record:
|
|
1043
|
+
all_diffs = []
|
|
1044
|
+
for cls in self.classes():
|
|
1045
|
+
for conn in conns:
|
|
1046
|
+
diffs = db_differences(cls, conn)
|
|
1047
|
+
for diff in diffs:
|
|
1048
|
+
if len(conns) > 1:
|
|
1049
|
+
diff = ' (%s).%s: %s' % (
|
|
1050
|
+
conn.uri(), cls.sqlmeta.table, diff)
|
|
1051
|
+
else:
|
|
1052
|
+
diff = ' %s: %s' % (cls.sqlmeta.table, diff)
|
|
1053
|
+
all_diffs.append(diff)
|
|
1054
|
+
if all_diffs:
|
|
1055
|
+
print('Database does not match schema:')
|
|
1056
|
+
print('\n'.join(all_diffs))
|
|
1057
|
+
for conn in conns:
|
|
1058
|
+
self.update_db(version, conn)
|
|
1059
|
+
else:
|
|
1060
|
+
all_diffs = []
|
|
1061
|
+
if self.options.open_editor:
|
|
1062
|
+
if not last_version_dir:
|
|
1063
|
+
print("Cannot edit upgrader because there is no "
|
|
1064
|
+
"previous version")
|
|
1065
|
+
else:
|
|
1066
|
+
breaker = ('-' * 20 + ' lines below this will be ignored '
|
|
1067
|
+
+ '-' * 20)
|
|
1068
|
+
pre_text = breaker + '\n' + '\n'.join(all_diffs)
|
|
1069
|
+
text = self.open_editor('\n\n' + pre_text, breaker=breaker,
|
|
1070
|
+
extension='.sql')
|
|
1071
|
+
if text is not None:
|
|
1072
|
+
fn = os.path.join(last_version_dir,
|
|
1073
|
+
'upgrade_%s_%s.sql' %
|
|
1074
|
+
(dbName, version))
|
|
1075
|
+
f = open(fn, 'w')
|
|
1076
|
+
f.write(text)
|
|
1077
|
+
f.close()
|
|
1078
|
+
print('Wrote to %s' % fn)
|
|
1079
|
+
|
|
1080
|
+
def update_db(self, version, conn):
|
|
1081
|
+
v = self.options.verbose
|
|
1082
|
+
if not conn.tableExists(SQLObjectVersionTable.sqlmeta.table):
|
|
1083
|
+
if v:
|
|
1084
|
+
print('Creating table %s'
|
|
1085
|
+
% SQLObjectVersionTable.sqlmeta.table)
|
|
1086
|
+
sql = SQLObjectVersionTable.createTableSQL(connection=conn)
|
|
1087
|
+
if v > 1:
|
|
1088
|
+
print(sql)
|
|
1089
|
+
if not self.options.simulate:
|
|
1090
|
+
SQLObjectVersionTable.createTable(connection=conn)
|
|
1091
|
+
if not self.options.simulate:
|
|
1092
|
+
SQLObjectVersionTable.clearTable(connection=conn)
|
|
1093
|
+
SQLObjectVersionTable(
|
|
1094
|
+
version=version,
|
|
1095
|
+
connection=conn)
|
|
1096
|
+
|
|
1097
|
+
def strip_comments(self, sql):
|
|
1098
|
+
lines = [_l for _l in sql.splitlines()
|
|
1099
|
+
if not _l.strip().startswith('--')]
|
|
1100
|
+
return '\n'.join(lines)
|
|
1101
|
+
|
|
1102
|
+
def base_dir(self):
|
|
1103
|
+
base = self.options.output_dir
|
|
1104
|
+
if base is None:
|
|
1105
|
+
config = self.config()
|
|
1106
|
+
if config is not None:
|
|
1107
|
+
base = config.get('sqlobject_history_dir', '.')
|
|
1108
|
+
else:
|
|
1109
|
+
base = '.'
|
|
1110
|
+
if not os.path.exists(base):
|
|
1111
|
+
print('Creating history directory %s' %
|
|
1112
|
+
self.shorten_filename(base))
|
|
1113
|
+
if not self.options.simulate:
|
|
1114
|
+
os.makedirs(base)
|
|
1115
|
+
return base
|
|
1116
|
+
|
|
1117
|
+
def find_output_dir(self):
|
|
1118
|
+
today = time.strftime('%Y-%m-%d', time.localtime())
|
|
1119
|
+
if self.options.version_name:
|
|
1120
|
+
dir = os.path.join(self.base_dir(), today + '-'
|
|
1121
|
+
+ self.options.version_name)
|
|
1122
|
+
if os.path.exists(dir):
|
|
1123
|
+
print("Error, directory already exists: %s"
|
|
1124
|
+
% dir)
|
|
1125
|
+
sys.exit(1)
|
|
1126
|
+
return dir
|
|
1127
|
+
extra = ''
|
|
1128
|
+
while 1:
|
|
1129
|
+
dir = os.path.join(self.base_dir(), today + extra)
|
|
1130
|
+
if not os.path.exists(dir):
|
|
1131
|
+
return dir
|
|
1132
|
+
if not extra:
|
|
1133
|
+
extra = 'a'
|
|
1134
|
+
else:
|
|
1135
|
+
extra = chr(ord(extra) + 1)
|
|
1136
|
+
|
|
1137
|
+
def find_last_version(self):
|
|
1138
|
+
names = []
|
|
1139
|
+
for fn in os.listdir(self.base_dir()):
|
|
1140
|
+
if not self.version_regex.search(fn):
|
|
1141
|
+
continue
|
|
1142
|
+
names.append(fn)
|
|
1143
|
+
if not names:
|
|
1144
|
+
return None
|
|
1145
|
+
names.sort()
|
|
1146
|
+
return os.path.join(self.base_dir(), names[-1])
|
|
1147
|
+
|
|
1148
|
+
def command_force_db_version(self):
|
|
1149
|
+
v = self.options.verbose
|
|
1150
|
+
sim = self.options.simulate
|
|
1151
|
+
version = self.options.force_db_version
|
|
1152
|
+
if not self.version_regex.search(version):
|
|
1153
|
+
print("Versions must be in the format YYYY-MM-DD...")
|
|
1154
|
+
print("You version %s does not fit this" % version)
|
|
1155
|
+
return
|
|
1156
|
+
version_dir = os.path.join(self.base_dir(), version)
|
|
1157
|
+
if not os.path.exists(version_dir):
|
|
1158
|
+
if v:
|
|
1159
|
+
print('Creating %s' % self.shorten_filename(version_dir))
|
|
1160
|
+
if not sim:
|
|
1161
|
+
os.mkdir(version_dir)
|
|
1162
|
+
elif v:
|
|
1163
|
+
print('Directory %s exists'
|
|
1164
|
+
% self.shorten_filename(version_dir))
|
|
1165
|
+
if self.options.db_record:
|
|
1166
|
+
self.update_db(version, self.connection())
|
|
1167
|
+
|
|
1168
|
+
|
|
1169
|
+
class CommandUpgrade(CommandRecord):
|
|
1170
|
+
|
|
1171
|
+
name = 'upgrade'
|
|
1172
|
+
summary = 'Update the database to a new version (as created by record)'
|
|
1173
|
+
help = ('This command runs scripts (that you write by hand) to '
|
|
1174
|
+
'upgrade a database. The database\'s current version is in '
|
|
1175
|
+
'the sqlobject_version table (use record --force-db-version '
|
|
1176
|
+
'if a database does not have a sqlobject_version table), '
|
|
1177
|
+
'and upgrade scripts are in the version directory you are '
|
|
1178
|
+
'upgrading FROM, named upgrade_DBNAME_VERSION.sql, like '
|
|
1179
|
+
'"upgrade_mysql_2004-12-01b.sql".')
|
|
1180
|
+
|
|
1181
|
+
parser = standard_parser(find_modules=False)
|
|
1182
|
+
parser.add_option('--upgrade-to',
|
|
1183
|
+
help="Upgrade to the given version "
|
|
1184
|
+
"(default: newest version)",
|
|
1185
|
+
dest="upgrade_to",
|
|
1186
|
+
metavar="VERSION")
|
|
1187
|
+
parser.add_option('--output-dir',
|
|
1188
|
+
help="Base directory for recorded definitions",
|
|
1189
|
+
dest="output_dir",
|
|
1190
|
+
metavar="DIR",
|
|
1191
|
+
default=None)
|
|
1192
|
+
|
|
1193
|
+
upgrade_regex = re.compile(r'^upgrade_([a-z]*)_([^.]*)\.sql$', re.I)
|
|
1194
|
+
|
|
1195
|
+
def command(self):
|
|
1196
|
+
v = self.options.verbose
|
|
1197
|
+
sim = self.options.simulate
|
|
1198
|
+
if self.options.upgrade_to:
|
|
1199
|
+
version_to = self.options.upgrade_to
|
|
1200
|
+
else:
|
|
1201
|
+
fname = self.find_last_version()
|
|
1202
|
+
if fname is None:
|
|
1203
|
+
print("No version exists, use 'record' command to create one")
|
|
1204
|
+
return
|
|
1205
|
+
version_to = os.path.basename(fname)
|
|
1206
|
+
current = self.current_version()
|
|
1207
|
+
if v:
|
|
1208
|
+
print('Current version: %s' % current)
|
|
1209
|
+
version_list = self.make_plan(current, version_to)
|
|
1210
|
+
if not version_list:
|
|
1211
|
+
print('Database up to date')
|
|
1212
|
+
return
|
|
1213
|
+
if v:
|
|
1214
|
+
print('Plan:')
|
|
1215
|
+
for next_version, upgrader in version_list:
|
|
1216
|
+
print(' Use %s to upgrade to %s' % (
|
|
1217
|
+
self.shorten_filename(upgrader), next_version))
|
|
1218
|
+
conn = self.connection()
|
|
1219
|
+
for next_version, upgrader in version_list:
|
|
1220
|
+
f = open(upgrader)
|
|
1221
|
+
sql = f.read()
|
|
1222
|
+
f.close()
|
|
1223
|
+
if v:
|
|
1224
|
+
print("Running:")
|
|
1225
|
+
print(sql)
|
|
1226
|
+
print('-' * 60)
|
|
1227
|
+
if not sim:
|
|
1228
|
+
try:
|
|
1229
|
+
conn.query(sql)
|
|
1230
|
+
except Exception:
|
|
1231
|
+
print("Error in script: %s" % upgrader)
|
|
1232
|
+
raise
|
|
1233
|
+
self.update_db(next_version, conn)
|
|
1234
|
+
print('Done.')
|
|
1235
|
+
|
|
1236
|
+
def current_version(self):
|
|
1237
|
+
conn = self.connection()
|
|
1238
|
+
if not conn.tableExists(SQLObjectVersionTable.sqlmeta.table):
|
|
1239
|
+
print('No sqlobject_version table!')
|
|
1240
|
+
sys.exit(1)
|
|
1241
|
+
versions = list(SQLObjectVersionTable.select(connection=conn))
|
|
1242
|
+
if not versions:
|
|
1243
|
+
print('No rows in sqlobject_version!')
|
|
1244
|
+
sys.exit(1)
|
|
1245
|
+
if len(versions) > 1:
|
|
1246
|
+
print('Ambiguous sqlobject_version_table')
|
|
1247
|
+
sys.exit(1)
|
|
1248
|
+
return versions[0].version
|
|
1249
|
+
|
|
1250
|
+
def make_plan(self, current, dest):
|
|
1251
|
+
if current == dest:
|
|
1252
|
+
return []
|
|
1253
|
+
dbname = self.connection().dbName
|
|
1254
|
+
next_version, upgrader = self.best_upgrade(current, dest, dbname)
|
|
1255
|
+
if not upgrader:
|
|
1256
|
+
print('No way to upgrade from %s to %s' % (current, dest))
|
|
1257
|
+
print('(you need a %s/upgrade_%s_%s.sql script)'
|
|
1258
|
+
% (current, dbname, dest))
|
|
1259
|
+
sys.exit(1)
|
|
1260
|
+
plan = [(next_version, upgrader)]
|
|
1261
|
+
if next_version == dest:
|
|
1262
|
+
return plan
|
|
1263
|
+
else:
|
|
1264
|
+
return plan + self.make_plan(next_version, dest)
|
|
1265
|
+
|
|
1266
|
+
def best_upgrade(self, current, dest, target_dbname):
|
|
1267
|
+
current_dir = os.path.join(self.base_dir(), current)
|
|
1268
|
+
if self.options.verbose > 1:
|
|
1269
|
+
print('Looking in %s for upgraders'
|
|
1270
|
+
% self.shorten_filename(current_dir))
|
|
1271
|
+
upgraders = []
|
|
1272
|
+
for fn in os.listdir(current_dir):
|
|
1273
|
+
match = self.upgrade_regex.search(fn)
|
|
1274
|
+
if not match:
|
|
1275
|
+
if self.options.verbose > 1:
|
|
1276
|
+
print('Not an upgrade script: %s' % fn)
|
|
1277
|
+
continue
|
|
1278
|
+
dbname = match.group(1)
|
|
1279
|
+
version = match.group(2)
|
|
1280
|
+
if dbname != target_dbname:
|
|
1281
|
+
if self.options.verbose > 1:
|
|
1282
|
+
print('Not for this database: %s (want %s)' % (
|
|
1283
|
+
dbname, target_dbname))
|
|
1284
|
+
continue
|
|
1285
|
+
if version > dest:
|
|
1286
|
+
if self.options.verbose > 1:
|
|
1287
|
+
print('Version too new: %s (only want %s)' % (
|
|
1288
|
+
version, dest))
|
|
1289
|
+
upgraders.append((version, os.path.join(current_dir, fn)))
|
|
1290
|
+
if not upgraders:
|
|
1291
|
+
if self.options.verbose > 1:
|
|
1292
|
+
print('No upgraders found in %s' % current_dir)
|
|
1293
|
+
return None, None
|
|
1294
|
+
upgraders.sort()
|
|
1295
|
+
return upgraders[-1]
|
|
1296
|
+
|
|
1297
|
+
|
|
1298
|
+
def update_sys_path(paths, verbose):
|
|
1299
|
+
if isinstance(paths, string_type):
|
|
1300
|
+
paths = [paths]
|
|
1301
|
+
for path in paths:
|
|
1302
|
+
path = os.path.abspath(path)
|
|
1303
|
+
if path not in sys.path:
|
|
1304
|
+
if verbose > 1:
|
|
1305
|
+
print('Adding %s to path' % path)
|
|
1306
|
+
sys.path.insert(0, path)
|
|
1307
|
+
|
|
1308
|
+
if __name__ == '__main__':
|
|
1309
|
+
the_runner.run(sys.argv)
|