Flask-Migrate init

Flask-Migrate init
This commit is contained in:
SamR1 2018-01-20 19:12:34 +01:00
parent dc9183be80
commit 90201d4dd1
11 changed files with 253 additions and 18 deletions

View File

@ -41,7 +41,8 @@ before_script:
script: script:
- psql -c 'create database mpwo_test;' -U postgres - psql -c 'create database mpwo_test;' -U postgres
- docker-compose -f docker-compose-ci.yml run mpwo-api flask init_db - docker-compose -f docker-compose-ci.yml run mpwo-api flask db migrate
- docker-compose -f docker-compose-ci.yml run mpwo-api flask db upgrade
- sh test.sh - sh test.sh
after_script: after_script:

View File

@ -7,7 +7,10 @@ make-p:
set -m; (for p in $(P); do ($(MAKE) $$p || kill 0)& done; wait) set -m; (for p in $(P); do ($(MAKE) $$p || kill 0)& done; wait)
init-db: init-db:
$(FLASK) init_db $(FLASK) drop_db
$(FLASK) db migrate
$(FLASK) db upgrade
$(FLASK) init_data
install: install-client install-python install: install-client install-python
@ -26,6 +29,9 @@ lint-python:
lint-react: lint-react:
$(NPM) lint $(NPM) lint
migrate-db:
$(FLASK) db migrate
serve-python: serve-python:
$(FLASK) run --with-threads -h $(HOST) -p $(API_PORT) $(FLASK) run --with-threads -h $(HOST) -p $(API_PORT)
@ -40,3 +46,6 @@ test-e2e:
test-python: test-python:
$(FLASK) test_local $(FLASK) test_local
upgrade-db:
$(FLASK) db upgrade

1
migrations/README Executable file
View File

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

45
migrations/alembic.ini Normal file
View File

@ -0,0 +1,45 @@
# A generic, single database configuration.
[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

87
migrations/env.py Executable file
View File

@ -0,0 +1,87 @@
from __future__ import with_statement
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig
import logging
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from flask import current_app
config.set_main_option('sqlalchemy.url',
current_app.config.get('SQLALCHEMY_DATABASE_URI'))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
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.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(url=url)
with context.begin_transaction():
context.run_migrations()
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.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
engine = engine_from_config(config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool)
connection = engine.connect()
context.configure(connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
**current_app.extensions['migrate'].configure_args)
try:
with context.begin_transaction():
context.run_migrations()
finally:
connection.close()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

24
migrations/script.py.mako Executable file
View File

@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@ -0,0 +1,44 @@
"""empty message
Revision ID: 9741fc7834da
Revises:
Create Date: 2018-01-20 18:59:49.200032
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '9741fc7834da'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sa.String(length=20), nullable=False),
sa.Column('email', sa.String(length=120), nullable=False),
sa.Column('password', sa.String(length=255), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('admin', sa.Boolean(), nullable=False),
sa.Column('first_name', sa.String(length=80), nullable=True),
sa.Column('last_name', sa.String(length=80), nullable=True),
sa.Column('birth_date', sa.DateTime(), nullable=True),
sa.Column('location', sa.String(length=80), nullable=True),
sa.Column('bio', sa.String(length=200), nullable=True),
sa.Column('picture', sa.String(length=255), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email'),
sa.UniqueConstraint('username')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('users')
# ### end Alembic commands ###

View File

@ -2,11 +2,13 @@ import logging
from flask import Flask from flask import Flask
from flask_bcrypt import Bcrypt from flask_bcrypt import Bcrypt
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy() db = SQLAlchemy()
bcrypt = Bcrypt() bcrypt = Bcrypt()
migrate = Migrate()
appLog = logging.getLogger('mpwo_api') appLog = logging.getLogger('mpwo_api')
# instantiate the app # instantiate the app
@ -19,6 +21,7 @@ with app.app_context():
# set up extensions # set up extensions
db.init_app(app) db.init_app(app)
bcrypt.init_app(app) bcrypt.init_app(app)
migrate.init_app(app, db)
from .users.auth import auth_blueprint # noqa from .users.auth import auth_blueprint # noqa
from .users.users import users_blueprint # noqa from .users.users import users_blueprint # noqa

View File

@ -6,6 +6,7 @@ flake8-isort==2.2.2
flake8-polyfill==1.0.1 flake8-polyfill==1.0.1
Flask==0.12.2 Flask==0.12.2
Flask-Bcrypt==0.7.1 Flask-Bcrypt==0.7.1
Flask-Migrate==2.1.1
Flask-SQLAlchemy==2.3.2 Flask-SQLAlchemy==2.3.2
Flask-Testing==0.6.2 Flask-Testing==0.6.2
isort==4.2.15 isort==4.2.15

View File

@ -5,10 +5,15 @@ from mpwo_api.users.models import User
@app.cli.command() @app.cli.command()
def init_db(): def drop_db():
"""Init the database."""
db.drop_all() db.drop_all()
db.create_all() db.session.commit()
print('Database dropped.')
@app.cli.command()
def init_data():
"""Init the database."""
admin = User( admin = User(
username='admin', username='admin',
email='admin@example.com', email='admin@example.com',
@ -16,7 +21,7 @@ def init_db():
admin.admin = True admin.admin = True
db.session.add(admin) db.session.add(admin)
db.session.commit() db.session.commit()
print('Database initialization done.') print('Admin created.')
def run_test(test_path='mpwo_api/tests'): def run_test(test_path='mpwo_api/tests'):

View File

@ -1495,7 +1495,7 @@ chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
strip-ansi "^3.0.0" strip-ansi "^3.0.0"
supports-color "^2.0.0" supports-color "^2.0.0"
chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.0: chalk@2.3.0, chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.0:
version "2.3.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba"
dependencies: dependencies:
@ -1684,7 +1684,7 @@ concat-map@0.0.1:
version "0.0.1" version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
concat-stream@^1.6.0: concat-stream@^1.4.7, concat-stream@^1.6.0:
version "1.6.0" version "1.6.0"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
dependencies: dependencies:
@ -3285,10 +3285,6 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3" inherits "^2.0.3"
minimalistic-assert "^1.0.0" minimalistic-assert "^1.0.0"
hashids@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/hashids/-/hashids-1.1.4.tgz#e4ff92ad66b684a3bd6aace7c17d66618ee5fa21"
hawk@3.1.3, hawk@~3.1.3: hawk@3.1.3, hawk@~3.1.3:
version "3.1.3" version "3.1.3"
resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
@ -4921,6 +4917,10 @@ os-locale@^2.0.0:
lcid "^1.0.0" lcid "^1.0.0"
mem "^1.1.0" mem "^1.1.0"
os-shim@^0.1.2:
version "0.1.3"
resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917"
os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
@ -5424,6 +5424,14 @@ postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.13:
source-map "^0.6.1" source-map "^0.6.1"
supports-color "^5.1.0" supports-color "^5.1.0"
pre-commit@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/pre-commit/-/pre-commit-1.2.2.tgz#dbcee0ee9de7235e57f79c56d7ce94641a69eec6"
dependencies:
cross-spawn "^5.0.1"
spawn-sync "^1.0.15"
which "1.2.x"
prelude-ls@~1.1.2: prelude-ls@~1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@ -5673,12 +5681,6 @@ react-helmet@^5.2.0:
prop-types "^15.5.4" prop-types "^15.5.4"
react-side-effect "^1.1.0" react-side-effect "^1.1.0"
react-key-index@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/react-key-index/-/react-key-index-0.1.1.tgz#8319e4f0961ae44a8eb0a4f76e4c210ef6d39cda"
dependencies:
hashids "^1.1.1"
react-redux@^5.0.6: react-redux@^5.0.6:
version "5.0.6" version "5.0.6"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.6.tgz#23ed3a4f986359d68b5212eaaa681e60d6574946" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.6.tgz#23ed3a4f986359d68b5212eaaa681e60d6574946"
@ -6414,6 +6416,13 @@ source-map@^0.6.1, source-map@~0.6.1:
version "0.6.1" version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
spawn-sync@^1.0.15:
version "1.0.15"
resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476"
dependencies:
concat-stream "^1.4.7"
os-shim "^0.1.2"
spdx-correct@~1.0.0: spdx-correct@~1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
@ -7362,6 +7371,12 @@ which-promise@^1.0.0:
pinkie-promise "^1.0.0" pinkie-promise "^1.0.0"
which "^1.1.2" which "^1.1.2"
which@1.2.x:
version "1.2.14"
resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5"
dependencies:
isexe "^2.0.0"
which@^1.1.2, which@^1.2.12, which@^1.2.14, which@^1.2.9: which@^1.1.2, which@^1.2.12, which@^1.2.14, which@^1.2.9:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"