From a537bae62afa7c8d687694c859c5916374b794d5 Mon Sep 17 00:00:00 2001 From: F-G Fernandez <26927750+frgfm@users.noreply.github.com> Date: Sat, 30 Mar 2024 02:38:19 +0100 Subject: [PATCH 01/11] fix(migrations): fix migrations scripts --- src/alembic/script.py.mako | 26 ------ .../versions/2023-11-07-15-35_eb1a6567456c.py | 57 ------------ .../versions/2024-02-23-11-56_4c08af871a9e.py | 90 ------------------- .../versions/2024-02-27-10-09_d7067d13464c.py | 74 --------------- src/{alembic/README.md => migrations/README} | 4 +- src/{alembic => migrations}/env.py | 36 ++++---- src/migrations/script.py.mako | 27 ++++++ 7 files changed, 47 insertions(+), 267 deletions(-) delete mode 100755 src/alembic/script.py.mako delete mode 100755 src/alembic/versions/2023-11-07-15-35_eb1a6567456c.py delete mode 100755 src/alembic/versions/2024-02-23-11-56_4c08af871a9e.py delete mode 100755 src/alembic/versions/2024-02-27-10-09_d7067d13464c.py rename src/{alembic/README.md => migrations/README} (86%) rename src/{alembic => migrations}/env.py (74%) create mode 100755 src/migrations/script.py.mako diff --git a/src/alembic/script.py.mako b/src/alembic/script.py.mako deleted file mode 100755 index c963b02..0000000 --- a/src/alembic/script.py.mako +++ /dev/null @@ -1,26 +0,0 @@ -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision | comma,n} -Create Date: ${create_date} - -""" -from alembic import op -import sqlalchemy as sa -# import sqlalchemy_utils -# import sqlmodel # added -${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"} diff --git a/src/alembic/versions/2023-11-07-15-35_eb1a6567456c.py b/src/alembic/versions/2023-11-07-15-35_eb1a6567456c.py deleted file mode 100755 index c9a449e..0000000 --- a/src/alembic/versions/2023-11-07-15-35_eb1a6567456c.py +++ /dev/null @@ -1,57 +0,0 @@ -"""empty message - -Revision ID: eb1a6567456c -Revises: -Create Date: 2023-11-07 15:35:08.955263 - -""" - -import sqlalchemy as sa -from alembic import op -from sqlalchemy.dialects import postgresql -from sqlalchemy.sql import text - -# revision identifiers, used by Alembic. -revision = "eb1a6567456c" -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - # Add the is_active column default to True - op.add_column("repository", sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.false())) - # Updates using value of removed_at - bind = op.get_bind() - session = sa.orm.Session(bind=bind) - try: - # Update is_active to True where removed_at is NULL - session.execute(text("UPDATE repository SET is_active = (removed_at IS NULL)")) - session.commit() - finally: - session.close() - # Remove the legacy column - op.drop_column("repository", "removed_at") - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column("repository", sa.Column("removed_at", postgresql.TIMESTAMP(), autoincrement=False, nullable=True)) - - # Use a session to update the removed_at column based on is_active being False - bind = op.get_bind() - session = sa.orm.Session(bind=bind) - try: - # Set removed_at to a specific time where is_active is False - # You may choose to leave it as NULL or set it to a specific timestamp - session.execute( - text("UPDATE repository SET removed_at = CASE WHEN is_active = false THEN CURRENT_TIMESTAMP ELSE NULL END") - ) - session.commit() - finally: - session.close() - # Remove the column - op.drop_column("repository", "is_active") - # ### end Alembic commands ### diff --git a/src/alembic/versions/2024-02-23-11-56_4c08af871a9e.py b/src/alembic/versions/2024-02-23-11-56_4c08af871a9e.py deleted file mode 100755 index 438a0a9..0000000 --- a/src/alembic/versions/2024-02-23-11-56_4c08af871a9e.py +++ /dev/null @@ -1,90 +0,0 @@ -"""empty message - -Revision ID: 4c08af871a9e -Revises: eb1a6567456c -Create Date: 2024-02-23 11:56:53.633856 - -""" - -import sqlalchemy as sa -from alembic import op -from sqlalchemy.sql import func, text - -# import sqlalchemy_utils -# import sqlmodel # added - -# revision identifiers, used by Alembic. -revision = "4c08af871a9e" -down_revision = "eb1a6567456c" -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - # User table modifications - op.alter_column("user", "id", new_column_name="provider_user_id", existing_type=sa.Integer(), nullable=True) - op.add_column("user", sa.Column("id", sa.Integer(), nullable=False, primary_key=True, autoincrement=True)) - op.alter_column("user", "login", nullable=True) - op.alter_column("user", "hashed_password", nullable=True) - op.add_column("user", sa.Column("created_at", sa.DateTime(), server_default=func.now(), nullable=False)) - - # Repository table modifications - op.alter_column("repository", "id", new_column_name="provider_repo_id", existing_type=sa.Integer(), nullable=True) - op.add_column("repository", sa.Column("id", sa.Integer(), nullable=False, primary_key=True, autoincrement=True)) - op.alter_column("repository", "full_name", new_column_name="name") - op.alter_column("repository", "installed_at", new_column_name="created_at") - op.drop_column("repository", "is_active") - op.drop_column("repository", "installed_by") - - # Guideline table modifications - op.alter_column("guideline", "details", new_column_name="content") - op.add_column("guideline", sa.Column("creator_id", sa.Integer(), nullable=True)) - # Enforce the foreign key after initializing it - op.execute( - text( - """ - DO $$ - DECLARE - first_user_id INT; - BEGIN - SELECT id INTO first_user_id FROM "user" LIMIT 1; - - IF FOUND THEN - UPDATE guideline SET creator_id = first_user_id; - ALTER TABLE guideline ALTER COLUMN creator_id SET NOT NULL; - ALTER TABLE guideline ADD CONSTRAINT fk_guideline_creator_id FOREIGN KEY (creator_id) REFERENCES "user" (id); - END IF; - END $$; - """ - ) - ) - op.drop_column("guideline", "repo_id") - op.drop_column("guideline", "title") - op.drop_column("guideline", "order") - # ### end Alembic commands ### - - -def downgrade(): - # Guideline table modifications - op.alter_column("guideline", "content", new_column_name="details") - op.add_column("guideline", sa.Column("repo_id", sa.Integer(), nullable=False)) - op.add_column("guideline", sa.Column("title", sa.String(length=100), nullable=False)) - op.add_column("guideline", sa.Column("order", sa.Integer(), nullable=False)) - op.drop_column("guideline", "creator_id") - - # Repository table modifications - op.drop_column("repository", "id") # As above for PK handling - op.alter_column("repository", "provider_repo_id", new_column_name="id", existing_type=sa.Integer(), nullable=False) - op.alter_column("repository", "name", new_column_name="full_name") - op.alter_column("repository", "created_at", new_column_name="installed_at") - op.add_column("repository", sa.Column("is_active", sa.Boolean(), nullable=False)) - op.add_column("repository", sa.Column("installed_by", sa.Integer(), nullable=False)) - # User table modifications - op.execute(text('DELETE FROM "user" WHERE provider_user_id IS NULL')) - op.drop_column("user", "created_at") - op.alter_column("user", "login", nullable=False) - op.alter_column("user", "hashed_password", nullable=False) - op.drop_column("user", "id") # This will remove the new PK; you might need to handle PK sequence reset manually - op.alter_column("user", "provider_user_id", new_column_name="id", existing_type=sa.Integer(), nullable=False) - # ### end Alembic commands ### diff --git a/src/alembic/versions/2024-02-27-10-09_d7067d13464c.py b/src/alembic/versions/2024-02-27-10-09_d7067d13464c.py deleted file mode 100755 index 9ed2f58..0000000 --- a/src/alembic/versions/2024-02-27-10-09_d7067d13464c.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Set enum types correctly - -Revision ID: d7067d13464c -Revises: 4c08af871a9e -Create Date: 2024-02-27 10:09:10.976034 - -""" - -import sqlalchemy as sa -from alembic import op -from sqlalchemy.engine.reflection import Inspector -from sqlalchemy.sql import text - -# revision identifiers, used by Alembic. -revision = "d7067d13464c" -down_revision = "4c08af871a9e" -branch_labels = None -depends_on = None - -enum_type = sa.Enum("ADMIN", "USER", name="userscope") - - -def get_conn(): - return op.get_bind() - - -def get_inspector(): - return Inspector.from_engine(get_conn()) - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - inspector = get_inspector() - table_constraints = inspector.get_unique_constraints("user") - constraint_names = {constraint["name"] for constraint in table_constraints} - if "uq_user_id" not in constraint_names: - op.create_unique_constraint("uq_user_id", "user", ["id"]) - op.alter_column("guideline", "creator_id", existing_type=sa.INTEGER(), nullable=False) - table_constraints = inspector.get_foreign_keys("guideline") - constraint_names = {constraint["name"] for constraint in table_constraints} - if "fk_guideline_creator_id" not in constraint_names: - op.create_foreign_key("fk_guideline_creator_id", "guideline", "user", ["creator_id"], ["id"]) - enum_type.create(op.get_bind(), checkfirst=True) - op.execute( - text(""" - ALTER TABLE "user" - ALTER COLUMN scope TYPE userscope - USING (CASE scope WHEN 'admin' THEN 'ADMIN'::userscope WHEN 'user' THEN 'USER'::userscope END) - """) - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - inspector = get_inspector() - op.execute( - text(""" - ALTER TABLE "user" - ALTER COLUMN scope TYPE VARCHAR - USING (CASE WHEN scope = 'ADMIN' THEN 'admin'::VARCHAR WHEN scope = 'USER' THEN 'user'::VARCHAR END) - """) - ) - enum_type.drop(get_conn(), checkfirst=True) - table_constraints = inspector.get_foreign_keys("guideline") - constraint_names = {constraint["name"] for constraint in table_constraints} - if "fk_guideline_creator_id" in constraint_names: - op.drop_constraint("fk_guideline_creator_id", "guideline", type_="foreignkey") - op.alter_column("guideline", "creator_id", existing_type=sa.INTEGER(), nullable=True) - table_constraints = inspector.get_unique_constraints("user") - constraint_names = {constraint["name"] for constraint in table_constraints} - if "uq_user_id" in constraint_names: - op.drop_constraint("uq_user_id", "user", type_="unique") - # ### end Alembic commands ### diff --git a/src/alembic/README.md b/src/migrations/README similarity index 86% rename from src/alembic/README.md rename to src/migrations/README index bb2bebe..9ea191d 100755 --- a/src/alembic/README.md +++ b/src/migrations/README @@ -3,7 +3,7 @@ All commands should be run after spinning the containers using ```shell -make run +docker compose up -d db backend ``` ## Create a revision @@ -11,7 +11,7 @@ make run Alembic allows you to record migration operation using DB operations. Let's create a revision file: ```shell -docker compose exec backend alembic revision --autogenerate +docker compose exec -T backend alembic revision --autogenerate ``` Once generated, you should edit the revision file in src/alembic/versions that was created. See example [here](https://github.com/jonra1993/fastapi-alembic-sqlmodel-async/blob/main/fastapi-alembic-sqlmodel-async/alembic/versions/2022-09-25-19-46_60d49bf413b8.py). diff --git a/src/alembic/env.py b/src/migrations/env.py similarity index 74% rename from src/alembic/env.py rename to src/migrations/env.py index fcccfbd..22a2541 100755 --- a/src/alembic/env.py +++ b/src/migrations/env.py @@ -3,22 +3,15 @@ # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. - -from __future__ import with_statement - import asyncio -import pathlib -import sys from logging.config import fileConfig from alembic import context -from sqlalchemy.ext.asyncio import AsyncEngine -from sqlmodel import SQLModel, create_engine +from sqlalchemy.engine import Connection +from sqlalchemy.ext.asyncio import create_async_engine +from sqlmodel import SQLModel from app.core.config import settings - -sys.path.append(str(pathlib.Path(__file__).resolve().parents[1])) - from app.models import * # this is the Alembic Config object, which provides @@ -27,29 +20,36 @@ # Interpret the config file for Python logging. # This line sets up loggers basically. -fileConfig(config.config_file_name) - +if config.config_file_name is not None: + fileConfig(config.config_file_name) +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata target_metadata = SQLModel.metadata +db_url = settings.POSTGRES_URL # 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(): +def run_migrations_offline() -> None: """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 = settings.POSTGRES_URL context.configure( - url=url, + url=settings.POSTGRES_URL, target_metadata=target_metadata, literal_binds=True, compare_type=True, @@ -60,19 +60,19 @@ def run_migrations_offline(): context.run_migrations() -def do_run_migrations(connection): +def do_run_migrations(connection: Connection) -> None: context.configure(connection=connection, target_metadata=target_metadata) with context.begin_transaction(): context.run_migrations() -async def run_migrations_online(): +async def run_migrations_online() -> None: """Run migrations in 'online' mode. In this scenario we need to create an Engine and associate a connection with the context. """ - connectable = AsyncEngine(create_engine(settings.POSTGRES_URL, echo=True, future=True)) + connectable = create_async_engine(settings.POSTGRES_URL, echo=True, future=True) async with connectable.connect() as connection: await connection.run_sync(do_run_migrations) diff --git a/src/migrations/script.py.mako b/src/migrations/script.py.mako new file mode 100755 index 0000000..6ce3351 --- /dev/null +++ b/src/migrations/script.py.mako @@ -0,0 +1,27 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import sqlmodel +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} From a38716d5f237b7dddc6de7f7e9b5dc580922a021 Mon Sep 17 00:00:00 2001 From: F-G Fernandez <26927750+frgfm@users.noreply.github.com> Date: Sat, 30 Mar 2024 02:38:55 +0100 Subject: [PATCH 02/11] ci(github): update labeler --- .github/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index 5f6419f..723a3ef 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -78,7 +78,7 @@ - changed-files: - any-glob-to-any-file: - src/alembic.ini - - src/alembic/* + - src/migrations/* 'topic: ci': - changed-files: From 5ec954729112a9cbfd4db45a113c14a3dd0ceb5d Mon Sep 17 00:00:00 2001 From: F-G Fernandez <26927750+frgfm@users.noreply.github.com> Date: Sat, 30 Mar 2024 02:39:14 +0100 Subject: [PATCH 03/11] style(ruff): update config --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d691678..1c82d8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,8 +133,8 @@ known-third-party = ["fastapi"] ".github/**.py" = ["D", "T201", "ANN"] "client/docs/**.py" = ["E402"] "src/tests/**.py" = ["D103", "CPY001", "S101", "T201", "ANN001", "ANN201", "ARG001"] -"src/alembic/versions/**.py" = ["CPY001"] -"src/alembic/**.py" = ["ANN"] +"src/migrations/versions/**.py" = ["CPY001"] +"src/migrations/**.py" = ["ANN"] "client/tests/**.py" = ["D103", "CPY001", "S101"] "src/app/main.py" = ["ANN"] "src/app/schemas/**.py" = ["A"] From 298b1864acf67c6f681242e7036d362d09c38d3b Mon Sep 17 00:00:00 2001 From: F-G Fernandez <26927750+frgfm@users.noreply.github.com> Date: Sat, 30 Mar 2024 02:39:28 +0100 Subject: [PATCH 04/11] refactor(migrations): update alembic ini --- src/alembic.ini | 64 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 11 deletions(-) mode change 100644 => 100755 src/alembic.ini diff --git a/src/alembic.ini b/src/alembic.ini old mode 100644 new mode 100755 index 9eeab9b..c7ab82e --- a/src/alembic.ini +++ b/src/alembic.ini @@ -2,21 +2,27 @@ [alembic] # path to migration scripts -script_location = alembic +script_location = migrations -# template used to generate migration files -# file_template = %%(rev)s_%%(slug)s -file_template = %%(year)d-%%(month).2d-%%(day).2d-%%(hour).2d-%%(minute).2d_%%(rev)s +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s -# timezone to use when rendering the date -# within the migration file as well as the filename. +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python-dateutil library that can be +# installed by adding `alembic[tz]` to the pip requirements # string value is passed to dateutil.tz.gettz() # leave blank for localtime # timezone = # max length of characters to apply to the # "slug" field -#truncate_slug_length = 40 +# truncate_slug_length = 40 # set to 'true' to run the environment during # the 'revision' command, regardless of autogenerate @@ -27,15 +33,51 @@ file_template = %%(year)d-%%(month).2d-%%(day).2d-%%(hour).2d-%%(minute).2d_%%(r # versions/ directory # sourceless = false -# version location specification; this defaults -# to alembic/versions. When using multiple version -# directories, initial revisions must be specified with --version-path -# version_locations = %(here)s/bar %(here)s/bat alembic/versions +# version location specification; This defaults +# to migrations/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false # the output encoding used when revision files # are written from script.py.mako # output_encoding = utf-8 +# sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the exec runner, execute a binary +# hooks = ruff +# ruff.type = exec +# ruff.executable = %(here)s/.venv/bin/ruff +# ruff.options = --fix REVISION_SCRIPT_FILENAME + # Logging configuration [loggers] keys = root,sqlalchemy,alembic From e8a5053cef82a6763b4738bacd95199b3b13b82a Mon Sep 17 00:00:00 2001 From: F-G Fernandez <26927750+frgfm@users.noreply.github.com> Date: Sat, 30 Mar 2024 02:41:06 +0100 Subject: [PATCH 05/11] refactor(migrations): remove default init to allow migrations --- src/app/db.py | 9 +++++++++ src/app/main.py | 7 ------- src/app/models.py | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/app/db.py b/src/app/db.py index 4eae737..c8fe315 100644 --- a/src/app/db.py +++ b/src/app/db.py @@ -3,6 +3,7 @@ # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. +import asyncio import logging from sqlalchemy.ext.asyncio.engine import AsyncEngine @@ -50,3 +51,11 @@ async def init_db() -> None: ) ) await session.commit() + + +async def main() -> None: + await init_db() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/app/main.py b/src/app/main.py index cd9a300..7ac2c8f 100644 --- a/src/app/main.py +++ b/src/app/main.py @@ -18,7 +18,6 @@ from app.api.api_v1.router import api_router from app.core.config import settings -from app.db import init_db from app.schemas.base import Status logger = logging.getLogger("uvicorn.error") @@ -50,12 +49,6 @@ ) -# Database connection -@app.on_event("startup") -async def startup() -> None: - await init_db() - - # Healthcheck @app.get("/status", status_code=status.HTTP_200_OK, summary="Healthcheck for the API", include_in_schema=False) def get_status() -> Status: diff --git a/src/app/models.py b/src/app/models.py index cf291b6..69553fe 100644 --- a/src/app/models.py +++ b/src/app/models.py @@ -9,7 +9,7 @@ from sqlmodel import Field, SQLModel -__all__ = ["GHRole", "Guideline", "Repository", "User", "UserScope"] +__all__ = ["Guideline", "Repository", "User"] class GHRole(str, Enum): From b715237c5e8fcd11a5d77795bf14bddb1cddebc0 Mon Sep 17 00:00:00 2001 From: F-G Fernandez <26927750+frgfm@users.noreply.github.com> Date: Sat, 30 Mar 2024 02:41:49 +0100 Subject: [PATCH 06/11] refactor(docker): update docker --- docker-compose.dev.yml | 2 +- docker-compose.prod.yml | 2 +- docker-compose.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 5a49161..87fa290 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -53,7 +53,7 @@ services: - DEBUG=true volumes: - ./src/:/app/ - command: uvicorn app.main:app --reload --host 0.0.0.0 --port 5050 + command: "sh -c 'python app/db.py && uvicorn app.main:app --reload --host 0.0.0.0 --port 5050 --proxy-headers'" healthcheck: test: ["CMD-SHELL", "nc -vz localhost 5050"] interval: 10s diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 2548dbf..ad1bc74 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -89,7 +89,7 @@ services: - "traefik.http.routers.backend.entrypoints=websecure" - "traefik.http.routers.backend.tls.certresolver=quackresolver" - "traefik.http.services.backend.loadbalancer.server.port=5050" - command: uvicorn app.main:app --host 0.0.0.0 --port 5050 --proxy-headers + command: "sh -c 'alembic upgrade head && python app/db.py && uvicorn app.main:app --reload --host 0.0.0.0 --port 5050 --proxy-headers'" healthcheck: test: ["CMD-SHELL", "nc -vz localhost 5050"] interval: 10s diff --git a/docker-compose.yml b/docker-compose.yml index 666fab2..aaf8267 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -58,7 +58,7 @@ services: - PROMETHEUS_ENABLED=true volumes: - ./src/:/app/ - command: uvicorn app.main:app --reload --host 0.0.0.0 --port 5050 --proxy-headers + command: "sh -c 'alembic upgrade head && python app/db.py && uvicorn app.main:app --reload --host 0.0.0.0 --port 5050 --proxy-headers'" healthcheck: test: ["CMD-SHELL", "nc -vz localhost 5050"] interval: 10s From c7ef42948b0359e786858db88f4318e4263e9dae Mon Sep 17 00:00:00 2001 From: F-G Fernandez <26927750+frgfm@users.noreply.github.com> Date: Sat, 30 Mar 2024 02:44:54 +0100 Subject: [PATCH 07/11] feat(migrations): add initial revision --- .../versions/2024_03_30_0143-66a64868bce4_.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100755 src/migrations/versions/2024_03_30_0143-66a64868bce4_.py diff --git a/src/migrations/versions/2024_03_30_0143-66a64868bce4_.py b/src/migrations/versions/2024_03_30_0143-66a64868bce4_.py new file mode 100755 index 0000000..783017b --- /dev/null +++ b/src/migrations/versions/2024_03_30_0143-66a64868bce4_.py @@ -0,0 +1,62 @@ +"""empty message + +Revision ID: 66a64868bce4 +Revises: +Create Date: 2024-03-30 01:43:18.370633 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +import sqlmodel +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "66a64868bce4" +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: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "repository", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("provider_repo_id", sa.Integer(), nullable=True), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_repository_provider_repo_id"), "repository", ["provider_repo_id"], unique=False) + op.create_table( + "user", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("scope", sa.Enum("ADMIN", "USER", name="userscope"), nullable=False), + sa.Column("provider_user_id", sa.Integer(), nullable=True), + sa.Column("login", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("hashed_password", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "guideline", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("content", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("creator_id", sa.Integer(), nullable=False), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.Column("updated_at", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(["creator_id"], ["user.id"]), + sa.PrimaryKeyConstraint("id"), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("guideline") + op.drop_table("user") + op.drop_index(op.f("ix_repository_provider_repo_id"), table_name="repository") + op.drop_table("repository") + # ### end Alembic commands ### From 082547632136e8b72b291f79c18baad513b0bd63 Mon Sep 17 00:00:00 2001 From: F-G Fernandez <26927750+frgfm@users.noreply.github.com> Date: Sat, 30 Mar 2024 02:50:28 +0100 Subject: [PATCH 08/11] ci(github): reflect migration changes on CI jobs --- .github/workflows/push.yml | 2 +- .github/workflows/tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index a2cd9f0..bb348ef 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -73,7 +73,7 @@ jobs: docker volume rm -f $(docker volume ls -f "dangling=true" -q) # Update the service docker compose pull backend gradio - docker compose stop backend gradio && docker compose up -d --wait && docker compose exec backend alembic upgrade head + docker compose stop backend gradio && docker compose up -d --wait # Check update docker inspect -f '{{ .Created }}' $(docker compose images -q backend) docker inspect -f '{{ .Created }}' $(docker compose images -q gradio) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 69b047e..01cc6c2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -83,4 +83,4 @@ jobs: owner: 'Quack AI' starting-year: 2023 ignore-files: 'version.py,__init__.py' - ignore-folders: 'src/tests,src/alembic/versions' + ignore-folders: 'src/tests,src/migrations/versions' From 718e258cfc2acbf2c2603fad0783335da745a259 Mon Sep 17 00:00:00 2001 From: F-G Fernandez <26927750+frgfm@users.noreply.github.com> Date: Sat, 30 Mar 2024 02:53:13 +0100 Subject: [PATCH 09/11] fix(docker): fix copy command for migrations --- src/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dockerfile b/src/Dockerfile index 0cd516d..211051e 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -18,5 +18,5 @@ RUN set -eux \ # copy project COPY src/alembic.ini /app/alembic.ini -COPY src/alembic /app/alembic +COPY src/migrations /app/migrations COPY src/app /app/app From 18b335d9e16c781652425d2289c60429b6186e1e Mon Sep 17 00:00:00 2001 From: F-G Fernandez <26927750+frgfm@users.noreply.github.com> Date: Sat, 30 Mar 2024 02:54:14 +0100 Subject: [PATCH 10/11] docs(readme): update migration readme --- src/migrations/README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/migrations/README b/src/migrations/README index 9ea191d..25e41c3 100755 --- a/src/migrations/README +++ b/src/migrations/README @@ -14,7 +14,7 @@ Alembic allows you to record migration operation using DB operations. Let's crea docker compose exec -T backend alembic revision --autogenerate ``` -Once generated, you should edit the revision file in src/alembic/versions that was created. See example [here](https://github.com/jonra1993/fastapi-alembic-sqlmodel-async/blob/main/fastapi-alembic-sqlmodel-async/alembic/versions/2022-09-25-19-46_60d49bf413b8.py). +Once generated, you should edit the revision file in src/migrations/versions that was created. See example [here](https://github.com/jonra1993/fastapi-alembic-sqlmodel-async/blob/main/fastapi-alembic-sqlmodel-async/alembic/versions/2022-09-25-19-46_60d49bf413b8.py). ## Apply revisions From 9f04edf31835719c0e6a9946dae1ad609fd372ed Mon Sep 17 00:00:00 2001 From: F-G Fernandez <26927750+frgfm@users.noreply.github.com> Date: Sat, 30 Mar 2024 03:08:26 +0100 Subject: [PATCH 11/11] fix(migrations): fix the enum scope downgrade --- .github/workflows/tests.yml | 1 - src/migrations/versions/2024_03_30_0143-66a64868bce4_.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 01cc6c2..d8f158f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -63,7 +63,6 @@ jobs: docker compose -f docker-compose.dev.yml exec -T backend alembic current docker compose -f docker-compose.dev.yml exec -T backend alembic history --verbose docker compose -f docker-compose.dev.yml exec -T backend alembic stamp head - docker compose -f docker-compose.dev.yml exec -T backend alembic history --verbose docker compose -f docker-compose.dev.yml exec -T backend alembic downgrade -1 docker compose -f docker-compose.dev.yml exec -T backend alembic upgrade +1 diff --git a/src/migrations/versions/2024_03_30_0143-66a64868bce4_.py b/src/migrations/versions/2024_03_30_0143-66a64868bce4_.py index 783017b..5127d3f 100755 --- a/src/migrations/versions/2024_03_30_0143-66a64868bce4_.py +++ b/src/migrations/versions/2024_03_30_0143-66a64868bce4_.py @@ -57,6 +57,7 @@ def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.drop_table("guideline") op.drop_table("user") + sa.Enum(name="userscope").drop(op.get_bind(), checkfirst=False) op.drop_index(op.f("ix_repository_provider_repo_id"), table_name="repository") op.drop_table("repository") # ### end Alembic commands ###