diff --git a/README b/README index 77a8467..3bbce04 100644 --- a/README +++ b/README @@ -1,19 +1,25 @@ -SQLAlchemy-migrate is a tool and a set of APIs for managing changes to -database schemas. +Inspired by Ruby on Rails' migrations, Migrate provides a way to deal with database schema changes in `SQLAlchemy `_ projects. + +Migrate extends SQLAlchemy to have database changeset handling. It provides a database change repository mechanism which can be used from the command line as well as from inside python code. Help ---- -Help is available at the project page [1] and at the public users -mailing list [2]. +Sphinx documentation is available at the project page [1]. + +Users and developers can be found at #sqlalchemy-migrate on Freenode IRC network +and at the public users mailing list [2]. New releases and major changes are announced at the public announce mailing list [3] and at the Python package index [4]. -[1] http://code.google.com/p/sqlalchemy-migrate/ +Homepage is located at [5] + +[1] http://packages.python.org/sqlalchemy-migrate/ [2] http://groups.google.com/group/migrate-users [3] http://groups.google.com/group/migrate-announce [4] http://pypi.python.org/pypi/sqlalchemy-migrate +[5] http://code.google.com/p/sqlalchemy-migrate/ Tests and Bugs -------------- diff --git a/migrate/versioning/api.py b/migrate/versioning/api.py index d6eca16..48a9dd3 100644 --- a/migrate/versioning/api.py +++ b/migrate/versioning/api.py @@ -248,7 +248,7 @@ def drop_version_control(url, repository, **opts): def manage(file, **opts): - """%prog manage FILENAME VARIABLES... + """%prog manage FILENAME [VARIABLES...] Creates a script that runs Migrate with a set of default values. @@ -263,7 +263,7 @@ def manage(file, **opts): python manage.py version %prog version --repository=/path/to/repository """ - return repository.manage(file, **opts) + return Repository.create_manage_file(file, **opts) def compare_model_to_db(url, model, repository, **opts): diff --git a/migrate/versioning/repository.py b/migrate/versioning/repository.py index 16f4b80..fa33105 100644 --- a/migrate/versioning/repository.py +++ b/migrate/versioning/repository.py @@ -80,7 +80,7 @@ class Repository(pathed.Pathed): """ Ensure the target path is a valid repository. - :raises: :exc:`InvalidRepositoryError` if not valid + :raises: :exc:`InvalidRepositoryError ` """ # Ensure the existance of required files try: @@ -90,22 +90,22 @@ class Repository(pathed.Pathed): except exceptions.PathNotFoundError, e: raise exceptions.InvalidRepositoryError(path) + # TODO: what are those options? @classmethod def prepare_config(cls, pkg, rsrc, name, **opts): """ Prepare a project configuration file for a new project. """ # Prepare opts - defaults=dict( - version_table='migrate_version', - repository_id=name, - required_dbs=[], ) - for key, val in defaults.iteritems(): - if (key not in opts) or (opts[key] is None): - opts[key]=val + defaults = dict( + version_table = 'migrate_version', + repository_id = name, + required_dbs = []) + + defaults.update(opts) tmpl = resource_string(pkg, rsrc) - ret = string.Template(tmpl).substitute(opts) + ret = string.Template(tmpl).substitute(defaults) return ret @classmethod @@ -127,40 +127,46 @@ class Repository(pathed.Pathed): fd.close() # Create a management script manager = os.path.join(path, 'manage.py') - manage(manager, repository=path) + Repository.create_manage_file(manager, repository=path) except: log.error("There was an error creating your repository") return cls(path) def create_script(self, description, **k): + """""" self.versions.create_new_python_version(description, **k) def create_script_sql(self, database, **k): + """""" self.versions.create_new_sql_version(database, **k) @property def latest(self): + """""" return self.versions.latest @property def version_table(self): + """""" return self.config.get('db_settings', 'version_table') @property def id(self): + """""" return self.config.get('db_settings', 'repository_id') def version(self, *p, **k): + """""" return self.versions.version(*p, **k) @classmethod def clear(cls): + """""" super(Repository, cls).clear() version.Collection.clear() def changeset(self, database, start, end=None): - """ - Create a changeset to migrate this dbms from ver. start to end/latest. + """Create a changeset to migrate this dbms from ver. start to end/latest. """ start = version.VerNum(start) if end is None: @@ -181,13 +187,19 @@ class Repository(pathed.Pathed): return ret -def manage(file, **opts): - """Create a project management script""" - pkg, rsrc = template.manage(as_pkg=True) - tmpl = resource_string(pkg, rsrc) - vars = ",".join(["%s='%s'" % vars for vars in opts.iteritems()]) - result = tmpl % dict(defaults=vars) + @classmethod + def create_manage_file(cls, file_, **opts): + """Create a project management script (manage.py) + + :param file_: Destination file to be written + :param **opts: Options that are passed to template + """ + vars_ = ",".join(["%s='%s'" % var for var in opts.iteritems()]) - fd = open(file, 'w') - fd.write(result) - fd.close() + pkg, rsrc = template.manage(as_pkg=True) + tmpl = resource_string(pkg, rsrc) + result = tmpl % dict(defaults=vars_) + + fd = open(file_, 'w') + fd.write(result) + fd.close() diff --git a/setup.py b/setup.py index e52a9cf..8f9d884 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,7 @@ #!/usr/bin/python +import os + try: from setuptools import setup, find_packages except ImportError: @@ -14,6 +16,7 @@ except ImportError: test_requirements = ['nose >= 0.10'] required_deps = ['sqlalchemy >= 0.5', 'decorator'] +readme_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'README') setup( name = "sqlalchemy-migrate", @@ -21,12 +24,7 @@ setup( packages = find_packages(exclude=['test*']), include_package_data = True, description = "Database schema migration for SQLAlchemy", - long_description = """ -Inspired by Ruby on Rails' migrations, Migrate provides a way to deal with database schema changes in `SQLAlchemy `_ projects. - -Migrate extends SQLAlchemy to have database changeset handling. It provides a database change repository mechanism which can be used from the command line as well as from inside python code. -""", - + long_description = readme_file.read(), install_requires = required_deps, tests_require = test_requirements, extras_require = { @@ -38,7 +36,6 @@ Migrate extends SQLAlchemy to have database changeset handling. It provides a da maintainer = "Jan Dittberner", maintainer_email = "jan@dittberner.info", license = "MIT", - entry_points = """ [console_scripts] migrate = migrate.versioning.shell:main diff --git a/test/versioning/test_repository.py b/test/versioning/test_repository.py index 53f78a2..7801e8c 100644 --- a/test/versioning/test_repository.py +++ b/test/versioning/test_repository.py @@ -1,98 +1,116 @@ -from test import fixture -from migrate.versioning.repository import * +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import shutil + from migrate.versioning import exceptions -import os,shutil +from migrate.versioning.repository import * +from nose.tools import raises + +from test import fixture + class TestRepository(fixture.Pathed): def test_create(self): """Repositories are created successfully""" - path=self.tmp_repos() - name='repository_name' + path = self.tmp_repos() + name = 'repository_name' + # Creating a repository that doesn't exist should succeed - repos=Repository.create(path,name) - config_path=repos.config.path - manage_path=os.path.join(repos.path,'manage.py') - self.assert_(repos) + repo = Repository.create(path, name) + config_path = repo.config.path + manage_path = os.path.join(repo.path, 'manage.py') + self.assert_(repo) + # Files should actually be created self.assert_(os.path.exists(path)) self.assert_(os.path.exists(config_path)) self.assert_(os.path.exists(manage_path)) + # Can't create it again: it already exists - self.assertRaises(exceptions.PathFoundError,Repository.create,path,name) + self.assertRaises(exceptions.PathFoundError, Repository.create, path, name) return path def test_load(self): """We should be able to load information about an existing repository""" # Create a repository to load - path=self.test_create() - repos=Repository(path) + path = self.test_create() + repos = Repository(path) + self.assert_(repos) self.assert_(repos.config) - self.assert_(repos.config.get('db_settings','version_table')) + self.assert_(repos.config.get('db_settings', 'version_table')) + # version_table's default isn't none - self.assertNotEquals(repos.config.get('db_settings','version_table'),'None') - from nose.tools import raises + self.assertNotEquals(repos.config.get('db_settings', 'version_table'), 'None') def test_load_notfound(self): """Nonexistant repositories shouldn't be loaded""" - path=self.tmp_repos() + path = self.tmp_repos() self.assert_(not os.path.exists(path)) - self.assertRaises(exceptions.InvalidRepositoryError,Repository,path) + self.assertRaises(exceptions.InvalidRepositoryError, Repository, path) def test_load_invalid(self): """Invalid repos shouldn't be loaded""" # Here, invalid=empty directory. There may be other conditions too, # but we shouldn't need to test all of them - path=self.tmp_repos() + path = self.tmp_repos() os.mkdir(path) - self.assertRaises(exceptions.InvalidRepositoryError,Repository,path) + self.assertRaises(exceptions.InvalidRepositoryError, Repository, path) class TestVersionedRepository(fixture.Pathed): """Tests on an existing repository with a single python script""" - script_cls = script.PythonScript + def setUp(self): super(TestVersionedRepository, self).setUp() Repository.clear() - self.path_repos=self.tmp_repos() - # Create repository, script - Repository.create(self.path_repos,'repository_name') + self.path_repos = self.tmp_repos() + Repository.create(self.path_repos, 'repository_name') def test_version(self): """We should correctly detect the version of a repository""" - repos=Repository(self.path_repos) + repos = Repository(self.path_repos) + # Get latest version, or detect if a specified version exists - self.assertEquals(repos.latest,0) + self.assertEquals(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) - self.assert_(repos.latest<1) + self.assert_(repos.latest >= 0) + self.assert_(repos.latest < 1) + # Create a script and test again repos.create_script('') - self.assertEquals(repos.latest,1) - self.assert_(repos.latest>=0) - self.assert_(repos.latest>=1) - self.assert_(repos.latest<2) + self.assertEquals(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.assert_(repos.latest>=0) - self.assert_(repos.latest>=1) - self.assert_(repos.latest>=2) - self.assert_(repos.latest<3) + self.assertEquals(repos.latest, 2) + self.assert_(repos.latest >= 0) + self.assert_(repos.latest >= 1) + self.assert_(repos.latest >= 2) + self.assert_(repos.latest < 3) + def test_source(self): """Get a script object by version number and view its source""" # Load repository and commit script - repos=Repository(self.path_repos) - repos.create_script('') + repo = Repository(self.path_repos) + repo.create_script('') + # Get script object - source=repos.version(1).script().source() + source = repo.version(1).script().source() + # Source is valid: script must have an upgrade function # (not a very thorough test, but should be plenty) - self.assert_(source.find('def upgrade')>=0) + self.assert_(source.find('def upgrade') >= 0) + def test_latestversion(self): """Repository.version() (no params) returns the latest version""" - repos=Repository(self.path_repos) + repos = Repository(self.path_repos) repos.create_script('') self.assert_(repos.version(repos.latest) is repos.version()) self.assert_(repos.version() is not None) @@ -100,26 +118,26 @@ class TestVersionedRepository(fixture.Pathed): def test_changeset(self): """Repositories can create changesets properly""" # Create a nonzero-version repository of empty scripts - repos=Repository(self.path_repos) + repos = Repository(self.path_repos) for i in range(10): repos.create_script('') - def check_changeset(params,length): + def check_changeset(params, length): """Creates and verifies a changeset""" - changeset = repos.changeset('postgres',*params) - self.assertEquals(len(changeset),length) - self.assert_(isinstance(changeset,Changeset)) + changeset = repos.changeset('postgres', *params) + self.assertEquals(len(changeset), length) + self.assert_(isinstance(changeset, Changeset)) uniq = list() # Changesets are iterable - for version,change in changeset: - self.assert_(isinstance(change,script.BaseScript)) + for version, change in changeset: + self.assert_(isinstance(change, script.BaseScript)) # Changes aren't identical self.assert_(id(change) not in uniq) uniq.append(id(change)) return changeset # Upgrade to a specified version... - cs=check_changeset((0,10),10) + 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 @@ -165,3 +183,4 @@ class TestVersionedRepository(fixture.Pathed): self.assert_(os.path.exists('%s/versions/1000.py' % self.path_repos)) self.assert_(os.path.exists('%s/versions/1001.py' % self.path_repos)) +# TODO: test manage file