Browse Source

Chapter 5: Database migrations with Flask-Migrate (5d)

T. Meissner 2 years ago
parent
commit
7783c73cbd
5 changed files with 159 additions and 0 deletions
  1. 2
    0
      hello.py
  2. 1
    0
      migrations/README
  3. 45
    0
      migrations/alembic.ini
  4. 87
    0
      migrations/env.py
  5. 24
    0
      migrations/script.py.mako

+ 2
- 0
hello.py View File

@@ -6,6 +6,7 @@ from flask_wtf import FlaskForm
6 6
 from wtforms import StringField, SubmitField
7 7
 from wtforms.validators import DataRequired
8 8
 from flask_sqlalchemy import SQLAlchemy
9
+from flask_migrate import Migrate
9 10
 
10 11
 
11 12
 basedir = os.path.abspath(os.path.dirname(__file__))
@@ -19,6 +20,7 @@ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
19 20
 bootstrap = Bootstrap(app)
20 21
 moment = Moment(app)
21 22
 db = SQLAlchemy(app)
23
+migrate = Migrate(app, db)
22 24
 
23 25
 
24 26
 class Role(db.Model):

+ 1
- 0
migrations/README View File

@@ -0,0 +1 @@
1
+Generic single-database configuration.

+ 45
- 0
migrations/alembic.ini View File

@@ -0,0 +1,45 @@
1
+# A generic, single database configuration.
2
+
3
+[alembic]
4
+# template used to generate migration files
5
+# file_template = %%(rev)s_%%(slug)s
6
+
7
+# set to 'true' to run the environment during
8
+# the 'revision' command, regardless of autogenerate
9
+# revision_environment = false
10
+
11
+
12
+# Logging configuration
13
+[loggers]
14
+keys = root,sqlalchemy,alembic
15
+
16
+[handlers]
17
+keys = console
18
+
19
+[formatters]
20
+keys = generic
21
+
22
+[logger_root]
23
+level = WARN
24
+handlers = console
25
+qualname =
26
+
27
+[logger_sqlalchemy]
28
+level = WARN
29
+handlers =
30
+qualname = sqlalchemy.engine
31
+
32
+[logger_alembic]
33
+level = INFO
34
+handlers =
35
+qualname = alembic
36
+
37
+[handler_console]
38
+class = StreamHandler
39
+args = (sys.stderr,)
40
+level = NOTSET
41
+formatter = generic
42
+
43
+[formatter_generic]
44
+format = %(levelname)-5.5s [%(name)s] %(message)s
45
+datefmt = %H:%M:%S

+ 87
- 0
migrations/env.py View File

@@ -0,0 +1,87 @@
1
+from __future__ import with_statement
2
+from alembic import context
3
+from sqlalchemy import engine_from_config, pool
4
+from logging.config import fileConfig
5
+import logging
6
+
7
+# this is the Alembic Config object, which provides
8
+# access to the values within the .ini file in use.
9
+config = context.config
10
+
11
+# Interpret the config file for Python logging.
12
+# This line sets up loggers basically.
13
+fileConfig(config.config_file_name)
14
+logger = logging.getLogger('alembic.env')
15
+
16
+# add your model's MetaData object here
17
+# for 'autogenerate' support
18
+# from myapp import mymodel
19
+# target_metadata = mymodel.Base.metadata
20
+from flask import current_app
21
+config.set_main_option('sqlalchemy.url',
22
+                       current_app.config.get('SQLALCHEMY_DATABASE_URI'))
23
+target_metadata = current_app.extensions['migrate'].db.metadata
24
+
25
+# other values from the config, defined by the needs of env.py,
26
+# can be acquired:
27
+# my_important_option = config.get_main_option("my_important_option")
28
+# ... etc.
29
+
30
+
31
+def run_migrations_offline():
32
+    """Run migrations in 'offline' mode.
33
+
34
+    This configures the context with just a URL
35
+    and not an Engine, though an Engine is acceptable
36
+    here as well.  By skipping the Engine creation
37
+    we don't even need a DBAPI to be available.
38
+
39
+    Calls to context.execute() here emit the given string to the
40
+    script output.
41
+
42
+    """
43
+    url = config.get_main_option("sqlalchemy.url")
44
+    context.configure(url=url)
45
+
46
+    with context.begin_transaction():
47
+        context.run_migrations()
48
+
49
+
50
+def run_migrations_online():
51
+    """Run migrations in 'online' mode.
52
+
53
+    In this scenario we need to create an Engine
54
+    and associate a connection with the context.
55
+
56
+    """
57
+
58
+    # this callback is used to prevent an auto-migration from being generated
59
+    # when there are no changes to the schema
60
+    # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
61
+    def process_revision_directives(context, revision, directives):
62
+        if getattr(config.cmd_opts, 'autogenerate', False):
63
+            script = directives[0]
64
+            if script.upgrade_ops.is_empty():
65
+                directives[:] = []
66
+                logger.info('No changes in schema detected.')
67
+
68
+    engine = engine_from_config(config.get_section(config.config_ini_section),
69
+                                prefix='sqlalchemy.',
70
+                                poolclass=pool.NullPool)
71
+
72
+    connection = engine.connect()
73
+    context.configure(connection=connection,
74
+                      target_metadata=target_metadata,
75
+                      process_revision_directives=process_revision_directives,
76
+                      **current_app.extensions['migrate'].configure_args)
77
+
78
+    try:
79
+        with context.begin_transaction():
80
+            context.run_migrations()
81
+    finally:
82
+        connection.close()
83
+
84
+if context.is_offline_mode():
85
+    run_migrations_offline()
86
+else:
87
+    run_migrations_online()

+ 24
- 0
migrations/script.py.mako View File

@@ -0,0 +1,24 @@
1
+"""${message}
2
+
3
+Revision ID: ${up_revision}
4
+Revises: ${down_revision | comma,n}
5
+Create Date: ${create_date}
6
+
7
+"""
8
+from alembic import op
9
+import sqlalchemy as sa
10
+${imports if imports else ""}
11
+
12
+# revision identifiers, used by Alembic.
13
+revision = ${repr(up_revision)}
14
+down_revision = ${repr(down_revision)}
15
+branch_labels = ${repr(branch_labels)}
16
+depends_on = ${repr(depends_on)}
17
+
18
+
19
+def upgrade():
20
+    ${upgrades if upgrades else "pass"}
21
+
22
+
23
+def downgrade():
24
+    ${downgrades if downgrades else "pass"}