[Python-modules-commits] r1111 - in /packages/sqlobject: ./
branches/ branches/upstream/
branches/upstream/current/ branches/upstream/current/scripts/
branches/upstream/current/sqlobject/
branches/upstream/current/sqlobject/manager/ tags/
kov at users.alioth.debian.org
kov at users.alioth.debian.org
Tue Jul 4 00:01:55 UTC 2006
Author: kov
Date: Tue Jul 4 00:01:52 2006
New Revision: 1111
URL: http://svn.debian.org/wsvn/python-modules/?sc=1&rev=1111
Log:
[svn-inject] Installing original source of sqlobject
Added:
packages/sqlobject/
packages/sqlobject/branches/
packages/sqlobject/branches/upstream/
packages/sqlobject/branches/upstream/current/
packages/sqlobject/branches/upstream/current/scripts/
packages/sqlobject/branches/upstream/current/scripts/sqlobject-admin (with props)
packages/sqlobject/branches/upstream/current/setup.py
packages/sqlobject/branches/upstream/current/sqlobject/
packages/sqlobject/branches/upstream/current/sqlobject/manager/
packages/sqlobject/branches/upstream/current/sqlobject/manager/command.py (with props)
packages/sqlobject/tags/
Added: packages/sqlobject/branches/upstream/current/scripts/sqlobject-admin
URL: http://svn.debian.org/wsvn/python-modules/packages/sqlobject/branches/upstream/current/scripts/sqlobject-admin?rev=1111&op=file
==============================================================================
--- packages/sqlobject/branches/upstream/current/scripts/sqlobject-admin (added)
+++ packages/sqlobject/branches/upstream/current/scripts/sqlobject-admin Tue Jul 4 00:01:52 2006
@@ -1,0 +1,35 @@
+#!/usr/bin/env python
+import sys
+import os
+
+try:
+ import pkg_resources
+ pkg_resources.require('SQLObject>0.6.1')
+except (ImportError, pkg_resources.DistributionNotFound):
+ # Oh well, we tried...
+ pass
+
+try:
+ import sqlobject.manager
+except ImportError:
+ try:
+ here = __file__
+ except NameError:
+ here = sys.argv[0]
+ updir = os.path.join(
+ os.path.dirname(os.path.dirname(os.path.abspath(here))),
+ 'sqlobject')
+ if os.path.exists(updir):
+ sys.path.insert(0, os.path.dirname(updir))
+ else:
+ print 'I cannot find the sqlobject module'
+ print 'If SQLObject is installed, you may need to set $PYTHONPATH'
+ sys.exit(3)
+ # Now we have to get rid of possibly stale modules from that import
+ # up there
+ for name, value in sys.modules.items():
+ if name.startswith('sqlobject'):
+ del sys.modules[name]
+
+from sqlobject.manager import command
+command.the_runner.run(sys.argv)
Propchange: packages/sqlobject/branches/upstream/current/scripts/sqlobject-admin
------------------------------------------------------------------------------
svn:executable =
Added: packages/sqlobject/branches/upstream/current/setup.py
URL: http://svn.debian.org/wsvn/python-modules/packages/sqlobject/branches/upstream/current/setup.py?rev=1111&op=file
==============================================================================
--- packages/sqlobject/branches/upstream/current/setup.py (added)
+++ packages/sqlobject/branches/upstream/current/setup.py Tue Jul 4 00:01:52 2006
@@ -1,0 +1,108 @@
+# ez_setup doesn't work with Python 2.2, so we use distutils
+# in that case:
+try:
+ from ez_setup import use_setuptools
+ use_setuptools()
+ from setuptools import setup
+except ImportError:
+ from distutils.core import setup
+
+subpackages = ['firebird', 'inheritance', 'mysql', 'postgres',
+ 'sqlite', 'sybase', 'maxdb', 'util', 'manager']
+
+import sys
+# patch distutils if it can't cope with the "classifiers" keyword
+if sys.version < '2.2.3':
+ from distutils.dist import DistributionMetadata
+ DistributionMetadata.classifiers = None
+ DistributionMetadata.download_url = None
+
+setup(name="SQLObject",
+ version="0.7.0",
+ description="Object-Relational Manager, aka database wrapper",
+ long_description="""\
+SQLObject is a popular *Object Relational Manager* for providing an
+object interface to your database, with tables as classes, rows as
+instances, and columns as attributes.
+
+SQLObject includes a Python-object-based query language that makes SQL
+more abstract, and provides substantial database independence for
+applications.
+
+Supports MySQL, PostgreSQL, SQLite, Firebird, Sybase, and MaxDB
+(SAPDB).
+
+For development see the `subversion repository
+<http://svn.colorstudy.com/SQLObject/trunk#egg=SQLObject-0.8dev>`_
+""",
+ classifiers=[
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
+ "Programming Language :: Python",
+ "Topic :: Database",
+ "Topic :: Database :: Front-Ends",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ ],
+ author="Ian Bicking",
+ author_email="ianb at colorstudy.com",
+ url="http://sqlobject.org",
+ license="LGPL",
+ packages=["sqlobject"] + ['sqlobject.%s' % package for package in subpackages],
+ scripts=["scripts/sqlobject-admin"],
+ install_requires=["FormEncode>=0.2.2"],
+ extras_require={
+ 'postgresql': ['psycopg'],
+ 'mysql': ['MySQLdb'],
+ 'sqlite': ['pysqlite'],
+ # Others?
+ },
+ )
+
+# Send announce to:
+# sqlobject-discuss at lists.sourceforge.net
+# db-sig at python.org
+# python-announce at python.org
+# python-list at python.org
+
+# Email tempate:
+"""
+@@ INTRO
+
+What is SQLObject
+=================
+
+SQLObject is an object-relational mapper. Your database tables are described as classes, and rows are instances of those classes. SQLObject is meant to be easy to use and quick to get started with.
+
+SQLObject supports a number of backends: MySQL, PostgreSQL, SQLite, and Firebird. It also has newly added support for Sybase and MaxDB (also known as SAPDB).
+
+
+Where is SQLObject
+==================
+
+Site:
+http://sqlobject.org
+
+Mailing list:
+https://lists.sourceforge.net/mailman/listinfo/sqlobject-discuss
+
+Archives:
+http://news.gmane.org/gmane.comp.python.sqlobject
+
+Download:
+http://prdownloads.sourceforge.net/sqlobject/SQLObject-@@.tar.gz?download
+
+News and changes:
+http://sqlobject.org/docs/News.html
+
+
+What's New
+==========
+
+@@ CHANGES
+
+For a more complete list, please see the news: http://sqlobject.org/docs/News.html
+
+--
+Ian Bicking / ianb at colorstudy.com / http://blog.ianbicking.org
+"""
Added: packages/sqlobject/branches/upstream/current/sqlobject/manager/command.py
URL: http://svn.debian.org/wsvn/python-modules/packages/sqlobject/branches/upstream/current/sqlobject/manager/command.py?rev=1111&op=file
==============================================================================
--- packages/sqlobject/branches/upstream/current/sqlobject/manager/command.py (added)
+++ packages/sqlobject/branches/upstream/current/sqlobject/manager/command.py Tue Jul 4 00:01:52 2006
@@ -1,0 +1,1153 @@
+#!/usr/bin/env python
+import optparse
+import fnmatch
+import re
+import os
+import sys
+import textwrap
+import warnings
+try:
+ from paste import pyconfig
+ from paste import CONFIG
+except ImportError, e:
+ pyconfig = None
+ CONFIG = {}
+import time
+
+import sqlobject
+from sqlobject import col
+from sqlobject.util import moduleloader
+from sqlobject.declarative import DeclarativeMeta
+
+# It's not very unsafe to use tempnam like we are doing:
+warnings.filterwarnings(
+ 'ignore', 'tempnam is a potential security risk.*',
+ RuntimeWarning, '.*command', 28)
+
+def nowarning_tempnam(*args, **kw):
+ return os.tempnam(*args, **kw)
+
+class SQLObjectVersionTable(sqlobject.SQLObject):
+ """
+ This table is used to store information about the database and
+ its version (used with record and update commands).
+ """
+ class sqlmeta:
+ table = 'sqlobject_db_version'
+ version = col.StringCol()
+ updated = col.DateTimeCol(default=col.DateTimeCol.now)
+
+def db_differences(soClass, conn):
+ """
+ Returns the differences between a class and the table in a
+ connection. Returns [] if no differences are found. This
+ function does the best it can; it can miss many differences.
+ """
+ # @@: Repeats a lot from CommandStatus.command, but it's hard
+ # to actually factor out the display logic. Or I'm too lazy
+ # to do so.
+ diffs = []
+ if not conn.tableExists(soClass.sqlmeta.table):
+ if soClass.sqlmeta.columns:
+ diffs.append('Does not exist in database')
+ else:
+ try:
+ columns = conn.columnsFromSchema(soClass.sqlmeta.table,
+ soClass)
+ except AttributeError:
+ # Database does not support reading columns
+ pass
+ else:
+ existing = {}
+ for col in columns:
+ col = col.withClass(soClass)
+ existing[col.dbName] = col
+ missing = {}
+ for col in soClass.sqlmeta.columnList:
+ if existing.has_key(col.dbName):
+ del existing[col.dbName]
+ else:
+ missing[col.dbName] = col
+ for col in existing.values():
+ diffs.append('Database has extra column: %s'
+ % col.dbName)
+ for col in missing.values():
+ diffs.append('Database missing column: %s' % col.dbName)
+ return diffs
+
+class CommandRunner(object):
+
+ def __init__(self):
+ self.commands = {}
+ self.command_aliases = {}
+
+ def run(self, argv):
+ invoked_as = argv[0]
+ args = argv[1:]
+ for i in range(len(args)):
+ if not args[i].startswith('-'):
+ # this must be a command
+ command = args[i].lower()
+ del args[i]
+ break
+ else:
+ # no command found
+ self.invalid('No COMMAND given (try "%s help")'
+ % os.path.basename(invoked_as))
+ real_command = self.command_aliases.get(command, command)
+ if real_command not in self.commands.keys():
+ self.invalid('COMMAND %s unknown' % command)
+ runner = self.commands[real_command](
+ invoked_as, command, args, self)
+ runner.run()
+
+ def register(self, command):
+ name = command.name
+ self.commands[name] = command
+ for alias in command.aliases:
+ self.command_aliases[alias] = name
+
+ def invalid(self, msg, code=2):
+ print msg
+ sys.exit(code)
+
+the_runner = CommandRunner()
+register = the_runner.register
+
+def standard_parser(connection=True, simulate=True,
+ interactive=False, find_modules=True):
+ parser = optparse.OptionParser()
+ parser.add_option('-v', '--verbose',
+ help='Be verbose (multiple times for more verbosity)',
+ action='count',
+ dest='verbose',
+ default=0)
+ if simulate:
+ parser.add_option('-n', '--simulate',
+ help="Don't actually do anything (implies -v)",
+ action='store_true',
+ dest='simulate')
+ if connection:
+ parser.add_option('-c', '--connection',
+ help="The database connection URI",
+ metavar='URI',
+ dest='connection_uri')
+ parser.add_option('-f', '--config-file',
+ help="The Paste config file that contains the database URI (in the database key)",
+ metavar="FILE",
+ dest="config_file")
+ if find_modules:
+ parser.add_option('-m', '--module',
+ help="Module in which to find SQLObject classes",
+ action='append',
+ metavar='MODULE',
+ dest='modules',
+ default=[])
+ parser.add_option('-p', '--package',
+ help="Package to search for SQLObject classes",
+ action="append",
+ metavar="PACKAGE",
+ dest="packages",
+ default=[])
+ parser.add_option('--class',
+ help="Select only named classes (wildcards allowed)",
+ action="append",
+ metavar="NAME",
+ dest="class_matchers",
+ default=[])
+ if interactive:
+ parser.add_option('-i', '--interactive',
+ help="Ask before doing anything (use twice to be more careful)",
+ action="count",
+ dest="interactive",
+ default=0)
+ parser.add_option('--egg',
+ help="Select modules from the given Egg, using sqlobject.txt",
+ action="append",
+ metavar="EGG_SPEC",
+ dest="eggs",
+ default=[])
+ return parser
+
+class Command(object):
+
+ __metaclass__ = DeclarativeMeta
+
+ min_args = 0
+ min_args_error = 'You must provide at least %(min_args)s arguments'
+ max_args = 0
+ max_args_error = 'You must provide no more than %(max_args)s arguments'
+ aliases = ()
+ required_args = []
+ description = None
+
+ help = ''
+
+ def __classinit__(cls, new_args):
+ if cls.__bases__ == (object,):
+ # This abstract base class
+ return
+ register(cls)
+
+ def __init__(self, invoked_as, command_name, args, runner):
+ self.invoked_as = invoked_as
+ self.command_name = command_name
+ self.raw_args = args
+ self.runner = runner
+
+ def run(self):
+ self.parser.usage = "%%prog [options]\n%s" % self.summary
+ if self.help:
+ help = textwrap.fill(
+ self.help, int(os.environ.get('COLUMNS', 80))-4)
+ self.parser.usage += '\n' + help
+ self.parser.prog = '%s %s' % (
+ os.path.basename(self.invoked_as),
+ self.command_name)
+ if self.description:
+ self.parser.description = description
+ self.options, self.args = self.parser.parse_args(self.raw_args)
+ if (getattr(self.options, 'simulate', False)
+ and not self.options.verbose):
+ self.options.verbose = 1
+ if self.min_args is not None and len(self.args) < self.min_args:
+ self.runner.invalid(
+ self.min_args_error % {'min_args': self.min_args,
+ 'actual_args': len(self.args)})
+ if self.max_args is not None and len(self.args) > self.max_args:
+ self.runner.invalid(
+ self.max_args_error % {'max_args': self.max_args,
+ 'actual_args': len(self.args)})
+ for var_name, option_name in self.required_args:
+ if not getattr(self.options, var_name, None):
+ self.runner.invalid(
+ 'You must provide the option %s' % option_name)
+ conf = self.config()
+ if conf and conf.get('sys_path'):
+ update_sys_path(conf['sys_path'], self.options.verbose)
+ if conf and conf.get('database'):
+ conn = sqlobject.connectionForURI(conf['database'])
+ sqlobject.sqlhub.processConnection = conn
+ for egg_spec in getattr(self.options, 'eggs', []):
+ self.load_options_from_egg(egg_spec)
+ self.command()
+
+ def classes(self, require_connection=True,
+ require_some=False):
+ all = []
+ conf = self.config()
+ for module_name in self.options.modules:
+ all.extend(self.classes_from_module(
+ moduleloader.load_module(module_name)))
+ for package_name in self.options.packages:
+ all.extend(self.classes_from_package(package_name))
+ for egg_spec in self.options.eggs:
+ all.extend(self.classes_from_egg(egg_spec))
+ if self.options.class_matchers:
+ filtered = []
+ for soClass in all:
+ name = soClass.__name__
+ for matcher in self.options.class_matchers:
+ if fnmatch.fnmatch(name, matcher):
+ filtered.append(soClass)
+ break
+ all = filtered
+ conn = self.connection()
+ if conn:
+ for soClass in all:
+ soClass._connection = conn
+ else:
+ missing = []
+ for soClass in all:
+ try:
+ if not soClass._connection:
+ missing.append(soClass)
+ except AttributeError:
+ missing.append(soClass)
+ if missing and require_connection:
+ self.runner.invalid(
+ 'These classes do not have connections set:\n * %s\n'
+ 'You must indicate --connection=URI'
+ % '\n * '.join([soClass.__name__
+ for soClass in missing]))
+ if require_some and not all:
+ print 'No classes found!'
+ if self.options.modules:
+ print 'Looked in modules: %s' % ', '.join(self.options.modules)
+ else:
+ print 'No modules specified'
+ if self.options.packages:
+ print 'Looked in packages: %s' % ', '.join(self.options.packages)
+ else:
+ print 'No packages specified'
+ if self.options.class_matchers:
+ print 'Matching class pattern: %s' % self.options.class_matches
+ if self.options.eggs:
+ print 'Looked in eggs: %s' % ', '.join(self.options.eggs)
+ else:
+ print 'No eggs specified'
+ sys.exit(1)
+ return all
+
+ def classes_from_module(self, module):
+ all = []
+ if hasattr(module, 'soClasses'):
+ for name_or_class in module.soClasses:
+ if isinstance(name_or_class, str):
+ name_or_class = getattr(module, name_or_class)
+ all.append(name_or_class)
+ else:
+ for name in dir(module):
+ value = getattr(module, name)
+ if (isinstance(value, type)
+ and issubclass(value, sqlobject.SQLObject)
+ and value.__module__ == module.__name__):
+ all.append(value)
+ return all
+
+ def connection(self):
+ config = self.config()
+ if config is not None:
+ assert config.get('database'), (
+ "No database variable found in config file %s"
+ % self.options.config_file)
+ return sqlobject.connectionForURI(config['database'])
+ elif getattr(self.options, 'connection_uri', None):
+ return sqlobject.connectionForURI(self.options.connection_uri)
+ else:
+ return None
+
+ def config(self):
+ if not getattr(self.options, 'config_file', None):
+ return None
+ if pyconfig and self.options.config_fn.endswith('.conf'):
+ config = pyconfig.Config(with_default=True)
+ config.load(self.options.config_file)
+ CONFIG.push_process_config(config)
+ return config
+ else:
+ return self.ini_config(self.options.config_file)
+
+ def ini_config(self, conf_fn):
+ conf_section = 'main'
+ if '#' in conf_fn:
+ conf_fn, conf_section = conf_fn.split('#', 1)
+
+ from ConfigParser import ConfigParser
+ p = ConfigParser()
+ # Case-sensitive:
+ p.optionxform = str
+ if not os.path.exists(conf_fn):
+ # Stupid RawConfigParser doesn't give an error for
+ # non-existant files:
+ raise OSError(
+ "Config file %s does not exist" % self.options.config_file)
+ p.read([conf_fn])
+ p._defaults.setdefault(
+ 'here', os.path.dirname(os.path.abspath(conf_fn)))
+
+ possible_sections = []
+ for section in p.sections():
+ name = section.strip().lower()
+ if (conf_section == name or
+ (conf_section == name.split(':')[-1]
+ and name.split(':')[0] in ('app', 'application'))):
+ possible_sections.append(section)
+
+ if not possible_sections:
+ raise OSError(
+ "Config file %s does not have a section [%s] or [*:%s]"
+ % (conf_fn, conf_section, conf_section))
+ if len(possible_sections) > 1:
+ raise OSError(
+ "Config file %s has multiple sections matching %s: %s"
+ % (conf_fn, conf_section, ', '.join(possible_sections)))
+
+ config = {}
+ for op in p.options(possible_sections[0]):
+ config[op] = p.get(possible_sections[0], op)
+ return config
+
+ def classes_from_package(self, package_name):
+ all = []
+ package = moduleloader.load_module(package_name)
+ package_dir = os.path.dirname(package.__file__)
+
+ def find_classes_in_file(arg, dir_name, filenames):
+ if dir_name.startswith('.svn'):
+ return
+ filenames = filter(lambda fname: fname.endswith('.py') and fname != '__init__.py',
+ filenames)
+ for fname in filenames:
+ module_name = os.path.join(dir_name, fname)
+ module_name = module_name[module_name.find(package_name):]
+ module_name = module_name.replace(os.path.sep,'.')[:-3]
+ try:
+ module = moduleloader.load_module(module_name)
+ except ImportError, err:
+ if self.options.verbose:
+ print 'Could not import module "%s". Error was : "%s"' % (module_name, err)
+ continue
+ except Exception, exc:
+ if self.options.verbose:
+ print 'Unknown exception while processing module "%s" : "%s"' % (module_name, exc)
+ continue
+ classes = self.classes_from_module(module)
+ all.extend(classes)
+
+ os.path.walk(package_dir, find_classes_in_file, None)
+ return all
+
+ def classes_from_egg(self, egg_spec):
+ modules = []
+ dist, conf = self.config_from_egg(egg_spec, warn_no_sqlobject=True)
+ for mod in conf.get('db_module', '').split(','):
+ mod = mod.strip()
+ if not mod:
+ continue
+ if self.options.verbose:
+ print 'Looking in module %s' % mod
+ modules.extend(self.classes_from_module(
+ moduleloader.load_module(mod)))
+ return modules
+
+ def load_options_from_egg(self, egg_spec):
+ dist, conf = self.config_from_egg(egg_spec)
+ if (hasattr(self.options, 'output_dir')
+ and not self.options.output_dir
+ and conf.get('history_dir')):
+ dir = conf['history_dir']
+ dir = dir.replace('$base', dist.location)
+ self.options.output_dir = dir
+
+ def config_from_egg(self, egg_spec, warn_no_sqlobject=True):
+ import pkg_resources
+ pkg_resources.require(egg_spec)
+ dist = pkg_resources.working_set.find(pkg_resources.Requirement(egg_spec))
+ if not dist.has_metadata('sqlobject.txt'):
+ if warn_no_sqlobject:
+ print 'No sqlobject.txt in %s egg info' % egg_spec
+ return {}
+ result = {}
+ for line in dist.get_metadata_lines('sqlobject.txt'):
+ line = line.strip()
+ if not line or line.startswith('#'):
+ continue
+ name, value = line.split('=', 1)
+ name = name.strip().lower()
+ if name in result:
+ print 'Warning: %s appears more than one in sqlobject.txt' % name
+ result[name.strip().lower()] = value.strip()
+ return dist, result
+
+ def command(self):
+ raise NotImplementedError
+
+ def _get_prog_name(self):
+ return os.path.basename(self.invoked_as)
+ prog_name = property(_get_prog_name)
+
+ def ask(self, prompt, safe=False, default=True):
+ if self.options.interactive >= 2:
+ default = safe
+ if default:
+ prompt += ' [Y/n]? '
+ else:
+ prompt += ' [y/N]? '
+ while 1:
+ response = raw_input(prompt).strip()
+ if not response.strip():
+ return default
+ if response and response[0].lower() in ('y', 'n'):
+ return response[0].lower() == 'y'
+ print 'Y or N please'
+
+ def shorten_filename(self, fn):
+ """
+ Shortens a filename to make it relative to the current
+ directory (if it can). For display purposes.
+ """
+ if fn.startswith(os.getcwd() + '/'):
+ fn = fn[len(os.getcwd())+1:]
+ return fn
+
+ def open_editor(self, pretext, breaker=None, extension='.txt'):
+ """
+ Open an editor with the given text. Return the new text,
+ or None if no edits were made. If given, everything after
+ `breaker` will be ignored.
+ """
+ fn = nowarning_tempnam() + extension
+ f = open(fn, 'w')
+ f.write(pretext)
+ f.close()
+ print '$EDITOR %s' % fn
+ os.system('$EDITOR %s' % fn)
+ f = open(fn, 'r')
+ content = f.read()
+ f.close()
+ if breaker:
+ content = content.split(breaker)[0]
+ pretext = pretext.split(breaker)[0]
+ if content == pretext or not content.strip():
+ return None
+ return content
+
+class CommandSQL(Command):
+
+ name = 'sql'
+ summary = 'Show SQL CREATE statements'
+
+ parser = standard_parser(simulate=False)
+
+ def command(self):
+ classes = self.classes()
+ for cls in classes:
+ if self.options.verbose >= 1:
+ print '-- %s from %s' % (
+ cls.__name__, cls.__module__)
+ print cls.createTableSQL().strip() + ';\n'
+
+class CommandList(Command):
+
+ name = 'list'
+ summary = 'Show all SQLObject classes found'
+
+ parser = standard_parser(simulate=False, connection=False)
+
+ def command(self):
+ if self.options.verbose >= 1:
+ print 'Classes found:'
+ classes = self.classes(require_connection=False)
+ for soClass in classes:
+ print '%s.%s' % (soClass.__module__, soClass.__name__)
+ if self.options.verbose >= 1:
+ print ' Table: %s' % soClass.sqlmeta.table
+
+class CommandCreate(Command):
+
+ name = 'create'
+ summary = 'Create tables'
+
+ parser = standard_parser(interactive=True)
+ parser.add_option('--create-db',
+ action='store_true',
+ dest='create_db',
+ help="Create the database")
+
+ def command(self):
+ v = self.options.verbose
+ created = 0
+ existing = 0
+ dbs_created = []
+ for soClass in self.classes(require_some=True):
+ if (self.options.create_db
+ and soClass._connection not in dbs_created):
+ if not self.options.simulate:
+ soClass._connection.createEmptyDatabase()
+ else:
+ print '(simulating; cannot create database)'
+ dbs_created.append(soClass._connection)
+ exists = soClass._connection.tableExists(soClass.sqlmeta.table)
+ if v >= 1:
+ if exists:
+ existing += 1
+ print '%s already exists.' % soClass.__name__
+ else:
+ print 'Creating %s' % soClass.__name__
+ if v >= 2:
+ print soClass.createTableSQL()
+ if (not self.options.simulate
+ and not exists):
+ if self.options.interactive:
+ if self.ask('Create %s' % soClass.__name__):
+ created += 1
+ soClass.createTable()
+ else:
+ print 'Cancelled'
+ else:
+ created += 1
+ soClass.createTable()
+ if v >= 1:
+ print '%i tables created (%i already exist)' % (
+ created, existing)
+
+
+class CommandDrop(Command):
+
+ name = 'drop'
+ summary = 'Drop tables'
+
+ parser = standard_parser(interactive=True)
+
+ def command(self):
+ v = self.options.verbose
+ dropped = 0
+ not_existing = 0
+ for soClass in self.classes():
+ exists = soClass._connection.tableExists(soClass.sqlmeta.table)
+ if v >= 1:
+ if exists:
+ print 'Dropping %s' % soClass.__name__
+ else:
+ not_existing += 1
+ print '%s does not exist.' % soClass.__name__
+ if (not self.options.simulate
+ and exists):
+ if self.options.interactive:
+ if self.ask('Drop %s' % soClass.__name__):
+ dropped += 1
+ soClass.dropTable()
+ else:
+ print 'Cancelled'
+ else:
+ dropped += 1
+ soClass.dropTable()
+ if v >= 1:
+ print '%i tables dropped (%i didn\'t exist)' % (
+ dropped, not_existing)
+
+class CommandStatus(Command):
+
+ name = 'status'
+ summary = 'Show status of classes vs. database'
+ help = ('This command checks the SQLObject definition and checks if '
+ 'the tables in the database match. It can always test for '
+ 'missing tables, and on some databases can test for the '
+ 'existance of other tables. Column types are not currently '
+ 'checked.')
+
+ parser = standard_parser(simulate=False)
+
+ def print_class(self, soClass):
+ if self.printed:
+ return
+ self.printed = True
+ print 'Checking %s...' % soClass.__name__
+
+ def command(self):
+ good = 0
+ bad = 0
+ missing_tables = 0
+ columnsFromSchema_warning = False
+ for soClass in self.classes(require_some=True):
+ conn = soClass._connection
+ self.printed = False
+ if self.options.verbose:
+ self.print_class(soClass)
+ if not conn.tableExists(soClass.sqlmeta.table):
+ self.print_class(soClass)
+ print ' Does not exist in database'
+ missing_tables += 1
+ continue
+ try:
+ columns = conn.columnsFromSchema(soClass.sqlmeta.table,
+ soClass)
+ except AttributeError:
+ if not columnsFromSchema_warning:
+ print 'Database does not support reading columns'
+ columnsFromSchema_warning = True
+ good += 1
+ continue
+ except AssertionError, e:
+ print 'Cannot read db table %s: %s' % (
+ soClass.sqlmeta.table, e)
+ continue
+ existing = {}
+ for col in columns:
+ col = col.withClass(soClass)
+ existing[col.dbName] = col
+ missing = {}
+ for col in soClass.sqlmeta.columnList:
+ if existing.has_key(col.dbName):
+ del existing[col.dbName]
+ else:
+ missing[col.dbName] = col
+ if existing:
+ self.print_class(soClass)
+ for col in existing.values():
+ print ' Database has extra column: %s' % col.dbName
+ if missing:
+ self.print_class(soClass)
+ for col in missing.values():
+ print ' Database missing column: %s' % col.dbName
+ if existing or missing:
+ bad += 1
+ else:
+ good += 1
+ if self.options.verbose:
+ print '%i in sync; %i out of sync; %i not in database' % (
+ good, bad, missing_tables)
+
+class CommandHelp(Command):
+
+ name = 'help'
+ summary = 'Show help'
+
+ parser = optparse.OptionParser()
+
+ max_args = 1
+
+ def command(self):
+ if self.args:
+ the_runner.run([self.invoked_as, self.args[0], '-h'])
+ else:
+ print 'Available commands:'
+ print ' (use "%s help COMMAND" or "%s COMMAND -h" ' % (
+ self.prog_name, self.prog_name)
+ print ' for more information)'
+ items = the_runner.commands.items()
+ items.sort()
+ max_len = max([len(cn) for cn, c in items])
+ for command_name, command in items:
+ print '%s:%s %s' % (command_name,
+ ' '*(max_len-len(command_name)),
+ command.summary)
+ if command.aliases:
+ print '%s (Aliases: %s)' % (
+ ' '*max_len, ', '.join(command.aliases))
+
+class CommandExecute(Command):
+
+ name = 'execute'
+ summary = 'Execute SQL statements'
+ help = ('Runs SQL statements directly in the database, with no '
+ 'intervention. Useful when used with a configuration file. '
+ 'Each argument is executed as an individual statement.')
+
+ parser = standard_parser(find_modules=False)
+ parser.add_option('--stdin',
+ help="Read SQL from stdin (normally takes SQL from the command line)",
+ dest="use_stdin",
+ action="store_true")
+
+ max_args = None
+
+ def command(self):
+ args = self.args
+ if self.options.use_stdin:
+ if self.options.verbose:
+ print "Reading additional SQL from stdin (Ctrl-D or Ctrl-Z to finish)..."
+ args.append(sys.stdin.read())
+ self.conn = self.connection().getConnection()
+ self.cursor = self.conn.cursor()
+ for sql in args:
+ self.execute_sql(sql)
+
+ def execute_sql(self, sql):
+ if self.options.verbose:
+ print sql
+ try:
+ self.cursor.execute(sql)
+ except Exception, e:
+ if not self.options.verbose:
+ print sql
+ print "****Error:"
+ print ' ', e
+ return
+ desc = self.cursor.description
+ rows = self.cursor.fetchall()
+ if self.options.verbose:
+ if not self.cursor.rowcount:
+ print "No rows accessed"
+ else:
+ print "%i rows accessed" % self.cursor.rowcount
+ if desc:
+ for name, type_code, display_size, internal_size, precision, scale, null_ok in desc:
+ sys.stdout.write("%s\t" % name)
+ sys.stdout.write("\n")
+ for row in rows:
+ for col in row:
+ sys.stdout.write("%r\t" % col)
+ sys.stdout.write("\n")
+ print
+
+class CommandRecord(Command):
+
+ name = 'record'
+ summary = 'Record historical information about the database status'
+ help = ('Record state of table definitions. The state of each '
+ 'table is written out to a separate file in a directory, '
+ 'and that directory forms a "version". A table is also '
+ 'added to you datebase (%s) that reflects the version the '
+ 'database is currently at. Use the upgrade command to '
+ 'sync databases with code.'
+ % SQLObjectVersionTable.sqlmeta.table)
+
+ parser = standard_parser()
+ parser.add_option('--output-dir',
+ help="Base directory for recorded definitions",
+ dest="output_dir",
+ metavar="DIR",
+ default=None)
+ parser.add_option('--no-db-record',
+ help="Don't record version to database",
+ dest="db_record",
+ action="store_false",
+ default=True)
+ parser.add_option('--force-create',
+ help="Create a new version even if appears to be "
+ "identical to the last version",
+ action="store_true",
+ dest="force_create")
+ parser.add_option('--name',
+ help="The name to append to the version. The "
+ "version should sort after previous versions (so "
+ "any versions from the same day should come "
+ "alphabetically before this version).",
+ dest="version_name",
+ metavar="NAME")
+ parser.add_option('--force-db-version',
+ help="Update the database version, and include no "
+ "database information. This is for databases that "
+ "were developed without any interaction with "
+ "this tool, to create a 'beginning' revision.",
+ metavar="VERSION_NAME",
+ dest="force_db_version")
+ parser.add_option('--edit',
+ help="Open an editor for the upgrader in the last "
+ "version (using $EDITOR).",
+ action="store_true",
+ dest="open_editor")
+
+ version_regex = re.compile(r'^\d\d\d\d-\d\d-\d\d')
+
+ def command(self):
+ if self.options.force_db_version:
+ self.command_force_db_version()
+ return
+
+ v = self.options.verbose
+ sim = self.options.simulate
+ classes = self.classes()
+ if not classes:
+ print "No classes found!"
+ return
+
+ output_dir = self.find_output_dir()
+ version = os.path.basename(output_dir)
+ print "Creating version %s" % version
+ conns = []
+ files = {}
+ for cls in self.classes():
+ dbName = cls._connection.dbName
+ if cls._connection not in conns:
+ conns.append(cls._connection)
+ fn = os.path.join(cls.__name__
+ + '_' + dbName + '.sql')
+ if sim:
+ continue
+ files[fn] = ''.join([
+ '-- Exported definition from %s\n'
+ % time.strftime('%Y-%m-%dT%H:%M:%S'),
+ '-- Class %s.%s\n'
+ % (cls.__module__, cls.__name__),
+ '-- Database: %s\n'
+ % dbName,
+ cls.createTableSQL().strip(),
+ '\n'])
+ last_version_dir = self.find_last_version()
+ if last_version_dir and not self.options.force_create:
+ if v > 1:
+ print "Checking %s to see if it is current" % last_version_dir
+ files_copy = files.copy()
+ for fn in os.listdir(last_version_dir):
+ if not fn.endswith('.sql'):
+ continue
+ if not files_copy.has_key(fn):
+ if v > 1:
+ print "Missing file %s" % fn
+ break
+ f = open(os.path.join(last_version_dir, fn), 'r')
+ content = f.read()
+ f.close()
+ if (self.strip_comments(files_copy[fn])
+ != self.strip_comments(content)):
+ if v > 1:
+ print "Content does not match: %s" % fn
+ break
+ del files_copy[fn]
+ else:
+ # No differences so far
+ if not files_copy:
+ # Used up all files
+ print ("Current status matches version %s"
+ % os.path.basename(last_version_dir))
+ return
+ if v > 1:
+ print "Extra files: %s" % ', '.join(files_copy.keys())
+ if v:
+ print ("Current state does not match %s"
+ % os.path.basename(last_version_dir))
+ if v > 1 and not last_version_dir:
+ print "No last version to check"
+ if not sim:
+ os.mkdir(output_dir)
+ if v:
+ print 'Making directory %s' % self.shorten_filename(output_dir)
+ files = files.items()
+ files.sort()
+ for fn, content in files:
+ if v:
+ print ' Writing %s' % self.shorten_filename(fn)
+ if not sim:
+ f = open(os.path.join(output_dir, fn), 'w')
+ f.write(content)
+ f.close()
+ all_diffs = []
+ for cls in self.classes():
+ for conn in conns:
+ diffs = db_differences(cls, conn)
+ for diff in diffs:
+ if len(conns) > 1:
+ diff = ' (%s).%s: %s' % (
+ conn.uri(), cls.sqlmeta.table, diff)
+ else:
+ diff = ' %s: %s' % (cls.sqlmeta.table, diff)
+ all_diffs.append(diff)
+ if all_diffs:
+ print 'Database does not match schema:'
+ print '\n'.join(all_diffs)
+ if self.options.db_record:
+ print '(Not updating database version)'
+ elif self.options.db_record:
+ for conn in conns:
+ self.update_db(version, conn)
+ if self.options.open_editor:
+ if not last_version_dir:
+ print ("Cannot edit upgrader because there is no "
+ "previous version")
+ else:
+ breaker = ('-'*20 + ' lines below this will be ignored '
+ + '-'*20)
+ pre_text = breaker + '\n' + '\n'.join(all_diffs)
+ text = self.open_editor('\n\n' + pre_text, breaker=breaker,
+ extension='.sql')
+ if text is not None:
+ fn = os.path.join(last_version_dir,
+ 'upgrade_%s_%s.sql' %
+ (dbName, version))
+ f = open(fn, 'w')
+ f.write(text)
+ f.close()
+ print 'Wrote to %s' % fn
+
+ def update_db(self, version, conn):
+ v = self.options.verbose
+ if not conn.tableExists(SQLObjectVersionTable.sqlmeta.table):
+ if v:
+ print ('Creating table %s'
+ % SQLObjectVersionTable.sqlmeta.table)
+ sql = SQLObjectVersionTable.createTableSQL(connection=conn)
+ if v > 1:
+ print sql
+ if not self.options.simulate:
+ SQLObjectVersionTable.createTable(connection=conn)
+ if not self.options.simulate:
+ SQLObjectVersionTable.clearTable(connection=conn)
+ SQLObjectVersionTable(
+ version=version,
+ connection=conn)
+
+ def strip_comments(self, sql):
+ lines = [l for l in sql.splitlines()
+ if not l.strip().startswith('--')]
+ return '\n'.join(lines)
+
+ def base_dir(self):
+ base = self.options.output_dir
+ if base is None:
+ base = CONFIG.get('sqlobject_history_dir', '.')
+ if not os.path.exists(base):
+ print 'Creating history directory %s' % self.shorten_filename(base)
+ if not self.options.simulate:
+ os.makedirs(base)
+ return base
+
+ def find_output_dir(self):
+ today = time.strftime('%Y-%m-%d', time.localtime())
+ if self.options.version_name:
+ dir = os.path.join(self.base_dir(), today + '-' +
+ self.options.version_name)
+ if os.path.exists(dir):
+ print ("Error, directory already exists: %s"
+ % dir)
+ sys.exit(1)
+ return dir
+ extra = ''
+ while 1:
+ dir = os.path.join(self.base_dir(), today + extra)
+ if not os.path.exists(dir):
+ return dir
+ if not extra:
+ extra = 'a'
+ else:
+ extra = chr(ord(extra)+1)
+
+ def find_last_version(self):
+ names = []
+ for fn in os.listdir(self.base_dir()):
+ if not self.version_regex.search(fn):
+ continue
+ names.append(fn)
+ if not names:
+ return None
+ names.sort()
+ return os.path.join(self.base_dir(), names[-1])
+
+ def command_force_db_version(self):
+ v = self.options.verbose
+ sim = self.options.simulate
+ version = self.options.force_db_version
+ if not self.version_regex.search(version):
+ print "Versions must be in the format YYYY-MM-DD..."
+ print "You version %s does not fit this" % version
+ return
+ version_dir = os.path.join(self.base_dir(), version)
+ if not os.path.exists(version_dir):
+ if v:
+ print 'Creating %s' % self.shorten_filename(version_dir)
+ if not sim:
+ os.mkdir(version_dir)
+ elif v:
+ print ('Directory %s exists'
+ % self.shorten_filename(version_dir))
+ if self.options.db_record:
+ self.update_db(version, self.connection())
+
+class CommandUpgrade(CommandRecord):
+
+ name = 'upgrade'
+ summary = 'Update the database to a new version (as created by record)'
+ help = ('This command runs scripts (that you write by hand) to '
+ 'upgrade a database. The database\'s current version is in '
+ 'the sqlobject_version table (use record --force-db-version '
+ 'if a database does not have a sqlobject_version table), '
+ 'and upgrade scripts are in the version directory you are '
+ 'upgrading FROM, named upgrade_DBNAME_VERSION.sql, like '
+ '"upgrade_mysql_2004-12-01b.sql".')
+
+ parser = standard_parser(find_modules=False)
+ parser.add_option('--upgrade-to',
+ help="Upgrade to the given version (default: newest version)",
+ dest="upgrade_to",
+ metavar="VERSION")
+ parser.add_option('--output-dir',
+ help="Base directory for recorded definitions",
+ dest="output_dir",
+ metavar="DIR",
+ default=None)
+
+ upgrade_regex = re.compile(r'^upgrade_([a-z]*)_([^.]*)\.sql$', re.I)
+
+ def command(self):
+ v = self.options.verbose
+ sim = self.options.simulate
+ if self.options.upgrade_to:
+ version_to = self.options.upgrade_to
+ else:
+ version_to = os.path.basename(self.find_last_version())
+ current = self.current_version()
+ if v:
+ print 'Current version: %s' % current
+ version_list = self.make_plan(current, version_to)
+ if not version_list:
+ print 'Database up to date'
+ return
+ if v:
+ print 'Plan:'
+ for next_version, upgrader in version_list:
+ print ' Use %s to upgrade to %s' % (
+ self.shorten_filename(upgrader), next_version)
+ conn = self.connection()
+ for next_version, upgrader in version_list:
+ f = open(upgrader)
+ sql = f.read()
+ f.close()
+ if v:
+ print "Running:"
+ print sql
+ print '-'*60
+ if not sim:
+ try:
+ conn.query(sql)
+ except:
+ print "Error in script: %s" % upgrader
+ raise
+ self.update_db(next_version, conn)
+ print 'Done.'
+
+
+ def current_version(self):
+ conn = self.connection()
+ if not conn.tableExists(SQLObjectVersionTable.sqlmeta.table):
+ print 'No sqlobject_version table!'
+ sys.exit(1)
+ versions = list(SQLObjectVersionTable.select(connection=conn))
+ if not versions:
+ print 'No rows in sqlobject_version!'
+ sys.exit(1)
+ if len(versions) > 1:
+ print 'Ambiguous sqlobject_version_table'
+ sys.exit(1)
+ return versions[0].version
+
+ def make_plan(self, current, dest):
+ if current == dest:
+ return []
+ dbname = self.connection().dbName
+ next_version, upgrader = self.best_upgrade(current, dest, dbname)
+ if not upgrader:
+ print 'No way to upgrade from %s to %s' % (current, dest)
+ print ('(you need a %s/upgrade_%s_%s.sql script)'
+ % (current, dbname, dest))
+ sys.exit(1)
+ plan = [(next_version, upgrader)]
+ if next_version == dest:
+ return plan
+ else:
+ return plan + self.make_plan(next_version, dest)
+
+ def best_upgrade(self, current, dest, target_dbname):
+ current_dir = os.path.join(self.base_dir(), current)
+ if self.options.verbose > 1:
+ print ('Looking in %s for upgraders'
+ % self.shorten_filename(current_dir))
+ upgraders = []
+ for fn in os.listdir(current_dir):
+ match = self.upgrade_regex.search(fn)
+ if not match:
+ if self.options.verbose > 1:
+ print 'Not an upgrade script: %s' % fn
+ continue
+ dbname = match.group(1)
+ version = match.group(2)
+ if dbname != target_dbname:
+ if self.options.verbose > 1:
+ print 'Not for this database: %s (want %s)' % (
+ dbname, target_dbname)
+ continue
+ if version > dest:
+ if self.options.verbose > 1:
+ print 'Version too new: %s (only want %s)' % (
+ version, dest)
+ upgraders.append((version, os.path.join(current_dir, fn)))
+ if not upgraders:
+ if self.options.verbose > 1:
+ print 'No upgraders found in %s' % current_dir
+ return None, None
+ upgraders.sort()
+ return upgraders[-1]
+
+def update_sys_path(paths, verbose):
+ if isinstance(paths, (str, unicode)):
+ paths = [paths]
+ for path in paths:
+ path = os.path.abspath(path)
+ if path not in sys.path:
+ if verbose > 1:
+ print 'Adding %s to path' % path
+ sys.path.insert(0, path)
+
+if __name__ == '__main__':
+ the_runner.run(sys.argv)
Propchange: packages/sqlobject/branches/upstream/current/sqlobject/manager/command.py
------------------------------------------------------------------------------
svn:executable =
More information about the Python-modules-commits
mailing list