diff options
| -rw-r--r-- | MANIFEST.in | 2 | ||||
| -rw-r--r-- | alembic.ini | 57 | ||||
| -rw-r--r-- | src/mailman/config/config.py | 2 | ||||
| -rw-r--r-- | src/mailman/database/alembic/env.py | 8 | ||||
| -rw-r--r-- | src/mailman/database/alembic/versions/429e08420177_initial.py | 29 | ||||
| -rw-r--r-- | src/mailman/database/factory.py | 60 |
6 files changed, 99 insertions, 59 deletions
diff --git a/MANIFEST.in b/MANIFEST.in index 97857a71c..c119f141e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -13,3 +13,5 @@ prune eggs prune parts include MANIFEST.in include src/mailman/testing/config.pck +include src/mailman/databases/alembic/scripts.py.mako +include src/mailman/databases/alembic/versions/*.py diff --git a/alembic.ini b/alembic.ini deleted file mode 100644 index a7247743c..000000000 --- a/alembic.ini +++ /dev/null @@ -1,57 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# path to migration scripts -script_location = src/mailman/database/alembic - -# template used to generate migration files -# file_template = %%(rev)s_%%(slug)s - -# max length of characters to apply to the -# "slug" field -#truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/src/mailman/config/config.py b/src/mailman/config/config.py index e8c8ebc8b..287de9798 100644 --- a/src/mailman/config/config.py +++ b/src/mailman/config/config.py @@ -87,6 +87,7 @@ class Configuration: self.pipelines = {} self.commands = {} self.password_context = None + self.initialized = False def _clear(self): """Clear the cached configuration variables.""" @@ -136,6 +137,7 @@ class Configuration: # Expand and set up all directories. self._expand_paths() self.ensure_directories_exist() + self.initialized = True notify(ConfigurationUpdatedEvent(self)) def _expand_paths(self): diff --git a/src/mailman/database/alembic/env.py b/src/mailman/database/alembic/env.py index 2d9d48fd7..402c4f7da 100644 --- a/src/mailman/database/alembic/env.py +++ b/src/mailman/database/alembic/env.py @@ -21,6 +21,7 @@ from alembic import context from alembic.config import Config from sqlalchemy import create_engine, pool +from mailman.core import initialize from mailman.config import config from mailman.utilities.string import expand from mailman.database.model import Model @@ -40,6 +41,11 @@ def run_migrations_offline(): script output. """ + if not config.initialized: + initialize.initialize_1(context.config.config_file_name) + alembic_cfg= Config() + alembic_cfg.set_main_option( + "script_location", config.alembic['script_location']) url = expand(config.database.url, config.paths) context.configure(url=url, target_metadata=target_metadata) @@ -54,6 +60,8 @@ def run_migrations_online(): and associate a connection with the context. """ + if not config.initialized: + initialize.initialize_1(context.config.config_file_name) alembic_cfg= Config() alembic_cfg.set_main_option( "script_location", config.alembic['script_location']) diff --git a/src/mailman/database/alembic/versions/429e08420177_initial.py b/src/mailman/database/alembic/versions/429e08420177_initial.py new file mode 100644 index 000000000..e8d612676 --- /dev/null +++ b/src/mailman/database/alembic/versions/429e08420177_initial.py @@ -0,0 +1,29 @@ +"""Initial migration + +This empty migration file makes sure there is always an alembic_version in the +database. As a consequence, if the DB version is reported as None, it means the +database needs to be created from scratch with SQLAlchemy itself. + +It also removes the "version" table left over from Storm (if it exists). + + +Revision ID: 429e08420177 +Revises: None +Create Date: 2014-10-02 10:18:17.333354 + +""" + +# revision identifiers, used by Alembic. +revision = '429e08420177' +down_revision = None + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.drop_table('version') + + +def downgrade(): + pass diff --git a/src/mailman/database/factory.py b/src/mailman/database/factory.py index 0a295331a..6111be8c5 100644 --- a/src/mailman/database/factory.py +++ b/src/mailman/database/factory.py @@ -29,7 +29,12 @@ __all__ = [ import os import types +from alembic import command +from alembic.config import Config as AlembicConfig +from alembic.migration import MigrationContext +from alembic.script import ScriptDirectory from flufl.lock import Lock +from sqlalchemy import MetaData from zope.interface import implementer from zope.interface.verify import verifyObject @@ -52,13 +57,64 @@ class DatabaseFactory: database = call_name(database_class) verifyObject(IDatabase, database) database.initialize() - Model.metadata.create_all(database.engine) - database.stamp() + schema_mgr = SchemaManager(database) + schema_mgr.setup_db() database.commit() return database +class SchemaManager: + + LAST_STORM_SCHEMA_VERSION = '20130406000000' + + def __init__(self, database): + self.database = database + self.alembic_cfg = AlembicConfig() + self.alembic_cfg.set_main_option( + "script_location", config.alembic['script_location']) + self.script = ScriptDirectory.from_config(self.alembic_cfg) + + def get_storm_schema_version(self): + md = MetaData() + md.reflect(bind=self.database.engine) + if "version" not in md.tables: + return None + Version = md.tables["version"] + last_version = self.database.store.query(Version.c.version).filter( + Version.c.component == "schema" + ).order_by(Version.c.version.desc()).first() + return last_version + + def setup_db(self): + context = MigrationContext.configure(self.database.store.connection()) + current_rev = context.get_current_revision() + head_rev = self.script.get_current_head() + if current_rev == head_rev: + return head_rev # already at the latest revision, nothing to do + if current_rev == None: + # no alembic information + storm_version = self.get_storm_schema_version() + if storm_version is None: + # initial DB creation + Model.metadata.create_all(self.database.engine) + command.stamp(self.alembic_cfg, "head") + else: + # DB from a previous version managed by Storm + if storm_version.version < self.LAST_STORM_SCHEMA_VERSION: + raise RuntimeError( + "Upgrading while skipping beta version is " + "unsupported, please install the previous " + "Mailman beta release") + # Run migrations to remove the Storm-specific table and + # upgrade to SQLAlchemy & Alembic + command.upgrade(self.alembic_cfg, "head") + elif current_rev != head_rev: + command.upgrade(self.alembic_cfg, "head") + return head_rev + + + def _reset(self): """See `IDatabase`.""" # Avoid a circular import at module level. |
