Skip to content

Commit 6bd9700

Browse files
committed
First go at a command-line client
git-svn-id: http://svn.colorstudy.com/trunk/SQLObject@702 95a46c32-92d2-0310-94a5-8d71aeb3d4b3
1 parent e3410a4 commit 6bd9700

5 files changed

Lines changed: 417 additions & 1 deletion

File tree

scripts/sqlobject-admin

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env python
2+
import sys
3+
import os
4+
try:
5+
import sqlobject
6+
except ImportError:
7+
try:
8+
here = __file__
9+
except NameError:
10+
here = sys.argv[0]
11+
updir = os.path.join(
12+
os.path.dirname(os.path.dirname(os.path.abspath(here))),
13+
'sqlobject')
14+
if sys.path.exists(updir):
15+
sys.path.insert(0, updir)
16+
else:
17+
print 'I cannot find the sqlobject module'
18+
print 'If SQLObject is installed, you may need to set $PYTHONPATH'
19+
sys.exit(3)
20+
21+
from sqlobject.manager import command
22+
command.the_runner.run(sys.argv)
23+

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
warnings.filterwarnings("ignore", "Unknown distribution option")
44

55
subpackages = ['firebird', 'include', 'inheritance', 'mysql', 'postgres',
6-
'sqlite', 'sybase', 'maxdb']
6+
'sqlite', 'sybase', 'maxdb', 'util', 'manager']
77

88
import sys
99
# patch distutils if it can't cope with the "classifiers" keyword
@@ -34,6 +34,7 @@
3434
url="http://sqlobject.org",
3535
license="LGPL",
3636
packages=["sqlobject"] + ['sqlobject.%s' % package for package in subpackages],
37+
scripts=["scripts/sqlobject-admin"],
3738
download_url="http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.6.1.tar.gz?download")
3839

3940
# Send announce to:

sqlobject/manager/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#

sqlobject/manager/command.py

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
#!/usr/bin/env python
2+
import optparse
3+
import fnmatch
4+
import os
5+
import sys
6+
7+
import sqlobject
8+
from sqlobject.util import moduleloader
9+
from sqlobject.declarative import DeclarativeMeta
10+
11+
class CommandRunner(object):
12+
13+
def __init__(self):
14+
self.commands = {}
15+
self.command_aliases = {}
16+
17+
def run(self, argv):
18+
invoked_as = argv[0]
19+
args = argv[1:]
20+
for i in range(len(args)):
21+
if not args[i].startswith('-'):
22+
# this must be a command
23+
command = args[i].lower()
24+
del args[i]
25+
break
26+
else:
27+
# no command found
28+
self.invalid('No COMMAND given')
29+
real_command = self.command_aliases.get(command, command)
30+
if real_command not in self.commands.keys():
31+
self.invalid('COMMAND %s unknown' % command)
32+
runner = self.commands[real_command](
33+
invoked_as, command, args, self)
34+
runner.run()
35+
36+
def register(self, command):
37+
name = command.name
38+
self.commands[name] = command
39+
for alias in command.aliases:
40+
self.command_aliases[alias] = name
41+
42+
def invalid(self, msg, code=2):
43+
print msg
44+
sys.exit(code)
45+
46+
the_runner = CommandRunner()
47+
register = the_runner.register
48+
49+
def standard_parser(connection=True, simulate=True,
50+
interactive=False):
51+
parser = optparse.OptionParser()
52+
parser.add_option('-v', '--verbose',
53+
help='Be verbose (multiple times for more verbosity)',
54+
action='count',
55+
dest='verbose',
56+
default=0)
57+
if simulate:
58+
parser.add_option('-n', '--simulate',
59+
help="Don't actually do anything (implies -v)",
60+
action='store_true',
61+
dest='simulate')
62+
if connection:
63+
parser.add_option('-c', '--connection',
64+
help="The database connection URI",
65+
metavar='URI',
66+
dest='connection_uri')
67+
parser.add_option('-m', '--module',
68+
help="Module in which to find SQLObject classes",
69+
action='append',
70+
metavar='MODULE',
71+
dest='modules',
72+
default=[])
73+
parser.add_option('-p', '--package',
74+
help="Package to search for SQLObject classes",
75+
action="append",
76+
metavar="PACKAGE",
77+
dest="packages",
78+
default=[])
79+
parser.add_option('--class',
80+
help="Select only named classes (wildcards allowed)",
81+
action="append",
82+
metavar="NAME",
83+
dest="class_matchers",
84+
default=[])
85+
if interactive:
86+
parser.add_option('-i', '--interactive',
87+
help="Ask before doing anything (use twice to be more careful)",
88+
action="count",
89+
dest="interactive",
90+
default=0)
91+
return parser
92+
93+
class Command(object):
94+
95+
__metaclass__ = DeclarativeMeta
96+
97+
min_args = 0
98+
min_args_error = 'You must provide at least %(min_args)s arguments'
99+
max_args = 0
100+
max_args_error = 'You must provide no more than %(max_args)s arguments'
101+
aliases = ()
102+
required_args = []
103+
104+
def __classinit__(cls, new_args):
105+
if cls.__bases__ == (object,):
106+
# This abstract base class
107+
return
108+
register(cls)
109+
110+
def __init__(self, invoked_as, command_name, args, runner):
111+
self.invoked_as = invoked_as
112+
self.command_name = command_name
113+
self.raw_args = args
114+
self.runner = runner
115+
116+
def run(self):
117+
self.options, self.args = self.parser.parse_args(self.raw_args)
118+
if (getattr(self.options, 'simulate', False)
119+
and not self.options.verbose):
120+
self.options.verbose = 1
121+
if self.min_args is not None and len(self.args) < self.min_args:
122+
self.runner.invalid(
123+
self.min_args_error % {'min_args': self.min_args,
124+
'actual_args': len(self.args)})
125+
if self.max_args is not None and len(self.args) > self.max_args:
126+
self.runner.invalid(
127+
self.max_args_error % {'max_args': self.max_args,
128+
'actual_args': len(self.args)})
129+
for var_name, option_name in self.required_args:
130+
if not getattr(self.options, var_name, None):
131+
self.runner.invalid(
132+
'You must provide the option %s' % option_name)
133+
self.command()
134+
135+
def classes(self, require_connection=True):
136+
all = []
137+
for module_name in self.options.modules:
138+
all.extend(self.classes_from_module(
139+
moduleloader.load_module(module_name)))
140+
for package_name in self.options.packages:
141+
all.extend(self.classes_from_package(package_name))
142+
if self.options.class_matchers:
143+
filtered = []
144+
for soClass in all:
145+
name = soClass.__name__
146+
for matcher in self.options.class_matchers:
147+
if fnmatch.fnmatch(name, matcher):
148+
filtered.append(soClass)
149+
break
150+
all = filtered
151+
conn = self.connection()
152+
if conn:
153+
for soClass in all:
154+
soClass._connection = conn
155+
else:
156+
missing = []
157+
for soClass in all:
158+
try:
159+
if not soClass._connection:
160+
missing.append(soClass)
161+
except AttributeError:
162+
missing.append(soClass)
163+
if missing and require_connection:
164+
self.runner.invalid(
165+
'These classes do not have connections set:\n * %s\n'
166+
'You must indicate --connection=URI'
167+
% '\n * '.join([soClass.__name__
168+
for soClass in missing]))
169+
return all
170+
171+
def classes_from_module(self, module):
172+
all = []
173+
if hasattr(module, 'soClasses'):
174+
for name_or_class in module.soClasses:
175+
if isinstance(name_or_class, str):
176+
name_or_class = getattr(module, name_or_class)
177+
all.append(name_or_class)
178+
else:
179+
for name in dir(module):
180+
value = getattr(module, name)
181+
if (isinstance(value, type)
182+
and issubclass(value, sqlobject.SQLObject)
183+
and value.__module__ == module.__name__):
184+
all.append(value)
185+
return all
186+
187+
def connection(self):
188+
if self.options.connection_uri:
189+
return sqlobject.connectionForURI(self.options.connection_uri)
190+
else:
191+
return None
192+
193+
def classes_from_package(self, package_name):
194+
raise NotImplementedError
195+
196+
def command(self):
197+
raise NotImplementedError
198+
199+
def _get_prog_name(self):
200+
return os.path.basename(self.invoked_as)
201+
prog_name = property(_get_prog_name)
202+
203+
def ask(self, prompt, safe=False, default=True):
204+
if self.options.interactive >= 2:
205+
default = safe
206+
if default:
207+
prompt += ' [Y/n]? '
208+
else:
209+
prompt += ' [y/N]? '
210+
while 1:
211+
response = raw_input(prompt).strip()
212+
if not response.strip():
213+
return default
214+
if response and response[0].lower() in ('y', 'n'):
215+
return response[0].lower() == 'y'
216+
print 'Y or N please'
217+
218+
class CommandSQL(Command):
219+
220+
name = 'sql'
221+
summary = 'Show SQL CREATE statements'
222+
223+
parser = standard_parser(simulate=False)
224+
225+
def command(self):
226+
classes = self.classes()
227+
for cls in classes:
228+
if self.options.verbose >= 1:
229+
print '-- %s from %s' % (
230+
cls.__name__, cls.__module__)
231+
print cls.createTableSQL().strip() + ';\n'
232+
233+
class CommandList(Command):
234+
235+
name = 'list'
236+
summary = 'Show all SQLObject classes found'
237+
238+
parser = standard_parser(simulate=False, connection=False)
239+
240+
def command(self):
241+
if self.options.verbose >= 1:
242+
print 'Classes found:'
243+
classes = self.classes(require_connection=False)
244+
for soClass in classes:
245+
print '%s.%s' % (soClass.__module__, soClass.__name__)
246+
if self.options.verbose >= 1:
247+
print ' Table: %s' % soClass.sqlmeta.table
248+
249+
class CommandCreate(Command):
250+
251+
name = 'create'
252+
summary = 'Create tables'
253+
254+
parser = standard_parser(interactive=True)
255+
256+
def command(self):
257+
v = self.options.verbose
258+
created = 0
259+
existing = 0
260+
for soClass in self.classes():
261+
exists = soClass._connection.tableExists(soClass.sqlmeta.table)
262+
if v >= 1:
263+
if exists:
264+
existing += 1
265+
print '%s already exists.' % soClass.__name__
266+
else:
267+
print 'Creating %s' % soClass.__name__
268+
if v >= 2:
269+
print soClass.createTableSQL()
270+
if (not self.options.simulate
271+
and not exists):
272+
if self.options.interactive:
273+
if self.ask('Create %s' % soClass.__name__):
274+
created += 1
275+
soClass.createTable()
276+
else:
277+
print 'Cancelled'
278+
else:
279+
created += 1
280+
soClass.createTable()
281+
if v >= 1:
282+
print '%i tables created (%i already exist)' % (
283+
created, existing)
284+
285+
286+
class CommandDrop(Command):
287+
288+
name = 'drop'
289+
summary = 'Drop tables'
290+
291+
parser = standard_parser(interactive=True)
292+
293+
def command(self):
294+
v = self.options.verbose
295+
dropped = 0
296+
not_existing = 0
297+
for soClass in self.classes():
298+
exists = soClass._connection.tableExists(soClass.sqlmeta.table)
299+
if v >= 1:
300+
if exists:
301+
print 'Dropping %s' % soClass.__name__
302+
else:
303+
not_existing += 1
304+
print '%s does not exist.' % soClass.__name__
305+
if (not self.options.simulate
306+
and exists):
307+
if self.options.interactive:
308+
if self.ask('Drop %s' % soClass.__name__):
309+
dropped += 1
310+
soClass.dropTable()
311+
else:
312+
print 'Cancelled'
313+
else:
314+
dropped += 1
315+
soClass.dropTable()
316+
if v >= 1:
317+
print '%i tables dropped (%i didn\'t exist)' % (
318+
dropped, not_existing)
319+
320+
class CommandHelp(Command):
321+
322+
name = 'help'
323+
summary = 'Show help'
324+
325+
parser = optparse.OptionParser()
326+
327+
max_args = 1
328+
329+
def command(self):
330+
if self.args:
331+
the_runner.run([self.invoked_as, self.args[0], '-h'])
332+
else:
333+
print 'Available commands:'
334+
print ' (use "%s help COMMAND" or "%s COMMAND -h" for more)' % (
335+
self.prog_name, self.prog_name)
336+
items = the_runner.commands.items()
337+
items.sort()
338+
max_len = max([len(cn) for cn, c in items])
339+
for command_name, command in items:
340+
print '%s:%s %s' % (command_name,
341+
' '*(max_len-len(command_name)),
342+
command.summary)
343+
if command.aliases:
344+
print '%s (Aliases: %s)' % (
345+
' '*max_len, ', '.join(command.aliases))
346+
347+
348+
if __name__ == '__main__':
349+
the_runner.run(sys.argv)

0 commit comments

Comments
 (0)