0001
0002import optparse
0003import fnmatch
0004import re
0005import os
0006import sys
0007import textwrap
0008import warnings
0009try:
0010 from paste import pyconfig
0011 from paste import CONFIG
0012except ImportError, e:
0013 pyconfig = None
0014 CONFIG = {}
0015import time
0016
0017import sqlobject
0018from sqlobject import col
0019from sqlobject.util import moduleloader
0020from sqlobject.declarative import DeclarativeMeta
0021
0022
0023warnings.filterwarnings(
0024 'ignore', 'tempnam is a potential security risk.*',
0025 RuntimeWarning, '.*command', 28)
0026
0027def nowarning_tempnam(*args, **kw):
0028 return os.tempnam(*args, **kw)
0029
0030class SQLObjectVersionTable(sqlobject.SQLObject):
0031 """
0032 This table is used to store information about the database and
0033 its version (used with record and update commands).
0034 """
0035 class sqlmeta:
0036 table = 'sqlobject_db_version'
0037 version = col.StringCol()
0038 updated = col.DateTimeCol(default=col.DateTimeCol.now)
0039
0040def db_differences(soClass, conn):
0041 """
0042 Returns the differences between a class and the table in a
0043 connection. Returns [] if no differences are found. This
0044 function does the best it can; it can miss many differences.
0045 """
0046
0047
0048
0049 diffs = []
0050 if not conn.tableExists(soClass.sqlmeta.table):
0051 if soClass.sqlmeta.columns:
0052 diffs.append('Does not exist in database')
0053 else:
0054 try:
0055 columns = conn.columnsFromSchema(soClass.sqlmeta.table,
0056 soClass)
0057 except AttributeError:
0058
0059 pass
0060 else:
0061 existing = {}
0062 for col in columns:
0063 col = col.withClass(soClass)
0064 existing[col.dbName] = col
0065 missing = {}
0066 for col in soClass.sqlmeta.columnList:
0067 if existing.has_key(col.dbName):
0068 del existing[col.dbName]
0069 else:
0070 missing[col.dbName] = col
0071 for col in existing.values():
0072 diffs.append('Database has extra column: %s'
0073 % col.dbName)
0074 for col in missing.values():
0075 diffs.append('Database missing column: %s' % col.dbName)
0076 return diffs
0077
0078class CommandRunner(object):
0079
0080 def __init__(self):
0081 self.commands = {}
0082 self.command_aliases = {}
0083
0084 def run(self, argv):
0085 invoked_as = argv[0]
0086 args = argv[1:]
0087 for i in range(len(args)):
0088 if not args[i].startswith('-'):
0089
0090 command = args[i].lower()
0091 del args[i]
0092 break
0093 else:
0094
0095 self.invalid('No COMMAND given (try "%s help")'
0096 % os.path.basename(invoked_as))
0097 real_command = self.command_aliases.get(command, command)
0098 if real_command not in self.commands.keys():
0099 self.invalid('COMMAND %s unknown' % command)
0100 runner = self.commands[real_command](
0101 invoked_as, command, args, self)
0102 runner.run()
0103
0104 def register(self, command):
0105 name = command.name
0106 self.commands[name] = command
0107 for alias in command.aliases:
0108 self.command_aliases[alias] = name
0109
0110 def invalid(self, msg, code=2):
0111 print msg
0112 sys.exit(code)
0113
0114the_runner = CommandRunner()
0115register = the_runner.register
0116
0117def standard_parser(connection=True, simulate=True,
0118 interactive=False, find_modules=True):
0119 parser = optparse.OptionParser()
0120 parser.add_option('-v', '--verbose',
0121 help='Be verbose (multiple times for more verbosity)',
0122 action='count',
0123 dest='verbose',
0124 default=0)
0125 if simulate:
0126 parser.add_option('-n', '--simulate',
0127 help="Don't actually do anything (implies -v)",
0128 action='store_true',
0129 dest='simulate')
0130 if connection:
0131 parser.add_option('-c', '--connection',
0132 help="The database connection URI",
0133 metavar='URI',
0134 dest='connection_uri')
0135 parser.add_option('-f', '--config-file',
0136 help="The Paste config file that contains the database URI (in the database key)",
0137 metavar="FILE",
0138 dest="config_file")
0139 if find_modules:
0140 parser.add_option('-m', '--module',
0141 help="Module in which to find SQLObject classes",
0142 action='append',
0143 metavar='MODULE',
0144 dest='modules',
0145 default=[])
0146 parser.add_option('-p', '--package',
0147 help="Package to search for SQLObject classes",
0148 action="append",
0149 metavar="PACKAGE",
0150 dest="packages",
0151 default=[])
0152 parser.add_option('--class',
0153 help="Select only named classes (wildcards allowed)",
0154 action="append",
0155 metavar="NAME",
0156 dest="class_matchers",
0157 default=[])
0158 if interactive:
0159 parser.add_option('-i', '--interactive',
0160 help="Ask before doing anything (use twice to be more careful)",
0161 action="count",
0162 dest="interactive",
0163 default=0)
0164 parser.add_option('--egg',
0165 help="Select modules from the given Egg, using sqlobject.txt",
0166 action="append",
0167 metavar="EGG_SPEC",
0168 dest="eggs",
0169 default=[])
0170 return parser
0171
0172class Command(object):
0173
0174 __metaclass__ = DeclarativeMeta
0175
0176 min_args = 0
0177 min_args_error = 'You must provide at least %(min_args)s arguments'
0178 max_args = 0
0179 max_args_error = 'You must provide no more than %(max_args)s arguments'
0180 aliases = ()
0181 required_args = []
0182 description = None
0183
0184 help = ''
0185
0186 def __classinit__(cls, new_args):
0187 if cls.__bases__ == (object,):
0188
0189 return
0190 register(cls)
0191
0192 def __init__(self, invoked_as, command_name, args, runner):
0193 self.invoked_as = invoked_as
0194 self.command_name = command_name
0195 self.raw_args = args
0196 self.runner = runner
0197
0198 def run(self):
0199 self.parser.usage = "%%prog [options]\n%s" % self.summary
0200 if self.help:
0201 help = textwrap.fill(
0202 self.help, int(os.environ.get('COLUMNS', 80))-4)
0203 self.parser.usage += '\n' + help
0204 self.parser.prog = '%s %s' % (
0205 os.path.basename(self.invoked_as),
0206 self.command_name)
0207 if self.description:
0208 self.parser.description = description
0209 self.options, self.args = self.parser.parse_args(self.raw_args)
0210 if (getattr(self.options, 'simulate', False)
0211 and not self.options.verbose):
0212 self.options.verbose = 1
0213 if self.min_args is not None and len(self.args) < self.min_args:
0214 self.runner.invalid(
0215 self.min_args_error % {'min_args': self.min_args,
0216 'actual_args': len(self.args)})
0217 if self.max_args is not None and len(self.args) > self.max_args:
0218 self.runner.invalid(
0219 self.max_args_error % {'max_args': self.max_args,
0220 'actual_args': len(self.args)})
0221 for var_name, option_name in self.required_args:
0222 if not getattr(self.options, var_name, None):
0223 self.runner.invalid(
0224 'You must provide the option %s' % option_name)
0225 conf = self.config()
0226 if conf and conf.get('sys_path'):
0227 update_sys_path(conf['sys_path'], self.options.verbose)
0228 if conf and conf.get('database'):
0229 conn = sqlobject.connectionForURI(conf['database'])
0230 sqlobject.sqlhub.processConnection = conn
0231 for egg_spec in getattr(self.options, 'eggs', []):
0232 self.load_options_from_egg(egg_spec)
0233 self.command()
0234
0235 def classes(self, require_connection=True,
0236 require_some=False):
0237 all = []
0238 conf = self.config()
0239 for module_name in self.options.modules:
0240 all.extend(self.classes_from_module(
0241 moduleloader.load_module(module_name)))
0242 for package_name in self.options.packages:
0243 all.extend(self.classes_from_package(package_name))
0244 for egg_spec in self.options.eggs:
0245 all.extend(self.classes_from_egg(egg_spec))
0246 if self.options.class_matchers:
0247 filtered = []
0248 for soClass in all:
0249 name = soClass.__name__
0250 for matcher in self.options.class_matchers:
0251 if fnmatch.fnmatch(name, matcher):
0252 filtered.append(soClass)
0253 break
0254 all = filtered
0255 conn = self.connection()
0256 if conn:
0257 for soClass in all:
0258 soClass._connection = conn
0259 else:
0260 missing = []
0261 for soClass in all:
0262 try:
0263 if not soClass._connection:
0264 missing.append(soClass)
0265 except AttributeError:
0266 missing.append(soClass)
0267 if missing and require_connection:
0268 self.runner.invalid(
0269 'These classes do not have connections set:\n * %s\n'
0270 'You must indicate --connection=URI'
0271 % '\n * '.join([soClass.__name__
0272 for soClass in missing]))
0273 if require_some and not all:
0274 print 'No classes found!'
0275 if self.options.modules:
0276 print 'Looked in modules: %s' % ', '.join(self.options.modules)
0277 else:
0278 print 'No modules specified'
0279 if self.options.packages:
0280 print 'Looked in packages: %s' % ', '.join(self.options.packages)
0281 else:
0282 print 'No packages specified'
0283 if self.options.class_matchers:
0284 print 'Matching class pattern: %s' % self.options.class_matches
0285 if self.options.eggs:
0286 print 'Looked in eggs: %s' % ', '.join(self.options.eggs)
0287 else:
0288 print 'No eggs specified'
0289 sys.exit(1)
0290 return all
0291
0292 def classes_from_module(self, module):
0293 all = []
0294 if hasattr(module, 'soClasses'):
0295 for name_or_class in module.soClasses:
0296 if isinstance(name_or_class, str):
0297 name_or_class = getattr(module, name_or_class)
0298 all.append(name_or_class)
0299 else:
0300 for name in dir(module):
0301 value = getattr(module, name)
0302 if (isinstance(value, type)
0303 and issubclass(value, sqlobject.SQLObject)
0304 and value.__module__ == module.__name__):
0305 all.append(value)
0306 return all
0307
0308 def connection(self):
0309 config = self.config()
0310 if config is not None:
0311 assert config.get('database'), (
0312 "No database variable found in config file %s"
0313 % self.options.config_file)
0314 return sqlobject.connectionForURI(config['database'])
0315 elif getattr(self.options, 'connection_uri', None):
0316 return sqlobject.connectionForURI(self.options.connection_uri)
0317 else:
0318 return None
0319
0320 def config(self):
0321 if not getattr(self.options, 'config_file', None):
0322 return None
0323 if pyconfig and self.options.config_fn.endswith('.conf'):
0324 config = pyconfig.Config(with_default=True)
0325 config.load(self.options.config_file)
0326 CONFIG.push_process_config(config)
0327 return config
0328 else:
0329 return self.ini_config(self.options.config_file)
0330
0331 def ini_config(self, conf_fn):
0332 conf_section = 'main'
0333 if '#' in conf_fn:
0334 conf_fn, conf_section = conf_fn.split('#', 1)
0335
0336 from ConfigParser import ConfigParser
0337 p = ConfigParser()
0338
0339 p.optionxform = str
0340 if not os.path.exists(conf_fn):
0341
0342
0343 raise OSError(
0344 "Config file %s does not exist" % self.options.config_file)
0345 p.read([conf_fn])
0346 p._defaults.setdefault(
0347 'here', os.path.dirname(os.path.abspath(conf_fn)))
0348
0349 possible_sections = []
0350 for section in p.sections():
0351 name = section.strip().lower()
0352 if (conf_section == name or
0353 (conf_section == name.split(':')[-1]
0354 and name.split(':')[0] in ('app', 'application'))):
0355 possible_sections.append(section)
0356
0357 if not possible_sections:
0358 raise OSError(
0359 "Config file %s does not have a section [%s] or [*:%s]"
0360 % (conf_fn, conf_section, conf_section))
0361 if len(possible_sections) > 1:
0362 raise OSError(
0363 "Config file %s has multiple sections matching %s: %s"
0364 % (conf_fn, conf_section, ', '.join(possible_sections)))
0365
0366 config = {}
0367 for op in p.options(possible_sections[0]):
0368 config[op] = p.get(possible_sections[0], op)
0369 return config
0370
0371 def classes_from_package(self, package_name):
0372 all = []
0373 package = moduleloader.load_module(package_name)
0374 package_dir = os.path.dirname(package.__file__)
0375
0376 def find_classes_in_file(arg, dir_name, filenames):
0377 if dir_name.startswith('.svn'):
0378 return
0379 filenames = filter(lambda fname: fname.endswith('.py') and fname != '__init__.py',
0380 filenames)
0381 for fname in filenames:
0382 module_name = os.path.join(dir_name, fname)
0383 module_name = module_name[module_name.find(package_name):]
0384 module_name = module_name.replace(os.path.sep,'.')[:-3]
0385 try:
0386 module = moduleloader.load_module(module_name)
0387 except ImportError, err:
0388 if self.options.verbose:
0389 print 'Could not import module "%s". Error was : "%s"' % (module_name, err)
0390 continue
0391 except Exception, exc:
0392 if self.options.verbose:
0393 print 'Unknown exception while processing module "%s" : "%s"' % (module_name, exc)
0394 continue
0395 classes = self.classes_from_module(module)
0396 all.extend(classes)
0397
0398 os.path.walk(package_dir, find_classes_in_file, None)
0399 return all
0400
0401 def classes_from_egg(self, egg_spec):
0402 modules = []
0403 dist, conf = self.config_from_egg(egg_spec, warn_no_sqlobject=True)
0404 for mod in conf.get('db_module', '').split(','):
0405 mod = mod.strip()
0406 if not mod:
0407 continue
0408 if self.options.verbose:
0409 print 'Looking in module %s' % mod
0410 modules.extend(self.classes_from_module(
0411 moduleloader.load_module(mod)))
0412 return modules
0413
0414 def load_options_from_egg(self, egg_spec):
0415 dist, conf = self.config_from_egg(egg_spec)
0416 if (