Merge lp:~abompard/mailman/sqlalchemy into lp:~raj-abhilash1/mailman/sqlalchemy
- sqlalchemy
- Merge into 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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Abhilash Raj | Pending | ||
Review via email: mp+236884@code.launchpad.net |
Commit message
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/
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 |