Skip to content

Commit 7379092

Browse files
committed
feat: add --new-data and --new-schema options to migrate command
1 parent 5fa5397 commit 7379092

File tree

16 files changed

+235
-85
lines changed

16 files changed

+235
-85
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,6 @@ cython_debug/
168168
.idea
169169

170170
# Specific project gitignore
171-
migrations/
171+
migrations
172172
.migrations-state.json
173173
settings.py

.pre-commit-config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,5 @@ repos:
3434
- id: mypy
3535
args: ["--config-file=pyproject.toml", "."]
3636
pass_filenames: false
37+
additional_dependencies:
38+
- typer

Dockerfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS base
22

3+
COPY pyproject.toml uv.lock ./mpt_tool/
4+
35
WORKDIR /mpt_tool
46

5-
COPY pyproject.toml uv.lock ./
67
RUN uv venv /opt/venv
78

89
ENV VIRTUAL_ENV=/opt/venv
910
ENV PATH=/opt/venv/bin:$PATH
1011

1112
FROM base AS build
1213

13-
COPY . /mpt_tool
14+
COPY . .
1415

1516
RUN uv sync --frozen --no-cache --all-groups --active
1617

compose.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,5 @@ services:
44
build:
55
context: .
66
target: dev
7-
working_dir: /mpt_tool
87
volumes:
98
- .:/mpt_tool

mpt_tool/cli.py

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,66 @@
1+
import datetime as dt
12
from pathlib import Path
23

34
import typer
45

5-
app = typer.Typer(help="MPT CLI - Migration tool for extensions.", no_args_is_help=True)
6-
7-
8-
@app.command()
9-
def init() -> None:
10-
"""Initialize the migrations tool workspace."""
11-
root = Path.cwd()
12-
13-
settings_file = root / "settings.py"
14-
if not settings_file.exists():
15-
typer.secho("Settings file does not exist.", fg=typer.colors.RED)
16-
raise typer.Exit(code=1)
17-
18-
migrations_dir = root / "migrations"
19-
if migrations_dir.exists():
20-
typer.secho("Migrations folder already exists.", fg=typer.colors.RED)
21-
raise typer.Exit(code=1)
6+
from mpt_tool.constants import MIGRATION_FOLDER
7+
from mpt_tool.templates import MIGRATION_SCAFFOLDING_TEMPLATE
228

23-
migrations_dir.mkdir(exist_ok=True)
24-
25-
init_file = migrations_dir / "__init__.py"
26-
init_file.touch(exist_ok=True)
27-
28-
state_file = root / ".migrations-state.json"
29-
if state_file.exists():
30-
typer.secho("State file already exists.", fg=typer.colors.RED)
31-
raise typer.Exit(code=1)
32-
33-
if not state_file.exists():
34-
state_file.write_text("{}\n")
9+
app = typer.Typer(help="MPT CLI - Migration tool for extensions.", no_args_is_help=True)
3510

3611

3712
@app.callback()
3813
def callback() -> None:
3914
"""MPT CLI - Migration tool for extensions."""
4015

4116

42-
@app.command()
43-
def migrate() -> None:
44-
"""Run the migration process."""
45-
typer.echo("Hello World!")
17+
@app.command("migrate")
18+
def migrate(
19+
new_data: str | None = typer.Option( # noqa: WPS404
20+
None,
21+
"--new-data",
22+
metavar="FILENAME",
23+
help="Scaffold a new data migration script with the provided filename.",
24+
),
25+
new_schema: str | None = typer.Option( # noqa: WPS404
26+
None,
27+
"--new-schema",
28+
metavar="FILENAME",
29+
help="Scaffold a new schema migration script with the provided filename.",
30+
),
31+
) -> None:
32+
"""Migrate command."""
33+
if new_data and new_schema:
34+
raise typer.BadParameter(
35+
"Options --new-data and --new-schema cannot be combined.",
36+
param_hint="migrate",
37+
)
38+
39+
if new_schema or new_data:
40+
filename_suffix = new_data or new_schema
41+
typer.echo(f"Scaffolding migration: {filename_suffix}.")
42+
migration_folder = Path(MIGRATION_FOLDER)
43+
migration_folder.mkdir(parents=True, exist_ok=True)
44+
timestamp = dt.datetime.now(tz=dt.UTC).strftime("%Y%m%d%H%M%S")
45+
# TODO: add filename validation
46+
filename = f"{timestamp}_{filename_suffix}.py"
47+
full_filename_path = migration_folder / filename
48+
try:
49+
full_filename_path.touch(exist_ok=False)
50+
except FileExistsError:
51+
typer.secho(f"File already exists: {filename}", fg=typer.colors.RED)
52+
raise typer.Abort
53+
54+
full_filename_path.write_text(
55+
encoding="utf-8",
56+
data=MIGRATION_SCAFFOLDING_TEMPLATE.substitute(
57+
command_name="DataBaseCommand" if new_data else "SchemaBaseCommand"
58+
),
59+
)
60+
typer.secho(f"Migration file: {filename} has been created.", fg=typer.colors.GREEN)
61+
return
62+
63+
typer.secho("Running migrations is not implemented yet.", fg=typer.colors.YELLOW)
4664

4765

4866
def main() -> None:

mpt_tool/commands/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from mpt_tool.commands.base import DataBaseCommand, SchemaBaseCommand
2+
3+
__all__ = ["DataBaseCommand", "SchemaBaseCommand"]

mpt_tool/commands/base.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from abc import ABC, abstractmethod
2+
3+
from mpt_tool.commands.enums import MigrationTypeEnum
4+
5+
6+
class BaseCommand(ABC):
7+
"""Abstract base class for all migration commands."""
8+
9+
@abstractmethod
10+
def run(self) -> None:
11+
"""Executes the command."""
12+
raise NotImplementedError
13+
14+
15+
class DataBaseCommand(BaseCommand, ABC):
16+
"""Base command for data migrations."""
17+
18+
_type = MigrationTypeEnum.DATA
19+
20+
21+
class SchemaBaseCommand(BaseCommand, ABC):
22+
"""Base command for schema migrations."""
23+
24+
_type = MigrationTypeEnum.SCHEMA

mpt_tool/commands/enums.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from enum import StrEnum
2+
3+
4+
class MigrationTypeEnum(StrEnum):
5+
"""Enumeration of migration types.
6+
7+
Attributes:
8+
DATA: Represents a data migration.
9+
SCHEMA: Represents a schema migration.
10+
"""
11+
12+
DATA = "data"
13+
SCHEMA = "schema"

mpt_tool/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
MIGRATION_FOLDER: str = "migrations"

mpt_tool/templates.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from string import Template
2+
3+
MIGRATION_SCAFFOLDING_FILE_TEXT = """\
4+
from mpt_tool.commands import $command_name
5+
6+
7+
class Command($command_name):
8+
def run(self):
9+
# implement your logic here
10+
pass
11+
"""
12+
MIGRATION_SCAFFOLDING_TEMPLATE: Template = Template(MIGRATION_SCAFFOLDING_FILE_TEXT)

0 commit comments

Comments
 (0)