Merge lp:~abompard/mailman/sqlalchemy into lp:~raj-abhilash1/mailman/sqlalchemy

Proposed by Aurélien Bompard
Status: Merged
Merged at revision: 7280
Proposed branch: lp:~abompard/mailman/sqlalchemy
Merge into: lp:~raj-abhilash1/mailman/sqlalchemy
Diff against target: 429 lines (+174/-66)
11 files modified
MANIFEST.in (+2/-2)
src/mailman/commands/docs/conf.rst (+3/-1)
src/mailman/config/alembic.cfg (+0/-36)
src/mailman/config/schema.cfg (+8/-4)
src/mailman/core/logging.py (+4/-0)
src/mailman/database/alembic/env.py (+0/-3)
src/mailman/database/base.py (+0/-10)
src/mailman/database/factory.py (+14/-6)
src/mailman/database/tests/test_factory.py (+134/-0)
src/mailman/testing/layers.py (+6/-1)
src/mailman/testing/testing.cfg (+3/-3)
To merge this branch: bzr merge lp:~abompard/mailman/sqlalchemy
Reviewer Review Type Date Requested Status
Abhilash Raj Pending
Review via email: mp+236884@code.launchpad.net

Description of the change

Here are my changes for automatic schema migration. The "migrate" subcommand is now useless, and I've fixed the "alembic revision" command, you can now create a new DB version with:
  alembic -c src/mailman/config/schema.cfg revision --autogenerate

It currently does generate something, by the way (it shouldn't if everything was perfectly ported from Storm to SQLAlchemy). I need to look into that (but first, unit tests).

To post a comment you must log in.
lp:~abompard/mailman/sqlalchemy updated
7276. By Abhilash Raj

Merge alembic setup from abompard

7277. By Abhilash Raj

Merge barry\'s branch with test fixes and clean code

7278. By Abhilash Raj

add central alembic config

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'MANIFEST.in'
--- MANIFEST.in 2014-10-02 12:50:42 +0000
+++ MANIFEST.in 2014-10-08 08:59:34 +0000
@@ -13,5 +13,5 @@
13prune parts13prune parts
14include MANIFEST.in14include MANIFEST.in
15include src/mailman/testing/config.pck15include src/mailman/testing/config.pck
16include src/mailman/databases/alembic/scripts.py.mako16include src/mailman/database/alembic/script.py.mako
17include src/mailman/databases/alembic/versions/*.py17include src/mailman/database/alembic/versions/*.py
1818
=== modified file 'src/mailman/commands/docs/conf.rst'
--- src/mailman/commands/docs/conf.rst 2014-09-28 00:17:05 +0000
+++ src/mailman/commands/docs/conf.rst 2014-10-08 08:59:34 +0000
@@ -22,7 +22,7 @@
22command without any options.22command without any options.
2323
24 >>> command.process(FakeArgs)24 >>> command.process(FakeArgs)
25 [logging.archiver] path: mailman.log25 [logging.dbmigration] path: mailman.log
26 ...26 ...
27 [passwords] password_length: 827 [passwords] password_length: 8
28 ...28 ...
@@ -43,12 +43,14 @@
43 >>> FakeArgs.section = None43 >>> FakeArgs.section = None
44 >>> FakeArgs.key = 'path'44 >>> FakeArgs.key = 'path'
45 >>> command.process(FakeArgs)45 >>> command.process(FakeArgs)
46 [logging.dbmigration] path: mailman.log
46 [logging.archiver] path: mailman.log47 [logging.archiver] path: mailman.log
47 [logging.locks] path: mailman.log48 [logging.locks] path: mailman.log
48 [logging.mischief] path: mailman.log49 [logging.mischief] path: mailman.log
49 [logging.config] path: mailman.log50 [logging.config] path: mailman.log
50 [logging.error] path: mailman.log51 [logging.error] path: mailman.log
51 [logging.smtp] path: smtp.log52 [logging.smtp] path: smtp.log
53 [logging.database] path: mailman.log
52 [logging.http] path: mailman.log54 [logging.http] path: mailman.log
53 [logging.root] path: mailman.log55 [logging.root] path: mailman.log
54 [logging.fromusenet] path: mailman.log56 [logging.fromusenet] path: mailman.log
5557
=== modified file 'src/mailman/config/alembic.cfg'
--- src/mailman/config/alembic.cfg 2014-10-03 16:26:50 +0000
+++ src/mailman/config/alembic.cfg 2014-10-08 08:59:34 +0000
@@ -19,39 +19,3 @@
19# a source .py file to be detected as revisions in the19# a source .py file to be detected as revisions in the
20# versions/ directory20# versions/ directory
21# sourceless = false21# sourceless = false
22
23
24# Logging configuration
25[loggers]
26keys = root,sqlalchemy,alembic
27
28[handlers]
29keys = console
30
31[formatters]
32keys = generic
33
34[logger_root]
35level = WARN
36handlers = console
37qualname =
38
39[logger_sqlalchemy]
40level = WARN
41handlers = console
42qualname = sqlalchemy.engine
43
44[logger_alembic]
45level = WARN
46handlers = console
47qualname = alembic
48
49[handler_console]
50class = StreamHandler
51args = (sys.stderr,)
52level = WARN
53formatter = generic
54
55[formatter_generic]
56format = %(levelname)-5.5s [%(name)s] %(message)s
57datefmt = %H:%M:%S
5822
=== modified file 'src/mailman/config/schema.cfg'
--- src/mailman/config/schema.cfg 2014-10-02 04:15:36 +0000
+++ src/mailman/config/schema.cfg 2014-10-08 08:59:34 +0000
@@ -204,10 +204,6 @@
204url: sqlite:///$DATA_DIR/mailman.db204url: sqlite:///$DATA_DIR/mailman.db
205debug: no205debug: no
206206
207# Where can we find the Alembic migration scripts? The `python:` schema means
208# that this is a module path relative to the Mailman 3 source installation.
209alembic_scripts: mailman.database:alembic
210
211207
212[logging.template]208[logging.template]
213# This defines various log settings. The options available are:209# This defines various log settings. The options available are:
@@ -242,6 +238,8 @@
242# - smtp-failure -- Unsuccessful SMTP activity238# - smtp-failure -- Unsuccessful SMTP activity
243# - subscribe -- Information about leaves/joins239# - subscribe -- Information about leaves/joins
244# - vette -- Message vetting information240# - vette -- Message vetting information
241# - database -- Database activity
242# - dbmigration -- Database migrations
245format: %(asctime)s (%(process)d) %(message)s243format: %(asctime)s (%(process)d) %(message)s
246datefmt: %b %d %H:%M:%S %Y244datefmt: %b %d %H:%M:%S %Y
247propagate: no245propagate: no
@@ -306,6 +304,12 @@
306304
307[logging.vette]305[logging.vette]
308306
307[logging.database]
308level: warn
309
310[logging.dbmigration]
311level: warn
312
309313
310[webservice]314[webservice]
311# The hostname at which admin web service resources are exposed.315# The hostname at which admin web service resources are exposed.
312316
=== modified file 'src/mailman/core/logging.py'
--- src/mailman/core/logging.py 2014-04-28 15:23:35 +0000
+++ src/mailman/core/logging.py 2014-10-08 08:59:34 +0000
@@ -126,6 +126,10 @@
126 continue126 continue
127 if sub_name == 'locks':127 if sub_name == 'locks':
128 log = logging.getLogger('flufl.lock')128 log = logging.getLogger('flufl.lock')
129 elif sub_name == 'database':
130 log = logging.getLogger('sqlalchemy')
131 elif sub_name == 'dbmigration':
132 log = logging.getLogger('alembic')
129 else:133 else:
130 logger_name = 'mailman.' + sub_name134 logger_name = 'mailman.' + sub_name
131 log = logging.getLogger(logger_name)135 log = logging.getLogger(logger_name)
132136
=== modified file 'src/mailman/database/alembic/env.py'
--- src/mailman/database/alembic/env.py 2014-10-03 16:26:50 +0000
+++ src/mailman/database/alembic/env.py 2014-10-08 08:59:34 +0000
@@ -28,7 +28,6 @@
2828
29from alembic import context29from alembic import context
30from contextlib import closing30from contextlib import closing
31from logging.config import fileConfig
32from sqlalchemy import create_engine31from sqlalchemy import create_engine
3332
34from mailman.core import initialize33from mailman.core import initialize
@@ -39,8 +38,6 @@
39from mailman.utilities.string import expand38from mailman.utilities.string import expand
4039
4140
42fileConfig(alembic_cfg.config_file_name)
43
4441
4542
46def run_migrations_offline():43def run_migrations_offline():
47 """Run migrations in 'offline' mode.44 """Run migrations in 'offline' mode.
4845
=== modified file 'src/mailman/database/base.py'
--- src/mailman/database/base.py 2014-10-03 16:26:50 +0000
+++ src/mailman/database/base.py 2014-10-08 08:59:34 +0000
@@ -31,7 +31,6 @@
31from zope.interface import implementer31from zope.interface import implementer
3232
33from mailman.config import config33from mailman.config import config
34from mailman.database.alembic import alembic_cfg
35from mailman.interfaces.database import IDatabase34from mailman.interfaces.database import IDatabase
36from mailman.utilities.string import expand35from mailman.utilities.string import expand
3736
@@ -91,15 +90,6 @@
91 """90 """
92 pass91 pass
9392
94 def stamp(self, debug=False):
95 """Stamp the database with the latest Alembic version."""
96 # Newly created databases don't need migrations from Alembic, since
97 # create_all() ceates the latest schema. This patches the database
98 # with the latest Alembic version to add an entry in the
99 # alembic_version table.
100 command.stamp(alembic_cfg, 'head')
101
102
103 def initialize(self, debug=None):93 def initialize(self, debug=None):
104 """See `IDatabase`."""94 """See `IDatabase`."""
105 # Calculate the engine url.95 # Calculate the engine url.
10696
=== modified file 'src/mailman/database/factory.py'
--- src/mailman/database/factory.py 2014-10-03 16:26:50 +0000
+++ src/mailman/database/factory.py 2014-10-08 08:59:34 +0000
@@ -70,8 +70,7 @@
7070
71 def __init__(self, database):71 def __init__(self, database):
72 self.database = database72 self.database = database
73 self.alembic_cfg = alembic_cfg73 self.script = ScriptDirectory.from_config(alembic_cfg)
74 self.script = ScriptDirectory.from_config(self.alembic_cfg)
7574
76 def get_storm_schema_version(self):75 def get_storm_schema_version(self):
77 md = MetaData()76 md = MetaData()
@@ -82,8 +81,18 @@
82 last_version = self.database.store.query(Version.c.version).filter(81 last_version = self.database.store.query(Version.c.version).filter(
83 Version.c.component == "schema"82 Version.c.component == "schema"
84 ).order_by(Version.c.version.desc()).first()83 ).order_by(Version.c.version.desc()).first()
84 # Don't leave open transactions or they will block any schema change
85 self.database.commit()
85 return last_version86 return last_version
8687
88 def _create(self):
89 # initial DB creation
90 Model.metadata.create_all(self.database.engine)
91 command.stamp(alembic_cfg, "head")
92
93 def _upgrade(self):
94 command.upgrade(alembic_cfg, "head")
95
87 def setup_db(self):96 def setup_db(self):
88 context = MigrationContext.configure(self.database.store.connection())97 context = MigrationContext.configure(self.database.store.connection())
89 current_rev = context.get_current_revision()98 current_rev = context.get_current_revision()
@@ -95,8 +104,7 @@
95 storm_version = self.get_storm_schema_version()104 storm_version = self.get_storm_schema_version()
96 if storm_version is None:105 if storm_version is None:
97 # initial DB creation106 # initial DB creation
98 Model.metadata.create_all(self.database.engine)107 self._create()
99 command.stamp(self.alembic_cfg, "head")
100 else:108 else:
101 # DB from a previous version managed by Storm109 # DB from a previous version managed by Storm
102 if storm_version.version < self.LAST_STORM_SCHEMA_VERSION:110 if storm_version.version < self.LAST_STORM_SCHEMA_VERSION:
@@ -106,9 +114,9 @@
106 "Mailman beta release")114 "Mailman beta release")
107 # Run migrations to remove the Storm-specific table and115 # Run migrations to remove the Storm-specific table and
108 # upgrade to SQLAlchemy & Alembic116 # upgrade to SQLAlchemy & Alembic
109 command.upgrade(self.alembic_cfg, "head")117 self._upgrade()
110 elif current_rev != head_rev:118 elif current_rev != head_rev:
111 command.upgrade(self.alembic_cfg, "head")119 self._upgrade()
112 return head_rev120 return head_rev
113121
114122
115123
=== added directory 'src/mailman/database/tests'
=== added file 'src/mailman/database/tests/__init__.py'
=== added file 'src/mailman/database/tests/test_factory.py'
--- src/mailman/database/tests/test_factory.py 1970-01-01 00:00:00 +0000
+++ src/mailman/database/tests/test_factory.py 2014-10-08 08:59:34 +0000
@@ -0,0 +1,134 @@
1# Copyright (C) 2013-2014 by the Free Software Foundation, Inc.
2#
3# This file is part of GNU Mailman.
4#
5# GNU Mailman is free software: you can redistribute it and/or modify it under
6# the terms of the GNU General Public License as published by the Free
7# Software Foundation, either version 3 of the License, or (at your option)
8# any later version.
9#
10# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13# more details.
14#
15# You should have received a copy of the GNU General Public License along with
16# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
17
18"""Test database schema migrations"""
19
20from __future__ import absolute_import, print_function, unicode_literals
21
22__metaclass__ = type
23__all__ = [
24 ]
25
26
27import unittest
28import types
29
30import alembic.command
31from mock import Mock
32from sqlalchemy import MetaData, Table, Column, Integer, Unicode
33
34from mailman.config import config
35from mailman.testing.layers import ConfigLayer
36from mailman.database.factory import SchemaManager, _reset
37from mailman.database.sqlite import SQLiteDatabase
38from mailman.database.alembic import alembic_cfg
39from mailman.database.model import Model
40
41
42
043
44class TestSchemaManager(unittest.TestCase):
45
46 layer = ConfigLayer
47
48 def setUp(self):
49 # Drop the existing database
50 Model.metadata.drop_all(config.db.engine)
51 md = MetaData()
52 md.reflect(bind=config.db.engine)
53 if "alembic_version" in md.tables:
54 md.tables["alembic_version"].drop(config.db.engine)
55 self.schema_mgr = SchemaManager(config.db)
56
57 def tearDown(self):
58 if "version" in Model.metadata.tables:
59 version = Model.metadata.tables["version"]
60 version.drop(config.db.engine, checkfirst=True)
61 Model.metadata.remove(version)
62 # Restore a virgin DB
63 Model.metadata.create_all(config.db.engine)
64
65
66 def _table_exists(self, tablename):
67 md = MetaData()
68 md.reflect(bind=config.db.engine)
69 return tablename in md.tables
70
71 def _create_storm_version_table(self, revision):
72 version_table = Table("version", Model.metadata,
73 Column("id", Integer, primary_key=True),
74 Column("component", Unicode),
75 Column("version", Unicode),
76 )
77 version_table.create(config.db.engine)
78 config.db.store.execute(version_table.insert().values(
79 component='schema', version=revision))
80 config.db.commit()
81
82
83 def test_current_db(self):
84 """The database is already at the latest version"""
85 alembic.command.stamp(alembic_cfg, "head")
86 self.schema_mgr._create = Mock()
87 self.schema_mgr._upgrade = Mock()
88 self.schema_mgr.setup_db()
89 self.assertFalse(self.schema_mgr._create.called)
90 self.assertFalse(self.schema_mgr._upgrade.called)
91
92 def test_initial(self):
93 """No existing database"""
94 self.assertFalse(self._table_exists("mailinglist"))
95 self.assertFalse(self._table_exists("alembic_version"))
96 self.schema_mgr._upgrade = Mock()
97 self.schema_mgr.setup_db()
98 self.assertFalse(self.schema_mgr._upgrade.called)
99 self.assertTrue(self._table_exists("mailinglist"))
100 self.assertTrue(self._table_exists("alembic_version"))
101
102 def test_storm(self):
103 """Existing Storm database"""
104 Model.metadata.create_all(config.db.engine)
105 self._create_storm_version_table(
106 self.schema_mgr.LAST_STORM_SCHEMA_VERSION)
107 self.schema_mgr._create = Mock()
108 self.schema_mgr.setup_db()
109 self.assertFalse(self.schema_mgr._create.called)
110 self.assertTrue(self._table_exists("mailinglist")
111 and self._table_exists("alembic_version")
112 and not self._table_exists("version"))
113
114 def test_old_storm(self):
115 """Existing Storm database in an old version"""
116 Model.metadata.create_all(config.db.engine)
117 self._create_storm_version_table("001")
118 self.schema_mgr._create = Mock()
119 self.assertRaises(RuntimeError, self.schema_mgr.setup_db)
120 self.assertFalse(self.schema_mgr._create.called)
121
122 def test_old_db(self):
123 """The database is in an old revision, must upgrade"""
124 alembic.command.stamp(alembic_cfg, "head")
125 md = MetaData()
126 md.reflect(bind=config.db.engine)
127 config.db.store.execute(md.tables["alembic_version"].delete())
128 config.db.store.execute(md.tables["alembic_version"].insert().values(
129 version_num="dummyrevision"))
130 config.db.commit()
131 self.schema_mgr._create = Mock()
132 self.schema_mgr._upgrade = Mock()
133 self.schema_mgr.setup_db()
134 self.assertFalse(self.schema_mgr._create.called)
135 self.assertTrue(self.schema_mgr._upgrade.called)
1136
=== modified file 'src/mailman/testing/layers.py'
--- src/mailman/testing/layers.py 2014-09-27 22:16:22 +0000
+++ src/mailman/testing/layers.py 2014-10-08 08:59:34 +0000
@@ -47,13 +47,16 @@
4747
48from lazr.config import as_boolean48from lazr.config import as_boolean
49from pkg_resources import resource_string49from pkg_resources import resource_string
50from sqlalchemy import MetaData
50from textwrap import dedent51from textwrap import dedent
52from zope import event
51from zope.component import getUtility53from zope.component import getUtility
5254
53from mailman.config import config55from mailman.config import config
54from mailman.core import initialize56from mailman.core import initialize
55from mailman.core.initialize import INHIBIT_CONFIG_FILE57from mailman.core.initialize import INHIBIT_CONFIG_FILE
56from mailman.core.logging import get_handler58from mailman.core.logging import get_handler
59from mailman.database.model import Model
57from mailman.database.transaction import transaction60from mailman.database.transaction import transaction
58from mailman.interfaces.domain import IDomainManager61from mailman.interfaces.domain import IDomainManager
59from mailman.testing.helpers import (62from mailman.testing.helpers import (
@@ -98,7 +101,9 @@
98 # Set up the basic configuration stuff. Turn off path creation until101 # Set up the basic configuration stuff. Turn off path creation until
99 # we've pushed the testing config.102 # we've pushed the testing config.
100 config.create_paths = False103 config.create_paths = False
101 initialize.initialize_1(INHIBIT_CONFIG_FILE)104 if not event.subscribers:
105 # only if not yet initialized by another layer
106 initialize.initialize_1(INHIBIT_CONFIG_FILE)
102 assert cls.var_dir is None, 'Layer already set up'107 assert cls.var_dir is None, 'Layer already set up'
103 # Calculate a temporary VAR_DIR directory so that run-time artifacts108 # Calculate a temporary VAR_DIR directory so that run-time artifacts
104 # of the tests won't tread on the installation's data. This also109 # of the tests won't tread on the installation's data. This also
105110
=== modified file 'src/mailman/testing/testing.cfg'
--- src/mailman/testing/testing.cfg 2014-10-03 16:26:50 +0000
+++ src/mailman/testing/testing.cfg 2014-10-08 08:59:34 +0000
@@ -18,9 +18,9 @@
18# A testing configuration.18# A testing configuration.
1919
20# For testing against PostgreSQL.20# For testing against PostgreSQL.
21# [database]21[database]
22# class: mailman.database.postgresql.PostgreSQLDatabase22class: mailman.database.postgresql.PostgreSQLDatabase
23# url: postgres://maxking:maxking@localhost/mailman_test23url: postgresql://maxking:maxking@localhost/mailman_test
2424
25[mailman]25[mailman]
26site_owner: noreply@example.com26site_owner: noreply@example.com

Subscribers

People subscribed via source and target branches