From a71799ea2cbfa1354c786991a85b610753f5474c Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Thu, 11 Jul 2013 16:24:50 -0400 Subject: Updated to OpenStack Build stuff. --- .gitignore | 4 + .testr.conf | 8 + MANIFEST.in | 3 +- README | 4 +- TODO | 1 - doc/source/Makefile | 75 +++ doc/source/api.rst | 200 +++++++ doc/source/changelog.rst | 342 +++++++++++ doc/source/changeset.rst | 282 +++++++++ doc/source/conf.py | 203 +++++++ doc/source/credits.rst | 124 ++++ doc/source/download.rst | 71 +++ doc/source/faq.rst | 13 + doc/source/glossary.rst | 26 + .../ProjectDesignDecisionsAutomation.trac | 26 + .../ProjectDesignDecisionsScriptFormat.trac | 147 +++++ .../ProjectDesignDecisionsVersioning.trac | 56 ++ doc/source/historical/ProjectDetailedDesign.trac | 29 + doc/source/historical/ProjectGoals.trac | 50 ++ doc/source/historical/ProjectProposal.txt | 73 +++ doc/source/historical/RepositoryFormat.trac | 56 ++ doc/source/historical/RepositoryFormat2.trac | 28 + doc/source/index.rst | 188 ++++++ doc/source/theme/almodovar.css | 288 +++++++++ doc/source/theme/layout.css | 123 ++++ doc/source/theme/layout.html | 90 +++ doc/source/tools.rst | 15 + doc/source/versioning.rst | 641 +++++++++++++++++++++ docs/Makefile | 75 --- docs/api.rst | 200 ------- docs/changelog.rst | 342 ----------- docs/changeset.rst | 282 --------- docs/conf.py | 203 ------- docs/credits.rst | 124 ---- docs/download.rst | 71 --- docs/faq.rst | 13 - docs/glossary.rst | 26 - .../ProjectDesignDecisionsAutomation.trac | 26 - .../ProjectDesignDecisionsScriptFormat.trac | 147 ----- .../ProjectDesignDecisionsVersioning.trac | 56 -- docs/historical/ProjectDetailedDesign.trac | 29 - docs/historical/ProjectGoals.trac | 50 -- docs/historical/ProjectProposal.txt | 73 --- docs/historical/RepositoryFormat.trac | 56 -- docs/historical/RepositoryFormat2.trac | 28 - docs/index.rst | 188 ------ docs/theme/almodovar.css | 288 --------- docs/theme/layout.css | 123 ---- docs/theme/layout.html | 90 --- docs/tools.rst | 15 - docs/versioning.rst | 641 --------------------- migrate/tests/changeset/test_changeset.py | 42 +- migrate/tests/changeset/test_constraint.py | 26 +- migrate/tests/fixture/__init__.py | 4 +- migrate/tests/fixture/base.py | 12 +- migrate/tests/integrated/test_docs.py | 2 +- migrate/tests/versioning/test_genmodel.py | 17 +- migrate/tests/versioning/test_keyedinstance.py | 2 +- migrate/tests/versioning/test_repository.py | 37 +- migrate/tests/versioning/test_runchangeset.py | 18 +- migrate/tests/versioning/test_schema.py | 20 +- migrate/tests/versioning/test_schemadiff.py | 25 +- migrate/tests/versioning/test_script.py | 2 +- migrate/tests/versioning/test_shell.py | 75 ++- migrate/tests/versioning/test_util.py | 6 +- migrate/tests/versioning/test_version.py | 26 +- requirements.txt | 5 +- setup.cfg | 48 +- setup.py | 47 +- test-requirements.txt | 28 +- tox.ini | 4 +- 71 files changed, 3392 insertions(+), 3366 deletions(-) create mode 100644 .testr.conf create mode 100644 doc/source/Makefile create mode 100644 doc/source/api.rst create mode 100644 doc/source/changelog.rst create mode 100644 doc/source/changeset.rst create mode 100644 doc/source/conf.py create mode 100644 doc/source/credits.rst create mode 100644 doc/source/download.rst create mode 100644 doc/source/faq.rst create mode 100644 doc/source/glossary.rst create mode 100644 doc/source/historical/ProjectDesignDecisionsAutomation.trac create mode 100644 doc/source/historical/ProjectDesignDecisionsScriptFormat.trac create mode 100644 doc/source/historical/ProjectDesignDecisionsVersioning.trac create mode 100644 doc/source/historical/ProjectDetailedDesign.trac create mode 100644 doc/source/historical/ProjectGoals.trac create mode 100644 doc/source/historical/ProjectProposal.txt create mode 100644 doc/source/historical/RepositoryFormat.trac create mode 100644 doc/source/historical/RepositoryFormat2.trac create mode 100644 doc/source/index.rst create mode 100644 doc/source/theme/almodovar.css create mode 100644 doc/source/theme/layout.css create mode 100644 doc/source/theme/layout.html create mode 100644 doc/source/tools.rst create mode 100644 doc/source/versioning.rst delete mode 100644 docs/Makefile delete mode 100644 docs/api.rst delete mode 100644 docs/changelog.rst delete mode 100644 docs/changeset.rst delete mode 100644 docs/conf.py delete mode 100644 docs/credits.rst delete mode 100644 docs/download.rst delete mode 100644 docs/faq.rst delete mode 100644 docs/glossary.rst delete mode 100644 docs/historical/ProjectDesignDecisionsAutomation.trac delete mode 100644 docs/historical/ProjectDesignDecisionsScriptFormat.trac delete mode 100644 docs/historical/ProjectDesignDecisionsVersioning.trac delete mode 100644 docs/historical/ProjectDetailedDesign.trac delete mode 100644 docs/historical/ProjectGoals.trac delete mode 100644 docs/historical/ProjectProposal.txt delete mode 100644 docs/historical/RepositoryFormat.trac delete mode 100644 docs/historical/RepositoryFormat2.trac delete mode 100644 docs/index.rst delete mode 100644 docs/theme/almodovar.css delete mode 100644 docs/theme/layout.css delete mode 100644 docs/theme/layout.html delete mode 100644 docs/tools.rst delete mode 100644 docs/versioning.rst diff --git a/.gitignore b/.gitignore index 5a7c657..7432d66 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +AUTHORS +ChangeLog +.tox/* +.testrepository/* *.pyc *data/* *build/* diff --git a/.testr.conf b/.testr.conf new file mode 100644 index 0000000..748029b --- /dev/null +++ b/.testr.conf @@ -0,0 +1,8 @@ +[DEFAULT] +test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ + OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ + OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ + ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION + +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/MANIFEST.in b/MANIFEST.in index 0f1c471..5b83d74 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,8 @@ +include AUTHORS +include ChangeLog include README recursive-include docs * recursive-include migrate * recursive-include tests * global-exclude *pyc -exclude .hgtags recursive-exclude docs/_build * diff --git a/README b/README index c90cde5..19e7c13 100644 --- a/README +++ b/README @@ -34,9 +34,9 @@ To run automated tests: * Copy test_db.cfg.tmpl to test_db.cfg * Edit test_db.cfg with database connection strings suitable for running tests. (Use empty databases.) -* $ pip install -r test-req.pip +* $ pip install -r requirements.txt -r test-requirements.txt * $ python setup.py develop -* $ nosetests +* $ testr run --parallel Please report any issues with sqlalchemy-migrate to the issue tracker at `code.google.com issues diff --git a/TODO b/TODO index da88c94..46599c9 100644 --- a/TODO +++ b/TODO @@ -26,7 +26,6 @@ Unknown milestone - verbose output on migration failures - interactive migration script resolution? - backend for versioning management -- port to unittest2 Documentation updates in 0.6.1 - glossary diff --git a/doc/source/Makefile b/doc/source/Makefile new file mode 100644 index 0000000..dbc6dec --- /dev/null +++ b/doc/source/Makefile @@ -0,0 +1,75 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html web pickle htmlhelp latex changes linkcheck + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview over all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + +clean: + -rm -rf _build/* + +html: + mkdir -p _build/html _build/doctrees + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html + @echo + @echo "Build finished. The HTML pages are in _build/html." + +pickle: + mkdir -p _build/pickle _build/doctrees + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +web: pickle + +json: + mkdir -p _build/json _build/doctrees + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + mkdir -p _build/htmlhelp _build/doctrees + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in _build/htmlhelp." + +latex: + mkdir -p _build/latex _build/doctrees + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex + @echo + @echo "Build finished; the LaTeX files are in _build/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: + mkdir -p _build/changes _build/doctrees + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes + @echo + @echo "The overview file is in _build/changes." + +linkcheck: + mkdir -p _build/linkcheck _build/doctrees + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in _build/linkcheck/output.txt." diff --git a/doc/source/api.rst b/doc/source/api.rst new file mode 100644 index 0000000..6d5a54e --- /dev/null +++ b/doc/source/api.rst @@ -0,0 +1,200 @@ +Module :mod:`migrate.changeset` -- Schema changes +================================================= + +Module :mod:`migrate.changeset` -- Schema migration API +------------------------------------------------------- + +.. automodule:: migrate.changeset + :members: + :synopsis: Database changeset management + +Module :mod:`ansisql ` -- Standard SQL implementation +------------------------------------------------------------------------------------ + +.. automodule:: migrate.changeset.ansisql + :members: + :member-order: groupwise + :synopsis: Standard SQL implementation for altering database schemas + +Module :mod:`constraint ` -- Constraint schema migration API +--------------------------------------------------------------------------------------------- + +.. automodule:: migrate.changeset.constraint + :members: + :inherited-members: + :show-inheritance: + :member-order: groupwise + :synopsis: Standalone schema constraint objects + +Module :mod:`databases ` -- Database specific schema migration +----------------------------------------------------------------------------------------------- + +.. automodule:: migrate.changeset.databases + :members: + :synopsis: Database specific changeset implementations + +.. _mysql-d: + +Module :mod:`mysql ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. automodule:: migrate.changeset.databases.mysql + :members: + :synopsis: MySQL database specific changeset implementations + +.. _firebird-d: + +Module :mod:`firebird ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. automodule:: migrate.changeset.databases.firebird + :members: + :synopsis: Firebird database specific changeset implementations + +.. _oracle-d: + +Module :mod:`oracle ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. automodule:: migrate.changeset.databases.oracle + :members: + :synopsis: Oracle database specific changeset implementations + +.. _postgres-d: + +Module :mod:`postgres ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: migrate.changeset.databases.postgres + :members: + :synopsis: PostgreSQL database specific changeset implementations + +.. _sqlite-d: + +Module :mod:`sqlite ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: migrate.changeset.databases.sqlite + :members: + :synopsis: SQLite database specific changeset implementations + +Module :mod:`visitor ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: migrate.changeset.databases.visitor + :members: + +Module :mod:`schema ` -- Additional API to SQLAlchemy for migrations +---------------------------------------------------------------------------------------------- + +.. automodule:: migrate.changeset.schema + :members: + :synopsis: Schema changeset handling functions + + +Module :mod:`migrate.versioning` -- Database versioning and repository management +================================================================================== + +.. automodule:: migrate.versioning + :members: + :synopsis: Database version and repository management + +.. _versioning-api: + +Module :mod:`api ` -- Python API commands +----------------------------------------------------------------- + +.. automodule:: migrate.versioning.api + :members: + :synopsis: External API for :mod:`migrate.versioning` + + +Module :mod:`genmodel ` -- ORM Model generator +------------------------------------------------------------------------------------- + +.. automodule:: migrate.versioning.genmodel + :members: + :synopsis: Python database model generator and differencer + +Module :mod:`pathed ` -- Path utilities +---------------------------------------------------------------------------- + +.. automodule:: migrate.versioning.pathed + :members: + :synopsis: File/Directory handling class + +Module :mod:`repository ` -- Repository management +------------------------------------------------------------------------------------- + +.. automodule:: migrate.versioning.repository + :members: + :synopsis: SQLAlchemy migrate repository management + :member-order: groupwise + +Module :mod:`schema ` -- Migration upgrade/downgrade +---------------------------------------------------------------------------------- + +.. automodule:: migrate.versioning.schema + :members: + :member-order: groupwise + :synopsis: Database schema management + +Module :mod:`schemadiff ` -- ORM Model differencing +------------------------------------------------------------------------------------- + +.. automodule:: migrate.versioning.schemadiff + :members: + :synopsis: Database schema and model differencing + +Module :mod:`script ` -- Script actions +-------------------------------------------------------------------- + +.. automodule:: migrate.versioning.script.base + :synopsis: Script utilities + :member-order: groupwise + :members: + +.. automodule:: migrate.versioning.script.py + :members: + :member-order: groupwise + :inherited-members: + :show-inheritance: + +.. automodule:: migrate.versioning.script.sql + :members: + :member-order: groupwise + :show-inheritance: + :inherited-members: + +Module :mod:`shell ` -- CLI interface +------------------------------------------------------------------ + +.. automodule:: migrate.versioning.shell + :members: + :synopsis: Shell commands + +Module :mod:`util ` -- Various utility functions +-------------------------------------------------------------------------- + +.. automodule:: migrate.versioning.util + :members: + :synopsis: Utility functions + +Module :mod:`version ` -- Versioning management +----------------------------------------------------------------------------- + +.. automodule:: migrate.versioning.version + :members: + :member-order: groupwise + :synopsis: Version management + +Module :mod:`exceptions ` -- Exception definitions +====================================================================== + +.. automodule:: migrate.exceptions + :members: + :synopsis: Migrate exception classes + diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst new file mode 100644 index 0000000..a61b6b9 --- /dev/null +++ b/doc/source/changelog.rst @@ -0,0 +1,342 @@ +0.7.3 (201x-xx-xx) +--------------------------- + +Changes +****************** + +- + +Documentation +****************** + +- + +Features +****************** + +- + +Fixed Bugs +****************** + +- #140: excludeTablesgetDiffOfModelAgainstModel is not passing excludeTables + correctly (patch by Jason Michalski) +- #72: Regression against issue #38, migrate drops engine reference (patch by + asuffield@gmail.com) +- #154: versioning/schema.py imports deprecated sqlalchemy.exceptions (patch by + Alex Favaro) +- fix deprecation warning using MetaData.reflect instead of reflect=True + constructor argument +- fix test failure by removing unsupported length argument for Text column + +0.7.2 (2011-11-01) +--------------------------- + +Changes +****************** + +- support for SQLAlchemy 0.5.x has been dropped +- Python 2.6 is the minimum supported Python version + +Documentation +****************** + +- add :ref:`credits ` for contributors +- add :ref:`glossary ` +- improve :ref:`advice on testing production changes ` +- improve Sphinx markup +- refine :ref:`Database Schema Versioning ` texts, add + example for adding/droping columns (#104) +- add more developer related information to :ref:`development` section +- use sphinxcontrib.issuetracker to link to Google Code issue tracker + +Features +****************** + +- improved :pep:`8` compliance (#122) +- optionally number versions with timestamps instead of sequences (partly + pulled from Pete Keen) +- allow descriptions in SQL change script filenames (by Pete Keen) +- improved model generation + +Fixed Bugs +****************** + +- #83: api test downgrade/upgrade does not work with sql scripts (pulled from + Yuen Ho Wong) +- #105: passing a unicode string as the migrate repository fails (add + regression test) +- #113: make_update_script_for_model fails with AttributeError: 'SchemaDiff' + object has no attribute 'colDiffs' (patch by Jeremy Cantrell) +- #118: upgrade and downgrade functions are reversed when using the command + "make_update_script_for_model" (patch by Jeremy Cantrell) +- #121: manage.py should use the "if __name__=='__main__'" trick +- #123: column creation in make_update_script_for_model and required API change + (by Gabriel de Perthuis) +- #124: compare_model_to_db gets confused by sqlite_sequence (pulled from + Dustin J. Mitchell) +- #125: drop column does not work on persistent sqlite databases (pulled from + Benoît Allard) +- #128: table rename failure with sqlalchemy 0.7.x (patch by Mark McLoughlin) +- #129: update documentation and help text (pulled from Yuen Ho Wong) + +0.7.1 (2011-05-27) +--------------------------- + +Fixed Bugs +****************** + +- docs/_build is excluded from source tarball builds +- use table.append_column() instead of column._set_parent() in + ChangesetColumn.add_to_table() +- fix source and issue tracking URLs in documentation + +0.7 (2011-05-27) +--------------------------- + +Features +****************** + +- compatibility with SQLAlchemy 0.7 +- add :py:data:`migrate.__version__` + +Fixed bugs +****************** + +- fix compatibility issues with SQLAlchemy 0.7 + +0.6.1 (2011-02-11) +--------------------------- + +Features +****************** + +- implemented column adding when foreign keys are present for sqlite +- implemented columns adding with unique constraints for sqlite +- implemented adding unique and foreign key constraints to columns + for sqlite +- remove experimental `alter_metadata` parameter + +Fixed bugs +****************** + +- updated tests for Python 2.7 +- repository keyword in :py:func:`migrate.versioning.api.version_control` can + also be unicode +- added if main condition for manage.py script +- make :py:func:`migrate.changeset.constraint.ForeignKeyConstraint.autoname` + work with SQLAlchemy 0.5 and 0.6 +- fixed case sensitivity in setup.py dependencies +- moved :py:mod:`migrate.changeset.exceptions` and + :py:mod:`migrate.versioning.exceptions` to :py:mod:`migrate.exceptions` +- cleared up test output and improved testing of deprecation warnings. +- some documentation fixes +- #107: fixed syntax error in genmodel.py +- #96: fixed bug with column dropping in sqlite +- #94: fixed bug that prevented non-unique indexes being created +- fixed bug with column dropping involving foreign keys +- fixed bug when dropping columns with unique constraints in sqlite +- rewrite of the schema diff internals, now supporting column + differences in additon to missing columns and tables. +- fixed bug when passing empty list in + :py:func:`migrate.versioning.shell.main` failed +- #108: Fixed issues with firebird support. + +0.6 (11.07.2010) +--------------------------- + +.. _backwards-06: + +.. warning:: **Backward incompatible changes**: + + - :py:func:`migrate.versioning.api.test` and schema comparison functions + now all accept `url` as first parameter and `repository` as second. + - python upgrade/downgrade scripts do not import `migrate_engine` + magically, but recieve engine as the only parameter to function (eg. + ``def upgrade(migrate_engine):``) + - :py:meth:`Column.alter ` + does not accept `current_name` anymore, it extracts name from the old + column. + +Features +************** + +- added support for :ref:`firebird ` +- added option to define custom templates through option ``--templates_path`` + and ``--templates_theme``, + read more in :ref:`tutorial section ` +- use Python logging for output, can be shut down by passing + ``--disable_logging`` to :py:func:`migrate.versioning.shell.main` +- deprecated `alter_column` comparing of columns. Just use explicit parameter + change. +- added support for SQLAlchemy 0.6.x by Michael Bayer +- Constraint classes have `cascade=True` keyword argument to issue ``DROP + CASCADE`` where supported +- added :py:class:`~migrate.changeset.constraint.UniqueConstraint`/ + :py:class:`~migrate.changeset.constraint.CheckConstraint` and corresponding + create/drop methods +- API `url` parameter can also be an :py:class:`Engine` instance (this usage is + discouraged though sometimes necessary) +- code coverage is up to 80% with more than 100 tests +- alter, create, drop column / rename table / rename index constructs now + accept `alter_metadata` parameter. If True, it will modify Column/Table + objects according to changes. Otherwise, everything will be untouched. +- added `populate_default` bool argument to :py:meth:`Column.create + ` which issues corresponding + UPDATE statements to set defaults after column creation +- :py:meth:`Column.create ` + accepts `primary_key_name`, `unique_name` and `index_name` as string value + which is used as contraint name when adding a column + +Fixed bugs +***************** + +- :term:`ORM` methods now accept `connection` parameter commonly used for + transactions +- `server_defaults` passed to :py:meth:`Column.create + ` are now issued correctly +- use SQLAlchemy quoting system to avoid name conflicts (#32) +- complete refactoring of :py:class:`~migrate.changeset.schema.ColumnDelta` + (#23) +- partial refactoring of :py:mod:`migrate.changeset` package +- fixed bug when :py:meth:`Column.alter + `\(server_default='string') + was not properly set +- constraints passed to :py:meth:`Column.create + ` are correctly interpreted + (``ALTER TABLE ADD CONSTRAINT`` is issued after ``ATLER TABLE ADD COLUMN``) +- script names don't break with dot in the name + +Documentation +********************* + +- :ref:`dialect support ` table was added to documentation +- major update to documentation + + +0.5.4 +----- + +- fixed preview_sql parameter for downgrade/upgrade. Now it prints SQL if the step is SQL script and runs step with mocked engine to only print SQL statements if ORM is used. [Domen Kozar] +- use entrypoints terminology to specify dotted model names (module.model:User) [Domen Kozar] +- added engine_dict and engine_arg_* parameters to all api functions (deprecated echo) [Domen Kozar] +- make --echo parameter a bit more forgivable (better Python API support) [Domen Kozar] +- apply patch to refactor cmd line parsing for Issue 54 by Domen Kozar + +0.5.3 +----- + +- apply patch for Issue 29 by Jonathan Ellis +- fix Issue 52 by removing needless parameters from object.__new__ calls + +0.5.2 +----- + +- move sphinx and nose dependencies to extras_require and tests_require +- integrate patch for Issue 36 by Kumar McMillan +- fix unit tests +- mark ALTER TABLE ADD COLUMN with FOREIGN KEY as not supported by SQLite + +0.5.1.2 +------- + +- corrected build + +0.5.1.1 +------- + +- add documentation in tarball +- add a MANIFEST.in + +0.5.1 +----- + +- SA 0.5.x support. SQLAlchemy < 0.5.1 not supported anymore. +- use nose instead of py.test for testing +- Added --echo=True option for all commands, which will make the sqlalchemy connection echo SQL statements. +- Better PostgreSQL support, especially for schemas. +- modification to the downgrade command to simplify the calling (old way still works just fine) +- improved support for SQLite +- add support for check constraints (EXPERIMENTAL) +- print statements removed from APIs +- improved sphinx based documentation +- removal of old commented code +- :pep:`8` clean code + +0.4.5 +----- + +- work by Christian Simms to compare metadata against databases +- new repository format +- a repository format migration tool is in migrate/versioning/migrate_repository.py +- support for default SQL scripts +- EXPERIMENTAL support for dumping database to model + +0.4.4 +----- + +- patch by pwannygoodness for Issue #15 +- fixed unit tests to work with py.test 0.9.1 +- fix for a SQLAlchemy deprecation warning + +0.4.3 +----- + +- patch by Kevin Dangoor to handle database versions as packages and ignore their __init__.py files in version.py +- fixed unit tests and Oracle changeset support by Christian Simms + +0.4.2 +----- + +- package name is sqlalchemy-migrate again to make pypi work +- make import of sqlalchemy's SchemaGenerator work regardless of previous imports + +0.4.1 +----- + +- setuptools patch by Kevin Dangoor +- re-rename module to migrate + +0.4.0 +----- + +- SA 0.4.0 compatibility thanks to Christian Simms +- all unit tests are working now (with sqlalchemy >= 0.3.10) + +0.3 +--- + +- SA 0.3.10 compatibility + +0.2.3 +----- + +- Removed lots of SA monkeypatching in Migrate's internals +- SA 0.3.3 compatibility +- Removed logsql (trac issue 75) +- Updated py.test version from 0.8 to 0.9; added a download link to setup.py +- Fixed incorrect "function not defined" error (trac issue 88) +- Fixed SQLite and .sql scripts (trac issue 87) + +0.2.2 +----- + +- Deprecated driver(engine) in favor of engine.name (trac issue 80) +- Deprecated logsql (trac issue 75) +- Comments in .sql scripts don't make things fail silently now (trac issue 74) +- Errors while downgrading (and probably other places) are shown on their own line +- Created mailing list and announcements list, updated documentation accordingly +- Automated tests now require py.test (trac issue 66) +- Documentation fix to .sql script commits (trac issue 72) +- Fixed a pretty major bug involving logengine, dealing with commits/tests (trac issue 64) +- Fixes to the online docs - default DB versioning table name (trac issue 68) +- Fixed the engine name in the scripts created by the command 'migrate script' (trac issue 69) +- Added Evan's email to the online docs + +0.2.1 +----- + +- Created this changelog +- Now requires (and is now compatible with) SA 0.3 +- Commits across filesystems now allowed (shutil.move instead of os.rename) (trac issue 62) diff --git a/doc/source/changeset.rst b/doc/source/changeset.rst new file mode 100644 index 0000000..1ae070e --- /dev/null +++ b/doc/source/changeset.rst @@ -0,0 +1,282 @@ +.. _changeset-system: +.. highlight:: python + +************************** +Database schema migrations +************************** + +.. currentmodule:: migrate.changeset.schema + +Importing :mod:`migrate.changeset` adds some new methods to existing SQLAlchemy +objects, as well as creating functions of its own. Most operations can be done +either by a method or a function. Methods match SQLAlchemy's existing API and +are more intuitive when the object is available; functions allow one to make +changes when only the name of an object is available (for example, adding a +column to a table in the database without having to load that table into +Python). + +Changeset operations can be used independently of SQLAlchemy Migrate's +:ref:`versioning `. + +For more information, see the API documentation for :mod:`migrate.changeset`. + +.. _summary-changeset-api: + +Here are some direct links to the relevent sections of the API documentations: + + +* :meth:`Create a column ` +* :meth:`Drop a column ` +* :meth:`Alter a column ` (follow a link for list of supported changes) +* :meth:`Rename a table ` +* :meth:`Rename an index ` +* :meth:`Create primary key constraint ` +* :meth:`Drop primary key constraint ` +* :meth:`Create foreign key contraint ` +* :meth:`Drop foreign key constraint ` +* :meth:`Create unique key contraint ` +* :meth:`Drop unique key constraint ` +* :meth:`Create check key contraint ` +* :meth:`Drop check key constraint ` + + +.. note:: + + Many of the schema modification methods above take an ``alter_metadata`` + keyword parameter. This parameter defaults to `True`. + +The following sections give examples of how to make various kinds of schema +changes. + +Column +====== + +Given a standard SQLAlchemy table: + +.. code-block:: python + + table = Table('mytable', meta, + Column('id', Integer, primary_key=True), + ) + table.create() + +.. _column-create: + +You can create a column with :meth:`~ChangesetColumn.create`: + +.. code-block:: python + + col = Column('col1', String, default='foobar') + col.create(table, populate_default=True) + + # Column is added to table based on its name + assert col is table.c.col1 + + # col1 is populated with 'foobar' because of `populate_default` + +.. _column-drop: + +.. note:: + + You can pass `primary_key_name`, `index_name` and `unique_name` to the + :meth:`~ChangesetColumn.create` method to issue ``ALTER TABLE ADD + CONSTRAINT`` after changing the column. + + For multi columns constraints and other advanced configuration, check the + :ref:`constraint tutorial `. + + .. versionadded:: 0.6.0 + +You can drop a column with :meth:`~ChangesetColumn.drop`: + +.. code-block:: python + + col.drop() + + +.. _column-alter: + +You can alter a column with :meth:`~ChangesetColumn.alter`: + +.. code-block:: python + + col.alter(name='col2') + + # Renaming a column affects how it's accessed by the table object + assert col is table.c.col2 + + # Other properties can be modified as well + col.alter(type=String(42), default="life, the universe, and everything", nullable=False) + + # Given another column object, col1.alter(col2), col1 will be changed to match col2 + col.alter(Column('col3', String(77), nullable=True)) + assert col.nullable + assert table.c.col3 is col + +.. deprecated:: 0.6.0 + Passing a :class:`~sqlalchemy.schema.Column` to + :meth:`ChangesetColumn.alter` is deprecated. Pass in explicit + parameters, such as `name` for a new column name and `type` for a + new column type, instead. Do **not** include any parameters that + are not changed. + +.. _table-rename: + +Table +===== + +SQLAlchemy includes support for `creating and dropping`__ tables.. + +Tables can be renamed with :meth:`~ChangesetTable.rename`: + +.. code-block:: python + + table.rename('newtablename') + +.. __: http://www.sqlalchemy.org/docs/core/schema.html#creating-and-dropping-database-tables +.. currentmodule:: migrate.changeset.constraint + + +.. _index-rename: + +Index +===== + +SQLAlchemy supports `creating and dropping`__ indexes. + +Indexes can be renamed using +:meth:`~migrate.changeset.schema.ChangesetIndex.rename`: + +.. code-block:: python + + index.rename('newindexname') + +.. __: http://www.sqlalchemy.org/docs/core/schema.html#indexes + + +.. _constraint-tutorial: + +Constraint +========== + +.. currentmodule:: migrate.changeset.constraint + +SQLAlchemy supports creating or dropping constraints at the same time a table +is created or dropped. SQLAlchemy Migrate adds support for creating and +dropping :class:`~sqlalchemy.schema.PrimaryKeyConstraint`, +:class:`~sqlalchemy.schema.ForeignKeyConstraint`, +:class:`~sqlalchemy.schema.CheckConstraint` and +:class:`~sqlalchemy.schema.UniqueConstraint` constraints independently using +``ALTER TABLE`` statements. + +The following rundowns are true for all constraints classes: + +#. Make sure you import the relevant constraint class from :mod:`migrate` and + not from :mod:`sqlalchemy`, for example: + + .. code-block:: python + + from migrate.changeset.constraint import ForeignKeyConstraint + + The classes in that module have the extra + :meth:`~ConstraintChangeset.create` and :meth:`~ConstraintChangeset.drop` + methods. + +#. You can also use constraints as in SQLAlchemy. In this case passing table + argument explicitly is required: + + .. code-block:: python + + cons = PrimaryKeyConstraint('id', 'num', table=self.table) + + # Create the constraint + cons.create() + + # Drop the constraint + cons.drop() + + You can also pass in :class:`~sqlalchemy.schema.Column` objects (and table + argument can be left out): + + .. code-block:: python + + cons = PrimaryKeyConstraint(col1, col2) + +#. Some dialects support ``CASCADE`` option when dropping constraints: + + .. code-block:: python + + cons = PrimaryKeyConstraint(col1, col2) + + # Create the constraint + cons.create() + + # Drop the constraint + cons.drop(cascade=True) + +.. note:: + SQLAlchemy Migrate will try to guess the name of the constraints for + databases, but if it's something other than the default, you'll need to + give its name. Best practice is to always name your constraints. Note that + Oracle requires that you state the name of the constraint to be created or + dropped. + + +Examples +--------- + +Primary key constraints: + +.. code-block:: python + + from migrate.changeset.constraint import PrimaryKeyConstraint + + cons = PrimaryKeyConstraint(col1, col2) + + # Create the constraint + cons.create() + + # Drop the constraint + cons.drop() + +Foreign key constraints: + +.. code-block:: python + + from migrate.changeset.constraint import ForeignKeyConstraint + + cons = ForeignKeyConstraint([table.c.fkey], [othertable.c.id]) + + # Create the constraint + cons.create() + + # Drop the constraint + cons.drop() + +Check constraints: + +.. code-block:: python + + from migrate.changeset.constraint import CheckConstraint + + cons = CheckConstraint('id > 3', columns=[table.c.id]) + + # Create the constraint + cons.create() + + # Drop the constraint + cons.drop() + +Unique constraints: + +.. code-block:: python + + from migrate.changeset.constraint import UniqueConstraint + + cons = UniqueConstraint('id', 'age', table=self.table) + + # Create the constraint + cons.create() + + # Drop the constraint + cons.drop() diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 0000000..f8f609c --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,203 @@ +# -*- coding: utf-8 -*- +# +# SQLAlchemy Migrate documentation build configuration file, created by +# sphinx-quickstart on Fri Feb 13 12:58:57 2009. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If your extensions are in another directory, add it here. If the directory +# is relative to the documentation root, use os.path.abspath to make it +# absolute, like shown here. +#sys.path.append(os.path.abspath('.')) +# Allow module docs to build without having sqlalchemy-migrate installed: +sys.path.append(os.path.dirname(os.path.abspath('.'))) + +# General configuration +# --------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinxcontrib.issuetracker'] + +# link to sqlalchemy docs +intersphinx_mapping = { + 'sqlalchemy': ('http://www.sqlalchemy.org/docs/', None), + 'python': ('http://docs.python.org/2.7', None)} + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'SQLAlchemy Migrate' +copyright = u'2011, Evan Rosson, Jan Dittberner, Domen Kožar, Chris Withers' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.7.3' +# The full version, including alpha/beta/rc tags. +release = '0.7.3.dev' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# Options for sphinxcontrib.issuetracker +# -------------------------------------- + +issuetracker = 'google code' +issuetracker_project = 'sqlalchemy-migrate' + + +# Options for HTML output +# ----------------------- + +# The style sheet to use for HTML and HTML Help pages. A file of that name +# must exist either in Sphinx' static/ path, or in one of the custom paths +# given in html_static_path. +html_style = 'default.css' + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_use_modindex = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, the reST sources are included in the HTML build as _sources/. +#html_copy_source = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'SQLAlchemyMigratedoc' + + +# Options for LaTeX output +# ------------------------ + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, document class [howto/manual]). +latex_documents = [ + ('index', 'SQLAlchemyMigrate.tex', ur'SQLAlchemy Migrate Documentation', + ur'Evan Rosson, Jan Dittberner, Domen Kožar', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/doc/source/credits.rst b/doc/source/credits.rst new file mode 100644 index 0000000..a2d0648 --- /dev/null +++ b/doc/source/credits.rst @@ -0,0 +1,124 @@ +.. _credits: + +Credits +------- + +sqlalchemy-migrate has been created by: + + - Evan Rosson + +Thanks to Google for sponsoring Evan's initial Summer of Code project. + +The project is maintained by the following people: + + - Domen Kožar + - Jan Dittberner + +The following people contributed patches, advice or bug reports that helped +improve sqlalchemy-migrate: + +.. hlist:: + :columns: 3 + + - Adam Lowry + - Adomas Paltanavicius + - Alexander Artemenko + - Alex Favaro + - Andrew Bialecki + - Andrew Grossman + - Andrew Lenards + - Andrew Svetlov + - Andrey Gladilin + - Andronikos Nedos + - Antoine Pitrou + - Ben Hesketh + - Ben Keroack + - Benjamin Johnson + - Branko Vukelic + - Bruno Lopes + - Ches Martin + - Chris Percious + - Chris Withers + - Christian Simms + - Christophe de Vienne + - Christopher Grebs + - Christopher Lee + - Dan Getelman + - David Kang + - Dustin J. Mitchell + - Emil Kroymann + - Eyal Sorek + - Florian Apolloner + - Fred Lin + - Gabriel de Perthuis + - Graham Higgins + - Ilya Shabalin + - James Mills + - Jarrod Chesney + - Jason Michalski + - Jason R. Coombs + - Jason Yamada-Hanff + - Jay Pipes + - Jayson Vantuyl + - Jeremy Cantrell + - Jeremy Slade + - Jeroen Ruigrok van der Werven + - Joe Heck + - Jonas Baumann + - Jonathan Ellis + - Jorge Vargas + - Joshua Ginsberg + - Jude Nagurney + - Juliusz Gonera + - Kevin Dangoor + - Kristaps Rāts + - Kristian Kvilekval + - Kumar McMillan + - Landon J. Fuller + - Lev Shamardin + - Lorin Hochstein + - Luca Barbato + - Lukasz Zukowski + - Mahmoud Abdelkader + - Marica Odagaki + - Marius Gedminas + - Mark Friedenbach + - Mark McLoughlin + - Martin Andrews + - Mathieu Leduc-Hamel + - Michael Bayer + - Michael Elsdörfer + - Mikael Lepistö + - Nathan Wright + - Nevare Stark + - Nicholas Retallack + - Nick Barendt + - Patrick Shields + - Paul Bonser + - Paul Johnston + - Pawel Bylina + - Pedro Algarvio + - Peter Strömberg + - Poli García + - Pradeep Kumar + - Rafał Kos + - Robert Forkel + - Robert Schiele + - Robert Sudwarts + - Romy Maxwell + - Ryan Wilcox + - Sami Dalouche + - Sergiu Toarca + - Simon Engledew + - Stephen Emslie + - Sylvain Prat + - Toshio Kuratomi + - Trey Stout + - Vasiliy Astanin + - Yeeland Chen + - Yuen Ho Wong + + - asuffield (at) gmail (dot) com + +If you helped us in the past and miss your name please tell us about your +contribution and we will add you to the list. diff --git a/doc/source/download.rst b/doc/source/download.rst new file mode 100644 index 0000000..d2d66de --- /dev/null +++ b/doc/source/download.rst @@ -0,0 +1,71 @@ +Download +-------- + +You can get the latest version of SQLAlchemy Migrate from the +`project's download page`_, the `cheese shop`_, pip_ or via easy_install_:: + + $ easy_install sqlalchemy-migrate + +or:: + + $ pip install sqlalchemy-migrate + +You should now be able to use the :command:`migrate` command from the command +line:: + + $ migrate + +This should list all available commands. To get more information regarding a +command use:: + + $ migrate help COMMAND + +If you'd like to be notified when new versions of SQLAlchemy Migrate +are released, subscribe to `migrate-announce`_. + +.. _pip: http://pip.openplans.org/ +.. _easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall#installing-easy-install +.. _sqlalchemy: http://www.sqlalchemy.org/download.html +.. _`project's download page`: http://code.google.com/p/sqlalchemy-migrate/downloads/list +.. _`cheese shop`: http://pypi.python.org/pypi/sqlalchemy-migrate +.. _`migrate-announce`: http://groups.google.com/group/migrate-announce + +.. _development: + +Development +----------- + +Migrate's Mercurial_ repository is located at `Google Code`_. + +To get the latest trunk:: + + $ hg clone http://sqlalchemy-migrate.googlecode.com/hg/ + +Patches should be submitted to the `issue tracker`_. You are free to create +your own clone to provide your patches. We are open to pull requests in our +`issue tracker`_. + +If you want to work on sqlalchemy-migrate you might want to use a `virtualenv`. + +To run the included test suite you have to copy :file:`test_db.cfg.tmpl` to +:file:`test_db.cfg` and put SQLAlchemy database URLs valid for your environment +into that file. We use `nose`_ for our tests and include a test requirements +file for pip. You might use the following commands to install the test +requirements and run the tests:: + + $ pip install -r test-req.pip + $ python setup.py develop + $ python setup.py nosetests + +If you are curious about status changes of sqlalchemy-migrate's issues you +might want to subscribe to `sqlalchemy-migrate-issues`_. + +We use a `Jenkins CI`_ continuous integration tool installation to +help us run tests on most of the databases that migrate supports. + +.. _Mercurial: http://www.mercurial-scm.org/ +.. _Google Code: http://sqlalchemy-migrate.googlecode.com/hg/ +.. _issue tracker: http://code.google.com/p/sqlalchemy-migrate/issues/list +.. _sqlalchemy-migrate-issues: http://groups.google.com/group/sqlalchemy-migrate-issues +.. _Jenkins CI: http://jenkins.gnuviech-server.de/job/sqlalchemy-migrate-all/ +.. _nose: http://readthedocs.org/docs/nose/ diff --git a/doc/source/faq.rst b/doc/source/faq.rst new file mode 100644 index 0000000..a36073c --- /dev/null +++ b/doc/source/faq.rst @@ -0,0 +1,13 @@ +FAQ +=== + +Q: Adding a **nullable=False** column +************************************** + +A: Your table probably already contains data. That means if you add column, it's contents will be NULL. +Thus adding NOT NULL column restriction will trigger IntegrityError on database level. + +You have basically two options: + +#. Add the column with a default value and then, after it is created, remove the default value property. This does not work for column types that do not allow default values at all (such as 'text' and 'blob' on MySQL). +#. Add the column without NOT NULL so all rows get a NULL value, UPDATE the column to set a value for all rows, then add the NOT NULL property to the column. This works for all column types. diff --git a/doc/source/glossary.rst b/doc/source/glossary.rst new file mode 100644 index 0000000..cee01c7 --- /dev/null +++ b/doc/source/glossary.rst @@ -0,0 +1,26 @@ +.. _glossary: + +******** +Glossary +******** + +.. glossary:: + :sorted: + + repository + A migration repository contains :command:`manage.py`, a configuration + file (:file:`migrate.cfg`) and the database :term:`changeset` scripts + which can be Python scripts or SQL files. + + changeset + A set of instructions how upgrades and downgrades to or from a specific + version of a database schema should be performed. + + ORM + Abbreviation for "object relational mapper". An ORM is a tool that maps + object hierarchies to database relations. + + version + A version in SQLAlchemy migrate is defined by a :term:`changeset`. + Versions may be numbered using ascending numbers or using timestamps + (as of SQLAlchemy migrate release 0.7.2) diff --git a/doc/source/historical/ProjectDesignDecisionsAutomation.trac b/doc/source/historical/ProjectDesignDecisionsAutomation.trac new file mode 100644 index 0000000..2f1c8b7 --- /dev/null +++ b/doc/source/historical/ProjectDesignDecisionsAutomation.trac @@ -0,0 +1,26 @@ +There are many migrations that don't require a lot of thought - for example, if we add a column to a table definition, we probably want to have an "ALTER TABLE...ADD COLUMN" statement show up in our migration. + +The difficulty lies in the automation of changes where the requirements aren't obvious. What happens when you add a unique constraint to a column whose data is not already unique? What happens when we split an existing table in two? Completely automating database migrations is not possible. + +That said - we shouldn't have to hunt down and handwrite the ALTER TABLE statements for every new column; this is often just tedious. Many other common migration tasks require little serious thought; such tasks are ripe for automation. Any automation attempted, however, should not interfere with our ability to write scripts by hand if we so choose; our tool should ''not'' be centered around automation. + + +Automatically generating the code for this sort of task seems like a good solution: + * It does not obstruct us from writing changes by hand; if we don't like the autogenerated code, delete it or don't generate it to begin with + * We can easily add other migration tasks to the autogenerated code + * We can see right away if the code is what we're expecting, or if it's wrong + * If the generated code is wrong, it is easily modified; we can use parts of the generated code, rather than being required to use either 100% or 0% + * Maintence, usually a problem with auto-generated code, is not an issue: old database migration scripts are not the subject of maintenance; the correct solution is usually a new migration script. + + +Implementation is a problem: finding the 'diff' of two databases to determine what columns to add is not trivial. Fortunately, there exist tools that claim to do this for us: [http://sqlfairy.sourceforge.net/ SQL::Translator] and [http://xml2ddl.berlios.de/ XML to DDL] both claim to have this capability. + +... + +All that said, this is ''not'' something I'm going to attempt during the Summer of Code. + * I'd have to rely tremendously on a tool I'm not at all familiar with + * Creates a risk of the project itself relying too much on the automation, a Bad Thing + * The project has a deadline and I have plenty else to do already + * Lots of people with more experience than me say this would take more time than it's worth + +It's something that might be considered for future work if this project is successful, though. \ No newline at end of file diff --git a/doc/source/historical/ProjectDesignDecisionsScriptFormat.trac b/doc/source/historical/ProjectDesignDecisionsScriptFormat.trac new file mode 100644 index 0000000..b23c2d4 --- /dev/null +++ b/doc/source/historical/ProjectDesignDecisionsScriptFormat.trac @@ -0,0 +1,147 @@ +Important to our system is the API used for making database changes. + +=== Raw SQL; .sql script === +Require users to write raw SQL. Migration scripts are .sql scripts (with database version information in a header comment). + ++ Familiar interface for experienced DBAs. + ++ No new API to learn[[br]] +SQL is used elsewhere; many people know SQL already. Those who are still learning SQL will gain expertise not in the API of a specific tool, but in a language which will help them elsewhere. (On the other hand, those who are familiar with Python with no desire to learn SQL might find a Python API more intuitive.) + +- Difficult to extend when necessary[[br]] +.sql scripts mean that we can't write new functions specific to our migration system when necessary. (We can't always assume that the DBMS supports functions/procedures.) + +- Lose the power of Python[[br]] +Some things are possible in Python that aren't in SQL - for example, suppose we want to use some functions from our application in a migration script. (The user might also simply prefer Python.) + +- Loss of database independence.[[br]] +There isn't much we can do to specify different actions for a particular DBMS besides copying the .sql file, which is obviously bad form. + +=== Raw SQL; Python script === +Require users to write raw SQL. Migration scripts are python scripts whose API does little beyond specifying what DBMS(es) a particular statement should apply to. + +For example, +{{{ +run("CREATE TABLE test[...]") # runs for all databases +run("ALTER TABLE test ADD COLUMN varchar2[...]",oracle) # runs for Oracle only +run("ALTER TABLE test ADD COLUMN varchar[...]",postgres|mysql) # runs for Postgres or MySQL only +}}} + +We could also allow parts of a single statement to apply to a specific DBMS: +{{{ +run("ALTER TABLE test ADD COLUMN"+sql("varchar",postgres|mysql)+sql("varchar2",oracle)) +}}} +or, the same thing: +{{{ +run("ALTER TABLE test ADD COLUMN"+sql("varchar",postgres|mysql,"varchar2",oracle)) +}}} + ++ Allows the user to write migration scripts for multiple DBMSes. + +- The user must manage the conflicts between different databases themselves. [[br]] +The user can write scripts to deal with conflicts between databases, but they're not really database-independent: the user has to deal with conflicts between databases; our system doesn't help them. + ++ Minimal new API to learn. [[br]] +There is a new API to learn, but it is extremely small, depending mostly on SQL DDL. This has the advantages of "no new API" in our first solution. + +- More verbose than .sql scripts. + +=== Raw SQL; automatic translation between each dialect === +Same as the above suggestion, but allow the user to specify a 'default' dialect of SQL that we'll interpret and whose quirks we'll deal with. +That is, write everything in SQL and try to automatically resolve the conflicts of different DBMSes. + +For example, take the following script: +{{{ +engine=postgres + +run(""" +CREATE TABLE test ( + id serial +) +""") +}}} +Running this on a Postgres database, surprisingly enough, would generate exactly what we typed: +{{{ +CREATE TABLE test ( + id serial +) +}}} + +Running it on a MySQL database, however, would generate something like +{{{ +CREATE TABLE test ( + id integer auto_increment +) +}}} + ++ Database-independence issues of the above SQL solutions are resolved.[[br]] +Ideally, this solution would be as database-independent as a Python API for database changes (discussed next), but with all the advantages of writing SQL (no new API). + +- Difficult implementation[[br]] +Obviously, this is not easy to implement - there is a great deal of parsing logic and a great many things that need to be accounted for. In addition, this is a complex operation; any implementation will likely have errors somewhere. + +It seems tools for this already exist; an effective tool would trivialize this implementation. I experimented a bit with [http://sqlfairy.sourceforge.net/ SQL::Translator] and [http://xml2ddl.berlios.de/ XML to DDL]; however, I had difficulties with both. + +- Database-specific features ensure that this cannot possibly be "complete". [[br]] +For example, Postgres has an 'interval' type to represent times and (AFAIK) MySQL does not. + +=== Database-independent Python API === +Create a Python API through which we may manage database changes. Scripts would be based on the existing SQLAlchemy API when possible. + +Scripts would look something like +{{{ +# Create a table +test_table = table('test' + ,Column('id',Integer,notNull=True) +) +table.create() +# Add a column to an existing table +test_table.add_column('id',Integer,notNull=True) +# Or, use a column object instead of its parameters +test_table.add_column(Column('id',Integer,notNull=True)) +# Or, don't use a table object at all +add_column('test','id',Integer,notNull=True) +}}} +This would use engines, similar to SQLAlchemy's, to deal with database-independence issues. + +We would, of course, allow users to write raw SQL if they wish. This would be done in the manner outlined in the second solution above; this allows us to write our entire script in SQL and ignore the Python API if we wish, or write parts of our solution in SQL to deal with specific databases. + ++ Deals with database-independence thoroughly and with minimal user effort.[[br]] +SQLAlchemy-style engines would be used for this; issues of different DBMS syntax are resolved with minimal user effort. (Database-specific features would still need handwritten SQL.) + ++ Familiar interface for SQLAlchemy users.[[br]] +In addition, we can often cut-and-paste column definitions from SQLAlchemy tables, easing one particular task. + +- Requires that the user learn a new API. [[br]] +SQL already exists; people know it. SQL newbies might be more comfortable with a Python interface, but folks who already know SQL must learn a whole new API. (On the other hand, the user *can* write things in SQL if they wish, learning only the most minimal of APIs, if they are willing to resolve issues of database-independence themself.) + +- More difficult to implement than pure SQL solutions. [[br]] +SQL already exists/has been tested. A new Python API does not/has not, and much of the work seems to consist of little more than reinventing the wheel. + +- Script behavior might change under different versions of the project.[[br]] +...where .sql scripts behave the same regardless of the project's version. + +=== Generate .sql scripts from a Python API === +Attempts to take the best of the first and last solutions. An API similar to the previous solution would be used, but rather than immediately being applied to the database, .sql scripts are generated for each type of database we're interested in. These .sql scripts are what's actually applied to the database. + +This would essentially allow users to skip the Python script step entirely if they wished, and write migration scripts in SQL instead, as in solution 1. + ++ Database-independence is an option, when needed. + ++ A familiar interface/an interface that can interact with other tools is an option, when needed. + ++ Easy to inspect the SQL generated by a script, to ensure it's what we're expecting. + ++ Migration scripts won't change behavior across different versions of the project. [[br]] +Once a Python script is translated to a .sql script, its behavior is consistent across different versions of the project, unlike a pure Python solution. + +- Multiple ways to do a single task: not Pythonic.[[br]] +I never really liked that word - "Pythonic" - but it does apply here. Multiple ways to do a single task has the potential to cause confusion, especially in a large project if many people do the same task different ways. We have to support both ways of doing things, as well. + +---- + +'''Conclusion''': The last solution, generating .sql scripts from a Python API, seems to be best. + +The first solution (.sql scripts) suffers from a lack of database-independence, but is familiar to experienced database developers, useful with other tools, and shows exactly what will be done to the database. The Python API solution has no trouble with database-independence, but suffers from other problems that the .sql solution doesn't. The last solution resolves both reasonably well. Multiple ways to do a single task might be called "not Pythonic", but IMO, the trade-off is worth this cost. + +Automatic translation between different dialects of SQL might have potential for use in a solution, but existing tools for this aren't reliable enough, as far as I can tell. \ No newline at end of file diff --git a/doc/source/historical/ProjectDesignDecisionsVersioning.trac b/doc/source/historical/ProjectDesignDecisionsVersioning.trac new file mode 100644 index 0000000..f06ba18 --- /dev/null +++ b/doc/source/historical/ProjectDesignDecisionsVersioning.trac @@ -0,0 +1,56 @@ +An important aspect of this project is database versioning. For migration scripts to be most useful, we need to know what version the database is: that is, has a particular migration script already been run? + +An option not discussed below is "no versioning"; that is, simply apply any script we're given, and rely on the user to ensure it's valid. This is entirely too error-prone to seriously consider, and takes a lot of the usefulness out of the proposed tool. + + +=== Database-wide version numbers === +A single integer version number would specify the version of each database. This is stored in the database in a table, let's call it "schema"; each migration script is associated with a certain database version number. + ++ Simple implementation[[br]] +Of the 3 solutions presented here, this one is by far the simplest. + ++ Past success[[br]] +Used in [http://www.rubyonrails.org/ Ruby on Rails' migrations]. + +~ Can detect corrupt schemas, but requires some extra work and a *complete* set of migrations.[[br]] +If we have a set of database migration scripts that build the database from the ground up, we can apply them in sequence to a 'dummy' database, dump a diff of the real and dummy schemas, and expect a valid schema to match the dummy schema. + +- Requires changes to the database schema.[[br]] +Not a tremendous change - a single table with a single column and a single row - but a change nonetheless. + +=== Table/object-specific version numbers === +Each database "object" - usually tables, though we might also deal with other database objects, such as stored procedures or Postgres' sequences - would have a version associated with it, initially 1. These versions are stored in a table, let's call it "schema". This table has two columns: the name of the database object and its current version number. + ++ Allows us to write migration scripts for a subset of the database.[[br]] +If we have multiple people working on a very large database, we may want to write migration scripts for a section of the database without stepping on another person's work. This allows unrelated to + +- Requires changes to the database schema. +Similar to the database-wide version number; the contents of the new table are more complex, but still shouldn't conflict with anything. + +- More difficult to implement than a database-wide version number. + +- Determining the version of database-specific objects (ie. stored procedures, functions) is difficult. + +- Ultimately gains nothing over the previous solution.[[br]] +The intent here was to allow multiple people to write scripts for a single database, but if database-wide version numbers aren't assigned until the script is placed in the repository, we could already do this. + +=== Version determined via introspection === +Each script has a schema associated with it, rather than a version number. The database schema is loaded, analyzed, and compared to the schema expected by the script. + ++ No modifications to the database are necessary for this versioning system.[[br]] +The primary advantage here is that no changes to the database are required. + +- Most difficult solution to implement, by far.[[br]] +Comparing the state of every schema object in the database is much more complex than simply comparing a version number, especially since we need to do it in a database-independent way (ie. we can't just diff the dump of each schema). SQLAlchemy's reflection would certainly be very helpful, but this remains the most complex solution. + ++ "Automatically" detects corrupt schemas.[[br]] +A corrupt schema won't match any migration script. + +- Difficult to deal with corrupt schemas.[[br]] +When version numbers are stored in the database, you have some idea of where an error occurred. Without this, we have no idea what version the database was in before corruption. + +- Potential ambiguity: what if two database migration scripts expect the same schema? + +---- + +'''Conclusion''': database-wide version numbers are the best way to go. \ No newline at end of file diff --git a/doc/source/historical/ProjectDetailedDesign.trac b/doc/source/historical/ProjectDetailedDesign.trac new file mode 100644 index 0000000..e295251 --- /dev/null +++ b/doc/source/historical/ProjectDetailedDesign.trac @@ -0,0 +1,29 @@ +This is very much a draft/brainstorm right now. It should be made prettier and thought about in more detail later, but it at least gives some idea of the direction we're headed right now. +---- + * Two distinct tools; should not be coupled (can work independently): + * Versioning tool + * Command line tool; let's call it "samigrate" + * Organizes old migration scripts into repositories + * Runs groups of migration scripts on a database, updating it to a specified version/latest version + * Helps run various tests + * usage + * "samigrate create PATH": Create project migration-script repository + * We shouldn't have to enter the path for every other command. Use a hidden file + * (This means we can't move the repository after it's created. Oh well) + * "samigrate add SCRIPT [VERSION]": Add script to this project's repository; latest version + * If a .sql script: how to determine engine, operation (up/down)? Options: + * specify at the command line: "samigrate add SCRIPT UP_OR_DOWN ENGINE" + * naming convention: SCRIPT is named something like NAME.postgres.up.sql + * "samigrate upgrade CONNECTION_STRING [VERSION] [SCRIPT...]": connect to the specified database and upgrade (or downgrade) it to the specified version (default latest) + * If SCRIPT... specified: act like these scripts are in the repository (useful for testing?) + * "samigrate dump CONNECTION_STRING [VERSION] [SCRIPT...]": like update, but sends all sql to stdout instead of the db + * (Later: some more commands, to be used for script testing tools) + * Alchemy API extensions for altering schema + * Operations here are DB-independent + * Each database modification is a script that may use this API + * Can handwrite SQL for all databases or a single database + * upgrade()/downgrade() functions: need only one file for both operations + * sql scripts reqire either (2 files, *.up.sql;*.down.sql) or (don't use downgrade) + * usage + * "python NAME.py ENGINE up": upgrade sql > stdout + * "python NAME.py ENGINE down": downgrade sql > stdout \ No newline at end of file diff --git a/doc/source/historical/ProjectGoals.trac b/doc/source/historical/ProjectGoals.trac new file mode 100644 index 0000000..5879196 --- /dev/null +++ b/doc/source/historical/ProjectGoals.trac @@ -0,0 +1,50 @@ +== Goals == + +=== DBMS-independent schema changes === +Many projects need to run on more than one DBMS. Similar changes need to be applied to both types of databases upon a schema change. The usual solution to database changes - .sql scripts with ALTER statements - runs into problems since different DBMSes have different dialects of SQL; we end up having to create a different script for each DBMS. This project will simplify this by providing an API, similar to the table definition API that already exists in SQLAlchemy, to alter a table independent of the DBMS being used, where possible. + +This project will support all DBMSes currently supported by SQLAlchemy: SQLite, Postgres, MySQL, Oracle, and MS SQL. Adding support for more should be as possible as it is in SQLAlchemy. + +Many are already used to writing .sql scripts for database changes, aren't interested in learning a new API, and have projects where DBMS-independence isn't an issue. Writing SQL statements as part of a (Python) change script must be an option, of course. Writing change scripts as .sql scripts, eliminating Python scripts from the picture entirely, would be nice too, although this is a lower-priority goal. + +=== Database versioning and change script organization === +Once we've accumulated a set of change scripts, it's important to know which ones have been applied/need to be applied to a particular database: suppose we need to upgrade a database that's extremenly out-of-date; figuring out the scripts to run by hand is tedious. Applying changes in the wrong order, or applying changes when they shouldn't be applied, is bad; attempting to manage all of this by hand inevitably leads to an accident. This project will be able to detect the version of a particular database and apply the scripts required to bring it up to the latest version, or up to any specified version number (given all change scripts required to reach that version number). + +Sometimes we need to be able to revert a schema to an older version. There's no automatic way to do this without rebuilding the database from scratch, so our project will allow one to write scripts to downgrade the database as well as upgrade it. If such scripts have been written, we should be able to apply them in the correct order, just like upgrading. + +Large projects inevitably accumulate a large number of database change scripts; it's important that we have a place to keep them. Once a script has been written, this project will deal with organizing it among existing change scripts, and the user will never have to look at it again. + +=== Change testing === +It's important to test one's database changes before applying them to a production database (unless you happen to like disasters). Much testing is up to the user and can't be automated, but there's a few places we can help ensure at least a minimal level of schema integrity. A few examples are below; we could add more later. + +Given an obsolete schema, a database change script, and an up-to-date schema known to be correct, this project will be able to ensure that applying the +change script to the obsolete schema will result in an up-to-date schema - all without actually changing the obsolete database. Folks who have SQLAlchemy create their database using table.create() might find this useful; this is also useful for ensuring database downgrade scripts are correct. + +Given a schema of a known version and a complete set of change scripts up to that version, this project will be able to detect if the schema matches its version. If a schema has gone through changes not present in migration scripts, this test will fail; if applying all scripts in sequence up to the specified version creates an identical schema, this test will succeed. Identifying that a schema is corrupt is sufficient; it would be nice if we could give a clue as to what's wrong, but this is lower priority. (Implementation: we'll probably show a diff of two schema dumps; this should be enough to tell the user what's gone wrong.) + +== Non-Goals == +ie. things we will '''not''' try to do (at least, during the Summer of Code) + +=== Automatic generation of schema changes === +For example, one might define a table: +{{{ +CREATE TABLE person ( + id integer, + name varchar(80) +); +}}} +Later, we might add additional columns to the definition: +{{{ +CREATE TABLE person ( + id integer, + name varchar(80), + profile text +); +}}} +It might be nice if a tool could look at both table definitions and spit out a change script; something like +{{{ +ALTER TABLE person ADD COLUMN profile text; +}}} +This is a difficult problem for a number of reasons. I have no intention of tackling this problem as part of the Summer of Code. This project aims to give you a better way to write that ALTER statement and make sure it's applied correctly, not to write it for you. + +(Using an [http://sqlfairy.sourceforge.net/ existing] [http://xml2ddl.berlios.de/ tool] to add this sort of thing later might be worth looking into, but it will not be done during the Summer of Code. Among other reasons, methinks it's best to start with a system that isn't dependent on this sort of automation.) \ No newline at end of file diff --git a/doc/source/historical/ProjectProposal.txt b/doc/source/historical/ProjectProposal.txt new file mode 100644 index 0000000..e270800 --- /dev/null +++ b/doc/source/historical/ProjectProposal.txt @@ -0,0 +1,73 @@ +Evan Rosson + +Project +--- +SQLAlchemy Schema Migration + + +Synopsis +--- +SQLAlchemy is an excellent object-relational database mapper for Python projects. Currently, it does a fine job of creating a database from scratch, but provides no tool to assist the user in modifying an existing database. This project aims to provide such a tool. + + +Benefits +--- + Application requirements change; a database schema must be able to change with them. It's possible to write SQL scripts that make the proper modifications without any special tools, but this setup quickly becomes difficult to manage - when we need to apply multiple updates to a database, organize old migration scripts, or have a single application support more than one DBMS, a tool to support database changes becomes necessary. This tool will aid the creation of organizing migration scripts, applying multiple updates or removing updates to revert to an old version, and creating DBMS-independent migration scripts. + + Writing one's schema migration scripts by hand often results in problems when dealing with multiple obsolete database instances - we must figure out what scripts are necessary to bring the database up-to-date. Database versioning tools are helpful for this task; this project will track the version of a particular database to determine what scripts are necessary to update an old schema. + + +Description +--- + The migration system used by Ruby on Rails has had much success, and for good reason - the system is easy to understand, generally database-independent, as powerful as the application itself, and capable of dealing nicely with a schema with multiple instances of different versions. A migration system similar to that of Rails is a fine place to begin this project. + + Each instance of the schema will have a version associated with it; this version is tracked using a single table with a single row and a single integer column. A set of changes to the database schema will increment the schema's version number; each migration script will be associated with a schema version. + + A migration script will be written by the user, and consist of two functions: +- upgrade(): brings an old database up-to-date, from version n-1 to version n +- downgrade(): reverts an up-to-date database to the previous schema; an 'undo' for upgrade() + + When applying multiple updates to an old schema instance, migration scripts are applied in sequence: when updating a schema to version n from version n-2, two migration scripts are run; n-2 => n-1 => n. + + A command-line tool will create empty migration scripts (empty upgrade()/downgrade() functions), display the SQL that will be generated by a migration script for a particular DBMS, and apply migration scripts to a specified database. + + This project will implement the command-line tool that manages the above functionality. This project will also extend SQLAlchemy with the functions necessary to construct DBMS-independent migration scripts: in particular, column creation/deletion/alteration and the ability to rename existing tables/indexes/columns will be implemented. We'll also need a way to write raw SQL for a specific DBMS/set of DBMSes for situations where our abstraction doesn't fit a script's requirements. The creation/deletion of existing tables and indexes are operations already provided by SQLAlchemy. + + + On DBMS support - I intend to support MySQL, Postgres, SQLite, Oracle, and MS-SQL by the end of the project. (Update: I previously omitted support for Oracle and MS-SQL because I don't have access to the full version of each; I wasn't aware Oracle Lite and MS-SQL Express were available for free.) The system will be abstracted in such a way that adding support for other databases will not be any more difficult than adding support for them in SQLAlchemy. + + +Schedule +--- +This project will be my primary activity this summer. Unfortunately, I am in school when things begin, until June 9, but I can still begin the project during that period. I have no other commitments this summer - I can easily make up any lost time. +I will be spending my spare time this summer further developing my online game (discussed below), but this has no deadline and will not interfere with the project proposed here. + + +I'll begin by familiarizing myself with the internals of SQLAlchemy and creating a detailed plan for the project. This plan will be reviewed by the current SQLAlchemy developers and other potential users, and will be modified based on their feedback. This will be completed no later than May 30, one week after SoC begins. + +Development will follow, in this order: +- The database versioning system. This will manage the creation and application of (initially empty) migration scripts. Complete by June 16. + - Access the database; read/update the schema's version number + - Apply a single (empty) script to the database + - Apply a set of (empty) scripts to upgrade/downgrade the database to a specified version; examine all migration scripts and apply all to update the database to the latest version available +- An API for table/column alterations, to make the above system useful. Complete by August 11. + - Implement an empty API - does nothing at this point, but written in such a way that syntax for each supported DBMS may be added as a module. Completed June 26-30, the mid-project review deadline. + - Implement/test the above API for a single DBMS (probably Postgres, as I'm familiar with it). Users should be able to test the 'complete' application with this DBMS. + - Implement the database modification API for other supported databases + +All development will have unit tests written where appropriate. Unit testing the SQL generated for each DBMS will be particularly important. + +The project will finish with various wrap-up activities, documentation, and some final tests, to be completed by the project deadline. + + +About me +--- +I am a 3rd year BS Computer Science student; Cal Poly, San Luis Obispo, California, USA; currently applying for a Master's degree in CS from the same school. I've taken several classes dealing with databases, though much of what I know on the subject is self-taught. Outside of class, I've developed a browser-based online game, Zeal, at http://zealgame.com ; it has been running for well over a year and gone through many changes. It has taught me firsthand the importance of using appropriate tools and designing one's application well early on (largely through the pain that follows when you don't); I've learned a great many other things from the experience as well. + +One recurring problem I've had with this project is dealing with changes to the database schema. I've thought much about how I'd like to see this solved, but hadn't done much to implement it. + +I'm now working on another project that will be making use of SQLAlchemy: it fits many of my project's requirements, but lacks a migration tool that will be much needed. This presents an opportunity for me to make my first contribution to open source - I've long been interested in open source software and use it regularly, but haven't contributed to any until now. I'm particularly interested in the application of this tool with the TurboGears framework, as this project was inspired by a suggestion the TurboGears mailing list and I'm working on a project using TurboGears - but there is no reason to couple an SQLAlchemy enhancement with TurboGears; this project may be used by anyone who uses SQLAlchemy. + + +Further information: +http://evan.zealgame.com/soc diff --git a/doc/source/historical/RepositoryFormat.trac b/doc/source/historical/RepositoryFormat.trac new file mode 100644 index 0000000..57f6bf6 --- /dev/null +++ b/doc/source/historical/RepositoryFormat.trac @@ -0,0 +1,56 @@ +This plan has several problems and has been modified; new plan is discussed in wiki:RepositoryFormat2 + +---- + +One problem with [http://www.rubyonrails.org/ Ruby on Rails'] (very good) schema migration system is the behavior of scripts that depend on outside sources; ie. the application. If those change, there's no guarantee that such scripts will behave as they did before, and you'll get strange results. + +For example, suppose one defines a SQLAlchemy table: +{{{ +users = Table('users', metadata, + Column('user_id', Integer, primary_key = True), + Column('user_name', String(16), nullable = False), + Column('password', String(20), nullable = False) +) +}}} +and creates it in a change script: +{{{ +from project import table + +def upgrade(): + table.users.create() +}}} + +Suppose we later add a column to this table. We write an appropriate change script: +{{{ +from project import table + +def upgrade(): + # This syntax isn't set in stone yet + table.users.add_column('email_address', String(60), key='email') +}}} +...and change our application's table definition: +{{{ +users = Table('users', metadata, + Column('user_id', Integer, primary_key = True), + Column('user_name', String(16), nullable = False), + Column('password', String(20), nullable = False), + Column('email_address', String(60), key='email') #new column +) +}}} + +Modifying the table definition changes how our first script behaves - it will create the table with the new column. This might work if we only apply change scripts to a few database which are always kept up to date (or very close), but we'll run into errors eventually if our migration scripts' behavior isn't consistent. + +---- + +One solution is to generate .sql files from a Python change script at the time it's added to a repository. The sql generated by the script for each database is set in stone at this point; changes to outside files won't affect it. + +This limits what change scripts are capable of - we can't write dynamic SQL; ie., we can't do something like this: +{{{ +for row in db.execute("select id from table1"): + db.execute("insert into table2 (table1_id, value) values (:id,42)",**row) +}}} +But SQL is usually powerful enough to where the above is rarely necessary in a migration script: +{{{ +db.execute("insert into table2 select id,42 from table1") +}}} +This is a reasonable solution. The limitations aren't serious (everything possible in a traditional .sql script is still possible), and change scripts are much less prone to error. diff --git a/doc/source/historical/RepositoryFormat2.trac b/doc/source/historical/RepositoryFormat2.trac new file mode 100644 index 0000000..496478c --- /dev/null +++ b/doc/source/historical/RepositoryFormat2.trac @@ -0,0 +1,28 @@ +My original plan for Migrate's RepositoryFormat had several problems: + + * Bind parameters: We needed to bind parameters into statements to get something suitable for an .sql file. For some types of parameters, there's no clean way to do this without writing an entire parser - too great a cost for this project. There's a reason why SQLAlchemy's logs display the statement and its parameters separately: the binding is done at a lower level than we have access to. + * Failure: Discussed in #17, the old format had no easy way to find the Python statements associated with an SQL error. This makes it difficult to debug scripts. + +A new format will be used to solve this problem instead. +Similar to our previous solution, where one .sql file was created per version/operation/DBMS (version_1.upgrade.postgres.sql, for example), one file will be created per version/operation/DBMS here. +These files will contain the following information: + + * The dialect used to perform the logging. Particularly, + * The paramstyle expected by the dbapi + * The DBMS this log applies to + * Information on each logged SQL statement, each of which contains: + * The text of the statement + * Parameters to be bound to the statement + * A Python stack trace at the point the statement was logged - this allows us to tell what Python statements are associated with an SQL statement when there's an error + +These files will be created by pickling a Python object with the above information. + +Such files may be executed by loading the log and having SQLAlchemy execute them as it might have before. + +Good: + * Since the statements and bind parameters are stored separately and executed as SQLAlchemy would normally execute them, one problem discussed above is eliminated. + * Storing the stack trace at the point each statement was logged allows us to identify what Python statements are responsible for an SQL error. This makes it much easier for users to debug their scripts. + +Bad: + * It's less trivial to commit .sql scripts to our repository, since they're no longer used internally. This isn't a huge loss, and .sql commits can still be implemented later if need be. + * There's some danger of script behavior changing if changes are made to the dbapi the script is associated with. The primary place where problems would occur is during parameter binding, but the chance of this changing significantly isn't large. The danger of changes in behavior due to changes in the user's application is not affected. \ No newline at end of file diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..1e464d8 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,188 @@ +:mod:`migrate` - SQLAlchemy Migrate (schema change management) +============================================================== + +.. module:: migrate +.. moduleauthor:: Evan Rosson + +:Author: Evan Rosson +:Maintainer: Domen Kožar +:Maintainer: Jan Dittberner +:Issues: http://code.google.com/p/sqlalchemy-migrate/issues/list +:Source Code: http://code.google.com/p/sqlalchemy-migrate/ +:CI Tool: http://jenkins.gnuviech-server.de/job/sqlalchemy-migrate-all/ +:Generated: |today| +:License: MIT +:Version: |release| + + +.. topic:: Overview + + Inspired by Ruby on Rails' migrations, SQLAlchemy Migrate provides a way to + deal with database schema changes in SQLAlchemy_ projects. + + Migrate was started as part of `Google's Summer of Code`_ by Evan Rosson, + mentored by Jonathan LaCour. + + The project was taken over by a small group of volunteers when Evan had no + free time for the project. It is now hosted as a `Google Code project`_. + During the hosting change the project was renamed to SQLAlchemy Migrate. + + Currently, sqlalchemy-migrate supports Python versions from 2.6 to 2.7. + SQLAlchemy Migrate 0.7.2 supports SQLAlchemy 0.6.x and 0.7.x branches. + + Support for Python 2.4 and 2.5 as well as SQLAlchemy 0.5.x has been dropped + after sqlalchemy-migrate 0.7.1. + +.. warning:: + + Version **0.6** broke backward compatibility, please read :ref:`changelog + ` for more info. + + +Download and Development +------------------------ + +.. toctree:: + + download + credits + + +.. _dialect-support: + +Dialect support +--------------- + +.. list-table:: + :header-rows: 1 + :widths: 25 10 10 10 10 10 11 + + * - Operation / Dialect + - :ref:`sqlite ` + - :ref:`postgres ` + - :ref:`mysql ` + - :ref:`oracle ` + - :ref:`firebird ` + - mssql + * - :ref:`ALTER TABLE RENAME TABLE ` + - yes + - yes + - yes + - yes + - no + - not supported + * - :ref:`ALTER TABLE RENAME COLUMN ` + - yes (workaround) [#1]_ + - yes + - yes + - yes + - yes + - not supported + * - :ref:`ALTER TABLE ADD COLUMN ` + - yes (workaround) [#2]_ + - yes + - yes + - yes + - yes + - not supported + * - :ref:`ALTER TABLE DROP COLUMN ` + - yes (workaround) [#1]_ + - yes + - yes + - yes + - yes + - not supported + * - :ref:`ALTER TABLE ALTER COLUMN ` + - yes (workaround) [#1]_ + - yes + - yes + - yes (with limitations) [#3]_ + - yes [#4]_ + - not supported + * - :ref:`ALTER TABLE ADD CONSTRAINT ` + - partial (workaround) [#1]_ + - yes + - yes + - yes + - yes + - not supported + * - :ref:`ALTER TABLE DROP CONSTRAINT ` + - partial (workaround) [#1]_ + - yes + - yes + - yes + - yes + - not supported + * - :ref:`RENAME INDEX ` + - no + - yes + - no + - yes + - yes + - not supported + + +.. [#1] Table is renamed to temporary table, new table is created followed by + INSERT statements. +.. [#2] See http://www.sqlite.org/lang_altertable.html for more information. + In cases not supported my sqlite, table is renamed to temporary table, + new table is created followed by INSERT statements. +.. [#3] You can not change datatype or rename column if table has NOT NULL + data, see http://blogs.x2line.com/al/archive/2005/08/30/1231.aspx for + more information. +.. [#4] Changing nullable is not supported + + +Tutorials +-------------- + +List of useful tutorials: + +* `Using migrate with Elixir `_ +* `Developing with migrations + `_ + + +User guide +------------- + +SQLAlchemy Migrate is split into two parts, database schema versioning +(:mod:`migrate.versioning`) and database migration management +(:mod:`migrate.changeset`). The versioning API is available as the +:ref:`migrate ` command. + +.. toctree:: + + versioning + changeset + tools + faq + glossary + +.. _`google's summer of code`: http://code.google.com/soc +.. _`Google Code project`: http://code.google.com/p/sqlalchemy-migrate +.. _sqlalchemy: http://www.sqlalchemy.org + + +API Documentation +------------------ + +.. toctree:: + + api + + +Changelog +--------- + +.. toctree:: + + changelog + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/doc/source/theme/almodovar.css b/doc/source/theme/almodovar.css new file mode 100644 index 0000000..c7f250b --- /dev/null +++ b/doc/source/theme/almodovar.css @@ -0,0 +1,288 @@ +/* + * Original theme modified by Evan Rosson + * http://erosson.com/migrate + * --- + * + * Theme Name: Almodovar + * Theme URI: http://blog.ratterobert.com/archiv/2005/03/09/almodovar/ + * Description: Das Theme basiert im Ursprung auf Michael Heilemanns Kubrick-Template und ist von dem einen oder anderen Gimmick anderer sehr guter Templates inspiriert worden. + * Version: 0.7 + * Author: ratte / robert + * Author URI: http://blog.ratterobert.com/ + * */ + +/* Begin Typography & Colors */ +body { + font-size: 75%; + font-family: 'Lucida Grande', 'Trebuchet MS', 'Bitstream Vera Sans', Sans-Serif; + background-color: #CCF; + color: #333; + text-align: center; +} + +#page { + background-color: #fff; + border: 1px solid #88f; + text-align: left; +} + +#content { + font-size: 1.2em; + margin: 1em; + } + +#content p, +#content ul, +#content blockquote { + line-height: 1.6em; +} + +#footer { + border-top: 1px solid #006; + margin-top: 2em; +} + +small { + font-family: 'Trebuchet MS', Arial, Helvetica, Sans-Serif; + font-size: 0.9em; + line-height: 1.5em; + } + +h1, h2, h3 { + font-family: 'Trebuchet MS', 'Lucida Grande', Verdana, Arial, Sans-Serif; + font-weight: bold; + margin-top: .7em; + margin-bottom: .7em; + } + +h1 { + font-size: 2.5em; +} +h2 { + font-size: 2em; +} +h3 { + font-size: 1.5em; +} + +h1, h2, h3 { + color: #33a; +} + +h1 a, h2 a, h3 a { + color: #33a; +} + +h1, h1 a, h1 a:hover, h1 a:visited, +h2, h2 a, h2 a:hover, h2 a:visited, +h3, h3 a, h3 a:hover, h3 a:visited, +cite { + text-decoration: none; +} + +#content p a:visited { + color: #004099; + /*font-weight: normal;*/ +} + +small, blockquote, strike { + color: #33a; +} + +#links ul ul li, #links li { + list-style: none; +} + +code { + font: 1.1em 'Courier', 'Courier New', Fixed; +} + +acronym, abbr, span.caps { + font-size: 0.9em; + letter-spacing: .07em; +} + +a { + color: #0050FF; + /*text-decoration: none;*/ + text-decoration:underline; + /*font-weight: bold;*/ +} +a:hover { + color: #0080FF; +} + +/* Special case doc-title */ +h1.doc-title { + text-transform: lowercase; + font-size: 4em; + margin: 0; +} +h1.doc-title a { + display: block; + padding-left: 0.8em; + padding-bottom: .5em; + padding-top: .5em; + margin: 0; + border-bottom: 1px #fff solid; +} +h1.doc-title, +h1.doc-title a, +h1.doc-title a:visited, +h1.doc-title a:hover { + text-decoration: none; + color: #0050FF; +} +/* End Typography & Colors */ + + +/* Begin Structure */ +body { + margin: 0; + padding: 0; +} + +#page { + background-color: white; + margin: 0 auto 0 9em; + padding: 0; + max-width: 60em; + border: 1px solid #555596; +} +* html #page { +* width: 60em; +* } +* +* #content { +* margin: 0 1em 0 3em; +* } +* +* #content h1 { +* margin-left: 0; +* } +* +* #footer { +* padding: 0 0 0 1px; +* margin: 0; +* margin-top: 1.5em; +* clear: both; +* } +* +* #footer p { +* margin: 1em; +* } +* +*/* End Structure */ + + + +/* Begin Headers */ +.description { + text-align: center; + } + +/* End Headers */ + + +/* Begin Form Elements */ +#searchform { + margin: 1em auto; + text-align: right; + } + +#searchform #s { + width: 100px; + padding: 2px; + } + +#searchsubmit { + padding: 1px; + } +/* End Form Elements */ + + +/* Begin Various Tags & Classes */ +acronym, abbr, span.caps { + cursor: help; +} + +acronym, abbr { + border-bottom: 1px dashed #999; +} + +blockquote { + margin: 15px 30px 0 10px; + padding-left: 20px; + border-left: 5px solid #CCC; +} + +blockquote cite { + margin: 5px 0 0; + display: block; +} + +hr { + display: none; +} + +a img { + border: none; +} + +.navigation { + display: block; + text-align: center; + margin-top: 10px; + margin-bottom: 60px; +} +/* End Various Tags & Classes*/ + +span a { color: #CCC; } + +span a:hover { color: #0050FF; } + +#navcontainer { + margin-top: 0px; + padding-top: 0px; + width: 100%; + background-color: #AAF; + text-align: right; +} + +#navlist ul { + margin-left: 0; + margin-right: 5px; + padding-left: 0; + white-space: nowrap; +} + +#navlist li { + display: inline; + list-style-type: none; +} + +#navlist a { + padding: 3px 10px; + color: #fff; + background-color: #339; + text-decoration: none; + border: 1px solid #44F; + font-weight: normal; +} + +#navlist a:hover { + color: #000; + background-color: #FFF; + text-decoration: none; + font-weight: normal; +} + +#navlist a:active, #navlist a.selected { + padding: 3px 10px; + color: #000; + background-color: #EEF; + text-decoration: none; + border: 1px solid #CCF; + font-weight: normal; +} + diff --git a/doc/source/theme/layout.css b/doc/source/theme/layout.css new file mode 100644 index 0000000..ed80572 --- /dev/null +++ b/doc/source/theme/layout.css @@ -0,0 +1,123 @@ +@import url("pudge.css"); +@import url("almodovar.css"); + +/* Basic Style +----------------------------------- */ + +h1.pudge-member-page-heading { + font-size: 300%; +} +h4.pudge-member-page-subheading { + font-size: 130%; + font-style: italic; + margin-top: -2.0em; + margin-left: 2em; + margin-bottom: .3em; + color: #0050CC; +} +p.pudge-member-blurb { + font-style: italic; + font-weight: bold; + font-size: 120%; + margin-top: 0.2em; + color: #999; +} +p.pudge-member-parent-link { + margin-top: 0; +} +/*div.pudge-module-doc { + max-width: 45em; +}*/ +div.pudge-section { + margin-left: 2em; + max-width: 45em; +} +/* Section Navigation +----------------------------------- */ + +div#pudge-section-nav +{ + margin: 1em 0 1.5em 0; + padding: 0; + height: 20px; +} + +div#pudge-section-nav ul { + border: 0; + margin: 0; + padding: 0; + list-style-type: none; + text-align: center; + border-right: 1px solid #aaa; +} +div#pudge-section-nav ul li +{ + display: block; + float: left; + text-align: center; + padding: 0; + margin: 0; +} + +div#pudge-section-nav ul li .pudge-section-link, +div#pudge-section-nav ul li .pudge-missing-section-link +{ + background: #aaa; + width: 9em; + height: 1.8em; + border: 1px solid #bbb; + padding: 0; + margin: 0 0 10px 0; + color: #ddd; + text-decoration: none; + display: block; + text-align: center; + font: 11px/20px "Verdana", "Lucida Grande"; + cursor: hand; + text-transform: lowercase; +} + +div#pudge-section-nav ul li a:hover { + color: #000; + background: #fff; +} + +div#pudge-section-nav ul li .pudge-section-link { + background: #888; + color: #eee; + border: 1px solid #bbb; +} + +/* Module Lists +----------------------------------- */ +dl.pudge-module-list dt { + font-style: normal; + font-size: 110%; +} +dl.pudge-module-list dd { + color: #555; +} + +/* Misc Overrides */ +.rst-doc p.topic-title a { + color: #777; +} +.rst-doc ul.auto-toc a, +.rst-doc div.contents a { + color: #333; +} +pre { background: #eee; } + +.rst-doc dl dt { + color: #444; + margin-top: 1em; + font-weight: bold; +} +.rst-doc dl dd { + margin-top: .2em; +} +.rst-doc hr { + display: block; + margin: 2em 0; +} + diff --git a/doc/source/theme/layout.html b/doc/source/theme/layout.html new file mode 100644 index 0000000..094e989 --- /dev/null +++ b/doc/source/theme/layout.html @@ -0,0 +1,90 @@ + + + + + + + + + ${title} + + + + + +
+

${home_title}

+ + +
+ +
+ + +
+ + + diff --git a/doc/source/tools.rst b/doc/source/tools.rst new file mode 100644 index 0000000..9c2a920 --- /dev/null +++ b/doc/source/tools.rst @@ -0,0 +1,15 @@ +Repository migration (0.4.5 -> 0.5.4) +================================================ + +.. index:: repository migration + +:command:`migrate_repository.py` should 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}` diff --git a/doc/source/versioning.rst b/doc/source/versioning.rst new file mode 100644 index 0000000..dd413d3 --- /dev/null +++ b/doc/source/versioning.rst @@ -0,0 +1,641 @@ +.. _versioning-system: +.. currentmodule:: migrate.versioning +.. highlight:: console + +*********************************** +Database schema versioning workflow +*********************************** + +SQLAlchemy migrate provides the :mod:`migrate.versioning` API that is +also available as the :ref:`migrate ` command. + +Purpose of this package is frontend for migrations. It provides commands to +manage migrate :term:`repository` and database selection as well as script +versioning. + + +Project setup +============= + +.. _create_change_repository: + +Create a change repository +-------------------------- + +To begin, we'll need to create a :term:`repository` for our project. + +All work with repositories is done using the :ref:`migrate +` command. Let's create our project's repository:: + + $ migrate create my_repository "Example project" + +This creates an initially empty :term:`repository` relative to current +directory at :file:`my_repository/` named `Example project`. + +The :term:`repository` directory contains a sub directory :file:`versions` that +will store the :ref:`schema versions `, a configuration file +:file:`migrate.cfg` that contains :ref:`repository configuration +` and a script :ref:`manage.py +` that has the same functionality as the +:ref:`migrate ` command but is preconfigured with +repository specific parameters. + +.. note:: + + Repositories are associated with a single database schema, and store + collections of change scripts to manage that schema. The scripts in a + :term:`repository` may be applied to any number of databases. Each + :term:`repository` has an unique name. This name is used to identify the + :term:`repository` we're working with. + + +Version control a database +-------------------------- + +Next we need to declare database to be under version control. Information on a +database's version is stored in the database itself; declaring a database to be +under version control creates a table named **migrate_version** and associates +it with your :term:`repository`. + +The database is specified as a `SQLAlchemy database url`_. + +.. _`sqlalchemy database url`: + http://www.sqlalchemy.org/docs/core/engines.html#database-urls + +The :option:`version_control` command assigns a specified database with a +:term:`repository`:: + + $ python my_repository/manage.py version_control sqlite:///project.db my_repository + +We can have any number of databases under this :term:`repository's +` version control. + +Each schema has a :term:`version` that SQLAlchemy Migrate manages. Each change +script applied to the database increments this version number. You can retrieve +a database's current :term:`version`:: + + $ python my_repository/manage.py db_version sqlite:///project.db my_repository + 0 + +A freshly versioned database begins at version 0 by default. This assumes the +database is empty or does only contain schema elements (tables, views, +constraints, indices, ...) that will not be affected by the changes in the +:term:`repository`. (If this is a bad assumption, you can specify the +:term:`version` at the time the database is put under version control, with the +:option:`version_control` command.) We'll see that creating and applying change +scripts changes the database's :term:`version` number. + +Similarly, we can also see the latest :term:`version` available in a +:term:`repository` with the command:: + + $ python my_repository/manage.py version my_repository + 0 + +We've entered no changes so far, so our :term:`repository` cannot upgrade a +database past version 0. + +Project management script +------------------------- + +.. _project_management_script: + +Many commands need to know our project's database url and :term:`repository` +path - typing them each time is tedious. We can create a script for our project +that remembers the database and :term:`repository` we're using, and use it to +perform commands:: + + $ migrate manage manage.py --repository=my_repository --url=sqlite:///project.db + $ python manage.py db_version + 0 + +The script :file:`manage.py` was created. All commands we perform with it are +the same as those performed with the :ref:`migrate ` tool, +using the :term:`repository` and database connection entered above. The +difference between the script :file:`manage.py` in the current directory and +the script inside the repository is, that the one in the current directory has +the database URL preconfigured. + +.. note:: + + Parameters specified in manage.py should be the same as in :ref:`versioning + api `. Preconfigured parameter should just be omitted from + :ref:`migrate ` command. + + +Making schema changes +===================== + +All changes to a database schema under version control should be done via +change scripts - you should avoid schema modifications (creating tables, etc.) +outside of change scripts. This allows you to determine what the schema looks +like based on the version number alone, and helps ensure multiple databases +you're working with are consistent. + +Create a change script +---------------------- + +Our first change script will create a simple table + +.. code-block:: python + + account = Table( + 'account', meta, + Column('id', Integer, primary_key=True), + Column('login', String(40)), + Column('passwd', String(40)), + ) + +This table should be created in a change script. Let's create one:: + + $ python manage.py script "Add account table" + +This creates an empty change script at +:file:`my_repository/versions/001_Add_account_table.py`. Next, we'll +edit this script to create our table. + + +Edit the change script +---------------------- + +Our change script predefines two functions, currently empty: +:py:func:`upgrade` and :py:func:`downgrade`. We'll fill those in: + +.. code-block:: python + + from sqlalchemy import Table, Column, Integer, String, MetaData + + meta = MetaData() + + account = Table( + 'account', meta, + Column('id', Integer, primary_key=True), + Column('login', String(40)), + Column('passwd', String(40)), + ) + + + def upgrade(migrate_engine): + meta.bind = migrate_engine + account.create() + + + def downgrade(migrate_engine): + meta.bind = migrate_engine + account.drop() + +.. note:: + + The generated script contains * imports from sqlalchemy and migrate. You + should tailor the imports to fit your actual demand. + +As you might have guessed, :py:func:`upgrade` upgrades the database to the next +version. This function should contain the :ref:`schema changes +` we want to perform (in our example we're creating a +table). + +:py:func:`downgrade` should reverse changes made by :py:func:`upgrade`. You'll +need to write both functions for every change script. (Well, you don't *have* +to write downgrade, but you won't be able to revert to an older version of the +database or test your scripts without it.) If you really don't want to support +downgrades it is a good idea to raise a :py:class:`NotImplementedError` or some +equivalent custom exception. If you let :py:func:`downgrade` pass silently you +might observe undesired behaviour for subsequent downgrade operations if +downgrading multiple :term:`versions `. + + +.. note:: + + As you can see, **migrate_engine** is passed to both functions. You should + use this in your change scripts, rather than creating your own engine. + +.. warning:: + + You should be very careful about importing files from the rest of your + application, as your change scripts might break when your application + changes. Read more about `writing scripts with consistent behavior`_. + + +Test the change script +------------------------ + +Change scripts should be tested before they are committed. Testing a script +will run its :func:`upgrade` and :func:`downgrade` functions on a specified +database; you can ensure the script runs without error. You should be testing +on a test database - if something goes wrong here, you'll need to correct it by +hand. If the test is successful, the database should appear unchanged after +:func:`upgrade` and :func:`downgrade` run. + +To test the script:: + + $ python manage.py test + Upgrading... done + Downgrading... done + Success + +Our script runs on our database (:file:`sqlite:///project.db`, as specified in +:file:`manage.py`) without any errors. + +Our :term:`repository's ` :term:`version` is:: + + $ python manage.py version + 1 + +.. note:: + + Due to #41 the database must be exactly one :term:`version` behind the + :term:`repository` :term:`version`. + +.. _production testing warning: + +.. warning:: + + The :option:`test` command executes actual scripts, be sure you are *NOT* + doing this on production database. + + If you need to test production changes you should: + + #. get a dump of your production database + #. import the dump into an empty database + #. run :option:`test` or :option:`upgrade` on that copy + + +Upgrade the database +-------------------- + +Now, we can apply this change script to our database:: + + $ python manage.py upgrade + 0 -> 1... + done + +This upgrades the database (:file:`sqlite:///project.db`, as specified when we +created :file:`manage.py` above) to the latest available :term:`version`. (We +could also specify a version number if we wished, using the :option:`--version` +option.) We can see the database's :term:`version` number has changed, and our +table has been created:: + + $ python manage.py db_version + 1 + $ sqlite3 project.db + sqlite> .tables + account migrate_version + sqlite> .schema account + CREATE TABLE account ( + id INTEGER NOT NULL, + login VARCHAR(40), + passwd VARCHAR(40), + PRIMARY KEY (id) + ); + +Our account table was created - success! + +Modifying existing tables +------------------------- + +After we have initialized the database schema we now want to add another Column +to the `account` table that we already have in our schema. + +First start a new :term:`changeset` by the commands learned above:: + + $ python manage.py script "Add email column" + +This creates a new :term:`changeset` template. Edit the resulting script +:file:`my_repository/versions/002_Add_email_column.py`: + +.. code-block:: python + + from sqlalchemy import Table, MetaData, String, Column + + + def upgrade(migrate_engine): + meta = MetaData(bind=migrate_engine) + account = Table('account', meta, autoload=True) + emailc = Column('email', String(128)) + emailc.create(account) + + + def downgrade(migrate_engine): + meta = MetaData(bind=migrate_engine) + account = Table('account', meta, autoload=True) + account.c.email.drop() + +As we can see in this example we can (and should) use SQLAlchemy's schema +reflection (autoload) mechanism to reference existing schema objects. We could +have defined the table objects as they are expected before upgrade or downgrade +as well but this would have been more work and is not as convenient. + +We can now apply the changeset to :file:`sqlite:///project.db`:: + + $ python manage.py upgrade + 1 -> 2... + done + +and get the following expected result:: + + $ sqlite3 project.db + sqlite> .schema account + CREATE TABLE account ( + id INTEGER NOT NULL, + login VARCHAR(40), + passwd VARCHAR(40), email VARCHAR(128), + PRIMARY KEY (id) + ); + + +Writing change scripts +====================== + +As our application evolves, we can create more change scripts using a similar +process. + +By default, change scripts may do anything any other SQLAlchemy program can do. + +SQLAlchemy Migrate extends SQLAlchemy with several operations used to change +existing schemas - ie. ``ALTER TABLE`` stuff. See :ref:`changeset +` documentation for details. + + +Writing scripts with consistent behavior +---------------------------------------- + +Normally, it's important to write change scripts in a way that's independent of +your application - the same SQL should be generated every time, despite any +changes to your app's source code. You don't want your change scripts' behavior +changing when your source code does. + +.. warning:: + + **Consider the following example of what NOT to do** + + Let's say your application defines a table in the :file:`model.py` file: + + .. code-block:: python + + from sqlalchemy import * + + meta = MetaData() + table = Table('mytable', meta, + Column('id', Integer, primary_key=True), + ) + + ... and uses this file to create a table in a change script: + + .. code-block:: python + + from sqlalchemy import * + from migrate import * + import model + + def upgrade(migrate_engine): + model.meta.bind = migrate_engine + + def downgrade(migrate_engine): + model.meta.bind = migrate_engine + model.table.drop() + + This runs successfully the first time. But what happens if we change the + table definition in :file:`model.py`? + + .. code-block:: python + + from sqlalchemy import * + + meta = MetaData() + table = Table('mytable', meta, + Column('id', Integer, primary_key=True), + Column('data', String(42)), + ) + + We'll create a new column with a matching change script + + .. code-block:: python + + from sqlalchemy import * + from migrate import * + import model + + def upgrade(migrate_engine): + model.meta.bind = migrate_engine + model.table.create() + + def downgrade(migrate_engine): + model.meta.bind = migrate_engine + model.table.drop() + + This appears to run fine when upgrading an existing database - but the + first script's behavior changed! Running all our change scripts on a new + database will result in an error - the first script creates the table based + on the new definition, with both columns; the second cannot add the column + because it already exists. + + To avoid the above problem, you should use SQLAlchemy schema reflection as + shown above or copy-paste your table definition into each change script + rather than importing parts of your application. + + .. note:: + Sometimes it is enough to just reflect tables with SQLAlchemy instead + of copy-pasting - but remember, explicit is better than implicit! + + +Writing for a specific database +------------------------------- + +Sometimes you need to write code for a specific database. Migrate scripts can +run under any database, however - the engine you're given might belong to any +database. Use engine.name to get the name of the database you're working with + +.. code-block:: python + + >>> from sqlalchemy import * + >>> from migrate import * + >>> + >>> engine = create_engine('sqlite:///:memory:') + >>> engine.name + 'sqlite' + + +Writings .sql scripts +--------------------- + +You might prefer to write your change scripts in SQL, as .sql files, rather +than as Python scripts. SQLAlchemy-migrate can work with that:: + + $ python manage.py version + 1 + $ python manage.py script_sql postgresql + +This creates two scripts +:file:`my_repository/versions/002_postgresql_upgrade.sql` and +:file:`my_repository/versions/002_postgresql_downgrade.sql`, one for each +*operation*, or function defined in a Python change script - upgrade and +downgrade. Both are specified to run with PostgreSQL databases - we can add +more for different databases if we like. Any database defined by SQLAlchemy may +be used here - ex. sqlite, postgresql, oracle, mysql... + + +.. _command-line-usage: + +Command line usage +================== + +.. currentmodule:: migrate.versioning.shell + +:command:`migrate` command is used for API interface. For list of commands and +help use:: + + $ migrate --help + +:command:`migrate` command executes :func:`main` function. +For ease of usage, generate your own :ref:`project management script +`, which calls :func:`main +` function with keywords arguments. You may want +to specify `url` and `repository` arguments which almost all API functions +require. + +If api command looks like:: + + $ migrate downgrade URL REPOSITORY VERSION [--preview_sql|--preview_py] + +and you have a project management script that looks like + +.. code-block:: python + + from migrate.versioning.shell import main + + main(url='sqlite://', repository='./project/migrations/') + +you have first two slots filed, and command line usage would look like:: + + # preview Python script + $ migrate downgrade 2 --preview_py + + # downgrade to version 2 + $ migrate downgrade 2 + +.. versionchanged:: 0.5.4 + Command line parsing refactored: positional parameters usage + +Whole command line parsing was rewriten from scratch with use of OptionParser. +Options passed as kwargs to :func:`~migrate.versioning.shell.main` are now +parsed correctly. Options are passed to commands in the following priority +(starting from highest): + +- optional (given by :option:`--some_option` in commandline) +- positional arguments +- kwargs passed to :func:`migrate.versioning.shell.main` + + +Python API +========== + +.. currentmodule:: migrate.versioning.api + +All commands available from the command line are also available for +your Python scripts by importing :mod:`migrate.versioning.api`. See the +:mod:`migrate.versioning.api` documentation for a list of functions; +function names match equivalent shell commands. You can use this to +help integrate SQLAlchemy Migrate with your existing update process. + +For example, the following commands are similar: + +*From the command line*:: + + $ migrate help help + /usr/bin/migrate help COMMAND + + Displays help on a given command. + +*From Python* + +.. code-block:: python + + import migrate.versioning.api + migrate.versioning.api.help('help') + # Output: + # %prog help COMMAND + # + # Displays help on a given command. + + +.. _migrate.versioning.api: module-migrate.versioning.api.html + +.. _repository_configuration: + + +Experimental commands +===================== + +Some interesting new features to create SQLAlchemy db models from existing +databases and vice versa were developed by Christian Simms during the +development of SQLAlchemy-migrate 0.4.5. These features are roughly documented +in a `thread in migrate-users`_. + +.. _`thread in migrate-users`: + http://groups.google.com/group/migrate-users/browse_thread/thread/a5605184e08abf33#msg_85c803b71b29993f + +Here are the commands' descriptions as given by ``migrate help ``: + +- ``compare_model_to_db``: Compare the current model (assumed to be a + module level variable of type sqlalchemy.MetaData) against the + current database. +- ``create_model``: Dump the current database as a Python model to + stdout. +- ``make_update_script_for_model``: Create a script changing the old + Python model to the new (current) Python model, sending to stdout. + +As this sections headline says: These features are *EXPERIMENTAL*. Take the +necessary arguments to the commands from the output of ``migrate +help ``. + + +Repository configuration +======================== + +SQLAlchemy-migrate :term:`repositories ` can be configured in their +:file:`migrate.cfg` files. The initial configuration is performed by the +`migrate create` call explained in :ref:`Create a change repository +`. The following options are available currently: + +- :option:`repository_id` Used to identify which repository this database is + versioned under. You can use the name of your project. +- :option:`version_table` The name of the database table used to track the + schema version. This name shouldn't already be used by your project. If this + is changed once a database is under version control, you'll need to change + the table name in each database too. +- :option:`required_dbs` When committing a change script, SQLAlchemy-migrate + will attempt to generate the sql for all supported databases; normally, if + one of them fails - probably because you don't have that database installed - + it is ignored and the commit continues, perhaps ending successfully. + Databases in this list MUST compile successfully during a commit, or the + entire commit will fail. List the databases your application will actually be + using to ensure your updates to that database work properly. This must be a + list; example: `['postgres', 'sqlite']` +- :option:`use_timestamp_numbering` When creating new change scripts, Migrate + will stamp the new script with a version number. By default this is + latest_version + 1. You can set this to 'true' to tell Migrate to use the UTC + timestamp instead. + + .. versionadded:: 0.7.2 + +.. _custom-templates: + + +Customize templates +=================== + +Users can pass ``templates_path`` to API functions to provide customized +templates path. Path should be a collection of templates, like +``migrate.versioning.templates`` package directory. + +One may also want to specify custom themes. API functions accept +``templates_theme`` for this purpose (which defaults to `default`) + +Example:: + + /home/user/templates/manage $ ls + default.py_tmpl + pylons.py_tmpl + + /home/user/templates/manage $ migrate manage manage.py --templates_path=/home/user/templates --templates_theme=pylons + +.. versionadded:: 0.6.0 diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index dbc6dec..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,75 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html web pickle htmlhelp latex changes linkcheck - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " changes to make an overview over all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - -clean: - -rm -rf _build/* - -html: - mkdir -p _build/html _build/doctrees - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html - @echo - @echo "Build finished. The HTML pages are in _build/html." - -pickle: - mkdir -p _build/pickle _build/doctrees - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -web: pickle - -json: - mkdir -p _build/json _build/doctrees - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - mkdir -p _build/htmlhelp _build/doctrees - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in _build/htmlhelp." - -latex: - mkdir -p _build/latex _build/doctrees - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex - @echo - @echo "Build finished; the LaTeX files are in _build/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." - -changes: - mkdir -p _build/changes _build/doctrees - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes - @echo - @echo "The overview file is in _build/changes." - -linkcheck: - mkdir -p _build/linkcheck _build/doctrees - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in _build/linkcheck/output.txt." diff --git a/docs/api.rst b/docs/api.rst deleted file mode 100644 index 6d5a54e..0000000 --- a/docs/api.rst +++ /dev/null @@ -1,200 +0,0 @@ -Module :mod:`migrate.changeset` -- Schema changes -================================================= - -Module :mod:`migrate.changeset` -- Schema migration API -------------------------------------------------------- - -.. automodule:: migrate.changeset - :members: - :synopsis: Database changeset management - -Module :mod:`ansisql ` -- Standard SQL implementation ------------------------------------------------------------------------------------- - -.. automodule:: migrate.changeset.ansisql - :members: - :member-order: groupwise - :synopsis: Standard SQL implementation for altering database schemas - -Module :mod:`constraint ` -- Constraint schema migration API ---------------------------------------------------------------------------------------------- - -.. automodule:: migrate.changeset.constraint - :members: - :inherited-members: - :show-inheritance: - :member-order: groupwise - :synopsis: Standalone schema constraint objects - -Module :mod:`databases ` -- Database specific schema migration ------------------------------------------------------------------------------------------------ - -.. automodule:: migrate.changeset.databases - :members: - :synopsis: Database specific changeset implementations - -.. _mysql-d: - -Module :mod:`mysql ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - -.. automodule:: migrate.changeset.databases.mysql - :members: - :synopsis: MySQL database specific changeset implementations - -.. _firebird-d: - -Module :mod:`firebird ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - -.. automodule:: migrate.changeset.databases.firebird - :members: - :synopsis: Firebird database specific changeset implementations - -.. _oracle-d: - -Module :mod:`oracle ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - -.. automodule:: migrate.changeset.databases.oracle - :members: - :synopsis: Oracle database specific changeset implementations - -.. _postgres-d: - -Module :mod:`postgres ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: migrate.changeset.databases.postgres - :members: - :synopsis: PostgreSQL database specific changeset implementations - -.. _sqlite-d: - -Module :mod:`sqlite ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: migrate.changeset.databases.sqlite - :members: - :synopsis: SQLite database specific changeset implementations - -Module :mod:`visitor ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: migrate.changeset.databases.visitor - :members: - -Module :mod:`schema ` -- Additional API to SQLAlchemy for migrations ----------------------------------------------------------------------------------------------- - -.. automodule:: migrate.changeset.schema - :members: - :synopsis: Schema changeset handling functions - - -Module :mod:`migrate.versioning` -- Database versioning and repository management -================================================================================== - -.. automodule:: migrate.versioning - :members: - :synopsis: Database version and repository management - -.. _versioning-api: - -Module :mod:`api ` -- Python API commands ------------------------------------------------------------------ - -.. automodule:: migrate.versioning.api - :members: - :synopsis: External API for :mod:`migrate.versioning` - - -Module :mod:`genmodel ` -- ORM Model generator -------------------------------------------------------------------------------------- - -.. automodule:: migrate.versioning.genmodel - :members: - :synopsis: Python database model generator and differencer - -Module :mod:`pathed ` -- Path utilities ----------------------------------------------------------------------------- - -.. automodule:: migrate.versioning.pathed - :members: - :synopsis: File/Directory handling class - -Module :mod:`repository ` -- Repository management -------------------------------------------------------------------------------------- - -.. automodule:: migrate.versioning.repository - :members: - :synopsis: SQLAlchemy migrate repository management - :member-order: groupwise - -Module :mod:`schema ` -- Migration upgrade/downgrade ----------------------------------------------------------------------------------- - -.. automodule:: migrate.versioning.schema - :members: - :member-order: groupwise - :synopsis: Database schema management - -Module :mod:`schemadiff ` -- ORM Model differencing -------------------------------------------------------------------------------------- - -.. automodule:: migrate.versioning.schemadiff - :members: - :synopsis: Database schema and model differencing - -Module :mod:`script ` -- Script actions --------------------------------------------------------------------- - -.. automodule:: migrate.versioning.script.base - :synopsis: Script utilities - :member-order: groupwise - :members: - -.. automodule:: migrate.versioning.script.py - :members: - :member-order: groupwise - :inherited-members: - :show-inheritance: - -.. automodule:: migrate.versioning.script.sql - :members: - :member-order: groupwise - :show-inheritance: - :inherited-members: - -Module :mod:`shell ` -- CLI interface ------------------------------------------------------------------- - -.. automodule:: migrate.versioning.shell - :members: - :synopsis: Shell commands - -Module :mod:`util ` -- Various utility functions --------------------------------------------------------------------------- - -.. automodule:: migrate.versioning.util - :members: - :synopsis: Utility functions - -Module :mod:`version ` -- Versioning management ------------------------------------------------------------------------------ - -.. automodule:: migrate.versioning.version - :members: - :member-order: groupwise - :synopsis: Version management - -Module :mod:`exceptions ` -- Exception definitions -====================================================================== - -.. automodule:: migrate.exceptions - :members: - :synopsis: Migrate exception classes - diff --git a/docs/changelog.rst b/docs/changelog.rst deleted file mode 100644 index a61b6b9..0000000 --- a/docs/changelog.rst +++ /dev/null @@ -1,342 +0,0 @@ -0.7.3 (201x-xx-xx) ---------------------------- - -Changes -****************** - -- - -Documentation -****************** - -- - -Features -****************** - -- - -Fixed Bugs -****************** - -- #140: excludeTablesgetDiffOfModelAgainstModel is not passing excludeTables - correctly (patch by Jason Michalski) -- #72: Regression against issue #38, migrate drops engine reference (patch by - asuffield@gmail.com) -- #154: versioning/schema.py imports deprecated sqlalchemy.exceptions (patch by - Alex Favaro) -- fix deprecation warning using MetaData.reflect instead of reflect=True - constructor argument -- fix test failure by removing unsupported length argument for Text column - -0.7.2 (2011-11-01) ---------------------------- - -Changes -****************** - -- support for SQLAlchemy 0.5.x has been dropped -- Python 2.6 is the minimum supported Python version - -Documentation -****************** - -- add :ref:`credits ` for contributors -- add :ref:`glossary ` -- improve :ref:`advice on testing production changes ` -- improve Sphinx markup -- refine :ref:`Database Schema Versioning ` texts, add - example for adding/droping columns (#104) -- add more developer related information to :ref:`development` section -- use sphinxcontrib.issuetracker to link to Google Code issue tracker - -Features -****************** - -- improved :pep:`8` compliance (#122) -- optionally number versions with timestamps instead of sequences (partly - pulled from Pete Keen) -- allow descriptions in SQL change script filenames (by Pete Keen) -- improved model generation - -Fixed Bugs -****************** - -- #83: api test downgrade/upgrade does not work with sql scripts (pulled from - Yuen Ho Wong) -- #105: passing a unicode string as the migrate repository fails (add - regression test) -- #113: make_update_script_for_model fails with AttributeError: 'SchemaDiff' - object has no attribute 'colDiffs' (patch by Jeremy Cantrell) -- #118: upgrade and downgrade functions are reversed when using the command - "make_update_script_for_model" (patch by Jeremy Cantrell) -- #121: manage.py should use the "if __name__=='__main__'" trick -- #123: column creation in make_update_script_for_model and required API change - (by Gabriel de Perthuis) -- #124: compare_model_to_db gets confused by sqlite_sequence (pulled from - Dustin J. Mitchell) -- #125: drop column does not work on persistent sqlite databases (pulled from - Benoît Allard) -- #128: table rename failure with sqlalchemy 0.7.x (patch by Mark McLoughlin) -- #129: update documentation and help text (pulled from Yuen Ho Wong) - -0.7.1 (2011-05-27) ---------------------------- - -Fixed Bugs -****************** - -- docs/_build is excluded from source tarball builds -- use table.append_column() instead of column._set_parent() in - ChangesetColumn.add_to_table() -- fix source and issue tracking URLs in documentation - -0.7 (2011-05-27) ---------------------------- - -Features -****************** - -- compatibility with SQLAlchemy 0.7 -- add :py:data:`migrate.__version__` - -Fixed bugs -****************** - -- fix compatibility issues with SQLAlchemy 0.7 - -0.6.1 (2011-02-11) ---------------------------- - -Features -****************** - -- implemented column adding when foreign keys are present for sqlite -- implemented columns adding with unique constraints for sqlite -- implemented adding unique and foreign key constraints to columns - for sqlite -- remove experimental `alter_metadata` parameter - -Fixed bugs -****************** - -- updated tests for Python 2.7 -- repository keyword in :py:func:`migrate.versioning.api.version_control` can - also be unicode -- added if main condition for manage.py script -- make :py:func:`migrate.changeset.constraint.ForeignKeyConstraint.autoname` - work with SQLAlchemy 0.5 and 0.6 -- fixed case sensitivity in setup.py dependencies -- moved :py:mod:`migrate.changeset.exceptions` and - :py:mod:`migrate.versioning.exceptions` to :py:mod:`migrate.exceptions` -- cleared up test output and improved testing of deprecation warnings. -- some documentation fixes -- #107: fixed syntax error in genmodel.py -- #96: fixed bug with column dropping in sqlite -- #94: fixed bug that prevented non-unique indexes being created -- fixed bug with column dropping involving foreign keys -- fixed bug when dropping columns with unique constraints in sqlite -- rewrite of the schema diff internals, now supporting column - differences in additon to missing columns and tables. -- fixed bug when passing empty list in - :py:func:`migrate.versioning.shell.main` failed -- #108: Fixed issues with firebird support. - -0.6 (11.07.2010) ---------------------------- - -.. _backwards-06: - -.. warning:: **Backward incompatible changes**: - - - :py:func:`migrate.versioning.api.test` and schema comparison functions - now all accept `url` as first parameter and `repository` as second. - - python upgrade/downgrade scripts do not import `migrate_engine` - magically, but recieve engine as the only parameter to function (eg. - ``def upgrade(migrate_engine):``) - - :py:meth:`Column.alter ` - does not accept `current_name` anymore, it extracts name from the old - column. - -Features -************** - -- added support for :ref:`firebird ` -- added option to define custom templates through option ``--templates_path`` - and ``--templates_theme``, - read more in :ref:`tutorial section ` -- use Python logging for output, can be shut down by passing - ``--disable_logging`` to :py:func:`migrate.versioning.shell.main` -- deprecated `alter_column` comparing of columns. Just use explicit parameter - change. -- added support for SQLAlchemy 0.6.x by Michael Bayer -- Constraint classes have `cascade=True` keyword argument to issue ``DROP - CASCADE`` where supported -- added :py:class:`~migrate.changeset.constraint.UniqueConstraint`/ - :py:class:`~migrate.changeset.constraint.CheckConstraint` and corresponding - create/drop methods -- API `url` parameter can also be an :py:class:`Engine` instance (this usage is - discouraged though sometimes necessary) -- code coverage is up to 80% with more than 100 tests -- alter, create, drop column / rename table / rename index constructs now - accept `alter_metadata` parameter. If True, it will modify Column/Table - objects according to changes. Otherwise, everything will be untouched. -- added `populate_default` bool argument to :py:meth:`Column.create - ` which issues corresponding - UPDATE statements to set defaults after column creation -- :py:meth:`Column.create ` - accepts `primary_key_name`, `unique_name` and `index_name` as string value - which is used as contraint name when adding a column - -Fixed bugs -***************** - -- :term:`ORM` methods now accept `connection` parameter commonly used for - transactions -- `server_defaults` passed to :py:meth:`Column.create - ` are now issued correctly -- use SQLAlchemy quoting system to avoid name conflicts (#32) -- complete refactoring of :py:class:`~migrate.changeset.schema.ColumnDelta` - (#23) -- partial refactoring of :py:mod:`migrate.changeset` package -- fixed bug when :py:meth:`Column.alter - `\(server_default='string') - was not properly set -- constraints passed to :py:meth:`Column.create - ` are correctly interpreted - (``ALTER TABLE ADD CONSTRAINT`` is issued after ``ATLER TABLE ADD COLUMN``) -- script names don't break with dot in the name - -Documentation -********************* - -- :ref:`dialect support ` table was added to documentation -- major update to documentation - - -0.5.4 ------ - -- fixed preview_sql parameter for downgrade/upgrade. Now it prints SQL if the step is SQL script and runs step with mocked engine to only print SQL statements if ORM is used. [Domen Kozar] -- use entrypoints terminology to specify dotted model names (module.model:User) [Domen Kozar] -- added engine_dict and engine_arg_* parameters to all api functions (deprecated echo) [Domen Kozar] -- make --echo parameter a bit more forgivable (better Python API support) [Domen Kozar] -- apply patch to refactor cmd line parsing for Issue 54 by Domen Kozar - -0.5.3 ------ - -- apply patch for Issue 29 by Jonathan Ellis -- fix Issue 52 by removing needless parameters from object.__new__ calls - -0.5.2 ------ - -- move sphinx and nose dependencies to extras_require and tests_require -- integrate patch for Issue 36 by Kumar McMillan -- fix unit tests -- mark ALTER TABLE ADD COLUMN with FOREIGN KEY as not supported by SQLite - -0.5.1.2 -------- - -- corrected build - -0.5.1.1 -------- - -- add documentation in tarball -- add a MANIFEST.in - -0.5.1 ------ - -- SA 0.5.x support. SQLAlchemy < 0.5.1 not supported anymore. -- use nose instead of py.test for testing -- Added --echo=True option for all commands, which will make the sqlalchemy connection echo SQL statements. -- Better PostgreSQL support, especially for schemas. -- modification to the downgrade command to simplify the calling (old way still works just fine) -- improved support for SQLite -- add support for check constraints (EXPERIMENTAL) -- print statements removed from APIs -- improved sphinx based documentation -- removal of old commented code -- :pep:`8` clean code - -0.4.5 ------ - -- work by Christian Simms to compare metadata against databases -- new repository format -- a repository format migration tool is in migrate/versioning/migrate_repository.py -- support for default SQL scripts -- EXPERIMENTAL support for dumping database to model - -0.4.4 ------ - -- patch by pwannygoodness for Issue #15 -- fixed unit tests to work with py.test 0.9.1 -- fix for a SQLAlchemy deprecation warning - -0.4.3 ------ - -- patch by Kevin Dangoor to handle database versions as packages and ignore their __init__.py files in version.py -- fixed unit tests and Oracle changeset support by Christian Simms - -0.4.2 ------ - -- package name is sqlalchemy-migrate again to make pypi work -- make import of sqlalchemy's SchemaGenerator work regardless of previous imports - -0.4.1 ------ - -- setuptools patch by Kevin Dangoor -- re-rename module to migrate - -0.4.0 ------ - -- SA 0.4.0 compatibility thanks to Christian Simms -- all unit tests are working now (with sqlalchemy >= 0.3.10) - -0.3 ---- - -- SA 0.3.10 compatibility - -0.2.3 ------ - -- Removed lots of SA monkeypatching in Migrate's internals -- SA 0.3.3 compatibility -- Removed logsql (trac issue 75) -- Updated py.test version from 0.8 to 0.9; added a download link to setup.py -- Fixed incorrect "function not defined" error (trac issue 88) -- Fixed SQLite and .sql scripts (trac issue 87) - -0.2.2 ------ - -- Deprecated driver(engine) in favor of engine.name (trac issue 80) -- Deprecated logsql (trac issue 75) -- Comments in .sql scripts don't make things fail silently now (trac issue 74) -- Errors while downgrading (and probably other places) are shown on their own line -- Created mailing list and announcements list, updated documentation accordingly -- Automated tests now require py.test (trac issue 66) -- Documentation fix to .sql script commits (trac issue 72) -- Fixed a pretty major bug involving logengine, dealing with commits/tests (trac issue 64) -- Fixes to the online docs - default DB versioning table name (trac issue 68) -- Fixed the engine name in the scripts created by the command 'migrate script' (trac issue 69) -- Added Evan's email to the online docs - -0.2.1 ------ - -- Created this changelog -- Now requires (and is now compatible with) SA 0.3 -- Commits across filesystems now allowed (shutil.move instead of os.rename) (trac issue 62) diff --git a/docs/changeset.rst b/docs/changeset.rst deleted file mode 100644 index 1ae070e..0000000 --- a/docs/changeset.rst +++ /dev/null @@ -1,282 +0,0 @@ -.. _changeset-system: -.. highlight:: python - -************************** -Database schema migrations -************************** - -.. currentmodule:: migrate.changeset.schema - -Importing :mod:`migrate.changeset` adds some new methods to existing SQLAlchemy -objects, as well as creating functions of its own. Most operations can be done -either by a method or a function. Methods match SQLAlchemy's existing API and -are more intuitive when the object is available; functions allow one to make -changes when only the name of an object is available (for example, adding a -column to a table in the database without having to load that table into -Python). - -Changeset operations can be used independently of SQLAlchemy Migrate's -:ref:`versioning `. - -For more information, see the API documentation for :mod:`migrate.changeset`. - -.. _summary-changeset-api: - -Here are some direct links to the relevent sections of the API documentations: - - -* :meth:`Create a column ` -* :meth:`Drop a column ` -* :meth:`Alter a column ` (follow a link for list of supported changes) -* :meth:`Rename a table ` -* :meth:`Rename an index ` -* :meth:`Create primary key constraint ` -* :meth:`Drop primary key constraint ` -* :meth:`Create foreign key contraint ` -* :meth:`Drop foreign key constraint ` -* :meth:`Create unique key contraint ` -* :meth:`Drop unique key constraint ` -* :meth:`Create check key contraint ` -* :meth:`Drop check key constraint ` - - -.. note:: - - Many of the schema modification methods above take an ``alter_metadata`` - keyword parameter. This parameter defaults to `True`. - -The following sections give examples of how to make various kinds of schema -changes. - -Column -====== - -Given a standard SQLAlchemy table: - -.. code-block:: python - - table = Table('mytable', meta, - Column('id', Integer, primary_key=True), - ) - table.create() - -.. _column-create: - -You can create a column with :meth:`~ChangesetColumn.create`: - -.. code-block:: python - - col = Column('col1', String, default='foobar') - col.create(table, populate_default=True) - - # Column is added to table based on its name - assert col is table.c.col1 - - # col1 is populated with 'foobar' because of `populate_default` - -.. _column-drop: - -.. note:: - - You can pass `primary_key_name`, `index_name` and `unique_name` to the - :meth:`~ChangesetColumn.create` method to issue ``ALTER TABLE ADD - CONSTRAINT`` after changing the column. - - For multi columns constraints and other advanced configuration, check the - :ref:`constraint tutorial `. - - .. versionadded:: 0.6.0 - -You can drop a column with :meth:`~ChangesetColumn.drop`: - -.. code-block:: python - - col.drop() - - -.. _column-alter: - -You can alter a column with :meth:`~ChangesetColumn.alter`: - -.. code-block:: python - - col.alter(name='col2') - - # Renaming a column affects how it's accessed by the table object - assert col is table.c.col2 - - # Other properties can be modified as well - col.alter(type=String(42), default="life, the universe, and everything", nullable=False) - - # Given another column object, col1.alter(col2), col1 will be changed to match col2 - col.alter(Column('col3', String(77), nullable=True)) - assert col.nullable - assert table.c.col3 is col - -.. deprecated:: 0.6.0 - Passing a :class:`~sqlalchemy.schema.Column` to - :meth:`ChangesetColumn.alter` is deprecated. Pass in explicit - parameters, such as `name` for a new column name and `type` for a - new column type, instead. Do **not** include any parameters that - are not changed. - -.. _table-rename: - -Table -===== - -SQLAlchemy includes support for `creating and dropping`__ tables.. - -Tables can be renamed with :meth:`~ChangesetTable.rename`: - -.. code-block:: python - - table.rename('newtablename') - -.. __: http://www.sqlalchemy.org/docs/core/schema.html#creating-and-dropping-database-tables -.. currentmodule:: migrate.changeset.constraint - - -.. _index-rename: - -Index -===== - -SQLAlchemy supports `creating and dropping`__ indexes. - -Indexes can be renamed using -:meth:`~migrate.changeset.schema.ChangesetIndex.rename`: - -.. code-block:: python - - index.rename('newindexname') - -.. __: http://www.sqlalchemy.org/docs/core/schema.html#indexes - - -.. _constraint-tutorial: - -Constraint -========== - -.. currentmodule:: migrate.changeset.constraint - -SQLAlchemy supports creating or dropping constraints at the same time a table -is created or dropped. SQLAlchemy Migrate adds support for creating and -dropping :class:`~sqlalchemy.schema.PrimaryKeyConstraint`, -:class:`~sqlalchemy.schema.ForeignKeyConstraint`, -:class:`~sqlalchemy.schema.CheckConstraint` and -:class:`~sqlalchemy.schema.UniqueConstraint` constraints independently using -``ALTER TABLE`` statements. - -The following rundowns are true for all constraints classes: - -#. Make sure you import the relevant constraint class from :mod:`migrate` and - not from :mod:`sqlalchemy`, for example: - - .. code-block:: python - - from migrate.changeset.constraint import ForeignKeyConstraint - - The classes in that module have the extra - :meth:`~ConstraintChangeset.create` and :meth:`~ConstraintChangeset.drop` - methods. - -#. You can also use constraints as in SQLAlchemy. In this case passing table - argument explicitly is required: - - .. code-block:: python - - cons = PrimaryKeyConstraint('id', 'num', table=self.table) - - # Create the constraint - cons.create() - - # Drop the constraint - cons.drop() - - You can also pass in :class:`~sqlalchemy.schema.Column` objects (and table - argument can be left out): - - .. code-block:: python - - cons = PrimaryKeyConstraint(col1, col2) - -#. Some dialects support ``CASCADE`` option when dropping constraints: - - .. code-block:: python - - cons = PrimaryKeyConstraint(col1, col2) - - # Create the constraint - cons.create() - - # Drop the constraint - cons.drop(cascade=True) - -.. note:: - SQLAlchemy Migrate will try to guess the name of the constraints for - databases, but if it's something other than the default, you'll need to - give its name. Best practice is to always name your constraints. Note that - Oracle requires that you state the name of the constraint to be created or - dropped. - - -Examples ---------- - -Primary key constraints: - -.. code-block:: python - - from migrate.changeset.constraint import PrimaryKeyConstraint - - cons = PrimaryKeyConstraint(col1, col2) - - # Create the constraint - cons.create() - - # Drop the constraint - cons.drop() - -Foreign key constraints: - -.. code-block:: python - - from migrate.changeset.constraint import ForeignKeyConstraint - - cons = ForeignKeyConstraint([table.c.fkey], [othertable.c.id]) - - # Create the constraint - cons.create() - - # Drop the constraint - cons.drop() - -Check constraints: - -.. code-block:: python - - from migrate.changeset.constraint import CheckConstraint - - cons = CheckConstraint('id > 3', columns=[table.c.id]) - - # Create the constraint - cons.create() - - # Drop the constraint - cons.drop() - -Unique constraints: - -.. code-block:: python - - from migrate.changeset.constraint import UniqueConstraint - - cons = UniqueConstraint('id', 'age', table=self.table) - - # Create the constraint - cons.create() - - # Drop the constraint - cons.drop() diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index f8f609c..0000000 --- a/docs/conf.py +++ /dev/null @@ -1,203 +0,0 @@ -# -*- coding: utf-8 -*- -# -# SQLAlchemy Migrate documentation build configuration file, created by -# sphinx-quickstart on Fri Feb 13 12:58:57 2009. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# The contents of this file are pickled, so don't put values in the namespace -# that aren't pickleable (module imports are okay, they're removed automatically). -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If your extensions are in another directory, add it here. If the directory -# is relative to the documentation root, use os.path.abspath to make it -# absolute, like shown here. -#sys.path.append(os.path.abspath('.')) -# Allow module docs to build without having sqlalchemy-migrate installed: -sys.path.append(os.path.dirname(os.path.abspath('.'))) - -# General configuration -# --------------------- - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinxcontrib.issuetracker'] - -# link to sqlalchemy docs -intersphinx_mapping = { - 'sqlalchemy': ('http://www.sqlalchemy.org/docs/', None), - 'python': ('http://docs.python.org/2.7', None)} - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'SQLAlchemy Migrate' -copyright = u'2011, Evan Rosson, Jan Dittberner, Domen Kožar, Chris Withers' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0.7.3' -# The full version, including alpha/beta/rc tags. -release = '0.7.3.dev' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of documents that shouldn't be included in the build. -#unused_docs = [] - -# List of directories, relative to source directory, that shouldn't be searched -# for source files. -exclude_trees = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# Options for sphinxcontrib.issuetracker -# -------------------------------------- - -issuetracker = 'google code' -issuetracker_project = 'sqlalchemy-migrate' - - -# Options for HTML output -# ----------------------- - -# The style sheet to use for HTML and HTML Help pages. A file of that name -# must exist either in Sphinx' static/ path, or in one of the custom paths -# given in html_static_path. -html_style = 'default.css' - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_use_modindex = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, the reST sources are included in the HTML build as _sources/. -#html_copy_source = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'SQLAlchemyMigratedoc' - - -# Options for LaTeX output -# ------------------------ - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, document class [howto/manual]). -latex_documents = [ - ('index', 'SQLAlchemyMigrate.tex', ur'SQLAlchemy Migrate Documentation', - ur'Evan Rosson, Jan Dittberner, Domen Kožar', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_use_modindex = True diff --git a/docs/credits.rst b/docs/credits.rst deleted file mode 100644 index a2d0648..0000000 --- a/docs/credits.rst +++ /dev/null @@ -1,124 +0,0 @@ -.. _credits: - -Credits -------- - -sqlalchemy-migrate has been created by: - - - Evan Rosson - -Thanks to Google for sponsoring Evan's initial Summer of Code project. - -The project is maintained by the following people: - - - Domen Kožar - - Jan Dittberner - -The following people contributed patches, advice or bug reports that helped -improve sqlalchemy-migrate: - -.. hlist:: - :columns: 3 - - - Adam Lowry - - Adomas Paltanavicius - - Alexander Artemenko - - Alex Favaro - - Andrew Bialecki - - Andrew Grossman - - Andrew Lenards - - Andrew Svetlov - - Andrey Gladilin - - Andronikos Nedos - - Antoine Pitrou - - Ben Hesketh - - Ben Keroack - - Benjamin Johnson - - Branko Vukelic - - Bruno Lopes - - Ches Martin - - Chris Percious - - Chris Withers - - Christian Simms - - Christophe de Vienne - - Christopher Grebs - - Christopher Lee - - Dan Getelman - - David Kang - - Dustin J. Mitchell - - Emil Kroymann - - Eyal Sorek - - Florian Apolloner - - Fred Lin - - Gabriel de Perthuis - - Graham Higgins - - Ilya Shabalin - - James Mills - - Jarrod Chesney - - Jason Michalski - - Jason R. Coombs - - Jason Yamada-Hanff - - Jay Pipes - - Jayson Vantuyl - - Jeremy Cantrell - - Jeremy Slade - - Jeroen Ruigrok van der Werven - - Joe Heck - - Jonas Baumann - - Jonathan Ellis - - Jorge Vargas - - Joshua Ginsberg - - Jude Nagurney - - Juliusz Gonera - - Kevin Dangoor - - Kristaps Rāts - - Kristian Kvilekval - - Kumar McMillan - - Landon J. Fuller - - Lev Shamardin - - Lorin Hochstein - - Luca Barbato - - Lukasz Zukowski - - Mahmoud Abdelkader - - Marica Odagaki - - Marius Gedminas - - Mark Friedenbach - - Mark McLoughlin - - Martin Andrews - - Mathieu Leduc-Hamel - - Michael Bayer - - Michael Elsdörfer - - Mikael Lepistö - - Nathan Wright - - Nevare Stark - - Nicholas Retallack - - Nick Barendt - - Patrick Shields - - Paul Bonser - - Paul Johnston - - Pawel Bylina - - Pedro Algarvio - - Peter Strömberg - - Poli García - - Pradeep Kumar - - Rafał Kos - - Robert Forkel - - Robert Schiele - - Robert Sudwarts - - Romy Maxwell - - Ryan Wilcox - - Sami Dalouche - - Sergiu Toarca - - Simon Engledew - - Stephen Emslie - - Sylvain Prat - - Toshio Kuratomi - - Trey Stout - - Vasiliy Astanin - - Yeeland Chen - - Yuen Ho Wong - - - asuffield (at) gmail (dot) com - -If you helped us in the past and miss your name please tell us about your -contribution and we will add you to the list. diff --git a/docs/download.rst b/docs/download.rst deleted file mode 100644 index d2d66de..0000000 --- a/docs/download.rst +++ /dev/null @@ -1,71 +0,0 @@ -Download --------- - -You can get the latest version of SQLAlchemy Migrate from the -`project's download page`_, the `cheese shop`_, pip_ or via easy_install_:: - - $ easy_install sqlalchemy-migrate - -or:: - - $ pip install sqlalchemy-migrate - -You should now be able to use the :command:`migrate` command from the command -line:: - - $ migrate - -This should list all available commands. To get more information regarding a -command use:: - - $ migrate help COMMAND - -If you'd like to be notified when new versions of SQLAlchemy Migrate -are released, subscribe to `migrate-announce`_. - -.. _pip: http://pip.openplans.org/ -.. _easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall#installing-easy-install -.. _sqlalchemy: http://www.sqlalchemy.org/download.html -.. _`project's download page`: http://code.google.com/p/sqlalchemy-migrate/downloads/list -.. _`cheese shop`: http://pypi.python.org/pypi/sqlalchemy-migrate -.. _`migrate-announce`: http://groups.google.com/group/migrate-announce - -.. _development: - -Development ------------ - -Migrate's Mercurial_ repository is located at `Google Code`_. - -To get the latest trunk:: - - $ hg clone http://sqlalchemy-migrate.googlecode.com/hg/ - -Patches should be submitted to the `issue tracker`_. You are free to create -your own clone to provide your patches. We are open to pull requests in our -`issue tracker`_. - -If you want to work on sqlalchemy-migrate you might want to use a `virtualenv`. - -To run the included test suite you have to copy :file:`test_db.cfg.tmpl` to -:file:`test_db.cfg` and put SQLAlchemy database URLs valid for your environment -into that file. We use `nose`_ for our tests and include a test requirements -file for pip. You might use the following commands to install the test -requirements and run the tests:: - - $ pip install -r test-req.pip - $ python setup.py develop - $ python setup.py nosetests - -If you are curious about status changes of sqlalchemy-migrate's issues you -might want to subscribe to `sqlalchemy-migrate-issues`_. - -We use a `Jenkins CI`_ continuous integration tool installation to -help us run tests on most of the databases that migrate supports. - -.. _Mercurial: http://www.mercurial-scm.org/ -.. _Google Code: http://sqlalchemy-migrate.googlecode.com/hg/ -.. _issue tracker: http://code.google.com/p/sqlalchemy-migrate/issues/list -.. _sqlalchemy-migrate-issues: http://groups.google.com/group/sqlalchemy-migrate-issues -.. _Jenkins CI: http://jenkins.gnuviech-server.de/job/sqlalchemy-migrate-all/ -.. _nose: http://readthedocs.org/docs/nose/ diff --git a/docs/faq.rst b/docs/faq.rst deleted file mode 100644 index a36073c..0000000 --- a/docs/faq.rst +++ /dev/null @@ -1,13 +0,0 @@ -FAQ -=== - -Q: Adding a **nullable=False** column -************************************** - -A: Your table probably already contains data. That means if you add column, it's contents will be NULL. -Thus adding NOT NULL column restriction will trigger IntegrityError on database level. - -You have basically two options: - -#. Add the column with a default value and then, after it is created, remove the default value property. This does not work for column types that do not allow default values at all (such as 'text' and 'blob' on MySQL). -#. Add the column without NOT NULL so all rows get a NULL value, UPDATE the column to set a value for all rows, then add the NOT NULL property to the column. This works for all column types. diff --git a/docs/glossary.rst b/docs/glossary.rst deleted file mode 100644 index cee01c7..0000000 --- a/docs/glossary.rst +++ /dev/null @@ -1,26 +0,0 @@ -.. _glossary: - -******** -Glossary -******** - -.. glossary:: - :sorted: - - repository - A migration repository contains :command:`manage.py`, a configuration - file (:file:`migrate.cfg`) and the database :term:`changeset` scripts - which can be Python scripts or SQL files. - - changeset - A set of instructions how upgrades and downgrades to or from a specific - version of a database schema should be performed. - - ORM - Abbreviation for "object relational mapper". An ORM is a tool that maps - object hierarchies to database relations. - - version - A version in SQLAlchemy migrate is defined by a :term:`changeset`. - Versions may be numbered using ascending numbers or using timestamps - (as of SQLAlchemy migrate release 0.7.2) diff --git a/docs/historical/ProjectDesignDecisionsAutomation.trac b/docs/historical/ProjectDesignDecisionsAutomation.trac deleted file mode 100644 index 2f1c8b7..0000000 --- a/docs/historical/ProjectDesignDecisionsAutomation.trac +++ /dev/null @@ -1,26 +0,0 @@ -There are many migrations that don't require a lot of thought - for example, if we add a column to a table definition, we probably want to have an "ALTER TABLE...ADD COLUMN" statement show up in our migration. - -The difficulty lies in the automation of changes where the requirements aren't obvious. What happens when you add a unique constraint to a column whose data is not already unique? What happens when we split an existing table in two? Completely automating database migrations is not possible. - -That said - we shouldn't have to hunt down and handwrite the ALTER TABLE statements for every new column; this is often just tedious. Many other common migration tasks require little serious thought; such tasks are ripe for automation. Any automation attempted, however, should not interfere with our ability to write scripts by hand if we so choose; our tool should ''not'' be centered around automation. - - -Automatically generating the code for this sort of task seems like a good solution: - * It does not obstruct us from writing changes by hand; if we don't like the autogenerated code, delete it or don't generate it to begin with - * We can easily add other migration tasks to the autogenerated code - * We can see right away if the code is what we're expecting, or if it's wrong - * If the generated code is wrong, it is easily modified; we can use parts of the generated code, rather than being required to use either 100% or 0% - * Maintence, usually a problem with auto-generated code, is not an issue: old database migration scripts are not the subject of maintenance; the correct solution is usually a new migration script. - - -Implementation is a problem: finding the 'diff' of two databases to determine what columns to add is not trivial. Fortunately, there exist tools that claim to do this for us: [http://sqlfairy.sourceforge.net/ SQL::Translator] and [http://xml2ddl.berlios.de/ XML to DDL] both claim to have this capability. - -... - -All that said, this is ''not'' something I'm going to attempt during the Summer of Code. - * I'd have to rely tremendously on a tool I'm not at all familiar with - * Creates a risk of the project itself relying too much on the automation, a Bad Thing - * The project has a deadline and I have plenty else to do already - * Lots of people with more experience than me say this would take more time than it's worth - -It's something that might be considered for future work if this project is successful, though. \ No newline at end of file diff --git a/docs/historical/ProjectDesignDecisionsScriptFormat.trac b/docs/historical/ProjectDesignDecisionsScriptFormat.trac deleted file mode 100644 index b23c2d4..0000000 --- a/docs/historical/ProjectDesignDecisionsScriptFormat.trac +++ /dev/null @@ -1,147 +0,0 @@ -Important to our system is the API used for making database changes. - -=== Raw SQL; .sql script === -Require users to write raw SQL. Migration scripts are .sql scripts (with database version information in a header comment). - -+ Familiar interface for experienced DBAs. - -+ No new API to learn[[br]] -SQL is used elsewhere; many people know SQL already. Those who are still learning SQL will gain expertise not in the API of a specific tool, but in a language which will help them elsewhere. (On the other hand, those who are familiar with Python with no desire to learn SQL might find a Python API more intuitive.) - -- Difficult to extend when necessary[[br]] -.sql scripts mean that we can't write new functions specific to our migration system when necessary. (We can't always assume that the DBMS supports functions/procedures.) - -- Lose the power of Python[[br]] -Some things are possible in Python that aren't in SQL - for example, suppose we want to use some functions from our application in a migration script. (The user might also simply prefer Python.) - -- Loss of database independence.[[br]] -There isn't much we can do to specify different actions for a particular DBMS besides copying the .sql file, which is obviously bad form. - -=== Raw SQL; Python script === -Require users to write raw SQL. Migration scripts are python scripts whose API does little beyond specifying what DBMS(es) a particular statement should apply to. - -For example, -{{{ -run("CREATE TABLE test[...]") # runs for all databases -run("ALTER TABLE test ADD COLUMN varchar2[...]",oracle) # runs for Oracle only -run("ALTER TABLE test ADD COLUMN varchar[...]",postgres|mysql) # runs for Postgres or MySQL only -}}} - -We could also allow parts of a single statement to apply to a specific DBMS: -{{{ -run("ALTER TABLE test ADD COLUMN"+sql("varchar",postgres|mysql)+sql("varchar2",oracle)) -}}} -or, the same thing: -{{{ -run("ALTER TABLE test ADD COLUMN"+sql("varchar",postgres|mysql,"varchar2",oracle)) -}}} - -+ Allows the user to write migration scripts for multiple DBMSes. - -- The user must manage the conflicts between different databases themselves. [[br]] -The user can write scripts to deal with conflicts between databases, but they're not really database-independent: the user has to deal with conflicts between databases; our system doesn't help them. - -+ Minimal new API to learn. [[br]] -There is a new API to learn, but it is extremely small, depending mostly on SQL DDL. This has the advantages of "no new API" in our first solution. - -- More verbose than .sql scripts. - -=== Raw SQL; automatic translation between each dialect === -Same as the above suggestion, but allow the user to specify a 'default' dialect of SQL that we'll interpret and whose quirks we'll deal with. -That is, write everything in SQL and try to automatically resolve the conflicts of different DBMSes. - -For example, take the following script: -{{{ -engine=postgres - -run(""" -CREATE TABLE test ( - id serial -) -""") -}}} -Running this on a Postgres database, surprisingly enough, would generate exactly what we typed: -{{{ -CREATE TABLE test ( - id serial -) -}}} - -Running it on a MySQL database, however, would generate something like -{{{ -CREATE TABLE test ( - id integer auto_increment -) -}}} - -+ Database-independence issues of the above SQL solutions are resolved.[[br]] -Ideally, this solution would be as database-independent as a Python API for database changes (discussed next), but with all the advantages of writing SQL (no new API). - -- Difficult implementation[[br]] -Obviously, this is not easy to implement - there is a great deal of parsing logic and a great many things that need to be accounted for. In addition, this is a complex operation; any implementation will likely have errors somewhere. - -It seems tools for this already exist; an effective tool would trivialize this implementation. I experimented a bit with [http://sqlfairy.sourceforge.net/ SQL::Translator] and [http://xml2ddl.berlios.de/ XML to DDL]; however, I had difficulties with both. - -- Database-specific features ensure that this cannot possibly be "complete". [[br]] -For example, Postgres has an 'interval' type to represent times and (AFAIK) MySQL does not. - -=== Database-independent Python API === -Create a Python API through which we may manage database changes. Scripts would be based on the existing SQLAlchemy API when possible. - -Scripts would look something like -{{{ -# Create a table -test_table = table('test' - ,Column('id',Integer,notNull=True) -) -table.create() -# Add a column to an existing table -test_table.add_column('id',Integer,notNull=True) -# Or, use a column object instead of its parameters -test_table.add_column(Column('id',Integer,notNull=True)) -# Or, don't use a table object at all -add_column('test','id',Integer,notNull=True) -}}} -This would use engines, similar to SQLAlchemy's, to deal with database-independence issues. - -We would, of course, allow users to write raw SQL if they wish. This would be done in the manner outlined in the second solution above; this allows us to write our entire script in SQL and ignore the Python API if we wish, or write parts of our solution in SQL to deal with specific databases. - -+ Deals with database-independence thoroughly and with minimal user effort.[[br]] -SQLAlchemy-style engines would be used for this; issues of different DBMS syntax are resolved with minimal user effort. (Database-specific features would still need handwritten SQL.) - -+ Familiar interface for SQLAlchemy users.[[br]] -In addition, we can often cut-and-paste column definitions from SQLAlchemy tables, easing one particular task. - -- Requires that the user learn a new API. [[br]] -SQL already exists; people know it. SQL newbies might be more comfortable with a Python interface, but folks who already know SQL must learn a whole new API. (On the other hand, the user *can* write things in SQL if they wish, learning only the most minimal of APIs, if they are willing to resolve issues of database-independence themself.) - -- More difficult to implement than pure SQL solutions. [[br]] -SQL already exists/has been tested. A new Python API does not/has not, and much of the work seems to consist of little more than reinventing the wheel. - -- Script behavior might change under different versions of the project.[[br]] -...where .sql scripts behave the same regardless of the project's version. - -=== Generate .sql scripts from a Python API === -Attempts to take the best of the first and last solutions. An API similar to the previous solution would be used, but rather than immediately being applied to the database, .sql scripts are generated for each type of database we're interested in. These .sql scripts are what's actually applied to the database. - -This would essentially allow users to skip the Python script step entirely if they wished, and write migration scripts in SQL instead, as in solution 1. - -+ Database-independence is an option, when needed. - -+ A familiar interface/an interface that can interact with other tools is an option, when needed. - -+ Easy to inspect the SQL generated by a script, to ensure it's what we're expecting. - -+ Migration scripts won't change behavior across different versions of the project. [[br]] -Once a Python script is translated to a .sql script, its behavior is consistent across different versions of the project, unlike a pure Python solution. - -- Multiple ways to do a single task: not Pythonic.[[br]] -I never really liked that word - "Pythonic" - but it does apply here. Multiple ways to do a single task has the potential to cause confusion, especially in a large project if many people do the same task different ways. We have to support both ways of doing things, as well. - ----- - -'''Conclusion''': The last solution, generating .sql scripts from a Python API, seems to be best. - -The first solution (.sql scripts) suffers from a lack of database-independence, but is familiar to experienced database developers, useful with other tools, and shows exactly what will be done to the database. The Python API solution has no trouble with database-independence, but suffers from other problems that the .sql solution doesn't. The last solution resolves both reasonably well. Multiple ways to do a single task might be called "not Pythonic", but IMO, the trade-off is worth this cost. - -Automatic translation between different dialects of SQL might have potential for use in a solution, but existing tools for this aren't reliable enough, as far as I can tell. \ No newline at end of file diff --git a/docs/historical/ProjectDesignDecisionsVersioning.trac b/docs/historical/ProjectDesignDecisionsVersioning.trac deleted file mode 100644 index f06ba18..0000000 --- a/docs/historical/ProjectDesignDecisionsVersioning.trac +++ /dev/null @@ -1,56 +0,0 @@ -An important aspect of this project is database versioning. For migration scripts to be most useful, we need to know what version the database is: that is, has a particular migration script already been run? - -An option not discussed below is "no versioning"; that is, simply apply any script we're given, and rely on the user to ensure it's valid. This is entirely too error-prone to seriously consider, and takes a lot of the usefulness out of the proposed tool. - - -=== Database-wide version numbers === -A single integer version number would specify the version of each database. This is stored in the database in a table, let's call it "schema"; each migration script is associated with a certain database version number. - -+ Simple implementation[[br]] -Of the 3 solutions presented here, this one is by far the simplest. - -+ Past success[[br]] -Used in [http://www.rubyonrails.org/ Ruby on Rails' migrations]. - -~ Can detect corrupt schemas, but requires some extra work and a *complete* set of migrations.[[br]] -If we have a set of database migration scripts that build the database from the ground up, we can apply them in sequence to a 'dummy' database, dump a diff of the real and dummy schemas, and expect a valid schema to match the dummy schema. - -- Requires changes to the database schema.[[br]] -Not a tremendous change - a single table with a single column and a single row - but a change nonetheless. - -=== Table/object-specific version numbers === -Each database "object" - usually tables, though we might also deal with other database objects, such as stored procedures or Postgres' sequences - would have a version associated with it, initially 1. These versions are stored in a table, let's call it "schema". This table has two columns: the name of the database object and its current version number. - -+ Allows us to write migration scripts for a subset of the database.[[br]] -If we have multiple people working on a very large database, we may want to write migration scripts for a section of the database without stepping on another person's work. This allows unrelated to - -- Requires changes to the database schema. -Similar to the database-wide version number; the contents of the new table are more complex, but still shouldn't conflict with anything. - -- More difficult to implement than a database-wide version number. - -- Determining the version of database-specific objects (ie. stored procedures, functions) is difficult. - -- Ultimately gains nothing over the previous solution.[[br]] -The intent here was to allow multiple people to write scripts for a single database, but if database-wide version numbers aren't assigned until the script is placed in the repository, we could already do this. - -=== Version determined via introspection === -Each script has a schema associated with it, rather than a version number. The database schema is loaded, analyzed, and compared to the schema expected by the script. - -+ No modifications to the database are necessary for this versioning system.[[br]] -The primary advantage here is that no changes to the database are required. - -- Most difficult solution to implement, by far.[[br]] -Comparing the state of every schema object in the database is much more complex than simply comparing a version number, especially since we need to do it in a database-independent way (ie. we can't just diff the dump of each schema). SQLAlchemy's reflection would certainly be very helpful, but this remains the most complex solution. - -+ "Automatically" detects corrupt schemas.[[br]] -A corrupt schema won't match any migration script. - -- Difficult to deal with corrupt schemas.[[br]] -When version numbers are stored in the database, you have some idea of where an error occurred. Without this, we have no idea what version the database was in before corruption. - -- Potential ambiguity: what if two database migration scripts expect the same schema? - ----- - -'''Conclusion''': database-wide version numbers are the best way to go. \ No newline at end of file diff --git a/docs/historical/ProjectDetailedDesign.trac b/docs/historical/ProjectDetailedDesign.trac deleted file mode 100644 index e295251..0000000 --- a/docs/historical/ProjectDetailedDesign.trac +++ /dev/null @@ -1,29 +0,0 @@ -This is very much a draft/brainstorm right now. It should be made prettier and thought about in more detail later, but it at least gives some idea of the direction we're headed right now. ----- - * Two distinct tools; should not be coupled (can work independently): - * Versioning tool - * Command line tool; let's call it "samigrate" - * Organizes old migration scripts into repositories - * Runs groups of migration scripts on a database, updating it to a specified version/latest version - * Helps run various tests - * usage - * "samigrate create PATH": Create project migration-script repository - * We shouldn't have to enter the path for every other command. Use a hidden file - * (This means we can't move the repository after it's created. Oh well) - * "samigrate add SCRIPT [VERSION]": Add script to this project's repository; latest version - * If a .sql script: how to determine engine, operation (up/down)? Options: - * specify at the command line: "samigrate add SCRIPT UP_OR_DOWN ENGINE" - * naming convention: SCRIPT is named something like NAME.postgres.up.sql - * "samigrate upgrade CONNECTION_STRING [VERSION] [SCRIPT...]": connect to the specified database and upgrade (or downgrade) it to the specified version (default latest) - * If SCRIPT... specified: act like these scripts are in the repository (useful for testing?) - * "samigrate dump CONNECTION_STRING [VERSION] [SCRIPT...]": like update, but sends all sql to stdout instead of the db - * (Later: some more commands, to be used for script testing tools) - * Alchemy API extensions for altering schema - * Operations here are DB-independent - * Each database modification is a script that may use this API - * Can handwrite SQL for all databases or a single database - * upgrade()/downgrade() functions: need only one file for both operations - * sql scripts reqire either (2 files, *.up.sql;*.down.sql) or (don't use downgrade) - * usage - * "python NAME.py ENGINE up": upgrade sql > stdout - * "python NAME.py ENGINE down": downgrade sql > stdout \ No newline at end of file diff --git a/docs/historical/ProjectGoals.trac b/docs/historical/ProjectGoals.trac deleted file mode 100644 index 5879196..0000000 --- a/docs/historical/ProjectGoals.trac +++ /dev/null @@ -1,50 +0,0 @@ -== Goals == - -=== DBMS-independent schema changes === -Many projects need to run on more than one DBMS. Similar changes need to be applied to both types of databases upon a schema change. The usual solution to database changes - .sql scripts with ALTER statements - runs into problems since different DBMSes have different dialects of SQL; we end up having to create a different script for each DBMS. This project will simplify this by providing an API, similar to the table definition API that already exists in SQLAlchemy, to alter a table independent of the DBMS being used, where possible. - -This project will support all DBMSes currently supported by SQLAlchemy: SQLite, Postgres, MySQL, Oracle, and MS SQL. Adding support for more should be as possible as it is in SQLAlchemy. - -Many are already used to writing .sql scripts for database changes, aren't interested in learning a new API, and have projects where DBMS-independence isn't an issue. Writing SQL statements as part of a (Python) change script must be an option, of course. Writing change scripts as .sql scripts, eliminating Python scripts from the picture entirely, would be nice too, although this is a lower-priority goal. - -=== Database versioning and change script organization === -Once we've accumulated a set of change scripts, it's important to know which ones have been applied/need to be applied to a particular database: suppose we need to upgrade a database that's extremenly out-of-date; figuring out the scripts to run by hand is tedious. Applying changes in the wrong order, or applying changes when they shouldn't be applied, is bad; attempting to manage all of this by hand inevitably leads to an accident. This project will be able to detect the version of a particular database and apply the scripts required to bring it up to the latest version, or up to any specified version number (given all change scripts required to reach that version number). - -Sometimes we need to be able to revert a schema to an older version. There's no automatic way to do this without rebuilding the database from scratch, so our project will allow one to write scripts to downgrade the database as well as upgrade it. If such scripts have been written, we should be able to apply them in the correct order, just like upgrading. - -Large projects inevitably accumulate a large number of database change scripts; it's important that we have a place to keep them. Once a script has been written, this project will deal with organizing it among existing change scripts, and the user will never have to look at it again. - -=== Change testing === -It's important to test one's database changes before applying them to a production database (unless you happen to like disasters). Much testing is up to the user and can't be automated, but there's a few places we can help ensure at least a minimal level of schema integrity. A few examples are below; we could add more later. - -Given an obsolete schema, a database change script, and an up-to-date schema known to be correct, this project will be able to ensure that applying the -change script to the obsolete schema will result in an up-to-date schema - all without actually changing the obsolete database. Folks who have SQLAlchemy create their database using table.create() might find this useful; this is also useful for ensuring database downgrade scripts are correct. - -Given a schema of a known version and a complete set of change scripts up to that version, this project will be able to detect if the schema matches its version. If a schema has gone through changes not present in migration scripts, this test will fail; if applying all scripts in sequence up to the specified version creates an identical schema, this test will succeed. Identifying that a schema is corrupt is sufficient; it would be nice if we could give a clue as to what's wrong, but this is lower priority. (Implementation: we'll probably show a diff of two schema dumps; this should be enough to tell the user what's gone wrong.) - -== Non-Goals == -ie. things we will '''not''' try to do (at least, during the Summer of Code) - -=== Automatic generation of schema changes === -For example, one might define a table: -{{{ -CREATE TABLE person ( - id integer, - name varchar(80) -); -}}} -Later, we might add additional columns to the definition: -{{{ -CREATE TABLE person ( - id integer, - name varchar(80), - profile text -); -}}} -It might be nice if a tool could look at both table definitions and spit out a change script; something like -{{{ -ALTER TABLE person ADD COLUMN profile text; -}}} -This is a difficult problem for a number of reasons. I have no intention of tackling this problem as part of the Summer of Code. This project aims to give you a better way to write that ALTER statement and make sure it's applied correctly, not to write it for you. - -(Using an [http://sqlfairy.sourceforge.net/ existing] [http://xml2ddl.berlios.de/ tool] to add this sort of thing later might be worth looking into, but it will not be done during the Summer of Code. Among other reasons, methinks it's best to start with a system that isn't dependent on this sort of automation.) \ No newline at end of file diff --git a/docs/historical/ProjectProposal.txt b/docs/historical/ProjectProposal.txt deleted file mode 100644 index e270800..0000000 --- a/docs/historical/ProjectProposal.txt +++ /dev/null @@ -1,73 +0,0 @@ -Evan Rosson - -Project ---- -SQLAlchemy Schema Migration - - -Synopsis ---- -SQLAlchemy is an excellent object-relational database mapper for Python projects. Currently, it does a fine job of creating a database from scratch, but provides no tool to assist the user in modifying an existing database. This project aims to provide such a tool. - - -Benefits ---- - Application requirements change; a database schema must be able to change with them. It's possible to write SQL scripts that make the proper modifications without any special tools, but this setup quickly becomes difficult to manage - when we need to apply multiple updates to a database, organize old migration scripts, or have a single application support more than one DBMS, a tool to support database changes becomes necessary. This tool will aid the creation of organizing migration scripts, applying multiple updates or removing updates to revert to an old version, and creating DBMS-independent migration scripts. - - Writing one's schema migration scripts by hand often results in problems when dealing with multiple obsolete database instances - we must figure out what scripts are necessary to bring the database up-to-date. Database versioning tools are helpful for this task; this project will track the version of a particular database to determine what scripts are necessary to update an old schema. - - -Description ---- - The migration system used by Ruby on Rails has had much success, and for good reason - the system is easy to understand, generally database-independent, as powerful as the application itself, and capable of dealing nicely with a schema with multiple instances of different versions. A migration system similar to that of Rails is a fine place to begin this project. - - Each instance of the schema will have a version associated with it; this version is tracked using a single table with a single row and a single integer column. A set of changes to the database schema will increment the schema's version number; each migration script will be associated with a schema version. - - A migration script will be written by the user, and consist of two functions: -- upgrade(): brings an old database up-to-date, from version n-1 to version n -- downgrade(): reverts an up-to-date database to the previous schema; an 'undo' for upgrade() - - When applying multiple updates to an old schema instance, migration scripts are applied in sequence: when updating a schema to version n from version n-2, two migration scripts are run; n-2 => n-1 => n. - - A command-line tool will create empty migration scripts (empty upgrade()/downgrade() functions), display the SQL that will be generated by a migration script for a particular DBMS, and apply migration scripts to a specified database. - - This project will implement the command-line tool that manages the above functionality. This project will also extend SQLAlchemy with the functions necessary to construct DBMS-independent migration scripts: in particular, column creation/deletion/alteration and the ability to rename existing tables/indexes/columns will be implemented. We'll also need a way to write raw SQL for a specific DBMS/set of DBMSes for situations where our abstraction doesn't fit a script's requirements. The creation/deletion of existing tables and indexes are operations already provided by SQLAlchemy. - - - On DBMS support - I intend to support MySQL, Postgres, SQLite, Oracle, and MS-SQL by the end of the project. (Update: I previously omitted support for Oracle and MS-SQL because I don't have access to the full version of each; I wasn't aware Oracle Lite and MS-SQL Express were available for free.) The system will be abstracted in such a way that adding support for other databases will not be any more difficult than adding support for them in SQLAlchemy. - - -Schedule ---- -This project will be my primary activity this summer. Unfortunately, I am in school when things begin, until June 9, but I can still begin the project during that period. I have no other commitments this summer - I can easily make up any lost time. -I will be spending my spare time this summer further developing my online game (discussed below), but this has no deadline and will not interfere with the project proposed here. - - -I'll begin by familiarizing myself with the internals of SQLAlchemy and creating a detailed plan for the project. This plan will be reviewed by the current SQLAlchemy developers and other potential users, and will be modified based on their feedback. This will be completed no later than May 30, one week after SoC begins. - -Development will follow, in this order: -- The database versioning system. This will manage the creation and application of (initially empty) migration scripts. Complete by June 16. - - Access the database; read/update the schema's version number - - Apply a single (empty) script to the database - - Apply a set of (empty) scripts to upgrade/downgrade the database to a specified version; examine all migration scripts and apply all to update the database to the latest version available -- An API for table/column alterations, to make the above system useful. Complete by August 11. - - Implement an empty API - does nothing at this point, but written in such a way that syntax for each supported DBMS may be added as a module. Completed June 26-30, the mid-project review deadline. - - Implement/test the above API for a single DBMS (probably Postgres, as I'm familiar with it). Users should be able to test the 'complete' application with this DBMS. - - Implement the database modification API for other supported databases - -All development will have unit tests written where appropriate. Unit testing the SQL generated for each DBMS will be particularly important. - -The project will finish with various wrap-up activities, documentation, and some final tests, to be completed by the project deadline. - - -About me ---- -I am a 3rd year BS Computer Science student; Cal Poly, San Luis Obispo, California, USA; currently applying for a Master's degree in CS from the same school. I've taken several classes dealing with databases, though much of what I know on the subject is self-taught. Outside of class, I've developed a browser-based online game, Zeal, at http://zealgame.com ; it has been running for well over a year and gone through many changes. It has taught me firsthand the importance of using appropriate tools and designing one's application well early on (largely through the pain that follows when you don't); I've learned a great many other things from the experience as well. - -One recurring problem I've had with this project is dealing with changes to the database schema. I've thought much about how I'd like to see this solved, but hadn't done much to implement it. - -I'm now working on another project that will be making use of SQLAlchemy: it fits many of my project's requirements, but lacks a migration tool that will be much needed. This presents an opportunity for me to make my first contribution to open source - I've long been interested in open source software and use it regularly, but haven't contributed to any until now. I'm particularly interested in the application of this tool with the TurboGears framework, as this project was inspired by a suggestion the TurboGears mailing list and I'm working on a project using TurboGears - but there is no reason to couple an SQLAlchemy enhancement with TurboGears; this project may be used by anyone who uses SQLAlchemy. - - -Further information: -http://evan.zealgame.com/soc diff --git a/docs/historical/RepositoryFormat.trac b/docs/historical/RepositoryFormat.trac deleted file mode 100644 index 57f6bf6..0000000 --- a/docs/historical/RepositoryFormat.trac +++ /dev/null @@ -1,56 +0,0 @@ -This plan has several problems and has been modified; new plan is discussed in wiki:RepositoryFormat2 - ----- - -One problem with [http://www.rubyonrails.org/ Ruby on Rails'] (very good) schema migration system is the behavior of scripts that depend on outside sources; ie. the application. If those change, there's no guarantee that such scripts will behave as they did before, and you'll get strange results. - -For example, suppose one defines a SQLAlchemy table: -{{{ -users = Table('users', metadata, - Column('user_id', Integer, primary_key = True), - Column('user_name', String(16), nullable = False), - Column('password', String(20), nullable = False) -) -}}} -and creates it in a change script: -{{{ -from project import table - -def upgrade(): - table.users.create() -}}} - -Suppose we later add a column to this table. We write an appropriate change script: -{{{ -from project import table - -def upgrade(): - # This syntax isn't set in stone yet - table.users.add_column('email_address', String(60), key='email') -}}} -...and change our application's table definition: -{{{ -users = Table('users', metadata, - Column('user_id', Integer, primary_key = True), - Column('user_name', String(16), nullable = False), - Column('password', String(20), nullable = False), - Column('email_address', String(60), key='email') #new column -) -}}} - -Modifying the table definition changes how our first script behaves - it will create the table with the new column. This might work if we only apply change scripts to a few database which are always kept up to date (or very close), but we'll run into errors eventually if our migration scripts' behavior isn't consistent. - ----- - -One solution is to generate .sql files from a Python change script at the time it's added to a repository. The sql generated by the script for each database is set in stone at this point; changes to outside files won't affect it. - -This limits what change scripts are capable of - we can't write dynamic SQL; ie., we can't do something like this: -{{{ -for row in db.execute("select id from table1"): - db.execute("insert into table2 (table1_id, value) values (:id,42)",**row) -}}} -But SQL is usually powerful enough to where the above is rarely necessary in a migration script: -{{{ -db.execute("insert into table2 select id,42 from table1") -}}} -This is a reasonable solution. The limitations aren't serious (everything possible in a traditional .sql script is still possible), and change scripts are much less prone to error. diff --git a/docs/historical/RepositoryFormat2.trac b/docs/historical/RepositoryFormat2.trac deleted file mode 100644 index 496478c..0000000 --- a/docs/historical/RepositoryFormat2.trac +++ /dev/null @@ -1,28 +0,0 @@ -My original plan for Migrate's RepositoryFormat had several problems: - - * Bind parameters: We needed to bind parameters into statements to get something suitable for an .sql file. For some types of parameters, there's no clean way to do this without writing an entire parser - too great a cost for this project. There's a reason why SQLAlchemy's logs display the statement and its parameters separately: the binding is done at a lower level than we have access to. - * Failure: Discussed in #17, the old format had no easy way to find the Python statements associated with an SQL error. This makes it difficult to debug scripts. - -A new format will be used to solve this problem instead. -Similar to our previous solution, where one .sql file was created per version/operation/DBMS (version_1.upgrade.postgres.sql, for example), one file will be created per version/operation/DBMS here. -These files will contain the following information: - - * The dialect used to perform the logging. Particularly, - * The paramstyle expected by the dbapi - * The DBMS this log applies to - * Information on each logged SQL statement, each of which contains: - * The text of the statement - * Parameters to be bound to the statement - * A Python stack trace at the point the statement was logged - this allows us to tell what Python statements are associated with an SQL statement when there's an error - -These files will be created by pickling a Python object with the above information. - -Such files may be executed by loading the log and having SQLAlchemy execute them as it might have before. - -Good: - * Since the statements and bind parameters are stored separately and executed as SQLAlchemy would normally execute them, one problem discussed above is eliminated. - * Storing the stack trace at the point each statement was logged allows us to identify what Python statements are responsible for an SQL error. This makes it much easier for users to debug their scripts. - -Bad: - * It's less trivial to commit .sql scripts to our repository, since they're no longer used internally. This isn't a huge loss, and .sql commits can still be implemented later if need be. - * There's some danger of script behavior changing if changes are made to the dbapi the script is associated with. The primary place where problems would occur is during parameter binding, but the chance of this changing significantly isn't large. The danger of changes in behavior due to changes in the user's application is not affected. \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 1e464d8..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,188 +0,0 @@ -:mod:`migrate` - SQLAlchemy Migrate (schema change management) -============================================================== - -.. module:: migrate -.. moduleauthor:: Evan Rosson - -:Author: Evan Rosson -:Maintainer: Domen Kožar -:Maintainer: Jan Dittberner -:Issues: http://code.google.com/p/sqlalchemy-migrate/issues/list -:Source Code: http://code.google.com/p/sqlalchemy-migrate/ -:CI Tool: http://jenkins.gnuviech-server.de/job/sqlalchemy-migrate-all/ -:Generated: |today| -:License: MIT -:Version: |release| - - -.. topic:: Overview - - Inspired by Ruby on Rails' migrations, SQLAlchemy Migrate provides a way to - deal with database schema changes in SQLAlchemy_ projects. - - Migrate was started as part of `Google's Summer of Code`_ by Evan Rosson, - mentored by Jonathan LaCour. - - The project was taken over by a small group of volunteers when Evan had no - free time for the project. It is now hosted as a `Google Code project`_. - During the hosting change the project was renamed to SQLAlchemy Migrate. - - Currently, sqlalchemy-migrate supports Python versions from 2.6 to 2.7. - SQLAlchemy Migrate 0.7.2 supports SQLAlchemy 0.6.x and 0.7.x branches. - - Support for Python 2.4 and 2.5 as well as SQLAlchemy 0.5.x has been dropped - after sqlalchemy-migrate 0.7.1. - -.. warning:: - - Version **0.6** broke backward compatibility, please read :ref:`changelog - ` for more info. - - -Download and Development ------------------------- - -.. toctree:: - - download - credits - - -.. _dialect-support: - -Dialect support ---------------- - -.. list-table:: - :header-rows: 1 - :widths: 25 10 10 10 10 10 11 - - * - Operation / Dialect - - :ref:`sqlite ` - - :ref:`postgres ` - - :ref:`mysql ` - - :ref:`oracle ` - - :ref:`firebird ` - - mssql - * - :ref:`ALTER TABLE RENAME TABLE ` - - yes - - yes - - yes - - yes - - no - - not supported - * - :ref:`ALTER TABLE RENAME COLUMN ` - - yes (workaround) [#1]_ - - yes - - yes - - yes - - yes - - not supported - * - :ref:`ALTER TABLE ADD COLUMN ` - - yes (workaround) [#2]_ - - yes - - yes - - yes - - yes - - not supported - * - :ref:`ALTER TABLE DROP COLUMN ` - - yes (workaround) [#1]_ - - yes - - yes - - yes - - yes - - not supported - * - :ref:`ALTER TABLE ALTER COLUMN ` - - yes (workaround) [#1]_ - - yes - - yes - - yes (with limitations) [#3]_ - - yes [#4]_ - - not supported - * - :ref:`ALTER TABLE ADD CONSTRAINT ` - - partial (workaround) [#1]_ - - yes - - yes - - yes - - yes - - not supported - * - :ref:`ALTER TABLE DROP CONSTRAINT ` - - partial (workaround) [#1]_ - - yes - - yes - - yes - - yes - - not supported - * - :ref:`RENAME INDEX ` - - no - - yes - - no - - yes - - yes - - not supported - - -.. [#1] Table is renamed to temporary table, new table is created followed by - INSERT statements. -.. [#2] See http://www.sqlite.org/lang_altertable.html for more information. - In cases not supported my sqlite, table is renamed to temporary table, - new table is created followed by INSERT statements. -.. [#3] You can not change datatype or rename column if table has NOT NULL - data, see http://blogs.x2line.com/al/archive/2005/08/30/1231.aspx for - more information. -.. [#4] Changing nullable is not supported - - -Tutorials --------------- - -List of useful tutorials: - -* `Using migrate with Elixir `_ -* `Developing with migrations - `_ - - -User guide -------------- - -SQLAlchemy Migrate is split into two parts, database schema versioning -(:mod:`migrate.versioning`) and database migration management -(:mod:`migrate.changeset`). The versioning API is available as the -:ref:`migrate ` command. - -.. toctree:: - - versioning - changeset - tools - faq - glossary - -.. _`google's summer of code`: http://code.google.com/soc -.. _`Google Code project`: http://code.google.com/p/sqlalchemy-migrate -.. _sqlalchemy: http://www.sqlalchemy.org - - -API Documentation ------------------- - -.. toctree:: - - api - - -Changelog ---------- - -.. toctree:: - - changelog - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/theme/almodovar.css b/docs/theme/almodovar.css deleted file mode 100644 index c7f250b..0000000 --- a/docs/theme/almodovar.css +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Original theme modified by Evan Rosson - * http://erosson.com/migrate - * --- - * - * Theme Name: Almodovar - * Theme URI: http://blog.ratterobert.com/archiv/2005/03/09/almodovar/ - * Description: Das Theme basiert im Ursprung auf Michael Heilemanns Kubrick-Template und ist von dem einen oder anderen Gimmick anderer sehr guter Templates inspiriert worden. - * Version: 0.7 - * Author: ratte / robert - * Author URI: http://blog.ratterobert.com/ - * */ - -/* Begin Typography & Colors */ -body { - font-size: 75%; - font-family: 'Lucida Grande', 'Trebuchet MS', 'Bitstream Vera Sans', Sans-Serif; - background-color: #CCF; - color: #333; - text-align: center; -} - -#page { - background-color: #fff; - border: 1px solid #88f; - text-align: left; -} - -#content { - font-size: 1.2em; - margin: 1em; - } - -#content p, -#content ul, -#content blockquote { - line-height: 1.6em; -} - -#footer { - border-top: 1px solid #006; - margin-top: 2em; -} - -small { - font-family: 'Trebuchet MS', Arial, Helvetica, Sans-Serif; - font-size: 0.9em; - line-height: 1.5em; - } - -h1, h2, h3 { - font-family: 'Trebuchet MS', 'Lucida Grande', Verdana, Arial, Sans-Serif; - font-weight: bold; - margin-top: .7em; - margin-bottom: .7em; - } - -h1 { - font-size: 2.5em; -} -h2 { - font-size: 2em; -} -h3 { - font-size: 1.5em; -} - -h1, h2, h3 { - color: #33a; -} - -h1 a, h2 a, h3 a { - color: #33a; -} - -h1, h1 a, h1 a:hover, h1 a:visited, -h2, h2 a, h2 a:hover, h2 a:visited, -h3, h3 a, h3 a:hover, h3 a:visited, -cite { - text-decoration: none; -} - -#content p a:visited { - color: #004099; - /*font-weight: normal;*/ -} - -small, blockquote, strike { - color: #33a; -} - -#links ul ul li, #links li { - list-style: none; -} - -code { - font: 1.1em 'Courier', 'Courier New', Fixed; -} - -acronym, abbr, span.caps { - font-size: 0.9em; - letter-spacing: .07em; -} - -a { - color: #0050FF; - /*text-decoration: none;*/ - text-decoration:underline; - /*font-weight: bold;*/ -} -a:hover { - color: #0080FF; -} - -/* Special case doc-title */ -h1.doc-title { - text-transform: lowercase; - font-size: 4em; - margin: 0; -} -h1.doc-title a { - display: block; - padding-left: 0.8em; - padding-bottom: .5em; - padding-top: .5em; - margin: 0; - border-bottom: 1px #fff solid; -} -h1.doc-title, -h1.doc-title a, -h1.doc-title a:visited, -h1.doc-title a:hover { - text-decoration: none; - color: #0050FF; -} -/* End Typography & Colors */ - - -/* Begin Structure */ -body { - margin: 0; - padding: 0; -} - -#page { - background-color: white; - margin: 0 auto 0 9em; - padding: 0; - max-width: 60em; - border: 1px solid #555596; -} -* html #page { -* width: 60em; -* } -* -* #content { -* margin: 0 1em 0 3em; -* } -* -* #content h1 { -* margin-left: 0; -* } -* -* #footer { -* padding: 0 0 0 1px; -* margin: 0; -* margin-top: 1.5em; -* clear: both; -* } -* -* #footer p { -* margin: 1em; -* } -* -*/* End Structure */ - - - -/* Begin Headers */ -.description { - text-align: center; - } - -/* End Headers */ - - -/* Begin Form Elements */ -#searchform { - margin: 1em auto; - text-align: right; - } - -#searchform #s { - width: 100px; - padding: 2px; - } - -#searchsubmit { - padding: 1px; - } -/* End Form Elements */ - - -/* Begin Various Tags & Classes */ -acronym, abbr, span.caps { - cursor: help; -} - -acronym, abbr { - border-bottom: 1px dashed #999; -} - -blockquote { - margin: 15px 30px 0 10px; - padding-left: 20px; - border-left: 5px solid #CCC; -} - -blockquote cite { - margin: 5px 0 0; - display: block; -} - -hr { - display: none; -} - -a img { - border: none; -} - -.navigation { - display: block; - text-align: center; - margin-top: 10px; - margin-bottom: 60px; -} -/* End Various Tags & Classes*/ - -span a { color: #CCC; } - -span a:hover { color: #0050FF; } - -#navcontainer { - margin-top: 0px; - padding-top: 0px; - width: 100%; - background-color: #AAF; - text-align: right; -} - -#navlist ul { - margin-left: 0; - margin-right: 5px; - padding-left: 0; - white-space: nowrap; -} - -#navlist li { - display: inline; - list-style-type: none; -} - -#navlist a { - padding: 3px 10px; - color: #fff; - background-color: #339; - text-decoration: none; - border: 1px solid #44F; - font-weight: normal; -} - -#navlist a:hover { - color: #000; - background-color: #FFF; - text-decoration: none; - font-weight: normal; -} - -#navlist a:active, #navlist a.selected { - padding: 3px 10px; - color: #000; - background-color: #EEF; - text-decoration: none; - border: 1px solid #CCF; - font-weight: normal; -} - diff --git a/docs/theme/layout.css b/docs/theme/layout.css deleted file mode 100644 index ed80572..0000000 --- a/docs/theme/layout.css +++ /dev/null @@ -1,123 +0,0 @@ -@import url("pudge.css"); -@import url("almodovar.css"); - -/* Basic Style ------------------------------------ */ - -h1.pudge-member-page-heading { - font-size: 300%; -} -h4.pudge-member-page-subheading { - font-size: 130%; - font-style: italic; - margin-top: -2.0em; - margin-left: 2em; - margin-bottom: .3em; - color: #0050CC; -} -p.pudge-member-blurb { - font-style: italic; - font-weight: bold; - font-size: 120%; - margin-top: 0.2em; - color: #999; -} -p.pudge-member-parent-link { - margin-top: 0; -} -/*div.pudge-module-doc { - max-width: 45em; -}*/ -div.pudge-section { - margin-left: 2em; - max-width: 45em; -} -/* Section Navigation ------------------------------------ */ - -div#pudge-section-nav -{ - margin: 1em 0 1.5em 0; - padding: 0; - height: 20px; -} - -div#pudge-section-nav ul { - border: 0; - margin: 0; - padding: 0; - list-style-type: none; - text-align: center; - border-right: 1px solid #aaa; -} -div#pudge-section-nav ul li -{ - display: block; - float: left; - text-align: center; - padding: 0; - margin: 0; -} - -div#pudge-section-nav ul li .pudge-section-link, -div#pudge-section-nav ul li .pudge-missing-section-link -{ - background: #aaa; - width: 9em; - height: 1.8em; - border: 1px solid #bbb; - padding: 0; - margin: 0 0 10px 0; - color: #ddd; - text-decoration: none; - display: block; - text-align: center; - font: 11px/20px "Verdana", "Lucida Grande"; - cursor: hand; - text-transform: lowercase; -} - -div#pudge-section-nav ul li a:hover { - color: #000; - background: #fff; -} - -div#pudge-section-nav ul li .pudge-section-link { - background: #888; - color: #eee; - border: 1px solid #bbb; -} - -/* Module Lists ------------------------------------ */ -dl.pudge-module-list dt { - font-style: normal; - font-size: 110%; -} -dl.pudge-module-list dd { - color: #555; -} - -/* Misc Overrides */ -.rst-doc p.topic-title a { - color: #777; -} -.rst-doc ul.auto-toc a, -.rst-doc div.contents a { - color: #333; -} -pre { background: #eee; } - -.rst-doc dl dt { - color: #444; - margin-top: 1em; - font-weight: bold; -} -.rst-doc dl dd { - margin-top: .2em; -} -.rst-doc hr { - display: block; - margin: 2em 0; -} - diff --git a/docs/theme/layout.html b/docs/theme/layout.html deleted file mode 100644 index 094e989..0000000 --- a/docs/theme/layout.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - ${title} - - - - - -
-

${home_title}

- - -
- -
- - -
- - - diff --git a/docs/tools.rst b/docs/tools.rst deleted file mode 100644 index 9c2a920..0000000 --- a/docs/tools.rst +++ /dev/null @@ -1,15 +0,0 @@ -Repository migration (0.4.5 -> 0.5.4) -================================================ - -.. index:: repository migration - -:command:`migrate_repository.py` should 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}` diff --git a/docs/versioning.rst b/docs/versioning.rst deleted file mode 100644 index dd413d3..0000000 --- a/docs/versioning.rst +++ /dev/null @@ -1,641 +0,0 @@ -.. _versioning-system: -.. currentmodule:: migrate.versioning -.. highlight:: console - -*********************************** -Database schema versioning workflow -*********************************** - -SQLAlchemy migrate provides the :mod:`migrate.versioning` API that is -also available as the :ref:`migrate ` command. - -Purpose of this package is frontend for migrations. It provides commands to -manage migrate :term:`repository` and database selection as well as script -versioning. - - -Project setup -============= - -.. _create_change_repository: - -Create a change repository --------------------------- - -To begin, we'll need to create a :term:`repository` for our project. - -All work with repositories is done using the :ref:`migrate -` command. Let's create our project's repository:: - - $ migrate create my_repository "Example project" - -This creates an initially empty :term:`repository` relative to current -directory at :file:`my_repository/` named `Example project`. - -The :term:`repository` directory contains a sub directory :file:`versions` that -will store the :ref:`schema versions `, a configuration file -:file:`migrate.cfg` that contains :ref:`repository configuration -` and a script :ref:`manage.py -` that has the same functionality as the -:ref:`migrate ` command but is preconfigured with -repository specific parameters. - -.. note:: - - Repositories are associated with a single database schema, and store - collections of change scripts to manage that schema. The scripts in a - :term:`repository` may be applied to any number of databases. Each - :term:`repository` has an unique name. This name is used to identify the - :term:`repository` we're working with. - - -Version control a database --------------------------- - -Next we need to declare database to be under version control. Information on a -database's version is stored in the database itself; declaring a database to be -under version control creates a table named **migrate_version** and associates -it with your :term:`repository`. - -The database is specified as a `SQLAlchemy database url`_. - -.. _`sqlalchemy database url`: - http://www.sqlalchemy.org/docs/core/engines.html#database-urls - -The :option:`version_control` command assigns a specified database with a -:term:`repository`:: - - $ python my_repository/manage.py version_control sqlite:///project.db my_repository - -We can have any number of databases under this :term:`repository's -` version control. - -Each schema has a :term:`version` that SQLAlchemy Migrate manages. Each change -script applied to the database increments this version number. You can retrieve -a database's current :term:`version`:: - - $ python my_repository/manage.py db_version sqlite:///project.db my_repository - 0 - -A freshly versioned database begins at version 0 by default. This assumes the -database is empty or does only contain schema elements (tables, views, -constraints, indices, ...) that will not be affected by the changes in the -:term:`repository`. (If this is a bad assumption, you can specify the -:term:`version` at the time the database is put under version control, with the -:option:`version_control` command.) We'll see that creating and applying change -scripts changes the database's :term:`version` number. - -Similarly, we can also see the latest :term:`version` available in a -:term:`repository` with the command:: - - $ python my_repository/manage.py version my_repository - 0 - -We've entered no changes so far, so our :term:`repository` cannot upgrade a -database past version 0. - -Project management script -------------------------- - -.. _project_management_script: - -Many commands need to know our project's database url and :term:`repository` -path - typing them each time is tedious. We can create a script for our project -that remembers the database and :term:`repository` we're using, and use it to -perform commands:: - - $ migrate manage manage.py --repository=my_repository --url=sqlite:///project.db - $ python manage.py db_version - 0 - -The script :file:`manage.py` was created. All commands we perform with it are -the same as those performed with the :ref:`migrate ` tool, -using the :term:`repository` and database connection entered above. The -difference between the script :file:`manage.py` in the current directory and -the script inside the repository is, that the one in the current directory has -the database URL preconfigured. - -.. note:: - - Parameters specified in manage.py should be the same as in :ref:`versioning - api `. Preconfigured parameter should just be omitted from - :ref:`migrate ` command. - - -Making schema changes -===================== - -All changes to a database schema under version control should be done via -change scripts - you should avoid schema modifications (creating tables, etc.) -outside of change scripts. This allows you to determine what the schema looks -like based on the version number alone, and helps ensure multiple databases -you're working with are consistent. - -Create a change script ----------------------- - -Our first change script will create a simple table - -.. code-block:: python - - account = Table( - 'account', meta, - Column('id', Integer, primary_key=True), - Column('login', String(40)), - Column('passwd', String(40)), - ) - -This table should be created in a change script. Let's create one:: - - $ python manage.py script "Add account table" - -This creates an empty change script at -:file:`my_repository/versions/001_Add_account_table.py`. Next, we'll -edit this script to create our table. - - -Edit the change script ----------------------- - -Our change script predefines two functions, currently empty: -:py:func:`upgrade` and :py:func:`downgrade`. We'll fill those in: - -.. code-block:: python - - from sqlalchemy import Table, Column, Integer, String, MetaData - - meta = MetaData() - - account = Table( - 'account', meta, - Column('id', Integer, primary_key=True), - Column('login', String(40)), - Column('passwd', String(40)), - ) - - - def upgrade(migrate_engine): - meta.bind = migrate_engine - account.create() - - - def downgrade(migrate_engine): - meta.bind = migrate_engine - account.drop() - -.. note:: - - The generated script contains * imports from sqlalchemy and migrate. You - should tailor the imports to fit your actual demand. - -As you might have guessed, :py:func:`upgrade` upgrades the database to the next -version. This function should contain the :ref:`schema changes -` we want to perform (in our example we're creating a -table). - -:py:func:`downgrade` should reverse changes made by :py:func:`upgrade`. You'll -need to write both functions for every change script. (Well, you don't *have* -to write downgrade, but you won't be able to revert to an older version of the -database or test your scripts without it.) If you really don't want to support -downgrades it is a good idea to raise a :py:class:`NotImplementedError` or some -equivalent custom exception. If you let :py:func:`downgrade` pass silently you -might observe undesired behaviour for subsequent downgrade operations if -downgrading multiple :term:`versions `. - - -.. note:: - - As you can see, **migrate_engine** is passed to both functions. You should - use this in your change scripts, rather than creating your own engine. - -.. warning:: - - You should be very careful about importing files from the rest of your - application, as your change scripts might break when your application - changes. Read more about `writing scripts with consistent behavior`_. - - -Test the change script ------------------------- - -Change scripts should be tested before they are committed. Testing a script -will run its :func:`upgrade` and :func:`downgrade` functions on a specified -database; you can ensure the script runs without error. You should be testing -on a test database - if something goes wrong here, you'll need to correct it by -hand. If the test is successful, the database should appear unchanged after -:func:`upgrade` and :func:`downgrade` run. - -To test the script:: - - $ python manage.py test - Upgrading... done - Downgrading... done - Success - -Our script runs on our database (:file:`sqlite:///project.db`, as specified in -:file:`manage.py`) without any errors. - -Our :term:`repository's ` :term:`version` is:: - - $ python manage.py version - 1 - -.. note:: - - Due to #41 the database must be exactly one :term:`version` behind the - :term:`repository` :term:`version`. - -.. _production testing warning: - -.. warning:: - - The :option:`test` command executes actual scripts, be sure you are *NOT* - doing this on production database. - - If you need to test production changes you should: - - #. get a dump of your production database - #. import the dump into an empty database - #. run :option:`test` or :option:`upgrade` on that copy - - -Upgrade the database --------------------- - -Now, we can apply this change script to our database:: - - $ python manage.py upgrade - 0 -> 1... - done - -This upgrades the database (:file:`sqlite:///project.db`, as specified when we -created :file:`manage.py` above) to the latest available :term:`version`. (We -could also specify a version number if we wished, using the :option:`--version` -option.) We can see the database's :term:`version` number has changed, and our -table has been created:: - - $ python manage.py db_version - 1 - $ sqlite3 project.db - sqlite> .tables - account migrate_version - sqlite> .schema account - CREATE TABLE account ( - id INTEGER NOT NULL, - login VARCHAR(40), - passwd VARCHAR(40), - PRIMARY KEY (id) - ); - -Our account table was created - success! - -Modifying existing tables -------------------------- - -After we have initialized the database schema we now want to add another Column -to the `account` table that we already have in our schema. - -First start a new :term:`changeset` by the commands learned above:: - - $ python manage.py script "Add email column" - -This creates a new :term:`changeset` template. Edit the resulting script -:file:`my_repository/versions/002_Add_email_column.py`: - -.. code-block:: python - - from sqlalchemy import Table, MetaData, String, Column - - - def upgrade(migrate_engine): - meta = MetaData(bind=migrate_engine) - account = Table('account', meta, autoload=True) - emailc = Column('email', String(128)) - emailc.create(account) - - - def downgrade(migrate_engine): - meta = MetaData(bind=migrate_engine) - account = Table('account', meta, autoload=True) - account.c.email.drop() - -As we can see in this example we can (and should) use SQLAlchemy's schema -reflection (autoload) mechanism to reference existing schema objects. We could -have defined the table objects as they are expected before upgrade or downgrade -as well but this would have been more work and is not as convenient. - -We can now apply the changeset to :file:`sqlite:///project.db`:: - - $ python manage.py upgrade - 1 -> 2... - done - -and get the following expected result:: - - $ sqlite3 project.db - sqlite> .schema account - CREATE TABLE account ( - id INTEGER NOT NULL, - login VARCHAR(40), - passwd VARCHAR(40), email VARCHAR(128), - PRIMARY KEY (id) - ); - - -Writing change scripts -====================== - -As our application evolves, we can create more change scripts using a similar -process. - -By default, change scripts may do anything any other SQLAlchemy program can do. - -SQLAlchemy Migrate extends SQLAlchemy with several operations used to change -existing schemas - ie. ``ALTER TABLE`` stuff. See :ref:`changeset -` documentation for details. - - -Writing scripts with consistent behavior ----------------------------------------- - -Normally, it's important to write change scripts in a way that's independent of -your application - the same SQL should be generated every time, despite any -changes to your app's source code. You don't want your change scripts' behavior -changing when your source code does. - -.. warning:: - - **Consider the following example of what NOT to do** - - Let's say your application defines a table in the :file:`model.py` file: - - .. code-block:: python - - from sqlalchemy import * - - meta = MetaData() - table = Table('mytable', meta, - Column('id', Integer, primary_key=True), - ) - - ... and uses this file to create a table in a change script: - - .. code-block:: python - - from sqlalchemy import * - from migrate import * - import model - - def upgrade(migrate_engine): - model.meta.bind = migrate_engine - - def downgrade(migrate_engine): - model.meta.bind = migrate_engine - model.table.drop() - - This runs successfully the first time. But what happens if we change the - table definition in :file:`model.py`? - - .. code-block:: python - - from sqlalchemy import * - - meta = MetaData() - table = Table('mytable', meta, - Column('id', Integer, primary_key=True), - Column('data', String(42)), - ) - - We'll create a new column with a matching change script - - .. code-block:: python - - from sqlalchemy import * - from migrate import * - import model - - def upgrade(migrate_engine): - model.meta.bind = migrate_engine - model.table.create() - - def downgrade(migrate_engine): - model.meta.bind = migrate_engine - model.table.drop() - - This appears to run fine when upgrading an existing database - but the - first script's behavior changed! Running all our change scripts on a new - database will result in an error - the first script creates the table based - on the new definition, with both columns; the second cannot add the column - because it already exists. - - To avoid the above problem, you should use SQLAlchemy schema reflection as - shown above or copy-paste your table definition into each change script - rather than importing parts of your application. - - .. note:: - Sometimes it is enough to just reflect tables with SQLAlchemy instead - of copy-pasting - but remember, explicit is better than implicit! - - -Writing for a specific database -------------------------------- - -Sometimes you need to write code for a specific database. Migrate scripts can -run under any database, however - the engine you're given might belong to any -database. Use engine.name to get the name of the database you're working with - -.. code-block:: python - - >>> from sqlalchemy import * - >>> from migrate import * - >>> - >>> engine = create_engine('sqlite:///:memory:') - >>> engine.name - 'sqlite' - - -Writings .sql scripts ---------------------- - -You might prefer to write your change scripts in SQL, as .sql files, rather -than as Python scripts. SQLAlchemy-migrate can work with that:: - - $ python manage.py version - 1 - $ python manage.py script_sql postgresql - -This creates two scripts -:file:`my_repository/versions/002_postgresql_upgrade.sql` and -:file:`my_repository/versions/002_postgresql_downgrade.sql`, one for each -*operation*, or function defined in a Python change script - upgrade and -downgrade. Both are specified to run with PostgreSQL databases - we can add -more for different databases if we like. Any database defined by SQLAlchemy may -be used here - ex. sqlite, postgresql, oracle, mysql... - - -.. _command-line-usage: - -Command line usage -================== - -.. currentmodule:: migrate.versioning.shell - -:command:`migrate` command is used for API interface. For list of commands and -help use:: - - $ migrate --help - -:command:`migrate` command executes :func:`main` function. -For ease of usage, generate your own :ref:`project management script -`, which calls :func:`main -` function with keywords arguments. You may want -to specify `url` and `repository` arguments which almost all API functions -require. - -If api command looks like:: - - $ migrate downgrade URL REPOSITORY VERSION [--preview_sql|--preview_py] - -and you have a project management script that looks like - -.. code-block:: python - - from migrate.versioning.shell import main - - main(url='sqlite://', repository='./project/migrations/') - -you have first two slots filed, and command line usage would look like:: - - # preview Python script - $ migrate downgrade 2 --preview_py - - # downgrade to version 2 - $ migrate downgrade 2 - -.. versionchanged:: 0.5.4 - Command line parsing refactored: positional parameters usage - -Whole command line parsing was rewriten from scratch with use of OptionParser. -Options passed as kwargs to :func:`~migrate.versioning.shell.main` are now -parsed correctly. Options are passed to commands in the following priority -(starting from highest): - -- optional (given by :option:`--some_option` in commandline) -- positional arguments -- kwargs passed to :func:`migrate.versioning.shell.main` - - -Python API -========== - -.. currentmodule:: migrate.versioning.api - -All commands available from the command line are also available for -your Python scripts by importing :mod:`migrate.versioning.api`. See the -:mod:`migrate.versioning.api` documentation for a list of functions; -function names match equivalent shell commands. You can use this to -help integrate SQLAlchemy Migrate with your existing update process. - -For example, the following commands are similar: - -*From the command line*:: - - $ migrate help help - /usr/bin/migrate help COMMAND - - Displays help on a given command. - -*From Python* - -.. code-block:: python - - import migrate.versioning.api - migrate.versioning.api.help('help') - # Output: - # %prog help COMMAND - # - # Displays help on a given command. - - -.. _migrate.versioning.api: module-migrate.versioning.api.html - -.. _repository_configuration: - - -Experimental commands -===================== - -Some interesting new features to create SQLAlchemy db models from existing -databases and vice versa were developed by Christian Simms during the -development of SQLAlchemy-migrate 0.4.5. These features are roughly documented -in a `thread in migrate-users`_. - -.. _`thread in migrate-users`: - http://groups.google.com/group/migrate-users/browse_thread/thread/a5605184e08abf33#msg_85c803b71b29993f - -Here are the commands' descriptions as given by ``migrate help ``: - -- ``compare_model_to_db``: Compare the current model (assumed to be a - module level variable of type sqlalchemy.MetaData) against the - current database. -- ``create_model``: Dump the current database as a Python model to - stdout. -- ``make_update_script_for_model``: Create a script changing the old - Python model to the new (current) Python model, sending to stdout. - -As this sections headline says: These features are *EXPERIMENTAL*. Take the -necessary arguments to the commands from the output of ``migrate -help ``. - - -Repository configuration -======================== - -SQLAlchemy-migrate :term:`repositories ` can be configured in their -:file:`migrate.cfg` files. The initial configuration is performed by the -`migrate create` call explained in :ref:`Create a change repository -`. The following options are available currently: - -- :option:`repository_id` Used to identify which repository this database is - versioned under. You can use the name of your project. -- :option:`version_table` The name of the database table used to track the - schema version. This name shouldn't already be used by your project. If this - is changed once a database is under version control, you'll need to change - the table name in each database too. -- :option:`required_dbs` When committing a change script, SQLAlchemy-migrate - will attempt to generate the sql for all supported databases; normally, if - one of them fails - probably because you don't have that database installed - - it is ignored and the commit continues, perhaps ending successfully. - Databases in this list MUST compile successfully during a commit, or the - entire commit will fail. List the databases your application will actually be - using to ensure your updates to that database work properly. This must be a - list; example: `['postgres', 'sqlite']` -- :option:`use_timestamp_numbering` When creating new change scripts, Migrate - will stamp the new script with a version number. By default this is - latest_version + 1. You can set this to 'true' to tell Migrate to use the UTC - timestamp instead. - - .. versionadded:: 0.7.2 - -.. _custom-templates: - - -Customize templates -=================== - -Users can pass ``templates_path`` to API functions to provide customized -templates path. Path should be a collection of templates, like -``migrate.versioning.templates`` package directory. - -One may also want to specify custom themes. API functions accept -``templates_theme`` for this purpose (which defaults to `default`) - -Example:: - - /home/user/templates/manage $ ls - default.py_tmpl - pylons.py_tmpl - - /home/user/templates/manage $ migrate manage manage.py --templates_path=/home/user/templates --templates_theme=pylons - -.. versionadded:: 0.6.0 diff --git a/migrate/tests/changeset/test_changeset.py b/migrate/tests/changeset/test_changeset.py index e1166f4..45cc046 100644 --- a/migrate/tests/changeset/test_changeset.py +++ b/migrate/tests/changeset/test_changeset.py @@ -44,11 +44,11 @@ class TestAddDropColumn(fixture.DB): self.refresh_table(self.table_name) result = len(self.table.c) - self.assertEquals(result, num_of_expected_cols), + self.assertEqual(result, num_of_expected_cols), if col_k.get('primary_key', None): # new primary key: check its length too result = len(self.table.primary_key) - self.assertEquals(result, num_of_expected_cols) + self.assertEqual(result, num_of_expected_cols) # we have 1 columns and there is no data column assert_numcols(1) @@ -485,7 +485,7 @@ class TestRename(fixture.DB): """ if not skip_object_check: # Table object check - self.assertEquals(self.table.name,expected) + self.assertEqual(self.table.name,expected) newname = self.table.name else: # we know the object's name isn't consistent: just assign it @@ -493,12 +493,12 @@ class TestRename(fixture.DB): # Table DB check self.meta.clear() self.table = Table(newname, self.meta, autoload=True) - self.assertEquals(self.table.name, expected) + self.assertEqual(self.table.name, expected) def assert_index_name(expected, skip_object_check=False): if not skip_object_check: # Index object check - self.assertEquals(self.index.name, expected) + self.assertEqual(self.index.name, expected) else: # object is inconsistent self.index.name = expected @@ -583,7 +583,7 @@ class TestColumnChange(fixture.DB): # Table content should be preserved in changed columns content = "fgsfds" self.engine.execute(self.table.insert(), data=content, id=42) - self.assertEquals(num_rows(self.table.c.data, content), 1) + self.assertEqual(num_rows(self.table.c.data, content), 1) # ...as a function, given a column object and the new name alter_column('data', name='data2', table=self.table) @@ -592,14 +592,14 @@ class TestColumnChange(fixture.DB): self.refresh_table(self.table.name) self.assert_('data' not in self.table.c.keys()) self.assert_('atad' in self.table.c.keys()) - self.assertEquals(num_rows(self.table.c.atad, content), 1) + self.assertEqual(num_rows(self.table.c.atad, content), 1) # ...as a method, given a new name self.table.c.atad.alter(name='data') self.refresh_table(self.table.name) self.assert_('atad' not in self.table.c.keys()) self.table.c.data # Should not raise exception - self.assertEquals(num_rows(self.table.c.data, content), 1) + self.assertEqual(num_rows(self.table.c.data, content), 1) # ...as a function, given a new object alter_column(self.table.c.data, @@ -608,7 +608,7 @@ class TestColumnChange(fixture.DB): self.refresh_table(self.table.name) self.assert_('data' not in self.table.c.keys()) self.table.c.atad # Should not raise exception - self.assertEquals(num_rows(self.table.c.atad, content), 1) + self.assertEqual(num_rows(self.table.c.atad, content), 1) # ...as a method, given a new object self.table.c.atad.alter( @@ -618,7 +618,7 @@ class TestColumnChange(fixture.DB): self.refresh_table(self.table.name) self.assert_('atad' not in self.table.c.keys()) self.table.c.data # Should not raise exception - self.assertEquals(num_rows(self.table.c.data,content), 1) + self.assertEqual(num_rows(self.table.c.data,content), 1) @fixture.usedb() def test_type(self): @@ -628,15 +628,15 @@ class TestColumnChange(fixture.DB): self.table.c.data.alter(type=String(43)) self.refresh_table(self.table.name) self.assert_(isinstance(self.table.c.data.type, String)) - self.assertEquals(self.table.c.data.type.length, 43) + self.assertEqual(self.table.c.data.type.length, 43) # Different type self.assert_(isinstance(self.table.c.id.type, Integer)) - self.assertEquals(self.table.c.id.nullable, False) + self.assertEqual(self.table.c.id.nullable, False) if not self.engine.name == 'firebird': self.table.c.id.alter(type=String(20)) - self.assertEquals(self.table.c.id.nullable, False) + self.assertEqual(self.table.c.id.nullable, False) self.refresh_table(self.table.name) self.assert_(isinstance(self.table.c.id.type, String)) @@ -646,13 +646,13 @@ class TestColumnChange(fixture.DB): Only DefaultClauses are changed here: others are managed by the application / by SA """ - self.assertEquals(self.table.c.data.server_default.arg, 'tluafed') + self.assertEqual(self.table.c.data.server_default.arg, 'tluafed') # Just the new default default = 'my_default' self.table.c.data.alter(server_default=DefaultClause(default)) self.refresh_table(self.table.name) - #self.assertEquals(self.table.c.data.server_default.arg,default) + #self.assertEqual(self.table.c.data.server_default.arg,default) # TextClause returned by autoload self.assert_(default in str(self.table.c.data.server_default.arg)) self.engine.execute(self.table.insert(), id=12) @@ -679,18 +679,18 @@ class TestColumnChange(fixture.DB): @fixture.usedb(not_supported='firebird') def test_null(self): """Can change a column's null constraint""" - self.assertEquals(self.table.c.data.nullable, True) + self.assertEqual(self.table.c.data.nullable, True) # Full column self.table.c.data.alter(type=String(40), nullable=False) self.table.nullable = None self.refresh_table(self.table.name) - self.assertEquals(self.table.c.data.nullable, False) + self.assertEqual(self.table.c.data.nullable, False) # Just the new status self.table.c.data.alter(nullable=True) self.refresh_table(self.table.name) - self.assertEquals(self.table.c.data.nullable, True) + self.assertEqual(self.table.c.data.nullable, True) @fixture.usedb() def test_alter_deprecated(self): @@ -793,7 +793,7 @@ class TestColumnDelta(fixture.DB): self.delta = ColumnDelta(original, *p, **k) result = self.delta.keys() result.sort() - self.assertEquals(expected, result) + self.assertEqual(expected, result) return self.delta def test_deltas_two_columns(self): @@ -885,8 +885,8 @@ class TestColumnDelta(fixture.DB): # Change name, given an up-to-date definition and the current name delta = self.verify(['name'], col_orig, name='blah') - self.assertEquals(delta.get('name'), 'blah') - self.assertEquals(delta.current_name, 'id') + self.assertEqual(delta.get('name'), 'blah') + self.assertEqual(delta.current_name, 'id') col_orig = self.mkcol(primary_key=True) self.verify(['name', 'type'], col_orig, name='id12', type=Text, alter_metadata=True) diff --git a/migrate/tests/changeset/test_constraint.py b/migrate/tests/changeset/test_constraint.py index f36698d..5527559 100644 --- a/migrate/tests/changeset/test_constraint.py +++ b/migrate/tests/changeset/test_constraint.py @@ -40,7 +40,7 @@ class CommonTestConstraint(fixture.DB): self.table.create() # make sure we start at zero - self.assertEquals(len(self.table.primary_key), 0) + self.assertEqual(len(self.table.primary_key), 0) self.assert_(isinstance(self.table.primary_key, schema.PrimaryKeyConstraint), self.table.primary_key.__class__) @@ -67,7 +67,7 @@ class TestConstraint(CommonTestConstraint): # pk.name = self.table.primary_key.name pk.drop() self.refresh_table() - self.assertEquals(len(self.table.primary_key), 0) + self.assertEqual(len(self.table.primary_key), 0) self.assert_(isinstance(self.table.primary_key, schema.PrimaryKeyConstraint)) return pk @@ -80,9 +80,9 @@ class TestConstraint(CommonTestConstraint): # Add a FK by creating a FK constraint if SQLA_07: - self.assertEquals(list(self.table.c.fkey.foreign_keys), []) + self.assertEqual(list(self.table.c.fkey.foreign_keys), []) else: - self.assertEquals(self.table.c.fkey.foreign_keys._list, []) + self.assertEqual(self.table.c.fkey.foreign_keys._list, []) fk = ForeignKeyConstraint([self.table.c.fkey], [self.table.c.id], name="fk_id_fkey", @@ -92,9 +92,9 @@ class TestConstraint(CommonTestConstraint): else: self.assert_(self.table.c.fkey.foreign_keys._list is not []) for key in fk.columns: - self.assertEquals(key, self.table.c.fkey.name) - self.assertEquals([e.column for e in fk.elements], [self.table.c.id]) - self.assertEquals(list(fk.referenced), [self.table.c.id]) + self.assertEqual(key, self.table.c.fkey.name) + self.assertEqual([e.column for e in fk.elements], [self.table.c.id]) + self.assertEqual(list(fk.referenced), [self.table.c.id]) if self.url.startswith('mysql'): # MySQL FKs need an index @@ -107,7 +107,7 @@ class TestConstraint(CommonTestConstraint): fkey = list(self.table.c.fkey.foreign_keys)[0] else: fkey = self.table.c.fkey.foreign_keys._list[0] - self.assertEquals(fkey.ondelete, "CASCADE") + self.assertEqual(fkey.ondelete, "CASCADE") # TODO: test on real db if it was set self.refresh_table() @@ -119,9 +119,9 @@ class TestConstraint(CommonTestConstraint): fk.drop() self.refresh_table() if SQLA_07: - self.assertEquals(list(self.table.c.fkey.foreign_keys), []) + self.assertEqual(list(self.table.c.fkey.foreign_keys), []) else: - self.assertEquals(self.table.c.fkey.foreign_keys._list, []) + self.assertEqual(self.table.c.fkey.foreign_keys._list, []) @fixture.usedb() def test_define_pk(self): @@ -203,7 +203,7 @@ class TestAutoname(CommonTestConstraint): cons.name = None cons.drop() self.refresh_table() - self.assertEquals(list(), list(self.table.primary_key)) + self.assertEqual(list(), list(self.table.primary_key)) # test string names cons = PrimaryKeyConstraint('id', table=self.table) @@ -234,9 +234,9 @@ class TestAutoname(CommonTestConstraint): cons.drop() self.refresh_table() if SQLA_07: - self.assertEquals(list(self.table.c.fkey.foreign_keys), list()) + self.assertEqual(list(self.table.c.fkey.foreign_keys), list()) else: - self.assertEquals(self.table.c.fkey.foreign_keys._list, list()) + self.assertEqual(self.table.c.fkey.foreign_keys._list, list()) # test string names cons = ForeignKeyConstraint(['fkey'], ['%s.id' % self.tablename], table=self.table) diff --git a/migrate/tests/fixture/__init__.py b/migrate/tests/fixture/__init__.py index 09fc2ca..cfc67b4 100644 --- a/migrate/tests/fixture/__init__.py +++ b/migrate/tests/fixture/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import unittest2 +import testtools def main(imports=None): if imports: @@ -10,7 +10,7 @@ def main(imports=None): defaultTest='fixture.suite' else: defaultTest=None - return unittest2.TestProgram(defaultTest=defaultTest) + return testtools.TestProgram(defaultTest=defaultTest) from base import Base from migrate.tests.fixture.pathed import Pathed diff --git a/migrate/tests/fixture/base.py b/migrate/tests/fixture/base.py index 67aabf8..38c91af 100644 --- a/migrate/tests/fixture/base.py +++ b/migrate/tests/fixture/base.py @@ -2,17 +2,11 @@ # -*- coding: utf-8 -*- import re -import unittest2 +import testtools -class Base(unittest2.TestCase): +class Base(testtools.TestCase): - def setup_method(self,func=None): - self.setUp() - - def teardown_method(self,func=None): - self.tearDown() - - def assertEqualsIgnoreWhitespace(self, v1, v2): + def assertEqualIgnoreWhitespace(self, v1, v2): """Compares two strings that should be\ identical except for whitespace """ diff --git a/migrate/tests/integrated/test_docs.py b/migrate/tests/integrated/test_docs.py index 6aed071..8e35427 100644 --- a/migrate/tests/integrated/test_docs.py +++ b/migrate/tests/integrated/test_docs.py @@ -6,7 +6,7 @@ from migrate.tests import fixture # Collect tests for all handwritten docs: doc/*.rst -dir = ('..','..','..','docs') +dir = ('..','..','..','doc','source') absdir = (os.path.dirname(os.path.abspath(__file__)),)+dir dirpath = os.path.join(*absdir) files = [f for f in os.listdir(dirpath) if f.endswith('.rst')] diff --git a/migrate/tests/versioning/test_genmodel.py b/migrate/tests/versioning/test_genmodel.py index db35aa4..e36bea1 100644 --- a/migrate/tests/versioning/test_genmodel.py +++ b/migrate/tests/versioning/test_genmodel.py @@ -4,7 +4,6 @@ import os import sqlalchemy from sqlalchemy import * -from nose.tools import eq_ from migrate.versioning import genmodel, schemadiff from migrate.changeset import schema @@ -45,7 +44,7 @@ class TestSchemaDiff(fixture.DB): def assertDiff(isDiff, tablesMissingInDatabase, tablesMissingInModel, tablesWithDiff): diff = schemadiff.getDiffOfModelAgainstDatabase(self.meta, self.engine, excludeTables=['migrate_version']) - eq_( + self.assertEqual( (diff.tables_missing_from_B, diff.tables_missing_from_A, diff.tables_different.keys(), @@ -66,7 +65,7 @@ class TestSchemaDiff(fixture.DB): # Feature test for a recent SQLa feature; # expect different output in that case. if repr(String()) == 'String()': - self.assertEqualsIgnoreWhitespace(decls, ''' + self.assertEqualIgnoreWhitespace(decls, ''' from migrate.changeset import schema pre_meta = MetaData() post_meta = MetaData() @@ -77,7 +76,7 @@ class TestSchemaDiff(fixture.DB): ) ''') else: - self.assertEqualsIgnoreWhitespace(decls, ''' + self.assertEqualIgnoreWhitespace(decls, ''' from migrate.changeset import schema pre_meta = MetaData() post_meta = MetaData() @@ -157,8 +156,8 @@ class TestSchemaDiff(fixture.DB): # Make sure data is still present. result = self.engine.execute(self.table.select(self.table.c.id==dataId)) rows = result.fetchall() - eq_(len(rows), 1) - eq_(rows[0].name, 'mydata') + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0].name, 'mydata') # Add data, later we'll make sure it's still present. result = self.engine.execute(self.table.insert(), id=2, name=u'mydata2', data2=123) @@ -185,9 +184,9 @@ class TestSchemaDiff(fixture.DB): # Make sure data is still present. result = self.engine.execute(self.table.select(self.table.c.id==dataId2)) rows = result.fetchall() - self.assertEquals(len(rows), 1) - self.assertEquals(rows[0].name, 'mydata2') - self.assertEquals(rows[0].data2, '123') + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0].name, 'mydata2') + self.assertEqual(rows[0].data2, '123') # Delete data, since we're about to make a required column. # Not even using sqlalchemy.PassiveDefault helps because we're doing explicit column select. diff --git a/migrate/tests/versioning/test_keyedinstance.py b/migrate/tests/versioning/test_keyedinstance.py index b2f87ac..28f3b2b 100644 --- a/migrate/tests/versioning/test_keyedinstance.py +++ b/migrate/tests/versioning/test_keyedinstance.py @@ -35,7 +35,7 @@ class TestKeydInstance(fixture.Base): self.assert_(a10 is a11) # __init__ is called - self.assertEquals(a10.value,'a') + self.assertEqual(a10.value,'a') # clear() causes us to forget all existing instances Uniq1.clear() diff --git a/migrate/tests/versioning/test_repository.py b/migrate/tests/versioning/test_repository.py index a926f2c..7065368 100644 --- a/migrate/tests/versioning/test_repository.py +++ b/migrate/tests/versioning/test_repository.py @@ -7,7 +7,6 @@ import shutil from migrate import exceptions from migrate.versioning.repository import * from migrate.versioning.script import * -from nose.tools import raises from migrate.tests import fixture from datetime import datetime @@ -76,7 +75,7 @@ class TestVersionedRepository(fixture.Pathed): repos = Repository(self.path_repos) # Get latest version, or detect if a specified version exists - self.assertEquals(repos.latest, 0) + self.assertEqual(repos.latest, 0) # repos.latest isn't an integer, but a VerNum # (so we can't just assume the following tests are correct) self.assert_(repos.latest >= 0) @@ -84,14 +83,14 @@ class TestVersionedRepository(fixture.Pathed): # Create a script and test again repos.create_script('') - self.assertEquals(repos.latest, 1) + self.assertEqual(repos.latest, 1) self.assert_(repos.latest >= 0) self.assert_(repos.latest >= 1) self.assert_(repos.latest < 2) # Create a new script and test again repos.create_script('') - self.assertEquals(repos.latest, 2) + self.assertEqual(repos.latest, 2) self.assert_(repos.latest >= 0) self.assert_(repos.latest >= 1) self.assert_(repos.latest >= 2) @@ -103,7 +102,7 @@ class TestVersionedRepository(fixture.Pathed): repos.config.set('db_settings', 'use_timestamp_numbering', 'True') # Get latest version, or detect if a specified version exists - self.assertEquals(repos.latest, 0) + self.assertEqual(repos.latest, 0) # repos.latest isn't an integer, but a VerNum # (so we can't just assume the following tests are correct) self.assert_(repos.latest >= 0) @@ -113,7 +112,7 @@ class TestVersionedRepository(fixture.Pathed): now = int(datetime.utcnow().strftime('%Y%m%d%H%M%S')) repos.create_script('') print repos.latest - self.assertEquals(repos.latest, now) + self.assertEqual(repos.latest, now) def test_source(self): """Get a script object by version number and view its source""" @@ -148,7 +147,7 @@ class TestVersionedRepository(fixture.Pathed): def check_changeset(params, length): """Creates and verifies a changeset""" changeset = repos.changeset('postgres', *params) - self.assertEquals(len(changeset), length) + self.assertEqual(len(changeset), length) self.assertTrue(isinstance(changeset, Changeset)) uniq = list() # Changesets are iterable @@ -161,10 +160,10 @@ class TestVersionedRepository(fixture.Pathed): # Upgrade to a specified version... cs = check_changeset((0, 10), 10) - self.assertEquals(cs.keys().pop(0),0 ) # 0 -> 1: index is starting version - self.assertEquals(cs.keys().pop(), 9) # 9 -> 10: index is starting version - self.assertEquals(cs.start, 0) # starting version - self.assertEquals(cs.end, 10) # ending version + self.assertEqual(cs.keys().pop(0),0 ) # 0 -> 1: index is starting version + self.assertEqual(cs.keys().pop(), 9) # 9 -> 10: index is starting version + self.assertEqual(cs.start, 0) # starting version + self.assertEqual(cs.end, 10) # ending version check_changeset((0, 1), 1) check_changeset((0, 5), 5) check_changeset((0, 0), 0) @@ -178,10 +177,10 @@ class TestVersionedRepository(fixture.Pathed): # Upgrade to the latest version... cs = check_changeset((0,), 10) - self.assertEquals(cs.keys().pop(0), 0) - self.assertEquals(cs.keys().pop(), 9) - self.assertEquals(cs.start, 0) - self.assertEquals(cs.end, 10) + self.assertEqual(cs.keys().pop(0), 0) + self.assertEqual(cs.keys().pop(), 9) + self.assertEqual(cs.start, 0) + self.assertEqual(cs.end, 10) check_changeset((1,), 9) check_changeset((5,), 5) check_changeset((9,), 1) @@ -196,10 +195,10 @@ class TestVersionedRepository(fixture.Pathed): # Downgrade cs = check_changeset((10, 0),10) - self.assertEquals(cs.keys().pop(0), 10) # 10 -> 9 - self.assertEquals(cs.keys().pop(), 1) # 1 -> 0 - self.assertEquals(cs.start, 10) - self.assertEquals(cs.end, 0) + self.assertEqual(cs.keys().pop(0), 10) # 10 -> 9 + self.assertEqual(cs.keys().pop(), 1) # 1 -> 0 + self.assertEqual(cs.start, 10) + self.assertEqual(cs.end, 0) check_changeset((10, 5), 5) check_changeset((5, 0), 5) diff --git a/migrate/tests/versioning/test_runchangeset.py b/migrate/tests/versioning/test_runchangeset.py index 52b0215..12bc77c 100644 --- a/migrate/tests/versioning/test_runchangeset.py +++ b/migrate/tests/versioning/test_runchangeset.py @@ -31,22 +31,22 @@ class TestRunChangeset(fixture.Pathed,fixture.DB): # Scripts are empty; we'll check version # correctness. # (Correct application of their content is checked elsewhere) - self.assertEquals(db.version,0) + self.assertEqual(db.version,0) db.upgrade(1) - self.assertEquals(db.version,1) + self.assertEqual(db.version,1) db.upgrade(5) - self.assertEquals(db.version,5) + self.assertEqual(db.version,5) db.upgrade(5) - self.assertEquals(db.version,5) + self.assertEqual(db.version,5) db.upgrade(None) # Latest is implied - self.assertEquals(db.version,10) + self.assertEqual(db.version,10) self.assertRaises(Exception,db.upgrade,11) - self.assertEquals(db.version,10) + self.assertEqual(db.version,10) db.upgrade(9) - self.assertEquals(db.version,9) + self.assertEqual(db.version,9) db.upgrade(0) - self.assertEquals(db.version,0) + self.assertEqual(db.version,0) self.assertRaises(Exception,db.upgrade,-1) - self.assertEquals(db.version,0) + self.assertEqual(db.version,0) #changeset = repos.changeset(self.url,0) db.drop() diff --git a/migrate/tests/versioning/test_schema.py b/migrate/tests/versioning/test_schema.py index 8b0033c..7502783 100644 --- a/migrate/tests/versioning/test_schema.py +++ b/migrate/tests/versioning/test_schema.py @@ -56,16 +56,16 @@ class TestControlledSchema(fixture.Pathed, fixture.DB): # We can load a controlled DB this way, too dbcontrol0 = ControlledSchema(self.engine, self.repos) - self.assertEquals(dbcontrol, dbcontrol0) + self.assertEqual(dbcontrol, dbcontrol0) # We can also use a repository path, instead of a repository dbcontrol0 = ControlledSchema(self.engine, self.repos.path) - self.assertEquals(dbcontrol, dbcontrol0) + self.assertEqual(dbcontrol, dbcontrol0) # We don't have to use the same connection engine = create_engine(self.url) dbcontrol0 = ControlledSchema(engine, self.repos.path) - self.assertEquals(dbcontrol, dbcontrol0) + self.assertEqual(dbcontrol, dbcontrol0) # Clean up: dbcontrol.drop() @@ -83,11 +83,11 @@ class TestControlledSchema(fixture.Pathed, fixture.DB): # Establish version control on this database version = 0 dbcontrol = ControlledSchema.create(self.engine, self.repos, version) - self.assertEquals(dbcontrol.version, version) + self.assertEqual(dbcontrol.version, version) # Correct when we load it, too dbcontrol = ControlledSchema(self.engine, self.repos) - self.assertEquals(dbcontrol.version, version) + self.assertEqual(dbcontrol.version, version) dbcontrol.drop() @@ -95,16 +95,16 @@ class TestControlledSchema(fixture.Pathed, fixture.DB): version = 10 for i in range(version): self.repos.create_script('') - self.assertEquals(self.repos.latest, version) + self.assertEqual(self.repos.latest, version) # Test with some mid-range value dbcontrol = ControlledSchema.create(self.engine,self.repos, 5) - self.assertEquals(dbcontrol.version, 5) + self.assertEqual(dbcontrol.version, 5) dbcontrol.drop() # Test with max value dbcontrol = ControlledSchema.create(self.engine, self.repos, version) - self.assertEquals(dbcontrol.version, version) + self.assertEqual(dbcontrol.version, version) dbcontrol.drop() @fixture.usedb() @@ -132,7 +132,7 @@ class TestControlledSchema(fixture.Pathed, fixture.DB): for i in range(5): self.repos.create_script('') - self.assertEquals(self.repos.latest, 5) + self.assertEqual(self.repos.latest, 5) cs = dbschema.changeset(5) self.assertEqual(len(cs), 5) @@ -147,7 +147,7 @@ class TestControlledSchema(fixture.Pathed, fixture.DB): for i in range(10): self.repos.create_script('') - self.assertEquals(self.repos.latest, 10) + self.assertEqual(self.repos.latest, 10) dbschema.upgrade(10) diff --git a/migrate/tests/versioning/test_schemadiff.py b/migrate/tests/versioning/test_schemadiff.py index e935eb1..74714e2 100644 --- a/migrate/tests/versioning/test_schemadiff.py +++ b/migrate/tests/versioning/test_schemadiff.py @@ -3,7 +3,6 @@ import os from sqlalchemy import * -from nose.tools import eq_ from migrate.versioning import schemadiff @@ -27,12 +26,12 @@ class SchemaDiffBase(fixture.DB): diff = self._run_diff() # print diff self.assertTrue(diff) - eq_(1,len(diff.tables_different)) + self.assertEqual(1,len(diff.tables_different)) td = diff.tables_different.values()[0] - eq_(1,len(td.columns_different)) + self.assertEqual(1,len(td.columns_different)) cd = td.columns_different.values()[0] label_width = max(len(self.name1), len(self.name2)) - eq_(('Schema diffs:\n' + self.assertEqual(('Schema diffs:\n' ' table with differences: xtable\n' ' column with differences: data\n' ' %*s: %r\n' @@ -59,7 +58,7 @@ class Test_getDiffOfModelAgainstDatabase(SchemaDiffBase): self._make_table(create=False) diff = self._run_diff() self.assertTrue(diff) - eq_('Schema diffs:\n tables missing from %s: xtable' % self.name2, + self.assertEqual('Schema diffs:\n tables missing from %s: xtable' % self.name2, str(diff)) @fixture.usedb() @@ -68,7 +67,7 @@ class Test_getDiffOfModelAgainstDatabase(SchemaDiffBase): self.meta.clear() diff = self._run_diff() self.assertTrue(diff) - eq_('Schema diffs:\n tables missing from %s: xtable' % self.name1, + self.assertEqual('Schema diffs:\n tables missing from %s: xtable' % self.name1, str(diff)) @fixture.usedb() @@ -86,7 +85,7 @@ class Test_getDiffOfModelAgainstDatabase(SchemaDiffBase): # run diff diff = self._run_diff() self.assertTrue(diff) - eq_('Schema diffs:\n' + self.assertEqual('Schema diffs:\n' ' table with differences: xtable\n' ' %s missing these columns: xcol' % self.name2, str(diff)) @@ -105,7 +104,7 @@ class Test_getDiffOfModelAgainstDatabase(SchemaDiffBase): # run diff diff = self._run_diff() self.assertTrue(diff) - eq_('Schema diffs:\n' + self.assertEqual('Schema diffs:\n' ' table with differences: xtable\n' ' %s missing these columns: xcol' % self.name1, str(diff)) @@ -134,14 +133,14 @@ class Test_getDiffOfModelAgainstDatabase(SchemaDiffBase): # ztable identical on both # ...so we expect no diff! self.assertFalse(diff) - eq_('No schema diffs',str(diff)) + self.assertEqual('No schema diffs',str(diff)) @fixture.usedb() def test_identical_just_pk(self): self._make_table() diff = self._run_diff() self.assertFalse(diff) - eq_('No schema diffs',str(diff)) + self.assertEqual('No schema diffs',str(diff)) @fixture.usedb() @@ -192,7 +191,7 @@ class Test_getDiffOfModelAgainstDatabase(SchemaDiffBase): Column('data', Integer()), ) diff = self._run_diff() - eq_('No schema diffs',str(diff)) + self.assertEqual('No schema diffs',str(diff)) self.assertFalse(diff) @fixture.usedb() @@ -201,7 +200,7 @@ class Test_getDiffOfModelAgainstDatabase(SchemaDiffBase): Column('data', String(10)), ) diff = self._run_diff() - eq_('No schema diffs',str(diff)) + self.assertEqual('No schema diffs',str(diff)) self.assertFalse(diff) @fixture.usedb() @@ -210,7 +209,7 @@ class Test_getDiffOfModelAgainstDatabase(SchemaDiffBase): Column('data', Text), ) diff = self._run_diff() - eq_('No schema diffs',str(diff)) + self.assertEqual('No schema diffs',str(diff)) self.assertFalse(diff) class Test_getDiffOfModelAgainstModel(Test_getDiffOfModelAgainstDatabase): diff --git a/migrate/tests/versioning/test_script.py b/migrate/tests/versioning/test_script.py index 53ef929..b52ddaa 100644 --- a/migrate/tests/versioning/test_script.py +++ b/migrate/tests/versioning/test_script.py @@ -126,7 +126,7 @@ def upgrade(migrate_engine): pyscript = self.cls(path) SQL = pyscript.preview_sql(self.url, 1) - self.assertEqualsIgnoreWhitespace(""" + self.assertEqualIgnoreWhitespace(""" CREATE TABLE "Link" ("link1ID" INTEGER, "link2ID" INTEGER, diff --git a/migrate/tests/versioning/test_shell.py b/migrate/tests/versioning/test_shell.py index 4b89c28..0bf5e22 100644 --- a/migrate/tests/versioning/test_shell.py +++ b/migrate/tests/versioning/test_shell.py @@ -7,7 +7,6 @@ import tempfile from cStringIO import StringIO from sqlalchemy import MetaData, Table -from nose.plugins.skip import SkipTest from migrate.exceptions import * from migrate.versioning.repository import Repository @@ -48,7 +47,7 @@ class TestShellCommands(Shell): def test_main_with_runpy(self): if sys.version_info[:2] == (2, 4): - raise SkipTest("runpy is not part of python2.4") + self.skipTest("runpy is not part of python2.4") from runpy import run_module try: original = sys.argv @@ -271,50 +270,50 @@ class TestShellDatabase(Shell, DB): repos_name = 'repos_name' repos_path = self.tmp() result = self.env.run('migrate create %(repos_path)s %(repos_name)s' % locals()) - self.assertEquals(self.run_version(repos_path), 0) + self.assertEqual(self.run_version(repos_path), 0) # Version the DB result = self.env.run('migrate drop_version_control %s %s' % (self.url, repos_path), expect_error=True) result = self.env.run('migrate version_control %s %s' % (self.url, repos_path)) # Upgrades with latest version == 0 - self.assertEquals(self.run_db_version(self.url, repos_path), 0) + self.assertEqual(self.run_db_version(self.url, repos_path), 0) result = self.env.run('migrate upgrade %s %s' % (self.url, repos_path)) - self.assertEquals(self.run_db_version(self.url, repos_path), 0) + self.assertEqual(self.run_db_version(self.url, repos_path), 0) result = self.env.run('migrate upgrade %s %s' % (self.url, repos_path)) - self.assertEquals(self.run_db_version(self.url, repos_path), 0) + self.assertEqual(self.run_db_version(self.url, repos_path), 0) result = self.env.run('migrate upgrade %s %s 1' % (self.url, repos_path), expect_error=True) - self.assertEquals(result.returncode, 1) + self.assertEqual(result.returncode, 1) result = self.env.run('migrate upgrade %s %s -1' % (self.url, repos_path), expect_error=True) - self.assertEquals(result.returncode, 2) + self.assertEqual(result.returncode, 2) # Add a script to the repository; upgrade the db result = self.env.run('migrate script Desc --repository=%s' % (repos_path)) - self.assertEquals(self.run_version(repos_path), 1) - self.assertEquals(self.run_db_version(self.url, repos_path), 0) + self.assertEqual(self.run_version(repos_path), 1) + self.assertEqual(self.run_db_version(self.url, repos_path), 0) # Test preview result = self.env.run('migrate upgrade %s %s 0 --preview_sql' % (self.url, repos_path)) result = self.env.run('migrate upgrade %s %s 0 --preview_py' % (self.url, repos_path)) result = self.env.run('migrate upgrade %s %s' % (self.url, repos_path)) - self.assertEquals(self.run_db_version(self.url, repos_path), 1) + self.assertEqual(self.run_db_version(self.url, repos_path), 1) # Downgrade must have a valid version specified result = self.env.run('migrate downgrade %s %s' % (self.url, repos_path), expect_error=True) - self.assertEquals(result.returncode, 2) + self.assertEqual(result.returncode, 2) result = self.env.run('migrate downgrade %s %s -1' % (self.url, repos_path), expect_error=True) - self.assertEquals(result.returncode, 2) + self.assertEqual(result.returncode, 2) result = self.env.run('migrate downgrade %s %s 2' % (self.url, repos_path), expect_error=True) - self.assertEquals(result.returncode, 2) - self.assertEquals(self.run_db_version(self.url, repos_path), 1) + self.assertEqual(result.returncode, 2) + self.assertEqual(self.run_db_version(self.url, repos_path), 1) result = self.env.run('migrate downgrade %s %s 0' % (self.url, repos_path)) - self.assertEquals(self.run_db_version(self.url, repos_path), 0) + self.assertEqual(self.run_db_version(self.url, repos_path), 0) result = self.env.run('migrate downgrade %s %s 1' % (self.url, repos_path), expect_error=True) - self.assertEquals(result.returncode, 2) - self.assertEquals(self.run_db_version(self.url, repos_path), 0) + self.assertEqual(result.returncode, 2) + self.assertEqual(self.run_db_version(self.url, repos_path), 0) result = self.env.run('migrate drop_version_control %s %s' % (self.url, repos_path)) @@ -326,26 +325,26 @@ class TestShellDatabase(Shell, DB): result = self.env.run('migrate create %s %s' % (repos_path, repos_name)) result = self.env.run('migrate drop_version_control %s %s' % (self.url, repos_path), expect_error=True) result = self.env.run('migrate version_control %s %s' % (self.url, repos_path)) - self.assertEquals(self.run_version(repos_path), 0) - self.assertEquals(self.run_db_version(self.url, repos_path), 0) + self.assertEqual(self.run_version(repos_path), 0) + self.assertEqual(self.run_db_version(self.url, repos_path), 0) beforeCount = len(os.listdir(os.path.join(repos_path, 'versions'))) # hmm, this number changes sometimes based on running from svn result = self.env.run('migrate script_sql %s --repository=%s' % ('postgres', repos_path)) - self.assertEquals(self.run_version(repos_path), 1) - self.assertEquals(len(os.listdir(os.path.join(repos_path, 'versions'))), beforeCount + 2) + self.assertEqual(self.run_version(repos_path), 1) + self.assertEqual(len(os.listdir(os.path.join(repos_path, 'versions'))), beforeCount + 2) open('%s/versions/001_postgres_upgrade.sql' % repos_path, 'a').write(upgrade_script) open('%s/versions/001_postgres_downgrade.sql' % repos_path, 'a').write(downgrade_script) - self.assertEquals(self.run_db_version(self.url, repos_path), 0) + self.assertEqual(self.run_db_version(self.url, repos_path), 0) self.assertRaises(Exception, self.engine.text('select * from t_table').execute) result = self.env.run('migrate upgrade %s %s' % (self.url, repos_path)) - self.assertEquals(self.run_db_version(self.url, repos_path), 1) + self.assertEqual(self.run_db_version(self.url, repos_path), 1) self.engine.text('select * from t_table').execute() result = self.env.run('migrate downgrade %s %s 0' % (self.url, repos_path)) - self.assertEquals(self.run_db_version(self.url, repos_path), 0) + self.assertEqual(self.run_db_version(self.url, repos_path), 0) self.assertRaises(Exception, self.engine.text('select * from t_table').execute) # The tests below are written with some postgres syntax, but the stuff @@ -387,14 +386,14 @@ class TestShellDatabase(Shell, DB): result = self.env.run('migrate create repository_name --repository=%s' % repos_path) result = self.env.run('migrate drop_version_control %s %s' % (self.url, repos_path), expect_error=True) result = self.env.run('migrate version_control %s %s' % (self.url, repos_path)) - self.assertEquals(self.run_version(repos_path), 0) - self.assertEquals(self.run_db_version(self.url, repos_path), 0) + self.assertEqual(self.run_version(repos_path), 0) + self.assertEqual(self.run_db_version(self.url, repos_path), 0) # Empty script should succeed result = self.env.run('migrate script Desc %s' % repos_path) result = self.env.run('migrate test %s %s' % (self.url, repos_path)) - self.assertEquals(self.run_version(repos_path), 1) - self.assertEquals(self.run_db_version(self.url, repos_path), 0) + self.assertEqual(self.run_version(repos_path), 1) + self.assertEqual(self.run_db_version(self.url, repos_path), 0) # Error script should fail script_path = self.tmp_py() @@ -416,8 +415,8 @@ class TestShellDatabase(Shell, DB): result = self.env.run('migrate test %s %s bla' % (self.url, repos_path), expect_error=True) self.assertEqual(result.returncode, 2) - self.assertEquals(self.run_version(repos_path), 1) - self.assertEquals(self.run_db_version(self.url, repos_path), 0) + self.assertEqual(self.run_version(repos_path), 1) + self.assertEqual(self.run_db_version(self.url, repos_path), 0) # Nonempty script using migrate_engine should succeed script_path = self.tmp_py() @@ -446,8 +445,8 @@ class TestShellDatabase(Shell, DB): file.write(script_text) file.close() result = self.env.run('migrate test %s %s' % (self.url, repos_path)) - self.assertEquals(self.run_version(repos_path), 1) - self.assertEquals(self.run_db_version(self.url, repos_path), 0) + self.assertEqual(self.run_version(repos_path), 1) + self.assertEqual(self.run_db_version(self.url, repos_path), 0) @usedb() def test_rundiffs_in_shell(self): @@ -468,8 +467,8 @@ class TestShellDatabase(Shell, DB): result = self.env.run('migrate create %s %s' % (repos_path, repos_name)) result = self.env.run('migrate drop_version_control %s %s' % (self.url, repos_path), expect_error=True) result = self.env.run('migrate version_control %s %s' % (self.url, repos_path)) - self.assertEquals(self.run_version(repos_path), 0) - self.assertEquals(self.run_db_version(self.url, repos_path), 0) + self.assertEqual(self.run_version(repos_path), 0) + self.assertEqual(self.run_db_version(self.url, repos_path), 0) # Setup helper script. result = self.env.run('migrate manage %s --repository=%s --url=%s --model=%s'\ @@ -491,8 +490,8 @@ class TestShellDatabase(Shell, DB): # Update db to latest model. result = self.env.run('migrate update_db_from_model %s %s %s'\ % (self.url, repos_path, model_module)) - self.assertEquals(self.run_version(repos_path), 0) - self.assertEquals(self.run_db_version(self.url, repos_path), 0) # version did not get bumped yet because new version not yet created + self.assertEqual(self.run_version(repos_path), 0) + self.assertEqual(self.run_db_version(self.url, repos_path), 0) # version did not get bumped yet because new version not yet created result = self.env.run('migrate compare_model_to_db %s %s %s'\ % (self.url, repos_path, model_module)) @@ -520,7 +519,7 @@ class TestShellDatabase(Shell, DB): #result_script = self.env.run('migrate make_update_script_for_model %s %s %s %s'\ #% (self.url, repos_path, old_model_module, model_module)) - #self.assertEqualsIgnoreWhitespace(result_script.stdout, + #self.assertEqualIgnoreWhitespace(result_script.stdout, #'''from sqlalchemy import * #from migrate import * diff --git a/migrate/tests/versioning/test_util.py b/migrate/tests/versioning/test_util.py index 6b22b7a..b6b1490 100644 --- a/migrate/tests/versioning/test_util.py +++ b/migrate/tests/versioning/test_util.py @@ -24,11 +24,11 @@ class TestUtil(fixture.Pathed): # keyword arg engine = construct_engine(url, engine_arg_encoding='utf-8') - self.assertEquals(engine.dialect.encoding, 'utf-8') + self.assertEqual(engine.dialect.encoding, 'utf-8') # dict engine = construct_engine(url, engine_dict={'encoding': 'utf-8'}) - self.assertEquals(engine.dialect.encoding, 'utf-8') + self.assertEqual(engine.dialect.encoding, 'utf-8') # engine parameter engine_orig = create_engine('sqlite://') @@ -38,7 +38,7 @@ class TestUtil(fixture.Pathed): # test precedance engine = construct_engine(url, engine_dict={'encoding': 'iso-8859-1'}, engine_arg_encoding='utf-8') - self.assertEquals(engine.dialect.encoding, 'utf-8') + self.assertEqual(engine.dialect.encoding, 'utf-8') # deprecated echo=True parameter try: diff --git a/migrate/tests/versioning/test_version.py b/migrate/tests/versioning/test_version.py index 253642f..436d5f9 100644 --- a/migrate/tests/versioning/test_version.py +++ b/migrate/tests/versioning/test_version.py @@ -76,14 +76,14 @@ class TestVersion(fixture.Pathed): super(TestVersion, self).setUp() def test_str_to_filename(self): - self.assertEquals(str_to_filename(''), '') - self.assertEquals(str_to_filename('__'), '_') - self.assertEquals(str_to_filename('a'), 'a') - self.assertEquals(str_to_filename('Abc Def'), 'Abc_Def') - self.assertEquals(str_to_filename('Abc "D" Ef'), 'Abc_D_Ef') - self.assertEquals(str_to_filename("Abc's Stuff"), 'Abc_s_Stuff') - self.assertEquals(str_to_filename("a b"), 'a_b') - self.assertEquals(str_to_filename("a.b to c"), 'a_b_to_c') + self.assertEqual(str_to_filename(''), '') + self.assertEqual(str_to_filename('__'), '_') + self.assertEqual(str_to_filename('a'), 'a') + self.assertEqual(str_to_filename('Abc Def'), 'Abc_Def') + self.assertEqual(str_to_filename('Abc "D" Ef'), 'Abc_D_Ef') + self.assertEqual(str_to_filename("Abc's Stuff"), 'Abc_s_Stuff') + self.assertEqual(str_to_filename("a b"), 'a_b') + self.assertEqual(str_to_filename("a.b to c"), 'a_b_to_c') def test_collection(self): """Let's see how we handle versions collection""" @@ -142,19 +142,19 @@ class TestVersion(fixture.Pathed): open(filepath, 'w').close() ver = Version(1, path, [sqlite_upgrade_file]) - self.assertEquals(os.path.basename(ver.script('sqlite', 'upgrade').path), sqlite_upgrade_file) + self.assertEqual(os.path.basename(ver.script('sqlite', 'upgrade').path), sqlite_upgrade_file) ver = Version(1, path, [default_upgrade_file]) - self.assertEquals(os.path.basename(ver.script('default', 'upgrade').path), default_upgrade_file) + self.assertEqual(os.path.basename(ver.script('default', 'upgrade').path), default_upgrade_file) ver = Version(1, path, [sqlite_upgrade_file, default_upgrade_file]) - self.assertEquals(os.path.basename(ver.script('sqlite', 'upgrade').path), sqlite_upgrade_file) + self.assertEqual(os.path.basename(ver.script('sqlite', 'upgrade').path), sqlite_upgrade_file) ver = Version(1, path, [sqlite_upgrade_file, default_upgrade_file, python_file]) - self.assertEquals(os.path.basename(ver.script('postgres', 'upgrade').path), default_upgrade_file) + self.assertEqual(os.path.basename(ver.script('postgres', 'upgrade').path), default_upgrade_file) ver = Version(1, path, [sqlite_upgrade_file, python_file]) - self.assertEquals(os.path.basename(ver.script('postgres', 'upgrade').path), python_file) + self.assertEqual(os.path.basename(ver.script('postgres', 'upgrade').path), python_file) def test_bad_version(self): ver = Version(1, self.temp_usable_dir, []) diff --git a/requirements.txt b/requirements.txt index 2599c00..d454507 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ +d2to1>=0.2.10,<0.3 +pbr>=0.5,<0.6 + SQLAlchemy >= 0.6 decorator Tempita >= 0.4 -Sphinx -sphinxcontrib_issuetracker diff --git a/setup.cfg b/setup.cfg index d20401b..f4ddf34 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,18 +1,40 @@ +[metadata] +name = sqlalchemy-migrate +summary = Database schema migration for SQLAlchemy +description-file = + README +author = OpenStack +author-email = openstack-dev@lists.openstack.org +home-page = http://www.openstack.org/ +classifier = + Environment :: OpenStack + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 2.6 + +[global] +setup-hooks = + pbr.hooks.setup_hook + +[files] +packages = + migrate + +[entry_points] +console_scripts = + migrate = migrate.versioning.shell:main + migrate-repository = migrate.versioning.migrate_repository:main + [build_sphinx] -source-dir = docs -build-dir = docs/_build +all_files = 1 +build-dir = doc/build +source-dir = doc/source [egg_info] tag_svn_revision = 1 tag_build = .dev - -[nosetests] -# uncomment these if you want tests to drop to pdb on first -# error or failure. -#nologcapture = -#pdb = -#pdb-failures = -#stop = - -[aliases] -release = egg_info -RDb '' diff --git a/setup.py b/setup.py index 24d4c30..b3e85a7 100644 --- a/setup.py +++ b/setup.py @@ -1,34 +1,21 @@ -#!/usr/bin/python - -import os +#!/usr/bin/env python +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. import setuptools -required_deps = ['SQLAlchemy >= 0.6', 'decorator', 'Tempita >= 0.4'] -readme_file = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'README')) - setuptools.setup( - name = "sqlalchemy-migrate", - version = "0.7.3", - packages = setuptools.find_packages(exclude=["migrate.tests*"]), - include_package_data = True, - description = "Database schema migration for SQLAlchemy", - long_description = readme_file.read(), - install_requires = required_deps, - extras_require = { - 'docs' : ['sphinx >= 0.5'], - }, - author = "Evan Rosson", - author_email = "evan.rosson@gmail.com", - url = "http://code.google.com/p/sqlalchemy-migrate/", - maintainer = "Jan Dittberner", - maintainer_email = "jan@dittberner.info", - license = "MIT", - entry_points = """ - [console_scripts] - migrate = migrate.versioning.shell:main - migrate-repository = migrate.versioning.migrate_repository:main - """, - test_suite = "nose.collector", -) + setup_requires=['d2to1>=0.2.10,<0.3', 'pbr>=0.5.10,<0.6'], + d2to1=True) diff --git a/test-requirements.txt b/test-requirements.txt index 273f886..24d5626 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,11 +1,25 @@ -coverage -nose -nosexcover +# Install bounded pep8/pyflakes first, then let flake8 install +pep8==1.4.5 +pyflakes==0.7.2 +flake8==2.0 +hacking>=0.5.3,<0.6 + +coverage>=3.6 +discover +feedparser +fixtures>=0.3.12 +mox==0.5.3 +MySQL-python +psycopg2 +pylint==0.25.2 +python-subunit +setuptools_git>=0.4 +sphinx>=1.1.2 +sphinxcontrib_issuetracker +testrepository>=0.0.13 +testtools>=0.9.27 + # NOTE: scripttest 1.0.1 removes base_path argument to ScriptTest scripttest==1.0 pytz -psycopg2 pysqlite -mysql-python -virtualenv -unittest2 diff --git a/tox.ini b/tox.ini index efbb9f6..eca9a3f 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ setenv = VIRTUAL_ENV={envdir} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = - nosetests {posargs} + python setup.py testr --slowest --testr-args='{posargs}' [testenv:pep8] commands = flake8 @@ -17,7 +17,7 @@ commands = {posargs} [testenv:cover] setenv = VIRTUAL_ENV={envdir} commands = - nosetests {posargs} + python setup.py testr --slowest --testr-args='{posargs}' [flake8] # F841 local variable 'json_template' is assigned to but never used -- cgit v1.2.1