Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bundle migrations and pre-configure Alembic #304

Merged
merged 1 commit into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,12 @@ See the data conversion document's section on [converting the audio files](../do
DearMEP is using [Alembic](https://alembic.sqlalchemy.org/) to manage the database and perform version upgrades to it.
Currently, this is a manual process.

Note that Alembic requires a configuration file telling it where to find the database and migrations.
Fortunately, DearMEP comes with a [prepared `alembic.ini`](dearmep/migrations/alembic.ini) as well as a special CLI subcommand, `dearmep alembic`, that will invoke Alembic with the environment variable `ALEMBIC_CONFIG` already pointing to the location of this configuration file.

### For Administrators of a DearMEP Instance

If you upgrade DearMEP, make sure to `alembic upgrade head` before starting the `dearmep serve` process again.
If you upgrade DearMEP, make sure to run `dearmep alembic upgrade head` before starting the `dearmep serve` process again.
It will ensure that all changes to the database structure required for the new version are applied.
Make sure to have a backup of your database, just in case.

Expand All @@ -122,10 +125,10 @@ Make sure to have a backup of your database, just in case.
If you change the models which are reflected in the database, you'll need to do use Alembic to handle database migrations. You can create migrations via

```sh
alembic revision --autogenerate --message "your short alembic message about the reason of this migration"
dearmep alembic revision --autogenerate --message "your short alembic message about the reason of this migration"
```

Alembic generates a file in `migrations/versions/`. **Check this file** for sanity. You can edit or delete this file though this usually should not be necessary. If you are happy about the migration, commit to version control.
Alembic generates a file in `dearmep/migrations/versions/`. **Check this file** for sanity. You can edit or delete this file though this usually should not be necessary. If you are happy about the migration, commit to version control.

Make sure to `upgrade` the database to the latest revision when developing. It is recommended to check the upgrades first, for example by making a copy of the database source and changing the `sqlalchemy.uri` in the `alembic.ini` or by checking the raw SQL with the `--sql` flag.

Expand Down
33 changes: 29 additions & 4 deletions server/dearmep/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import re
from argparse import ArgumentParser, Namespace
from contextlib import contextmanager
from sys import exit, stderr
from sys import argv, exit, stderr
from typing import TYPE_CHECKING

from dotenv import load_dotenv
Expand All @@ -19,16 +19,23 @@

from ..config import CMD_NAME
from ..progress import DummyTaskFactory, RichTaskFactory
from . import check, convert, db, dump, importing, serve, version
from . import check, convert, db, dump, importing, run_alembic, serve, version


if TYPE_CHECKING:
from collections.abc import Iterator


class Context:
def __init__(self, *, args: Namespace, raw_stdout: bool = False) -> None:
def __init__(
self,
*,
args: Namespace,
parser: ArgumentParser,
raw_stdout: bool = False,
) -> None:
self.args = args
self.parser = parser
# Let the Console run on stderr if we need stdout for raw data.
self.console = Console(stderr=raw_stdout)
self.raw_stdout = raw_stdout
Expand Down Expand Up @@ -87,17 +94,35 @@ def run() -> None:
subparsers = parser.add_subparsers(
metavar="COMMAND",
)
for module in (version, dump, serve, db, convert, importing, check):
for module in (
version,
dump,
serve,
db,
run_alembic,
convert,
importing,
check,
):
module.add_parser(
subparsers,
help_if_no_subcommand=help_if_no_subcommand,
)
help_if_no_subcommand(parser)

# Special hack for the `alembic` subcommand: Inject a double dash if it's
# used. This is because it needs to pass the rest of argv to Alembic
# verbatim, but argparse can't ignore arguments that look like flags.
# <https://github.com/python/cpython/issues/61252>
if argv[1] == "alembic":
argv.insert(2, "--")

args = parser.parse_args()

args.func(
Context(
args=args,
parser=parser,
# Commands can opt-in to have a raw stdout.
raw_stdout=getattr(args, "raw_stdout", False),
)
Expand Down
43 changes: 43 additions & 0 deletions server/dearmep/cli/run_alembic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# SPDX-FileCopyrightText: © 2024 Tim Weber
#
# SPDX-License-Identifier: AGPL-3.0-or-later

from __future__ import annotations

import logging
from os import environ
from pathlib import Path
from typing import TYPE_CHECKING, Callable

import alembic.config


if TYPE_CHECKING:
from argparse import ArgumentParser, _SubParsersAction

from . import Context


_logger = logging.getLogger(__name__)


CONFIG_VAR = "ALEMBIC_CONFIG"


def run(ctx: Context) -> None:
alembic_config = Path(__file__).parent.parent / "migrations/alembic.ini"
environ[CONFIG_VAR] = str(alembic_config)
alembic.config.main(prog=f"{ctx.parser.prog} alembic", argv=ctx.args.args)


def add_parser(
subparsers: _SubParsersAction,
help_if_no_subcommand: Callable, # noqa: ARG001
) -> None:
parser: ArgumentParser = subparsers.add_parser(
"alembic",
help="invoke pre-configured Alembic to execute database migrations",
add_help=False, # don't hijack Alembic's own `-h`
)
parser.add_argument("args", nargs="*")
parser.set_defaults(func=run)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
; SPDX-License-Identifier: AGPL-3.0-or-later

[alembic]
script_location = ./migrations
script_location = %(here)s
file_template = %%(year)d%%(month).2d%%(day).2d-%%(rev)s_%%(slug)s
prepend_sys_path = .
version_path_separator = os
Expand Down
File renamed without changes.
Loading