From 63584ccba6c12cebe806212e7e2a5032697d6c5c Mon Sep 17 00:00:00 2001 From: Mark Hobson Date: Fri, 20 Oct 2023 11:51:00 +0100 Subject: [PATCH] GH-4: Introduce Alembic for database migrations --- alembic.ini | 2 ++ pyproject.toml | 1 + schemes/__init__.py | 17 +++++++---- schemes/migrations/env.py | 7 +++++ schemes/migrations/script.py.mako | 24 ++++++++++++++++ .../bfc3cbc1bb13_create_users_table.py | 28 +++++++++++++++++++ 6 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 alembic.ini create mode 100644 schemes/migrations/env.py create mode 100644 schemes/migrations/script.py.mako create mode 100644 schemes/migrations/versions/bfc3cbc1bb13_create_users_table.py diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 00000000..d58f776a --- /dev/null +++ b/alembic.ini @@ -0,0 +1,2 @@ +[alembic] +script_location = schemes:migrations diff --git a/pyproject.toml b/pyproject.toml index f5a3ce8a..746bf1f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,6 +4,7 @@ version = "1.0.0" description = "ATE Schemes App." requires-python = ">=3.11" dependencies = [ + "alembic~=1.12.0", "authlib~=1.2.0", "flask~=2.3.0", "govuk-frontend-jinja~=2.7.0", diff --git a/schemes/__init__.py b/schemes/__init__.py index e30660ba..c21bacec 100644 --- a/schemes/__init__.py +++ b/schemes/__init__.py @@ -1,17 +1,19 @@ import os from typing import Any, Mapping +import alembic.config import inject +from alembic import command from authlib.integrations.flask_client import OAuth from authlib.oauth2.rfc7523 import PrivateKeyJWT from flask import Config, Flask, Response, render_template, request, url_for from inject import Binder from jinja2 import ChoiceLoader, FileSystemLoader, PackageLoader, PrefixLoader -from sqlalchemy import Engine, MetaData, create_engine +from sqlalchemy import Engine, create_engine from schemes import api, auth, home, start from schemes.config import DevConfig -from schemes.users import DatabaseUserRepository, User, UserRepository, add_tables +from schemes.users import DatabaseUserRepository, User, UserRepository def create_app(test_config: Mapping[str, Any] | None = None) -> Flask: @@ -107,11 +109,14 @@ def _configure_oidc(app: Flask) -> None: def _create_database() -> None: - metadata = MetaData() - add_tables(metadata) - engine = inject.instance(Engine) - metadata.create_all(engine) + + alembic_config = alembic.config.Config() + alembic_config.set_main_option("script_location", "schemes:migrations") + + with engine.connect() as connection: + alembic_config.attributes["connection"] = connection + command.upgrade(alembic_config, "head") def _configure_users() -> None: diff --git a/schemes/migrations/env.py b/schemes/migrations/env.py new file mode 100644 index 00000000..171a8e82 --- /dev/null +++ b/schemes/migrations/env.py @@ -0,0 +1,7 @@ +from alembic import context + +connection = context.config.attributes["connection"] +context.configure(connection=connection, target_metadata=None) + +with context.begin_transaction(): + context.run_migrations() diff --git a/schemes/migrations/script.py.mako b/schemes/migrations/script.py.mako new file mode 100644 index 00000000..765291af --- /dev/null +++ b/schemes/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +revision: str = ${repr(up_revision).replace("'", '"')} +down_revision: Union[str, None] = ${repr(down_revision).replace("'", '"')} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels).replace("'", '"')} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on).replace("'", '"')} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/schemes/migrations/versions/bfc3cbc1bb13_create_users_table.py b/schemes/migrations/versions/bfc3cbc1bb13_create_users_table.py new file mode 100644 index 00000000..496148e5 --- /dev/null +++ b/schemes/migrations/versions/bfc3cbc1bb13_create_users_table.py @@ -0,0 +1,28 @@ +"""Create users table + +Revision ID: bfc3cbc1bb13 +Revises: +Create Date: 2023-10-20 10:15:51.912421 + +""" +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +revision: str = "bfc3cbc1bb13" +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.create_table( + "users", + sa.Column("id", sa.Integer, primary_key=True), + sa.Column("email", sa.String(256), nullable=False, unique=True), + ) + + +def downgrade() -> None: + op.drop_table("users")