1 """Commands for the TurboGears command line tool."""
2
3 import glob
4 import optparse
5 import os
6 import sys
7
8 import pkg_resources
9 from peak.rules import NoApplicableMethods
10
11 from turbogears import config, database, widgets
12 from turbogears.identity import SecureObject, from_any_host
13 from turbogears.util import (get_model, get_project_name, get_project_config,
14 get_package_name, load_project_config)
15
16 from sacommand import sacommand
17
18 sys.path.insert(0, os.getcwd())
19
20 no_connection_param = ['help', 'list']
21 no_model_param = ['help']
22
23
25 """Try to remove file 'fname' but mute any error that may happen.
26
27 Returns True if file was actually removed and False otherwise.
28
29 """
30 try:
31 os.remove(fname)
32 return True
33 except os.error:
34 pass
35 return False
36
37
39 """Base class for commands that need to use the database"""
40
41 config = None
42
45
47 """Chooses the config file, trying to guess whether this is a
48 development or installed project."""
49 load_project_config(self.config)
50 self.dburi = config.get('sqlobject.dburi', None)
51 if self.dburi and self.dburi.startswith('notrans_'):
52 self.dburi = self.dburi[8:]
53
54
55 -class SQL(CommandWithDB):
56 """Wrapper command for sqlobject-admin, and some sqlalchemy support.
57
58 This automatically supplies sqlobject-admin with the database that
59 is found in the config file.
60
61 Will also supply the model module as appropriate.
62
63 """
64
65 desc = "Run the database provider manager"
66 need_project = True
67
69 if len(sys.argv) == 1 or sys.argv[1][0] == '-':
70 parser = optparse.OptionParser(
71 usage="%prog sql [command]\n\nhint: '%prog sql help'"
72 " will list the sql specific commands",
73 version='%prog ' + version)
74 parser.add_option('-c', '--config', help="config file",
75 dest='config')
76 options, args = parser.parse_args(sys.argv[1:3])
77
78 if not options.config:
79 parser.error("Please provide a valid option or command.")
80 self.config = options.config
81
82 if args:
83 del sys.argv[1]
84 else:
85 del sys.argv[1:3]
86
87 self.find_config()
88
90 """Run the sqlobject-admin tool or sacommand module functions."""
91
92 if not '--egg' in sys.argv and not get_project_name():
93 print "This doesn't look like a TurboGears project."
94 return
95 else:
96 command = sys.argv[1]
97
98 if config.get('sqlalchemy.dburi'):
99 try:
100 sacommand(command, sys.argv)
101 except NoApplicableMethods:
102 sacommand('help', [])
103 return
104
105 try:
106 from sqlobject.manager import command as socommand
107 except ImportError:
108 from turbogears.util import missing_dependency_error
109 print missing_dependency_error('SQLObject')
110 return
111
112 if command not in no_connection_param:
113 if self.dburi:
114 print "Using database URI %s" % self.dburi
115 sys.argv.insert(2, self.dburi)
116 sys.argv.insert(2, '-c')
117 else:
118 print ("Database URI not specified in the config file"
119 " (%s).\nPlease be sure it's on the command line."
120 % (self.config or get_project_config()))
121
122 if command not in no_model_param:
123 if not '--egg' in sys.argv:
124 eggname = glob.glob('*.egg-info')
125 if not eggname or not os.path.exists(
126 os.path.join(eggname[0], 'sqlobject.txt')):
127 eggname = self.fix_egginfo(eggname)
128 eggname = eggname[0].replace('.egg-info', '')
129 if not '.' in sys.path:
130 sys.path.append('.')
131 pkg_resources.working_set.add_entry('.')
132 sys.argv.insert(2, eggname)
133 sys.argv.insert(2, '--egg')
134
135 socommand.the_runner.run(sys.argv)
136
138 """Add egg-info directory if necessary."""
139 print """
140 This project seems incomplete. In order to use the sqlobject commands
141 without manually specifying a model, there needs to be an
142 egg-info directory with an appropriate sqlobject.txt file.
143
144 I can fix this automatically. Would you like me to?
145 """
146 dofix = raw_input("Enter [y] or n: ")
147 if not dofix or dofix.lower()[0] == 'y':
148 oldargs = sys.argv
149 sys.argv = ['setup.py', 'egg_info']
150 import imp
151 imp.load_module('setup', *imp.find_module('setup', ['.']))
152 sys.argv = oldargs
153
154 import setuptools
155 package = setuptools.find_packages()[0]
156 eggname = glob.glob('*.egg-info')
157 sqlobjectmeta = open(os.path.join(eggname[0], 'sqlobject.txt'), 'w')
158 sqlobjectmeta.write('db_module=%(package)s.model\n'
159 'history_dir=$base/%(package)s/sqlobject-history'
160 % dict(package=package))
161 else:
162 sys.exit(0)
163 return eggname
164
165
166 -class Shell(CommandWithDB):
167 """Convenient version of the Python interactive shell.
168
169 This shell attempts to locate your configuration file and model module
170 so that it can import everything from your model and make it available
171 in the Python shell namespace.
172
173 """
174
175 desc = "Start a Python prompt with your database available"
176 need_project = True
177
179 """Run the shell"""
180 self.find_config()
181
182 locals = dict(__name__='tg-admin')
183 try:
184 mod = get_model()
185 if mod:
186 locals.update(mod.__dict__)
187 except (pkg_resources.DistributionNotFound, ImportError), e:
188 mod = None
189 print "Warning: Failed to import your data model: %s" % e
190 print "You will not have access to your data model objects."
191 print
192
193 if config.get('sqlalchemy.dburi'):
194 using_sqlalchemy = True
195 database.bind_metadata()
196 locals.update(session=database.session,
197 metadata=database.metadata)
198 else:
199 using_sqlalchemy = False
200
201 class CustomShellMixin(object):
202 def commit_changes(self):
203 if mod:
204
205
206 r = raw_input("Do you wish to commit"
207 " your database changes? [yes]")
208 if not r.startswith('n'):
209 if using_sqlalchemy:
210 self.push("session.flush()")
211 else:
212 self.push("hub.commit()")
213
214 try:
215
216 from IPython import iplib, Shell
217
218 class CustomIPShell(iplib.InteractiveShell, CustomShellMixin):
219 def raw_input(self, *args, **kw):
220 try:
221
222 return iplib.InteractiveShell.raw_input(
223 self, *args, **kw)
224 except EOFError:
225 self.commit_changes()
226 raise EOFError
227
228 shell = Shell.IPShell(user_ns=locals, shell_class=CustomIPShell)
229 shell.mainloop()
230 except ImportError:
231 import code
232
233 class CustomShell(code.InteractiveConsole, CustomShellMixin):
234 def raw_input(self, *args, **kw):
235 try:
236 import readline
237 except ImportError:
238 pass
239 try:
240 r = code.InteractiveConsole.raw_input(self,
241 *args, **kw)
242 for encoding in (getattr(sys.stdin, 'encoding', None),
243 sys.getdefaultencoding(), 'utf-8', 'latin-1'):
244 if encoding:
245 try:
246 return r.decode(encoding)
247 except UnicodeError:
248 pass
249 return r
250 except EOFError:
251 self.commit_changes()
252 raise EOFError
253
254 shell = CustomShell(locals=locals)
255 shell.interact()
256
257
338
339
340 commands = None
341
343 """Main command runner. Manages the primary command line arguments."""
344
345 commands = {}
346 for entrypoint in pkg_resources.iter_entry_points('turbogears.command'):
347 command = entrypoint.load()
348 commands[entrypoint.name] = (command.desc, entrypoint)
349 from turbogears import __version__
350
351 def _help():
352 """Custom help text for tg-admin."""
353
354 print """
355 TurboGears %s command line interface
356
357 Usage: %s [options] <command>
358
359 Options:
360 -c CONFIG --config=CONFIG Config file to use
361 -e EGG_SPEC --egg=EGG_SPEC Run command on given Egg
362
363 Commands:""" % (__version__, sys.argv[0])
364
365 longest = max([len(key) for key in commands.keys()])
366 format = '%' + str(longest) + 's %s'
367 commandlist = commands.keys()
368 commandlist.sort()
369 for key in commandlist:
370 print format % (key, commands[key][0])
371
372 parser = optparse.OptionParser()
373 parser.allow_interspersed_args = False
374 parser.add_option('-c', '--config', dest='config')
375 parser.add_option('-e', '--egg', dest='egg')
376 parser.print_help = _help
377 options, args = parser.parse_args(sys.argv[1:])
378
379
380 if not args or args[0] not in commands:
381 _help()
382 sys.exit()
383
384 commandname = args[0]
385
386 sys.argv = [sys.argv[0]] + args[1:]
387 command = commands[commandname][1]
388 command = command.load()
389
390 if options.egg:
391 egg = pkg_resources.get_distribution(options.egg)
392 os.chdir(egg.location)
393
394 if hasattr(command, 'need_project'):
395 if not get_project_name():
396 print "This command needs to be run from inside a project directory"
397 return
398 elif not options.config and not os.path.isfile(get_project_config()):
399 print """No default config file was found.
400 If it has been renamed use:
401 tg-admin --config=<FILE> %s""" % commandname
402 return
403 command.config = options.config
404 command = command(__version__)
405 command.run()
406
407
408 __all__ = ["main"]
409