Package turbogears :: Package command :: Module base

Source Code for Module turbogears.command.base

  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   
24 -def silent_os_remove(fname):
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
38 -class CommandWithDB(object):
39 """Base class for commands that need to use the database""" 40 41 config = None 42
43 - def __init__(self, version):
44 pass
45
46 - def find_config(self):
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
68 - def __init__(self, version):
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 # get rid of our config option 82 if args: 83 del sys.argv[1] 84 else: 85 del sys.argv[1:3] 86 87 self.find_config()
88
89 - def run(self):
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
137 - def fix_egginfo(self, eggname):
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
178 - def run(self):
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 # XXX Can we check somehow, if there are actually any 205 # database changes to be commited? 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 # try to use IPython if possible 216 from IPython import iplib, Shell 217 218 class CustomIPShell(iplib.InteractiveShell, CustomShellMixin): 219 def raw_input(self, *args, **kw): 220 try: 221 # needs decoding (see below)? 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
258 -class ToolboxCommand(CommandWithDB):
259 260 desc = "Launch the TurboGears Toolbox" 261
262 - def __init__(self, version):
263 self.hostlist = ['127.0.0.1', '::1'] 264 265 parser = optparse.OptionParser( 266 usage="%prog toolbox [options]", 267 version='%prog ' + version) 268 parser.add_option('-n', '--no-open', 269 help="don't open browser automatically", 270 dest='noopen', action='store_true', default=False) 271 parser.add_option('-c', '--add-client', 272 help="allow client ip address specified to connect to toolbox" 273 " (can be specified more than once)", 274 dest='host', action='append', default=None) 275 parser.add_option('-p', '--port', 276 help="port to run the Toolbox on", 277 dest='port', default=7654) 278 parser.add_option('--config', help="config file to use", 279 dest='config', default=self.config or get_project_config()) 280 281 options, args = parser.parse_args(sys.argv[1:]) 282 self.port = int(options.port) 283 self.noopen = options.noopen 284 self.config = options.config 285 286 if options.host: 287 self.hostlist = self.hostlist + options.host 288 289 widgets.load_widgets()
290
291 - def openbrowser(self):
292 import webbrowser 293 webbrowser.open('http://localhost:%d' % self.port)
294
295 - def run(self):
296 import cherrypy 297 from turbogears import toolbox 298 299 # Make sure we have full configuration with every option 300 # in it so other plugins or whatever find what they need 301 # when starting even inside the toolblox 302 conf = get_package_name() 303 conf = conf and "%s.config" % conf or None 304 conf = config.config_obj(configfile=self.config, modulename=conf) 305 306 if 'global' in conf: 307 config.update({'global': conf['global']}) 308 309 root = SecureObject(toolbox.Toolbox(), from_any_host(self.hostlist), 310 exclude=['noaccess']) 311 312 cherrypy.tree.mount(root, '/') 313 314 # amend some parameters since we are running from the command 315 # line in order to change port, log methods... 316 config.update({'global': { 317 'server.socket_port': self.port, 318 'server.webpath': '/', 319 'server.environment': 'development', 320 'server.log_to_screen': True, 321 'autoreload.on': False, 322 'server.package': 'turbogears.toolbox', 323 'log_debug_info_filter.on': False, 324 'identity.failure_url': '/noaccess', 325 'identity.force_external_redirect': False, 326 'tg.strict_parameters': False, 327 'tg.defaultview': 'genshi', 328 'genshi.default_doctype': 'html', 329 'genshi.default_encoding': 'utf-8', 330 'kid.outputformat': 'html default', 331 'kid.encoding': 'utf-8' 332 }}) 333 334 if self.noopen: 335 cherrypy.server.start() 336 else: 337 cherrypy.server.start_with_callback(self.openbrowser)
338 339 340 commands = None 341
342 -def main():
343 """Main command runner. Manages the primary command line arguments.""" 344 # add commands defined by entrypoints 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 # if command is not found display help 380 if not args or args[0] not in commands: 381 _help() 382 sys.exit() 383 384 commandname = args[0] 385 # strip command and any global options from the sys.argv 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