Skip to content
Snippets Groups Projects
Commit f469121e authored by Thomas Schneider's avatar Thomas Schneider
Browse files

Use Alembic for database migrations

parent 7cbd472f
Branches
Tags
No related merge requests found
...@@ -66,7 +66,13 @@ configuration: ...@@ -66,7 +66,13 @@ configuration:
Do replace ``SECRET_KEY`` with a unique, random value; and customise Do replace ``SECRET_KEY`` with a unique, random value; and customise
``SQLALCHEMY_DATABASE_URI`` according to your environment. See `SQLAlchemy ``SQLALCHEMY_DATABASE_URI`` according to your environment. See `SQLAlchemy
documentation`_ for details. You may need to create a database in your database documentation`_ for details. You may need to create a database in your database
server beforehand. Unfortunately, SQLite support is broken at the moment. server beforehand.
Run database migrations:
.. code:: shell-session
% pdm run migrate
To start the development server: To start the development server:
......
[alembic]
script_location = schilder2000/migrations
[post_write_hooks]
hooks = ruff ruff_format
ruff.type = exec
ruff.executable = ruff
ruff.options = check --fix REVISION_SCRIPT_FILENAME
ruff_format.type = exec
ruff_format.executable = ruff
ruff_format.options = format REVISION_SCRIPT_FILENAME
[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
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
groups = ["default", "auth-ldap", "auth-oauth", "auth-saml", "db-mysql", "db-postgres", "dev"] groups = ["default", "auth-ldap", "auth-oauth", "auth-saml", "db-mysql", "db-postgres", "dev"]
strategy = ["inherit_metadata"] strategy = ["inherit_metadata"]
lock_version = "4.5.0" lock_version = "4.5.0"
content_hash = "sha256:1af461aea0170421afe47c809cb315739413db60f16b0d60a7d38d91ddf457ca" content_hash = "sha256:0fe1b318475e65a03e58297779bfe8676d845960ce808dc040660179751c3dd3"
[[metadata.targets]] [[metadata.targets]]
requires_python = ">=3.12" requires_python = ">=3.12"
...@@ -84,6 +84,24 @@ files = [ ...@@ -84,6 +84,24 @@ files = [
{file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"},
] ]
[[package]]
name = "alembic"
version = "1.13.2"
requires_python = ">=3.8"
summary = "A database migration tool for SQLAlchemy."
groups = ["default"]
dependencies = [
"Mako",
"SQLAlchemy>=1.3.0",
"importlib-metadata; python_version < \"3.9\"",
"importlib-resources; python_version < \"3.9\"",
"typing-extensions>=4",
]
files = [
{file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"},
{file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"},
]
[[package]] [[package]]
name = "arrow" name = "arrow"
version = "1.3.0" version = "1.3.0"
...@@ -856,6 +874,20 @@ files = [ ...@@ -856,6 +874,20 @@ files = [
{file = "lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f"}, {file = "lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f"},
] ]
[[package]]
name = "mako"
version = "1.3.5"
requires_python = ">=3.8"
summary = "A super-fast templating language that borrows the best ideas from the existing templating languages."
groups = ["default"]
dependencies = [
"MarkupSafe>=0.9.2",
]
files = [
{file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"},
{file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"},
]
[[package]] [[package]]
name = "markdown-it-py" name = "markdown-it-py"
version = "3.0.0" version = "3.0.0"
......
...@@ -20,6 +20,7 @@ dependencies = [ ...@@ -20,6 +20,7 @@ dependencies = [
"python-webpack-boilerplate~=1.0", "python-webpack-boilerplate~=1.0",
"pyipp @ git+https://github.com/ctalkington/python-ipp", "pyipp @ git+https://github.com/ctalkington/python-ipp",
"Flask-Multipass~=0.5", "Flask-Multipass~=0.5",
"alembic~=1.13",
] ]
[project.optional-dependencies] [project.optional-dependencies]
...@@ -50,6 +51,7 @@ dev = [ ...@@ -50,6 +51,7 @@ dev = [
[tool.pdm.scripts] [tool.pdm.scripts]
serve = "flask -A schilder2000 run --debug" serve = "flask -A schilder2000 run --debug"
migrate = "flask -A schilder2000 alembic upgrade head"
[tool.pdm.build] [tool.pdm.build]
includes = [ includes = [
......
...@@ -5,6 +5,7 @@ from flask_sqlalchemy import SQLAlchemy ...@@ -5,6 +5,7 @@ from flask_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import CSRFProtect from flask_wtf.csrf import CSRFProtect
from webpack_boilerplate.config import setup_jinja2_ext from webpack_boilerplate.config import setup_jinja2_ext
from . import cli
from .helpers import Flask, identity_handler, require_login from .helpers import Flask, identity_handler, require_login
...@@ -17,14 +18,13 @@ def create_app(): ...@@ -17,14 +18,13 @@ def create_app():
app = Flask(__name__, instance_relative_config=True) app = Flask(__name__, instance_relative_config=True)
app.config.from_pyfile("config.py") app.config.from_pyfile("config.py")
app.cli.add_command(cli.alembic)
db.init_app(app) db.init_app(app)
# Ignore linter error unused export: we need to import the models to # Ignore linter error unused export: we need to import the models to
# register them with SQLAlchemy # register them with SQLAlchemy
from . import models # noqa: F401 from . import models # noqa: F401
with app.app_context():
db.create_all()
csrf.init_app(app) csrf.init_app(app)
if app.config["REQUIRE_LOGIN"]: if app.config["REQUIRE_LOGIN"]:
......
import sys
from os.path import basename
import click
from flask.cli import with_appcontext
from alembic import config
@click.command(
context_settings={"ignore_unknown_options": True, "help_option_names": []}
)
@click.argument("argv", nargs=-1)
@with_appcontext
def alembic(argv):
"""Wrap the alembic CLI tool."""
prog = " ".join([basename(sys.argv[0]), sys.argv[1]])
cli = config.CommandLine(prog=prog)
cli.main(argv=argv)
from logging.config import fileConfig
from alembic import context
from flask import current_app as app
try:
app.app_context()
except RuntimeError:
import schilder2000
app = schilder2000.create_app()
db = app.extensions["sqlalchemy"]
# Provide access to the values within alembic.ini.
config = context.config
# Sets up Python logging.
fileConfig(config.config_file_name)
# Sets up metadata for autogenerate support,
target_metadata = db.metadata
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 = app.config["SQLALCHEMY_DATABASE_URI"]
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
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.
"""
# If you use Alembic revision's --autogenerate flag this function will
# prevent Alembic from creating an empty migration file if nothing changed.
# Source: https://alembic.sqlalchemy.org/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if config.cmd_opts.autogenerate:
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
with app.app_context():
connectable = db.engine
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
"""${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"}
"""Initial revision
Revision ID: 7a38fc23dda5
Revises:
Create Date: 2024-09-16 15:37:03.537826
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "7a38fc23dda5"
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
op.create_table(
"schild",
sa.Column("ident", sa.Uuid(), nullable=False),
sa.Column("title", sa.String(length=31), nullable=False),
sa.Column("text", sa.Text(), nullable=False),
sa.Column("image", sa.String(length=255), nullable=True),
sa.Column("template", sa.String(length=255), nullable=False),
sa.PrimaryKeyConstraint("ident"),
)
def downgrade():
op.drop_table("schild")
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment