migrate.versioning PEP-8 improvements, more documentation
- made api.py, cfgparse.py, exceptions.py, genmodel.py, migrate_repository.py, and pathed.py PEP-8 clean - add tools.rst documenting the usage of migrate_repository.py - add more modules to api.rst - reference tools.rst from index.rst
This commit is contained in:
parent
5be16f226f
commit
5289c4df3b
27
docs/api.rst
27
docs/api.rst
@ -84,3 +84,30 @@ Module :mod:`migrate.versioning`
|
|||||||
.. automodule:: migrate.versioning
|
.. automodule:: migrate.versioning
|
||||||
:members:
|
:members:
|
||||||
:synopsis: Database version and repository management
|
:synopsis: Database version and repository management
|
||||||
|
|
||||||
|
Module :mod:`api <migrate.versioning.api>`
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: migrate.versioning.api
|
||||||
|
:synopsis: External API for :mod:`migrate.versioning`
|
||||||
|
|
||||||
|
Module :mod:`exceptions <migrate.versioning.exceptions>`
|
||||||
|
--------------------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: migrate.versioning.exceptions
|
||||||
|
:members:
|
||||||
|
:synopsis: Exception classes for :mod:`migrate.versioning`
|
||||||
|
|
||||||
|
Module :mod:`genmodel <migrate.versioning.genmodel>`
|
||||||
|
----------------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: migrate.versioning.genmodel
|
||||||
|
:members:
|
||||||
|
:synopsis: Python database model generator and differencer
|
||||||
|
|
||||||
|
Module :mod:`pathed <migrate.versioning.pathed>`
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: migrate.versioning.pathed
|
||||||
|
:members:
|
||||||
|
:synopsis: File/Directory handling class
|
||||||
|
@ -28,6 +28,7 @@ versioning API is available as the :command:`migrate` command.
|
|||||||
|
|
||||||
versioning
|
versioning
|
||||||
changeset
|
changeset
|
||||||
|
tools
|
||||||
api
|
api
|
||||||
|
|
||||||
.. _`google's summer of code`: http://code.google.com/soc
|
.. _`google's summer of code`: http://code.google.com/soc
|
||||||
|
19
docs/tools.rst
Normal file
19
docs/tools.rst
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
SQLAlchemy migrate tools
|
||||||
|
========================
|
||||||
|
|
||||||
|
The most commonly used tool is the :command:`migrate` script that is
|
||||||
|
discussed in depth in the :ref:`Database schema versioning
|
||||||
|
<versioning-system>` part of the documentation.
|
||||||
|
|
||||||
|
.. index:: repository migration
|
||||||
|
|
||||||
|
There is a second tool :command:`migrate_repository.py` that may be
|
||||||
|
used to migrate your repository from a version before 0.4.5 of
|
||||||
|
SQLAlchemy migrate to the current version.
|
||||||
|
|
||||||
|
.. module:: migrate.versioning.migrate_repository
|
||||||
|
:synopsis: Tool for migrating pre 0.4.5 repositories to current layout
|
||||||
|
|
||||||
|
Running :command:`migrate_repository.py` is as easy as:
|
||||||
|
|
||||||
|
:samp:`migrate_repository.py {repository_directory}`
|
@ -1,7 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Module migrate.versioning
|
This package provides functionality to create and manage
|
||||||
-------------------------
|
repositories of database schema changesets and to apply these
|
||||||
|
changesets to databases.
|
||||||
This package provides functionality to create and manage repositories of
|
|
||||||
database schema changesets and to apply these changesets to databases.
|
|
||||||
"""
|
"""
|
||||||
|
@ -1,10 +1,21 @@
|
|||||||
"""An external API to the versioning system
|
|
||||||
Used by the shell utility; could also be used by other scripts
|
|
||||||
"""
|
"""
|
||||||
|
This module provides an external API to the versioning system.
|
||||||
|
|
||||||
|
Used by the shell utility; could also be used by other scripts
|
||||||
|
"""
|
||||||
|
# Dear migrate developers,
|
||||||
|
#
|
||||||
|
# please do not comment this module using sphinx syntax because its
|
||||||
|
# docstrings are presented as user help and most users cannot
|
||||||
|
# interpret sphinx annotated ReStructuredText.
|
||||||
|
#
|
||||||
|
# Thanks,
|
||||||
|
# Jan Dittberner
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import inspect
|
import inspect
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from migrate.versioning import exceptions,repository,schema,version
|
from migrate.versioning import exceptions, repository, schema, version
|
||||||
import script as script_ #command name conflict
|
import script as script_ #command name conflict
|
||||||
|
|
||||||
__all__=[
|
__all__=[
|
||||||
@ -32,7 +43,8 @@ cls_schema = schema.ControlledSchema
|
|||||||
cls_vernum = version.VerNum
|
cls_vernum = version.VerNum
|
||||||
cls_script_python = script_.PythonScript
|
cls_script_python = script_.PythonScript
|
||||||
|
|
||||||
def help(cmd=None,**opts):
|
|
||||||
|
def help(cmd=None, **opts):
|
||||||
"""%prog help COMMAND
|
"""%prog help COMMAND
|
||||||
|
|
||||||
Displays help on a given command.
|
Displays help on a given command.
|
||||||
@ -42,57 +54,74 @@ def help(cmd=None,**opts):
|
|||||||
try:
|
try:
|
||||||
func = globals()[cmd]
|
func = globals()[cmd]
|
||||||
except:
|
except:
|
||||||
raise exceptions.UsageError("'%s' isn't a valid command. Try 'help COMMAND'"%cmd)
|
raise exceptions.UsageError(
|
||||||
|
"'%s' isn't a valid command. Try 'help COMMAND'" % cmd)
|
||||||
ret = func.__doc__
|
ret = func.__doc__
|
||||||
if sys.argv[0]:
|
if sys.argv[0]:
|
||||||
ret = ret.replace('%prog',sys.argv[0])
|
ret = ret.replace('%prog', sys.argv[0])
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def create(repository,name,**opts):
|
|
||||||
|
def create(repository, name, **opts):
|
||||||
"""%prog create REPOSITORY_PATH NAME [--table=TABLE]
|
"""%prog create REPOSITORY_PATH NAME [--table=TABLE]
|
||||||
|
|
||||||
Create an empty repository at the specified path.
|
Create an empty repository at the specified path.
|
||||||
|
|
||||||
You can specify the version_table to be used; by default, it is '_version'.
|
You can specify the version_table to be used; by default, it is
|
||||||
This table is created in all version-controlled databases.
|
'_version'. This table is created in all version-controlled
|
||||||
|
databases.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
rep=cls_repository.create(repository,name,**opts)
|
rep=cls_repository.create(repository, name, **opts)
|
||||||
except exceptions.PathFoundError,e:
|
except exceptions.PathFoundError, e:
|
||||||
raise exceptions.KnownError("The path %s already exists"%e.args[0])
|
raise exceptions.KnownError("The path %s already exists" % e.args[0])
|
||||||
|
|
||||||
def script(description,repository=None,**opts):
|
|
||||||
|
def script(description, repository=None, **opts):
|
||||||
"""%prog script [--repository=REPOSITORY_PATH] DESCRIPTION
|
"""%prog script [--repository=REPOSITORY_PATH] DESCRIPTION
|
||||||
|
|
||||||
Create an empty change script using the next unused version number appended with the given description.
|
Create an empty change script using the next unused version number
|
||||||
For instance, manage.py script "Add initial tables" creates: repository/versions/001_Add_initial_tables.py
|
appended with the given description.
|
||||||
|
|
||||||
|
For instance, manage.py script "Add initial tables" creates:
|
||||||
|
repository/versions/001_Add_initial_tables.py
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if repository is None:
|
if repository is None:
|
||||||
raise exceptions.UsageError("A repository must be specified")
|
raise exceptions.UsageError("A repository must be specified")
|
||||||
repos = cls_repository(repository)
|
repos = cls_repository(repository)
|
||||||
repos.create_script(description,**opts)
|
repos.create_script(description, **opts)
|
||||||
except exceptions.PathFoundError,e:
|
except exceptions.PathFoundError, e:
|
||||||
raise exceptions.KnownError("The path %s already exists"%e.args[0])
|
raise exceptions.KnownError("The path %s already exists"%e.args[0])
|
||||||
|
|
||||||
def script_sql(database,repository=None,**opts):
|
|
||||||
|
def script_sql(database, repository=None, **opts):
|
||||||
"""%prog script_sql [--repository=REPOSITORY_PATH] DATABASE
|
"""%prog script_sql [--repository=REPOSITORY_PATH] DATABASE
|
||||||
|
|
||||||
Create empty change SQL scripts for given DATABASE, where DATABASE is either specific ('postgres', 'mysql',
|
Create empty change SQL scripts for given DATABASE, where DATABASE
|
||||||
'oracle', 'sqlite', etc.) or generic ('default').
|
is either specific ('postgres', 'mysql', 'oracle', 'sqlite', etc.)
|
||||||
|
or generic ('default').
|
||||||
|
|
||||||
For instance, manage.py script_sql postgres creates:
|
For instance, manage.py script_sql postgres creates:
|
||||||
repository/versions/001_upgrade_postgres.py and repository/versions/001_downgrade_postgres.py
|
repository/versions/001_upgrade_postgres.py and
|
||||||
|
repository/versions/001_downgrade_postgres.py
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if repository is None:
|
if repository is None:
|
||||||
raise exceptions.UsageError("A repository must be specified")
|
raise exceptions.UsageError("A repository must be specified")
|
||||||
repos = cls_repository(repository)
|
repos = cls_repository(repository)
|
||||||
repos.create_script_sql(database,**opts)
|
repos.create_script_sql(database, **opts)
|
||||||
except exceptions.PathFoundError,e:
|
except exceptions.PathFoundError, e:
|
||||||
raise exceptions.KnownError("The path %s already exists"%e.args[0])
|
raise exceptions.KnownError("The path %s already exists"%e.args[0])
|
||||||
|
|
||||||
def test(repository,url=None,**opts):
|
|
||||||
|
def test(repository, url=None, **opts):
|
||||||
"""%prog test REPOSITORY_PATH URL [VERSION]
|
"""%prog test REPOSITORY_PATH URL [VERSION]
|
||||||
|
|
||||||
|
Performs the upgrade and downgrade option on the given
|
||||||
|
database. This is not a real test and may leave the database in a
|
||||||
|
bad state. You should therefore better run the test on a copy of
|
||||||
|
your database.
|
||||||
"""
|
"""
|
||||||
engine=create_engine(url)
|
engine=create_engine(url)
|
||||||
repos=cls_repository(repository)
|
repos=cls_repository(repository)
|
||||||
@ -100,7 +129,7 @@ def test(repository,url=None,**opts):
|
|||||||
# Upgrade
|
# Upgrade
|
||||||
print "Upgrading...",
|
print "Upgrading...",
|
||||||
try:
|
try:
|
||||||
script.run(engine,1)
|
script.run(engine, 1)
|
||||||
except:
|
except:
|
||||||
print "ERROR"
|
print "ERROR"
|
||||||
raise
|
raise
|
||||||
@ -108,14 +137,15 @@ def test(repository,url=None,**opts):
|
|||||||
|
|
||||||
print "Downgrading...",
|
print "Downgrading...",
|
||||||
try:
|
try:
|
||||||
script.run(engine,-1)
|
script.run(engine, -1)
|
||||||
except:
|
except:
|
||||||
print "ERROR"
|
print "ERROR"
|
||||||
raise
|
raise
|
||||||
print "done"
|
print "done"
|
||||||
print "Success"
|
print "Success"
|
||||||
|
|
||||||
def version(repository,**opts):
|
|
||||||
|
def version(repository, **opts):
|
||||||
"""%prog version REPOSITORY_PATH
|
"""%prog version REPOSITORY_PATH
|
||||||
|
|
||||||
Display the latest version available in a repository.
|
Display the latest version available in a repository.
|
||||||
@ -123,111 +153,124 @@ def version(repository,**opts):
|
|||||||
repos=cls_repository(repository)
|
repos=cls_repository(repository)
|
||||||
return repos.latest
|
return repos.latest
|
||||||
|
|
||||||
def source(version,dest=None,repository=None,**opts):
|
|
||||||
|
def source(version, dest=None, repository=None, **opts):
|
||||||
"""%prog source VERSION [DESTINATION] --repository=REPOSITORY_PATH
|
"""%prog source VERSION [DESTINATION] --repository=REPOSITORY_PATH
|
||||||
|
|
||||||
Display the Python code for a particular version in this repository.
|
Display the Python code for a particular version in this
|
||||||
Save it to the file at DESTINATION or, if omitted, send to stdout.
|
repository. Save it to the file at DESTINATION or, if omitted,
|
||||||
|
send to stdout.
|
||||||
"""
|
"""
|
||||||
if repository is None:
|
if repository is None:
|
||||||
raise exceptions.UsageError("A repository must be specified")
|
raise exceptions.UsageError("A repository must be specified")
|
||||||
repos=cls_repository(repository)
|
repos=cls_repository(repository)
|
||||||
ret=repos.version(version).script().source()
|
ret=repos.version(version).script().source()
|
||||||
if dest is not None:
|
if dest is not None:
|
||||||
dest=open(dest,'w')
|
dest=open(dest, 'w')
|
||||||
dest.write(ret)
|
dest.write(ret)
|
||||||
ret=None
|
ret=None
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def version_control(url,repository,version=None,**opts):
|
|
||||||
|
def version_control(url, repository, version=None, **opts):
|
||||||
"""%prog version_control URL REPOSITORY_PATH [VERSION]
|
"""%prog version_control URL REPOSITORY_PATH [VERSION]
|
||||||
|
|
||||||
Mark a database as under this repository's version control.
|
Mark a database as under this repository's version control.
|
||||||
Once a database is under version control, schema changes should only be
|
|
||||||
done via change scripts in this repository.
|
Once a database is under version control, schema changes should
|
||||||
|
only be done via change scripts in this repository.
|
||||||
|
|
||||||
This creates the table version_table in the database.
|
This creates the table version_table in the database.
|
||||||
|
|
||||||
The url should be any valid SQLAlchemy connection string.
|
The url should be any valid SQLAlchemy connection string.
|
||||||
|
|
||||||
By default, the database begins at version 0 and is assumed to be empty.
|
By default, the database begins at version 0 and is assumed to be
|
||||||
If the database is not empty, you may specify a version at which to begin
|
empty. If the database is not empty, you may specify a version at
|
||||||
instead. No attempt is made to verify this version's correctness - the
|
which to begin instead. No attempt is made to verify this
|
||||||
database schema is expected to be identical to what it would be if the
|
version's correctness - the database schema is expected to be
|
||||||
database were created from scratch.
|
identical to what it would be if the database were created from
|
||||||
|
scratch.
|
||||||
"""
|
"""
|
||||||
echo = 'True' == opts.get('echo', False)
|
echo = 'True' == opts.get('echo', False)
|
||||||
engine = create_engine(url, echo=echo)
|
engine = create_engine(url, echo=echo)
|
||||||
cls_schema.create(engine,repository,version)
|
cls_schema.create(engine, repository, version)
|
||||||
|
|
||||||
def db_version(url,repository,**opts):
|
|
||||||
|
def db_version(url, repository, **opts):
|
||||||
"""%prog db_version URL REPOSITORY_PATH
|
"""%prog db_version URL REPOSITORY_PATH
|
||||||
|
|
||||||
Show the current version of the repository with the given connection
|
Show the current version of the repository with the given
|
||||||
string, under version control of the specified repository.
|
connection string, under version control of the specified
|
||||||
|
repository.
|
||||||
|
|
||||||
The url should be any valid SQLAlchemy connection string.
|
The url should be any valid SQLAlchemy connection string.
|
||||||
"""
|
"""
|
||||||
echo = 'True' == opts.get('echo', False)
|
echo = 'True' == opts.get('echo', False)
|
||||||
engine = create_engine(url, echo=echo)
|
engine = create_engine(url, echo=echo)
|
||||||
schema = cls_schema(engine,repository)
|
schema = cls_schema(engine, repository)
|
||||||
return schema.version
|
return schema.version
|
||||||
|
|
||||||
def upgrade(url,repository,version=None,**opts):
|
|
||||||
|
def upgrade(url, repository, version=None, **opts):
|
||||||
"""%prog upgrade URL REPOSITORY_PATH [VERSION] [--preview_py|--preview_sql]
|
"""%prog upgrade URL REPOSITORY_PATH [VERSION] [--preview_py|--preview_sql]
|
||||||
|
|
||||||
Upgrade a database to a later version.
|
Upgrade a database to a later version.
|
||||||
|
|
||||||
This runs the upgrade() function defined in your change scripts.
|
This runs the upgrade() function defined in your change scripts.
|
||||||
|
|
||||||
By default, the database is updated to the latest available version. You
|
By default, the database is updated to the latest available
|
||||||
may specify a version instead, if you wish.
|
version. You may specify a version instead, if you wish.
|
||||||
|
|
||||||
You may preview the Python or SQL code to be executed, rather than actually
|
You may preview the Python or SQL code to be executed, rather than
|
||||||
executing it, using the appropriate 'preview' option.
|
actually executing it, using the appropriate 'preview' option.
|
||||||
"""
|
"""
|
||||||
err = "Cannot upgrade a database of version %s to version %s. "\
|
err = "Cannot upgrade a database of version %s to version %s. "\
|
||||||
"Try 'downgrade' instead."
|
"Try 'downgrade' instead."
|
||||||
return _migrate(url,repository,version,upgrade=True,err=err,**opts)
|
return _migrate(url, repository, version, upgrade=True, err=err, **opts)
|
||||||
|
|
||||||
def downgrade(url,repository,version,**opts):
|
|
||||||
|
def downgrade(url, repository, version, **opts):
|
||||||
"""%prog downgrade URL REPOSITORY_PATH VERSION [--preview_py|--preview_sql]
|
"""%prog downgrade URL REPOSITORY_PATH VERSION [--preview_py|--preview_sql]
|
||||||
|
|
||||||
Downgrade a database to an earlier version.
|
Downgrade a database to an earlier version.
|
||||||
This is the reverse of upgrade; this runs the downgrade() function defined
|
|
||||||
in your change scripts.
|
|
||||||
|
|
||||||
You may preview the Python or SQL code to be executed, rather than actually
|
This is the reverse of upgrade; this runs the downgrade() function
|
||||||
executing it, using the appropriate 'preview' option.
|
defined in your change scripts.
|
||||||
|
|
||||||
|
You may preview the Python or SQL code to be executed, rather than
|
||||||
|
actually executing it, using the appropriate 'preview' option.
|
||||||
"""
|
"""
|
||||||
err = "Cannot downgrade a database of version %s to version %s. "\
|
err = "Cannot downgrade a database of version %s to version %s. "\
|
||||||
"Try 'upgrade' instead."
|
"Try 'upgrade' instead."
|
||||||
return _migrate(url,repository,version,upgrade=False,err=err,**opts)
|
return _migrate(url, repository, version, upgrade=False, err=err, **opts)
|
||||||
|
|
||||||
def _migrate(url,repository,version,upgrade,err,**opts):
|
|
||||||
|
def _migrate(url, repository, version, upgrade, err, **opts):
|
||||||
echo = 'True' == opts.get('echo', False)
|
echo = 'True' == opts.get('echo', False)
|
||||||
engine = create_engine(url, echo=echo)
|
engine = create_engine(url, echo=echo)
|
||||||
schema = cls_schema(engine,repository)
|
schema = cls_schema(engine, repository)
|
||||||
version = _migrate_version(schema,version,upgrade,err)
|
version = _migrate_version(schema, version, upgrade, err)
|
||||||
|
|
||||||
changeset = schema.changeset(version)
|
changeset = schema.changeset(version)
|
||||||
for ver,change in changeset:
|
for ver, change in changeset:
|
||||||
nextver = ver + changeset.step
|
nextver = ver + changeset.step
|
||||||
print '%s -> %s... '%(ver,nextver),
|
print '%s -> %s... '%(ver, nextver),
|
||||||
if opts.get('preview_sql'):
|
if opts.get('preview_sql'):
|
||||||
print
|
print
|
||||||
print change.log
|
print change.log
|
||||||
elif opts.get('preview_py'):
|
elif opts.get('preview_py'):
|
||||||
source_ver = max(ver,nextver)
|
source_ver = max(ver, nextver)
|
||||||
module = schema.repository.version(source_ver).script().module
|
module = schema.repository.version(source_ver).script().module
|
||||||
funcname = upgrade and "upgrade" or "downgrade"
|
funcname = upgrade and "upgrade" or "downgrade"
|
||||||
func = getattr(module,funcname)
|
func = getattr(module, funcname)
|
||||||
print
|
print
|
||||||
print inspect.getsource(module.upgrade)
|
print inspect.getsource(module.upgrade)
|
||||||
else:
|
else:
|
||||||
schema.runchange(ver,change,changeset.step)
|
schema.runchange(ver, change, changeset.step)
|
||||||
print 'done'
|
print 'done'
|
||||||
|
|
||||||
def _migrate_version(schema,version,upgrade,err):
|
|
||||||
|
def _migrate_version(schema, version, upgrade, err):
|
||||||
if version is None:
|
if version is None:
|
||||||
return version
|
return version
|
||||||
# Version is specified: ensure we're upgrading in the right direction
|
# Version is specified: ensure we're upgrading in the right direction
|
||||||
@ -240,48 +283,54 @@ def _migrate_version(schema,version,upgrade,err):
|
|||||||
else:
|
else:
|
||||||
direction = cur >= version
|
direction = cur >= version
|
||||||
if not direction:
|
if not direction:
|
||||||
raise exceptions.KnownError(err%(cur,version))
|
raise exceptions.KnownError(err%(cur, version))
|
||||||
return version
|
return version
|
||||||
|
|
||||||
def drop_version_control(url,repository,**opts):
|
|
||||||
|
def drop_version_control(url, repository, **opts):
|
||||||
"""%prog drop_version_control URL REPOSITORY_PATH
|
"""%prog drop_version_control URL REPOSITORY_PATH
|
||||||
|
|
||||||
Removes version control from a database.
|
Removes version control from a database.
|
||||||
"""
|
"""
|
||||||
echo = 'True' == opts.get('echo', False)
|
echo = 'True' == opts.get('echo', False)
|
||||||
engine = create_engine(url, echo=echo)
|
engine = create_engine(url, echo=echo)
|
||||||
schema=cls_schema(engine,repository)
|
schema=cls_schema(engine, repository)
|
||||||
schema.drop()
|
schema.drop()
|
||||||
|
|
||||||
def manage(file,**opts):
|
|
||||||
|
def manage(file, **opts):
|
||||||
"""%prog manage FILENAME VARIABLES...
|
"""%prog manage FILENAME VARIABLES...
|
||||||
|
|
||||||
Creates a script that runs Migrate with a set of default values.
|
Creates a script that runs Migrate with a set of default values.
|
||||||
|
|
||||||
For example::
|
For example::
|
||||||
|
|
||||||
%prog manage manage.py --repository=/path/to/repository --url=sqlite:///project.db
|
%prog manage manage.py --repository=/path/to/repository \
|
||||||
|
--url=sqlite:///project.db
|
||||||
|
|
||||||
would create the script manage.py. The following two commands would then
|
would create the script manage.py. The following two commands
|
||||||
have exactly the same results::
|
would then have exactly the same results::
|
||||||
|
|
||||||
python manage.py version
|
python manage.py version
|
||||||
%prog version --repository=/path/to/repository
|
%prog version --repository=/path/to/repository
|
||||||
"""
|
"""
|
||||||
return repository.manage(file,**opts)
|
return repository.manage(file, **opts)
|
||||||
|
|
||||||
def compare_model_to_db(url,model,repository,**opts):
|
|
||||||
|
def compare_model_to_db(url, model, repository, **opts):
|
||||||
"""%prog compare_model_to_db URL MODEL REPOSITORY_PATH
|
"""%prog compare_model_to_db URL MODEL REPOSITORY_PATH
|
||||||
|
|
||||||
Compare the current model (assumed to be a module level variable of type sqlalchemy.MetaData) against the current database.
|
Compare the current model (assumed to be a module level variable
|
||||||
|
of type sqlalchemy.MetaData) against the current database.
|
||||||
|
|
||||||
NOTE: This is EXPERIMENTAL.
|
NOTE: This is EXPERIMENTAL.
|
||||||
""" # TODO: get rid of EXPERIMENTAL label
|
""" # TODO: get rid of EXPERIMENTAL label
|
||||||
echo = 'True' == opts.get('echo', False)
|
echo = 'True' == opts.get('echo', False)
|
||||||
engine = create_engine(url, echo=echo)
|
engine = create_engine(url, echo=echo)
|
||||||
print cls_schema.compare_model_to_db(engine,model,repository)
|
print cls_schema.compare_model_to_db(engine, model, repository)
|
||||||
|
|
||||||
def create_model(url,repository,**opts):
|
|
||||||
|
def create_model(url, repository, **opts):
|
||||||
"""%prog create_model URL REPOSITORY_PATH
|
"""%prog create_model URL REPOSITORY_PATH
|
||||||
|
|
||||||
Dump the current database as a Python model to stdout.
|
Dump the current database as a Python model to stdout.
|
||||||
@ -291,32 +340,37 @@ def create_model(url,repository,**opts):
|
|||||||
echo = 'True' == opts.get('echo', False)
|
echo = 'True' == opts.get('echo', False)
|
||||||
engine = create_engine(url, echo=echo)
|
engine = create_engine(url, echo=echo)
|
||||||
declarative = opts.get('declarative', False)
|
declarative = opts.get('declarative', False)
|
||||||
print cls_schema.create_model(engine,repository,declarative)
|
print cls_schema.create_model(engine, repository, declarative)
|
||||||
|
|
||||||
def make_update_script_for_model(url,oldmodel,model,repository,**opts):
|
|
||||||
|
def make_update_script_for_model(url, oldmodel, model, repository, **opts):
|
||||||
"""%prog make_update_script_for_model URL OLDMODEL MODEL REPOSITORY_PATH
|
"""%prog make_update_script_for_model URL OLDMODEL MODEL REPOSITORY_PATH
|
||||||
|
|
||||||
Create a script changing the old Python model to the new (current) Python model, sending to stdout.
|
Create a script changing the old Python model to the new (current)
|
||||||
|
Python model, sending to stdout.
|
||||||
|
|
||||||
NOTE: This is EXPERIMENTAL.
|
NOTE: This is EXPERIMENTAL.
|
||||||
""" # TODO: get rid of EXPERIMENTAL label
|
""" # TODO: get rid of EXPERIMENTAL label
|
||||||
echo = 'True' == opts.get('echo', False)
|
echo = 'True' == opts.get('echo', False)
|
||||||
engine = create_engine(url, echo=echo)
|
engine = create_engine(url, echo=echo)
|
||||||
try:
|
try:
|
||||||
print cls_script_python.make_update_script_for_model(engine,oldmodel,model,repository,**opts)
|
print cls_script_python.make_update_script_for_model(
|
||||||
except exceptions.PathFoundError,e:
|
engine, oldmodel, model, repository, **opts)
|
||||||
raise exceptions.KnownError("The path %s already exists"%e.args[0]) # TODO: get rid of this? if we don't add back path param
|
except exceptions.PathFoundError, e:
|
||||||
|
# TODO: get rid of this? if we don't add back path param
|
||||||
|
raise exceptions.KnownError("The path %s already exists" % e.args[0])
|
||||||
|
|
||||||
def update_db_from_model(url,model,repository,**opts):
|
|
||||||
|
def update_db_from_model(url, model, repository, **opts):
|
||||||
"""%prog update_db_from_model URL MODEL REPOSITORY_PATH
|
"""%prog update_db_from_model URL MODEL REPOSITORY_PATH
|
||||||
|
|
||||||
Modify the database to match the structure of the current Python model.
|
Modify the database to match the structure of the current Python
|
||||||
This also sets the db_version number to the latest in the repository.
|
model. This also sets the db_version number to the latest in the
|
||||||
|
repository.
|
||||||
|
|
||||||
NOTE: This is EXPERIMENTAL.
|
NOTE: This is EXPERIMENTAL.
|
||||||
""" # TODO: get rid of EXPERIMENTAL label
|
""" # TODO: get rid of EXPERIMENTAL label
|
||||||
echo = 'True' == opts.get('echo', False)
|
echo = 'True' == opts.get('echo', False)
|
||||||
engine = create_engine(url, echo=echo)
|
engine = create_engine(url, echo=echo)
|
||||||
schema = cls_schema(engine,repository)
|
schema = cls_schema(engine, repository)
|
||||||
schema.update_db_from_model(model)
|
schema.update_db_from_model(model)
|
||||||
|
|
||||||
|
@ -1,19 +1,26 @@
|
|||||||
|
"""
|
||||||
|
Configuration parser module.
|
||||||
|
"""
|
||||||
|
|
||||||
from migrate.versioning.base import *
|
from migrate.versioning.base import *
|
||||||
from migrate.versioning import pathed
|
from migrate.versioning import pathed
|
||||||
from ConfigParser import ConfigParser
|
from ConfigParser import ConfigParser
|
||||||
|
|
||||||
#__all__=['MigrateConfigParser']
|
|
||||||
|
|
||||||
class Parser(ConfigParser):
|
class Parser(ConfigParser):
|
||||||
"""A project configuration file"""
|
"""A project configuration file."""
|
||||||
def to_dict(self,sections=None):
|
|
||||||
|
def to_dict(self, sections=None):
|
||||||
"""It's easier to access config values like dictionaries"""
|
"""It's easier to access config values like dictionaries"""
|
||||||
return self._sections
|
return self._sections
|
||||||
|
|
||||||
class Config(pathed.Pathed,Parser):
|
|
||||||
def __init__(self,path,*p,**k):
|
class Config(pathed.Pathed, Parser):
|
||||||
"""Confirm the config file exists; read it"""
|
"""Configuration class."""
|
||||||
|
|
||||||
|
def __init__(self, path, *p, **k):
|
||||||
|
"""Confirm the config file exists; read it."""
|
||||||
self.require_found(path)
|
self.require_found(path)
|
||||||
pathed.Pathed.__init__(self,path)
|
pathed.Pathed.__init__(self, path)
|
||||||
Parser.__init__(self,*p,**k)
|
Parser.__init__(self, *p, **k)
|
||||||
self.read(path)
|
self.read(path)
|
||||||
|
@ -1,32 +1,60 @@
|
|||||||
|
"""
|
||||||
|
Provide exception classes for :mod:`migrate.versioning`
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
|
"""Error base class."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ApiError(Error):
|
class ApiError(Error):
|
||||||
|
"""Base class for API errors."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class KnownError(ApiError):
|
class KnownError(ApiError):
|
||||||
"""A known error condition"""
|
"""A known error condition."""
|
||||||
|
|
||||||
|
|
||||||
class UsageError(ApiError):
|
class UsageError(ApiError):
|
||||||
"""A known error condition where help should be displayed"""
|
"""A known error condition where help should be displayed."""
|
||||||
|
|
||||||
|
|
||||||
class ControlledSchemaError(Error):
|
class ControlledSchemaError(Error):
|
||||||
pass
|
"""Base class for controlled schema errors."""
|
||||||
class InvalidVersionError(ControlledSchemaError):
|
|
||||||
"""Invalid version number"""
|
|
||||||
class DatabaseNotControlledError(ControlledSchemaError):
|
|
||||||
"""Database shouldn't be under vc, but it is"""
|
|
||||||
class DatabaseAlreadyControlledError(ControlledSchemaError):
|
|
||||||
"""Database should be under vc, but it's not"""
|
|
||||||
class WrongRepositoryError(ControlledSchemaError):
|
|
||||||
"""This database is under version control by another repository"""
|
|
||||||
class NoSuchTableError(ControlledSchemaError):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidVersionError(ControlledSchemaError):
|
||||||
|
"""Invalid version number."""
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseNotControlledError(ControlledSchemaError):
|
||||||
|
"""Database should be under version control, but it's not."""
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseAlreadyControlledError(ControlledSchemaError):
|
||||||
|
"""Database shouldn't be under version control, but it is"""
|
||||||
|
|
||||||
|
|
||||||
|
class WrongRepositoryError(ControlledSchemaError):
|
||||||
|
"""This database is under version control by another repository."""
|
||||||
|
|
||||||
|
|
||||||
|
class NoSuchTableError(ControlledSchemaError):
|
||||||
|
"""The table does not exist."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LogSqlError(Error):
|
class LogSqlError(Error):
|
||||||
"""A SQLError, with a traceback of where that statement was logged"""
|
"""A SQLError, with a traceback of where that statement was logged."""
|
||||||
def __init__(self,sqlerror,entry):
|
|
||||||
|
def __init__(self, sqlerror, entry):
|
||||||
Exception.__init__(self)
|
Exception.__init__(self)
|
||||||
self.sqlerror = sqlerror
|
self.sqlerror = sqlerror
|
||||||
self.entry = entry
|
self.entry = entry
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
ret = "SQL error in statement: \n%s\n"%(str(self.entry))
|
ret = "SQL error in statement: \n%s\n"%(str(self.entry))
|
||||||
ret += "Traceback from change script:\n"
|
ret += "Traceback from change script:\n"
|
||||||
@ -34,25 +62,42 @@ class LogSqlError(Error):
|
|||||||
ret += str(self.sqlerror)
|
ret += str(self.sqlerror)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class PathError(Error):
|
class PathError(Error):
|
||||||
|
"""Base class for path errors."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PathNotFoundError(PathError):
|
class PathNotFoundError(PathError):
|
||||||
"""A path with no file was required; found a file"""
|
"""A path with no file was required; found a file."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PathFoundError(PathError):
|
class PathFoundError(PathError):
|
||||||
"""A path with a file was required; found no file"""
|
"""A path with a file was required; found no file."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RepositoryError(Error):
|
class RepositoryError(Error):
|
||||||
|
"""Base class for repository errors."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidRepositoryError(RepositoryError):
|
class InvalidRepositoryError(RepositoryError):
|
||||||
|
"""Invalid repository error."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ScriptError(Error):
|
class ScriptError(Error):
|
||||||
|
"""Base class for script errors."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidScriptError(ScriptError):
|
class InvalidScriptError(ScriptError):
|
||||||
|
"""Invalid script error."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidVersionError(Error):
|
class InvalidVersionError(Error):
|
||||||
|
"""Invalid version error."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
|
"""
|
||||||
|
Code to generate a Python model from a database or differences
|
||||||
|
between a model and database.
|
||||||
|
|
||||||
# Code to generate a Python model from a database or differences between a model and database.
|
Some of this is borrowed heavily from the AutoCode project at:
|
||||||
# Some of this is borrowed heavily from the AutoCode project at: http://code.google.com/p/sqlautocode/
|
http://code.google.com/p/sqlautocode/
|
||||||
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import migrate, sqlalchemy
|
import migrate
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
|
||||||
HEADER = """
|
HEADER = """
|
||||||
@ -22,54 +27,66 @@ from sqlalchemy.ext import declarative
|
|||||||
Base = declarative.declarative_base()
|
Base = declarative.declarative_base()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class ModelGenerator(object):
|
class ModelGenerator(object):
|
||||||
|
|
||||||
def __init__(self, diff, declarative=False):
|
def __init__(self, diff, declarative=False):
|
||||||
self.diff = diff
|
self.diff = diff
|
||||||
self.declarative = declarative
|
self.declarative = declarative
|
||||||
dialectModule = sys.modules[self.diff.conn.dialect.__module__] # is there an easier way to get this?
|
# is there an easier way to get this?
|
||||||
self.colTypeMappings = dict( (v,k) for k,v in dialectModule.colspecs.items() )
|
dialectModule = sys.modules[self.diff.conn.dialect.__module__]
|
||||||
|
self.colTypeMappings = dict((v, k) for k, v in \
|
||||||
|
dialectModule.colspecs.items())
|
||||||
|
|
||||||
def column_repr(self, col):
|
def column_repr(self, col):
|
||||||
kwarg = []
|
kwarg = []
|
||||||
if col.key != col.name: kwarg.append('key')
|
if col.key != col.name:
|
||||||
|
kwarg.append('key')
|
||||||
if col.primary_key:
|
if col.primary_key:
|
||||||
col.primary_key = True # otherwise it dumps it as 1
|
col.primary_key = True # otherwise it dumps it as 1
|
||||||
kwarg.append('primary_key')
|
kwarg.append('primary_key')
|
||||||
if not col.nullable: kwarg.append('nullable')
|
if not col.nullable:
|
||||||
if col.onupdate: kwarg.append('onupdate')
|
kwarg.append('nullable')
|
||||||
|
if col.onupdate:
|
||||||
|
kwarg.append('onupdate')
|
||||||
if col.default:
|
if col.default:
|
||||||
if col.primary_key:
|
if col.primary_key:
|
||||||
# I found that Postgres automatically creates a default value for the sequence, but let's not show that.
|
# I found that PostgreSQL automatically creates a
|
||||||
|
# default value for the sequence, but let's not show
|
||||||
|
# that.
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
kwarg.append('default')
|
kwarg.append('default')
|
||||||
ks = ', '.join('%s=%r' % (k, getattr(col, k)) for k in kwarg )
|
ks = ', '.join('%s=%r' % (k, getattr(col, k)) for k in kwarg)
|
||||||
|
|
||||||
name = col.name.encode('utf8') # crs: not sure if this is good idea, but it gets rid of extra u''
|
# crs: not sure if this is good idea, but it gets rid of extra
|
||||||
|
# u''
|
||||||
|
name = col.name.encode('utf8')
|
||||||
type = self.colTypeMappings.get(col.type.__class__, None)
|
type = self.colTypeMappings.get(col.type.__class__, None)
|
||||||
if type:
|
if type:
|
||||||
# Make the column type be an instance of this type.
|
# Make the column type be an instance of this type.
|
||||||
type = type()
|
type = type()
|
||||||
else:
|
else:
|
||||||
# We must already be a model type, no need to map from the database-specific types.
|
# We must already be a model type, no need to map from the
|
||||||
|
# database-specific types.
|
||||||
type = col.type
|
type = col.type
|
||||||
|
|
||||||
data = {'name' : name,
|
data = {
|
||||||
'type' : type,
|
'name': name,
|
||||||
'constraints' : ', '.join([repr(cn) for cn in col.constraints]),
|
'type': type,
|
||||||
'args' : ks and ks or ''
|
'constraints': ', '.join([repr(cn) for cn in col.constraints]),
|
||||||
}
|
'args': ks and ks or ''}
|
||||||
|
|
||||||
if data['constraints']:
|
if data['constraints']:
|
||||||
if data['args']: data['args'] = ',' + data['args']
|
if data['args']:
|
||||||
|
data['args'] = ',' + data['args']
|
||||||
|
|
||||||
if data['constraints'] or data['args']:
|
if data['constraints'] or data['args']:
|
||||||
data['maybeComma'] = ','
|
data['maybeComma'] = ','
|
||||||
else:
|
else:
|
||||||
data['maybeComma'] = ''
|
data['maybeComma'] = ''
|
||||||
|
|
||||||
commonStuff = " %(maybeComma)s %(constraints)s %(args)s)""" % data
|
commonStuff = """ %(maybeComma)s %(constraints)s %(args)s)""" % data
|
||||||
commonStuff = commonStuff.strip()
|
commonStuff = commonStuff.strip()
|
||||||
data['commonStuff'] = commonStuff
|
data['commonStuff'] = commonStuff
|
||||||
if self.declarative:
|
if self.declarative:
|
||||||
@ -86,14 +103,15 @@ class ModelGenerator(object):
|
|||||||
for col in table.columns:
|
for col in table.columns:
|
||||||
out.append(" %s" % self.column_repr(col))
|
out.append(" %s" % self.column_repr(col))
|
||||||
else:
|
else:
|
||||||
out.append("%(table)s = Table('%(table)s', meta," % {'table': tableName})
|
out.append("%(table)s = Table('%(table)s', meta," % \
|
||||||
|
{'table': tableName})
|
||||||
for col in table.columns:
|
for col in table.columns:
|
||||||
out.append(" %s," % self.column_repr(col))
|
out.append(" %s," % self.column_repr(col))
|
||||||
out.append(")")
|
out.append(")")
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def toPython(self):
|
def toPython(self):
|
||||||
''' Assume database is current and model is empty. '''
|
"""Assume database is current and model is empty."""
|
||||||
out = []
|
out = []
|
||||||
if self.declarative:
|
if self.declarative:
|
||||||
out.append(DECLARATIVE_HEADER)
|
out.append(DECLARATIVE_HEADER)
|
||||||
@ -109,28 +127,30 @@ class ModelGenerator(object):
|
|||||||
''' Assume model is most current and database is out-of-date. '''
|
''' Assume model is most current and database is out-of-date. '''
|
||||||
|
|
||||||
decls = ['meta = MetaData(migrate_engine)']
|
decls = ['meta = MetaData(migrate_engine)']
|
||||||
for table in self.diff.tablesMissingInModel + self.diff.tablesMissingInDatabase:
|
for table in self.diff.tablesMissingInModel + \
|
||||||
|
self.diff.tablesMissingInDatabase:
|
||||||
decls.extend(self.getTableDefn(table))
|
decls.extend(self.getTableDefn(table))
|
||||||
|
|
||||||
upgradeCommands, downgradeCommands = [], []
|
upgradeCommands, downgradeCommands = [], []
|
||||||
for table in self.diff.tablesMissingInModel:
|
for table in self.diff.tablesMissingInModel:
|
||||||
tableName = table.name
|
tableName = table.name
|
||||||
upgradeCommands.append("%(table)s.drop()" % {'table': tableName})
|
upgradeCommands.append("%(table)s.drop()" % {'table': tableName})
|
||||||
downgradeCommands.append("%(table)s.create()" % {'table': tableName})
|
downgradeCommands.append("%(table)s.create()" % \
|
||||||
|
{'table': tableName})
|
||||||
for table in self.diff.tablesMissingInDatabase:
|
for table in self.diff.tablesMissingInDatabase:
|
||||||
tableName = table.name
|
tableName = table.name
|
||||||
upgradeCommands.append("%(table)s.create()" % {'table': tableName})
|
upgradeCommands.append("%(table)s.create()" % {'table': tableName})
|
||||||
downgradeCommands.append("%(table)s.drop()" % {'table': tableName})
|
downgradeCommands.append("%(table)s.drop()" % {'table': tableName})
|
||||||
|
|
||||||
return ('\n'.join(decls),
|
return (
|
||||||
|
'\n'.join(decls),
|
||||||
'\n'.join(['%s%s' % (indent, line) for line in upgradeCommands]),
|
'\n'.join(['%s%s' % (indent, line) for line in upgradeCommands]),
|
||||||
'\n'.join(['%s%s' % (indent, line) for line in downgradeCommands])
|
'\n'.join(['%s%s' % (indent, line) for line in downgradeCommands]))
|
||||||
)
|
|
||||||
|
|
||||||
def applyModel(self):
|
def applyModel(self):
|
||||||
''' Apply model to current database. '''
|
"""Apply model to current database."""
|
||||||
|
# Yuck! We have to import from changeset to apply the
|
||||||
# Yuck! We have to import from changeset to apply the monkey-patch to allow column adding/dropping.
|
# monkey-patch to allow column adding/dropping.
|
||||||
from migrate.changeset import schema
|
from migrate.changeset import schema
|
||||||
|
|
||||||
def dbCanHandleThisChange(missingInDatabase, missingInModel, diffDecl):
|
def dbCanHandleThisChange(missingInDatabase, missingInModel, diffDecl):
|
||||||
@ -152,8 +172,10 @@ class ModelGenerator(object):
|
|||||||
modelTable = modelTable.tometadata(meta)
|
modelTable = modelTable.tometadata(meta)
|
||||||
dbTable = self.diff.reflected_model.tables[modelTable.name]
|
dbTable = self.diff.reflected_model.tables[modelTable.name]
|
||||||
tableName = modelTable.name
|
tableName = modelTable.name
|
||||||
missingInDatabase, missingInModel, diffDecl = self.diff.colDiffs[tableName]
|
missingInDatabase, missingInModel, diffDecl = \
|
||||||
if dbCanHandleThisChange(missingInDatabase, missingInModel, diffDecl):
|
self.diff.colDiffs[tableName]
|
||||||
|
if dbCanHandleThisChange(missingInDatabase, missingInModel,
|
||||||
|
diffDecl):
|
||||||
for col in missingInDatabase:
|
for col in missingInDatabase:
|
||||||
modelTable.columns[col.name].create()
|
modelTable.columns[col.name].create()
|
||||||
for col in missingInModel:
|
for col in missingInModel:
|
||||||
@ -161,25 +183,34 @@ class ModelGenerator(object):
|
|||||||
for modelCol, databaseCol, modelDecl, databaseDecl in diffDecl:
|
for modelCol, databaseCol, modelDecl, databaseDecl in diffDecl:
|
||||||
databaseCol.alter(modelCol)
|
databaseCol.alter(modelCol)
|
||||||
else:
|
else:
|
||||||
# Sqlite doesn't support drop column, so you have to do more:
|
# Sqlite doesn't support drop column, so you have to
|
||||||
# create temp table, copy data to it, drop old table, create new table, copy data back.
|
# do more: create temp table, copy data to it, drop
|
||||||
|
# old table, create new table, copy data back.
|
||||||
|
#
|
||||||
|
# I wonder if this is guaranteed to be unique?
|
||||||
|
tempName = '_temp_%s' % modelTable.name
|
||||||
|
|
||||||
tempName = '_temp_%s' % modelTable.name # I wonder if this is guaranteed to be unique?
|
|
||||||
def getCopyStatement():
|
def getCopyStatement():
|
||||||
preparer = self.diff.conn.engine.dialect.preparer
|
preparer = self.diff.conn.engine.dialect.preparer
|
||||||
commonCols = []
|
commonCols = []
|
||||||
for modelCol in modelTable.columns:
|
for modelCol in modelTable.columns:
|
||||||
if dbTable.columns.has_key(modelCol.name):
|
if modelCol.name in dbTable.columns:
|
||||||
commonCols.append(modelCol.name)
|
commonCols.append(modelCol.name)
|
||||||
commonColsStr = ', '.join(commonCols)
|
commonColsStr = ', '.join(commonCols)
|
||||||
return 'INSERT INTO %s (%s) SELECT %s FROM %s' % (tableName, commonColsStr, commonColsStr, tempName)
|
return 'INSERT INTO %s (%s) SELECT %s FROM %s' % \
|
||||||
|
(tableName, commonColsStr, commonColsStr, tempName)
|
||||||
|
|
||||||
# Move the data in one transaction, so that we don't leave the database in a nasty state.
|
# Move the data in one transaction, so that we don't
|
||||||
|
# leave the database in a nasty state.
|
||||||
connection = self.diff.conn.connect()
|
connection = self.diff.conn.connect()
|
||||||
trans = connection.begin()
|
trans = connection.begin()
|
||||||
try:
|
try:
|
||||||
connection.execute('CREATE TEMPORARY TABLE %s as SELECT * from %s' % (tempName, modelTable.name))
|
connection.execute(
|
||||||
modelTable.drop(bind=connection) # make sure the drop takes place inside our transaction with the bind parameter
|
'CREATE TEMPORARY TABLE %s as SELECT * from %s' % \
|
||||||
|
(tempName, modelTable.name))
|
||||||
|
# make sure the drop takes place inside our
|
||||||
|
# transaction with the bind parameter
|
||||||
|
modelTable.drop(bind=connection)
|
||||||
modelTable.create(bind=connection)
|
modelTable.create(bind=connection)
|
||||||
connection.execute(getCopyStatement())
|
connection.execute(getCopyStatement())
|
||||||
connection.execute('DROP TABLE %s' % tempName)
|
connection.execute('DROP TABLE %s' % tempName)
|
||||||
@ -187,4 +218,3 @@ class ModelGenerator(object):
|
|||||||
except:
|
except:
|
||||||
trans.rollback()
|
trans.rollback()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
@ -1,17 +1,22 @@
|
|||||||
""" Script to migrate repository. This shouldn't use any other migrate
|
"""
|
||||||
modules, so that it can work in any version. """
|
Script to migrate repository from sqlalchemy <= 0.4.4 to the new
|
||||||
|
repository schema. This shouldn't use any other migrate modules, so
|
||||||
|
that it can work in any version.
|
||||||
|
"""
|
||||||
|
|
||||||
import os, os.path, sys
|
import os
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def usage():
|
def usage():
|
||||||
"""Gives usage information."""
|
"""Gives usage information."""
|
||||||
print '''Usage: %(prog)s repository-to-migrate
|
print """Usage: %(prog)s repository-to-migrate
|
||||||
|
|
||||||
Upgrade your repository to the new flat format.
|
Upgrade your repository to the new flat format.
|
||||||
|
|
||||||
NOTE: You should probably make a backup before running this.
|
NOTE: You should probably make a backup before running this.
|
||||||
''' % {'prog': sys.argv[0]}
|
""" % {'prog': sys.argv[0]}
|
||||||
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@ -27,7 +32,8 @@ def move_file(src, tgt):
|
|||||||
print ' Moving file %s to %s' % (src, tgt)
|
print ' Moving file %s to %s' % (src, tgt)
|
||||||
if os.path.exists(tgt):
|
if os.path.exists(tgt):
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Cannot move file %s because target %s already exists' % (src, tgt))
|
'Cannot move file %s because target %s already exists' % \
|
||||||
|
(src, tgt))
|
||||||
os.rename(src, tgt)
|
os.rename(src, tgt)
|
||||||
|
|
||||||
|
|
||||||
@ -43,7 +49,7 @@ def migrate_repository(repos):
|
|||||||
versions = '%s/versions' % repos
|
versions = '%s/versions' % repos
|
||||||
dirs = os.listdir(versions)
|
dirs = os.listdir(versions)
|
||||||
# Only use int's in list.
|
# Only use int's in list.
|
||||||
numdirs = [ int(dirname) for dirname in dirs if dirname.isdigit() ]
|
numdirs = [int(dirname) for dirname in dirs if dirname.isdigit()]
|
||||||
numdirs.sort() # Sort list.
|
numdirs.sort() # Sort list.
|
||||||
for dirname in numdirs:
|
for dirname in numdirs:
|
||||||
origdir = '%s/%s' % (versions, dirname)
|
origdir = '%s/%s' % (versions, dirname)
|
||||||
@ -51,7 +57,6 @@ def migrate_repository(repos):
|
|||||||
files = os.listdir(origdir)
|
files = os.listdir(origdir)
|
||||||
files.sort()
|
files.sort()
|
||||||
for filename in files:
|
for filename in files:
|
||||||
|
|
||||||
# Delete compiled Python files.
|
# Delete compiled Python files.
|
||||||
if filename.endswith('.pyc') or filename.endswith('.pyo'):
|
if filename.endswith('.pyc') or filename.endswith('.pyo'):
|
||||||
delete_file('%s/%s' % (origdir, filename))
|
delete_file('%s/%s' % (origdir, filename))
|
||||||
@ -91,4 +96,3 @@ def main():
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
@ -1,60 +1,72 @@
|
|||||||
|
"""
|
||||||
|
A path/directory class.
|
||||||
|
"""
|
||||||
|
|
||||||
from migrate.versioning.base import *
|
from migrate.versioning.base import *
|
||||||
from migrate.versioning.util import KeyedInstance
|
from migrate.versioning.util import KeyedInstance
|
||||||
import os,shutil
|
|
||||||
from migrate.versioning import exceptions
|
from migrate.versioning import exceptions
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
class Pathed(KeyedInstance):
|
class Pathed(KeyedInstance):
|
||||||
"""A class associated with a path/directory tree
|
"""
|
||||||
|
A class associated with a path/directory tree.
|
||||||
|
|
||||||
Only one instance of this class may exist for a particular file;
|
Only one instance of this class may exist for a particular file;
|
||||||
__new__ will return an existing instance if possible
|
__new__ will return an existing instance if possible
|
||||||
"""
|
"""
|
||||||
parent=None
|
parent=None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _key(cls,path):
|
def _key(cls, path):
|
||||||
return str(path)
|
return str(path)
|
||||||
|
|
||||||
def __init__(self,path):
|
def __init__(self, path):
|
||||||
self.path=path
|
self.path=path
|
||||||
if self.__class__.parent is not None:
|
if self.__class__.parent is not None:
|
||||||
self._init_parent(path)
|
self._init_parent(path)
|
||||||
|
|
||||||
def _init_parent(self,path):
|
def _init_parent(self, path):
|
||||||
"""Try to initialize this object's parent, if it has one"""
|
"""Try to initialize this object's parent, if it has one"""
|
||||||
parent_path=self.__class__._parent_path(path)
|
parent_path=self.__class__._parent_path(path)
|
||||||
self.parent=self.__class__.parent(parent_path)
|
self.parent=self.__class__.parent(parent_path)
|
||||||
log.info("Getting parent %r:%r"%(self.__class__.parent,parent_path))
|
log.info("Getting parent %r:%r" % (self.__class__.parent, parent_path))
|
||||||
self.parent._init_child(path,self)
|
self.parent._init_child(path, self)
|
||||||
|
|
||||||
def _init_child(self,child,path):
|
def _init_child(self, child, path):
|
||||||
"""Run when a child of this object is initialized
|
"""Run when a child of this object is initialized.
|
||||||
Parameters: the child object; the path to this object (its parent)
|
|
||||||
|
Parameters: the child object; the path to this object (its
|
||||||
|
parent)
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _parent_path(cls,path):
|
def _parent_path(cls, path):
|
||||||
"""Fetch the path of this object's parent from this object's path
|
|
||||||
"""
|
"""
|
||||||
# os.path.dirname(), but strip directories like files (like unix basename)
|
Fetch the path of this object's parent from this object's path.
|
||||||
|
"""
|
||||||
|
# os.path.dirname(), but strip directories like files (like
|
||||||
|
# unix basename)
|
||||||
|
#
|
||||||
# Treat directories like files...
|
# Treat directories like files...
|
||||||
if path[-1]=='/':
|
if path[-1] == '/':
|
||||||
path=path[:-1]
|
path=path[:-1]
|
||||||
ret = os.path.dirname(path)
|
ret = os.path.dirname(path)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def require_notfound(cls,path):
|
def require_notfound(cls, path):
|
||||||
"""Ensures a given path does not already exist"""
|
"""Ensures a given path does not already exist"""
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
raise exceptions.PathFoundError(path)
|
raise exceptions.PathFoundError(path)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def require_found(cls,path):
|
def require_found(cls, path):
|
||||||
"""Ensures a given path already exists"""
|
"""Ensures a given path already exists"""
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
raise exceptions.PathNotFoundError(path)
|
raise exceptions.PathNotFoundError(path)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.path
|
return self.path
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user