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
1=== modified file 'MANIFEST.in'
2--- MANIFEST.in 2014-10-02 12:50:42 +0000
3+++ MANIFEST.in 2014-10-08 08:59:34 +0000
4@@ -13,5 +13,5 @@
5 prune parts
6 include MANIFEST.in
7 include src/mailman/testing/config.pck
8-include src/mailman/databases/alembic/scripts.py.mako
9-include src/mailman/databases/alembic/versions/*.py
10+include src/mailman/database/alembic/script.py.mako
11+include src/mailman/database/alembic/versions/*.py
12
13=== modified file 'src/mailman/commands/docs/conf.rst'
14--- src/mailman/commands/docs/conf.rst 2014-09-28 00:17:05 +0000
15+++ src/mailman/commands/docs/conf.rst 2014-10-08 08:59:34 +0000
16@@ -22,7 +22,7 @@
17 command without any options.
18
19 >>> command.process(FakeArgs)
20- [logging.archiver] path: mailman.log
21+ [logging.dbmigration] path: mailman.log
22 ...
23 [passwords] password_length: 8
24 ...
25@@ -43,12 +43,14 @@
26 >>> FakeArgs.section = None
27 >>> FakeArgs.key = 'path'
28 >>> command.process(FakeArgs)
29+ [logging.dbmigration] path: mailman.log
30 [logging.archiver] path: mailman.log
31 [logging.locks] path: mailman.log
32 [logging.mischief] path: mailman.log
33 [logging.config] path: mailman.log
34 [logging.error] path: mailman.log
35 [logging.smtp] path: smtp.log
36+ [logging.database] path: mailman.log
37 [logging.http] path: mailman.log
38 [logging.root] path: mailman.log
39 [logging.fromusenet] path: mailman.log
40
41=== modified file 'src/mailman/config/alembic.cfg'
42--- src/mailman/config/alembic.cfg 2014-10-03 16:26:50 +0000
43+++ src/mailman/config/alembic.cfg 2014-10-08 08:59:34 +0000
44@@ -19,39 +19,3 @@
45 # a source .py file to be detected as revisions in the
46 # versions/ directory
47 # sourceless = false
48-
49-
50-# Logging configuration
51-[loggers]
52-keys = root,sqlalchemy,alembic
53-
54-[handlers]
55-keys = console
56-
57-[formatters]
58-keys = generic
59-
60-[logger_root]
61-level = WARN
62-handlers = console
63-qualname =
64-
65-[logger_sqlalchemy]
66-level = WARN
67-handlers = console
68-qualname = sqlalchemy.engine
69-
70-[logger_alembic]
71-level = WARN
72-handlers = console
73-qualname = alembic
74-
75-[handler_console]
76-class = StreamHandler
77-args = (sys.stderr,)
78-level = WARN
79-formatter = generic
80-
81-[formatter_generic]
82-format = %(levelname)-5.5s [%(name)s] %(message)s
83-datefmt = %H:%M:%S
84
85=== modified file 'src/mailman/config/schema.cfg'
86--- src/mailman/config/schema.cfg 2014-10-02 04:15:36 +0000
87+++ src/mailman/config/schema.cfg 2014-10-08 08:59:34 +0000
88@@ -204,10 +204,6 @@
89 url: sqlite:///$DATA_DIR/mailman.db
90 debug: no
91
92-# Where can we find the Alembic migration scripts? The `python:` schema means
93-# that this is a module path relative to the Mailman 3 source installation.
94-alembic_scripts: mailman.database:alembic
95-
96
97 [logging.template]
98 # This defines various log settings. The options available are:
99@@ -242,6 +238,8 @@
100 # - smtp-failure -- Unsuccessful SMTP activity
101 # - subscribe -- Information about leaves/joins
102 # - vette -- Message vetting information
103+# - database -- Database activity
104+# - dbmigration -- Database migrations
105 format: %(asctime)s (%(process)d) %(message)s
106 datefmt: %b %d %H:%M:%S %Y
107 propagate: no
108@@ -306,6 +304,12 @@
109
110 [logging.vette]
111
112+[logging.database]
113+level: warn
114+
115+[logging.dbmigration]
116+level: warn
117+
118
119 [webservice]
120 # The hostname at which admin web service resources are exposed.
121
122=== modified file 'src/mailman/core/logging.py'
123--- src/mailman/core/logging.py 2014-04-28 15:23:35 +0000
124+++ src/mailman/core/logging.py 2014-10-08 08:59:34 +0000
125@@ -126,6 +126,10 @@
126 continue
127 if sub_name == 'locks':
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')
133 else:
134 logger_name = 'mailman.' + sub_name
135 log = logging.getLogger(logger_name)
136
137=== modified file 'src/mailman/database/alembic/env.py'
138--- src/mailman/database/alembic/env.py 2014-10-03 16:26:50 +0000
139+++ src/mailman/database/alembic/env.py 2014-10-08 08:59:34 +0000
140@@ -28,7 +28,6 @@
141
142 from alembic import context
143 from contextlib import closing
144-from logging.config import fileConfig
145 from sqlalchemy import create_engine
146
147 from mailman.core import initialize
148@@ -39,8 +38,6 @@
149 from mailman.utilities.string import expand
150
151
152-fileConfig(alembic_cfg.config_file_name)
153-
154
155
156 def run_migrations_offline():
157 """Run migrations in 'offline' mode.
158
159=== modified file 'src/mailman/database/base.py'
160--- src/mailman/database/base.py 2014-10-03 16:26:50 +0000
161+++ src/mailman/database/base.py 2014-10-08 08:59:34 +0000
162@@ -31,7 +31,6 @@
163 from zope.interface import implementer
164
165 from mailman.config import config
166-from mailman.database.alembic import alembic_cfg
167 from mailman.interfaces.database import IDatabase
168 from mailman.utilities.string import expand
169
170@@ -91,15 +90,6 @@
171 """
172 pass
173
174- def stamp(self, debug=False):
175- """Stamp the database with the latest Alembic version."""
176- # Newly created databases don't need migrations from Alembic, since
177- # create_all() ceates the latest schema. This patches the database
178- # with the latest Alembic version to add an entry in the
179- # alembic_version table.
180- command.stamp(alembic_cfg, 'head')
181-
182-
183 def initialize(self, debug=None):
184 """See `IDatabase`."""
185 # Calculate the engine url.
186
187=== modified file 'src/mailman/database/factory.py'
188--- src/mailman/database/factory.py 2014-10-03 16:26:50 +0000
189+++ src/mailman/database/factory.py 2014-10-08 08:59:34 +0000
190@@ -70,8 +70,7 @@
191
192 def __init__(self, database):
193 self.database = database
194- self.alembic_cfg = alembic_cfg
195- self.script = ScriptDirectory.from_config(self.alembic_cfg)
196+ self.script = ScriptDirectory.from_config(alembic_cfg)
197
198 def get_storm_schema_version(self):
199 md = MetaData()
200@@ -82,8 +81,18 @@
201 last_version = self.database.store.query(Version.c.version).filter(
202 Version.c.component == "schema"
203 ).order_by(Version.c.version.desc()).first()
204+ # Don't leave open transactions or they will block any schema change
205+ self.database.commit()
206 return last_version
207
208+ def _create(self):
209+ # initial DB creation
210+ Model.metadata.create_all(self.database.engine)
211+ command.stamp(alembic_cfg, "head")
212+
213+ def _upgrade(self):
214+ command.upgrade(alembic_cfg, "head")
215+
216 def setup_db(self):
217 context = MigrationContext.configure(self.database.store.connection())
218 current_rev = context.get_current_revision()
219@@ -95,8 +104,7 @@
220 storm_version = self.get_storm_schema_version()
221 if storm_version is None:
222 # initial DB creation
223- Model.metadata.create_all(self.database.engine)
224- command.stamp(self.alembic_cfg, "head")
225+ self._create()
226 else:
227 # DB from a previous version managed by Storm
228 if storm_version.version < self.LAST_STORM_SCHEMA_VERSION:
229@@ -106,9 +114,9 @@
230 "Mailman beta release")
231 # Run migrations to remove the Storm-specific table and
232 # upgrade to SQLAlchemy & Alembic
233- command.upgrade(self.alembic_cfg, "head")
234+ self._upgrade()
235 elif current_rev != head_rev:
236- command.upgrade(self.alembic_cfg, "head")
237+ self._upgrade()
238 return head_rev
239
240
241
242=== added directory 'src/mailman/database/tests'
243=== added file 'src/mailman/database/tests/__init__.py'
244=== added file 'src/mailman/database/tests/test_factory.py'
245--- src/mailman/database/tests/test_factory.py 1970-01-01 00:00:00 +0000
246+++ src/mailman/database/tests/test_factory.py 2014-10-08 08:59:34 +0000
247@@ -0,0 +1,134 @@
248+# Copyright (C) 2013-2014 by the Free Software Foundation, Inc.
249+#
250+# This file is part of GNU Mailman.
251+#
252+# GNU Mailman is free software: you can redistribute it and/or modify it under
253+# the terms of the GNU General Public License as published by the Free
254+# Software Foundation, either version 3 of the License, or (at your option)
255+# any later version.
256+#
257+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
258+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
259+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
260+# more details.
261+#
262+# You should have received a copy of the GNU General Public License along with
263+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
264+
265+"""Test database schema migrations"""
266+
267+from __future__ import absolute_import, print_function, unicode_literals
268+
269+__metaclass__ = type
270+__all__ = [
271+ ]
272+
273+
274+import unittest
275+import types
276+
277+import alembic.command
278+from mock import Mock
279+from sqlalchemy import MetaData, Table, Column, Integer, Unicode
280+
281+from mailman.config import config
282+from mailman.testing.layers import ConfigLayer
283+from mailman.database.factory import SchemaManager, _reset
284+from mailman.database.sqlite import SQLiteDatabase
285+from mailman.database.alembic import alembic_cfg
286+from mailman.database.model import Model
287+
288+
289+
290
291+class TestSchemaManager(unittest.TestCase):
292+
293+ layer = ConfigLayer
294+
295+ def setUp(self):
296+ # Drop the existing database
297+ Model.metadata.drop_all(config.db.engine)
298+ md = MetaData()
299+ md.reflect(bind=config.db.engine)
300+ if "alembic_version" in md.tables:
301+ md.tables["alembic_version"].drop(config.db.engine)
302+ self.schema_mgr = SchemaManager(config.db)
303+
304+ def tearDown(self):
305+ if "version" in Model.metadata.tables:
306+ version = Model.metadata.tables["version"]
307+ version.drop(config.db.engine, checkfirst=True)
308+ Model.metadata.remove(version)
309+ # Restore a virgin DB
310+ Model.metadata.create_all(config.db.engine)
311+
312+
313+ def _table_exists(self, tablename):
314+ md = MetaData()
315+ md.reflect(bind=config.db.engine)
316+ return tablename in md.tables
317+
318+ def _create_storm_version_table(self, revision):
319+ version_table = Table("version", Model.metadata,
320+ Column("id", Integer, primary_key=True),
321+ Column("component", Unicode),
322+ Column("version", Unicode),
323+ )
324+ version_table.create(config.db.engine)
325+ config.db.store.execute(version_table.insert().values(
326+ component='schema', version=revision))
327+ config.db.commit()
328+
329+
330+ def test_current_db(self):
331+ """The database is already at the latest version"""
332+ alembic.command.stamp(alembic_cfg, "head")
333+ self.schema_mgr._create = Mock()
334+ self.schema_mgr._upgrade = Mock()
335+ self.schema_mgr.setup_db()
336+ self.assertFalse(self.schema_mgr._create.called)
337+ self.assertFalse(self.schema_mgr._upgrade.called)
338+
339+ def test_initial(self):
340+ """No existing database"""
341+ self.assertFalse(self._table_exists("mailinglist"))
342+ self.assertFalse(self._table_exists("alembic_version"))
343+ self.schema_mgr._upgrade = Mock()
344+ self.schema_mgr.setup_db()
345+ self.assertFalse(self.schema_mgr._upgrade.called)
346+ self.assertTrue(self._table_exists("mailinglist"))
347+ self.assertTrue(self._table_exists("alembic_version"))
348+
349+ def test_storm(self):
350+ """Existing Storm database"""
351+ Model.metadata.create_all(config.db.engine)
352+ self._create_storm_version_table(
353+ self.schema_mgr.LAST_STORM_SCHEMA_VERSION)
354+ self.schema_mgr._create = Mock()
355+ self.schema_mgr.setup_db()
356+ self.assertFalse(self.schema_mgr._create.called)
357+ self.assertTrue(self._table_exists("mailinglist")
358+ and self._table_exists("alembic_version")
359+ and not self._table_exists("version"))
360+
361+ def test_old_storm(self):
362+ """Existing Storm database in an old version"""
363+ Model.metadata.create_all(config.db.engine)
364+ self._create_storm_version_table("001")
365+ self.schema_mgr._create = Mock()
366+ self.assertRaises(RuntimeError, self.schema_mgr.setup_db)
367+ self.assertFalse(self.schema_mgr._create.called)
368+
369+ def test_old_db(self):
370+ """The database is in an old revision, must upgrade"""
371+ alembic.command.stamp(alembic_cfg, "head")
372+ md = MetaData()
373+ md.reflect(bind=config.db.engine)
374+ config.db.store.execute(md.tables["alembic_version"].delete())
375+ config.db.store.execute(md.tables["alembic_version"].insert().values(
376+ version_num="dummyrevision"))
377+ config.db.commit()
378+ self.schema_mgr._create = Mock()
379+ self.schema_mgr._upgrade = Mock()
380+ self.schema_mgr.setup_db()
381+ self.assertFalse(self.schema_mgr._create.called)
382+ self.assertTrue(self.schema_mgr._upgrade.called)
383
384=== modified file 'src/mailman/testing/layers.py'
385--- src/mailman/testing/layers.py 2014-09-27 22:16:22 +0000
386+++ src/mailman/testing/layers.py 2014-10-08 08:59:34 +0000
387@@ -47,13 +47,16 @@
388
389 from lazr.config import as_boolean
390 from pkg_resources import resource_string
391+from sqlalchemy import MetaData
392 from textwrap import dedent
393+from zope import event
394 from zope.component import getUtility
395
396 from mailman.config import config
397 from mailman.core import initialize
398 from mailman.core.initialize import INHIBIT_CONFIG_FILE
399 from mailman.core.logging import get_handler
400+from mailman.database.model import Model
401 from mailman.database.transaction import transaction
402 from mailman.interfaces.domain import IDomainManager
403 from mailman.testing.helpers import (
404@@ -98,7 +101,9 @@
405 # Set up the basic configuration stuff. Turn off path creation until
406 # we've pushed the testing config.
407 config.create_paths = False
408- initialize.initialize_1(INHIBIT_CONFIG_FILE)
409+ if not event.subscribers:
410+ # only if not yet initialized by another layer
411+ initialize.initialize_1(INHIBIT_CONFIG_FILE)
412 assert cls.var_dir is None, 'Layer already set up'
413 # Calculate a temporary VAR_DIR directory so that run-time artifacts
414 # of the tests won't tread on the installation's data. This also
415
416=== modified file 'src/mailman/testing/testing.cfg'
417--- src/mailman/testing/testing.cfg 2014-10-03 16:26:50 +0000
418+++ src/mailman/testing/testing.cfg 2014-10-08 08:59:34 +0000
419@@ -18,9 +18,9 @@
420 # A testing configuration.
421
422 # For testing against PostgreSQL.
423-# [database]
424-# class: mailman.database.postgresql.PostgreSQLDatabase
425-# url: postgres://maxking:maxking@localhost/mailman_test
426+[database]
427+class: mailman.database.postgresql.PostgreSQLDatabase
428+url: postgresql://maxking:maxking@localhost/mailman_test
429
430 [mailman]
431 site_owner: noreply@example.com

Subscribers

People subscribed via source and target branches