diff options
| -rw-r--r-- | src/mailman/commands/cli_migrate.py | 25 | ||||
| -rw-r--r-- | src/mailman/config/alembic.ini (renamed from alembic.ini) | 0 | ||||
| -rw-r--r-- | src/mailman/config/config.py | 11 | ||||
| -rw-r--r-- | src/mailman/config/schema.cfg | 9 | ||||
| -rw-r--r-- | src/mailman/database/alembic/env.py | 58 | ||||
| -rw-r--r-- | src/mailman/database/factory.py | 9 | ||||
| -rw-r--r-- | src/mailman/testing/layers.py | 6 | ||||
| -rw-r--r-- | src/mailman/utilities/modules.py | 15 |
8 files changed, 75 insertions, 58 deletions
diff --git a/src/mailman/commands/cli_migrate.py b/src/mailman/commands/cli_migrate.py index 85fd07bd4..8783b5c46 100644 --- a/src/mailman/commands/cli_migrate.py +++ b/src/mailman/commands/cli_migrate.py @@ -22,7 +22,8 @@ from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'Migrate', -] + ] + from alembic import command from alembic.config import Config @@ -31,11 +32,13 @@ from zope.interface import implementer from mailman.config import config from mailman.core.i18n import _ from mailman.interfaces.command import ICLISubCommand +from mailman.utilities.modules import expand_path + @implementer(ICLISubCommand) class Migrate: - """Migrate the mailman database to the schema.""" + """Migrate the Mailman database to the latest schema.""" name = 'migrate' @@ -43,16 +46,20 @@ class Migrate: """See `ICLISubCommand`.""" command_parser.add_argument( '-a', '--autogenerate', - action="store_true", help=_("""\ - Autogenerate the migration script using alembic""")) - pass + action='store_true', help=_("""\ + Autogenerate the migration script using Alembic.""")) + command.parser_add_argument( + '-q', '--quiet', + action='store_true', default=False, + help=('Produce less output.')) def process(self, args): - alembic_cfg= Config() + alembic_cfg = Config() alembic_cfg.set_main_option( - "script_location", config.alembic['script_location']) + 'script_location', expand_path(config.database['alembic_scripts'])) if args.autogenerate: command.revision(alembic_cfg, autogenerate=True) else: - command.upgrade(alembic_cfg, "head") - print("Updated the database schema.") + command.upgrade(alembic_cfg, 'head') + if not args.quiet: + print('Updated the database schema.') diff --git a/alembic.ini b/src/mailman/config/alembic.ini index a7247743c..a7247743c 100644 --- a/alembic.ini +++ b/src/mailman/config/alembic.ini diff --git a/src/mailman/config/config.py b/src/mailman/config/config.py index e8c8ebc8b..52cac414f 100644 --- a/src/mailman/config/config.py +++ b/src/mailman/config/config.py @@ -33,7 +33,7 @@ import sys from ConfigParser import SafeConfigParser from flufl.lock import Lock from lazr.config import ConfigSchema, as_boolean -from pkg_resources import resource_filename, resource_stream, resource_string +from pkg_resources import resource_stream, resource_string from string import Template from zope.component import getUtility from zope.event import notify @@ -46,7 +46,7 @@ from mailman.interfaces.configuration import ( ConfigurationUpdatedEvent, IConfiguration, MissingConfigurationFileError) from mailman.interfaces.languages import ILanguageManager from mailman.utilities.filesystem import makedirs -from mailman.utilities.modules import call_name +from mailman.utilities.modules import call_name, expand_path SPACE = ' ' @@ -304,12 +304,7 @@ def external_configuration(path): :return: A `ConfigParser` instance. """ # Is the context coming from a file system or Python path? - if path.startswith('python:'): - resource_path = path[7:] - package, dot, resource = resource_path.rpartition('.') - cfg_path = resource_filename(package, resource + '.cfg') - else: - cfg_path = path + cfg_path = expand_path(path) parser = SafeConfigParser() files = parser.read(cfg_path) if files != [cfg_path]: diff --git a/src/mailman/config/schema.cfg b/src/mailman/config/schema.cfg index 23382721c..3ed7d72da 100644 --- a/src/mailman/config/schema.cfg +++ b/src/mailman/config/schema.cfg @@ -204,8 +204,10 @@ class: mailman.database.sqlite.SQLiteDatabase url: sqlite:///$DATA_DIR/mailman.db debug: no -# The module path to the migrations modules. -migrations_path: mailman.database.schema +# Where can we find the Alembic migration scripts? The `python:` schema means +# that this is a module path relative to the Mailman 3 source installation. +alembic_scripts: python:mailman.database.alembic + [logging.template] # This defines various log settings. The options available are: @@ -640,6 +642,3 @@ rewrite_duplicate_headers: CC X-Original-CC Content-Transfer-Encoding X-Original-Content-Transfer-Encoding MIME-Version X-MIME-Version - -[alembic] -script_location: src/mailman/database/alembic diff --git a/src/mailman/database/alembic/env.py b/src/mailman/database/alembic/env.py index 11ea8f6da..ee6d8293f 100644 --- a/src/mailman/database/alembic/env.py +++ b/src/mailman/database/alembic/env.py @@ -1,4 +1,4 @@ -# Copyright (C) 2006-2014 by the Free Software Foundation, Inc. +# Copyright (C) 2014 by the Free Software Foundation, Inc. # # This file is part of GNU Mailman. # @@ -15,34 +15,41 @@ # You should have received a copy of the GNU General Public License along with # GNU Mailman. If not, see <http://www.gnu.org/licenses/>. -from __future__ import with_statement +"""Alembic migration environment.""" + +from __future__ import absolute_import, print_function, unicode_literals + +__metaclass__ = type +__all__ = [ + 'run_migrations_offline', + 'run_migrations_online', + ] + from alembic import context from alembic.config import Config -from sqlalchemy import create_engine, pool +from contextlib import closing +from sqlalchemy import create_engine from mailman.config import config -from mailman.utilities.string import expand from mailman.database.model import Model - -target_metadata = Model.metadata +from mailman.utilities.modules import expand_path +from mailman.utilities.string import expand + def run_migrations_offline(): """Run migrations in 'offline' mode. - This configures the context with just a URL - and not an Engine, though an Engine is acceptable - here as well. By skipping the Engine creation - we don't even need a DBAPI to be available. - - Calls to context.execute() here emit the given string to the - script output. + This configures the context with just a URL and not an Engine, + though an Engine is acceptable here as well. By skipping the Engine + creation we don't even need a DBAPI to be available. + Calls to context.execute() here emit the given string to the script + output. """ url = expand(config.database.url, config.paths) - context.configure(url=url, target_metadata=target_metadata) - + context.configure(url=url, target_metadata=Model.metadata) with context.begin_transaction(): context.run_migrations() @@ -50,27 +57,22 @@ def run_migrations_offline(): def run_migrations_online(): """Run migrations in 'online' mode. - In this scenario we need to create an Engine - and associate a connection with the context. - + In this scenario we need to create an Engine and associate a + connection with the context. """ - alembic_cfg= Config() + alembic_cfg = Config() alembic_cfg.set_main_option( - "script_location", config.alembic['script_location']) + 'script_location', expand_path(config.database['alembic_scripts'])) url = expand(config.database.url, config.paths) engine = create_engine(url) connection = engine.connect() - context.configure( - connection=connection, - target_metadata=target_metadata - ) - - try: + with closing(connection): + context.configure( + connection=connection, target_metadata=Model.metadata) with context.begin_transaction(): context.run_migrations() - finally: - connection.close() + if context.is_offline_mode(): run_migrations_offline() diff --git a/src/mailman/database/factory.py b/src/mailman/database/factory.py index d4857866a..189fd6ac4 100644 --- a/src/mailman/database/factory.py +++ b/src/mailman/database/factory.py @@ -29,10 +29,11 @@ __all__ = [ import os import types -from alembic.config import Config from alembic import command +from alembic.config import Config from flufl.lock import Lock +from pkg_resources import resource_filename from zope.interface import implementer from zope.interface.verify import verifyObject @@ -42,8 +43,6 @@ from mailman.interfaces.database import IDatabase, IDatabaseFactory from mailman.utilities.modules import call_name -alembic_cfg = Config("./alembic.ini") - @implementer(IDatabaseFactory) class DatabaseFactory: @@ -58,7 +57,9 @@ class DatabaseFactory: verifyObject(IDatabase, database) database.initialize() Model.metadata.create_all(database.engine) - command.stamp(alembic_cfg, "head") + alembic_cfg = Config( + resource_filename('mailman.config', 'alembic.ini')) + command.stamp(alembic_cfg, 'head') database.commit() return database diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py index 3d2a50782..4a9841fc2 100644 --- a/src/mailman/testing/layers.py +++ b/src/mailman/testing/layers.py @@ -190,9 +190,9 @@ class ConfigLayer(MockAndMonkeyLayer): @classmethod def tearDown(cls): assert cls.var_dir is not None, 'Layer not set up' - # Reset the test database after the tests are done so that there - # is no data incase the tests are rerun with a database layer like - # mysql or postgresql which are not deleted in teardown. + # Reset the test database after the tests are done so that there is no + # data in case the tests are rerun with a database layer like mysql or + # postgresql which are not deleted in teardown. reset_the_world() config.pop('test config') shutil.rmtree(cls.var_dir) diff --git a/src/mailman/utilities/modules.py b/src/mailman/utilities/modules.py index 5dfec95db..9ff0e50cd 100644 --- a/src/mailman/utilities/modules.py +++ b/src/mailman/utilities/modules.py @@ -22,6 +22,7 @@ from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'call_name', + 'expand_path', 'find_components', 'find_name', 'scan_module', @@ -31,7 +32,7 @@ __all__ = [ import os import sys -from pkg_resources import resource_listdir +from pkg_resources import resource_filename, resource_listdir @@ -110,3 +111,15 @@ def find_components(package, interface): continue for component in scan_module(module, interface): yield component + + + +def expand_path(url): + """Expand a python: path, returning the absolute file system path.""" + # Is the context coming from a file system or Python path? + if url.startswith('python:'): + resource_path = url[7:] + package, dot, resource = resource_path.rpartition('.') + return resource_filename(package, resource + '.cfg') + else: + return url |
