Skip to content

Commit 75e4f2b

Browse files
authored
MPT-16844: add --new-data and --new-schema options to migrate command (#10)
Fist iteration adding --new-data and --new-schema options <!-- This is an auto-generated comment: release notes by coderabbit.ai --> Closes [MPT-16844](https://softwareone.atlassian.net/browse/MPT-16844) - Add migrate CLI options --new-data and --new-schema (mutually exclusive) to scaffold timestamped migration files - When option provided: determine suffix, ensure migrations folder exists, create timestamped Python file using MIGRATION_SCAFFOLDING_TEMPLATE, abort on name collisions, and print success - When no option provided: print "Running migrations is not implemented yet." - Select scaffold base class by option: DataBaseCommand for data migrations, SchemaBaseCommand for schema migrations - Introduce BaseCommand ABC and specialized DataBaseCommand / SchemaBaseCommand; add MigrationTypeEnum (DATA, SCHEMA); re-export commands from mpt_tool.commands - Add MIGRATION_FOLDER constant and MIGRATION_SCAFFOLDING_TEMPLATE (and template text) in mpt_tool.templates.py - Tests: add CWD fixture, CLI help, mutual-exclusion validation, time-frozen parameterized tests for --new-data/--new-schema, scaffold file creation and collision handling, and BaseCommand subclass checks - Tooling/CI updates: add freezegun to dev deps; adjust coverage, flake8, ruff, and pre-commit settings; small Dockerfile and sonar config tweaks <!-- end of auto-generated comment: release notes by coderabbit.ai --> [MPT-16844]: https://softwareone.atlassian.net/browse/MPT-16844?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2 parents b99e9d1 + 7379092 commit 75e4f2b

File tree

16 files changed

+257
-19
lines changed

16 files changed

+257
-19
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,8 @@ cython_debug/
166166
.DS_Store
167167
.ruff_cache
168168
.idea
169+
170+
# Specific project gitignore
171+
migrations
172+
.migrations-state.json
173+
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: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
import datetime as dt
2+
from pathlib import Path
3+
14
import typer
25

6+
from mpt_tool.constants import MIGRATION_FOLDER
7+
from mpt_tool.templates import MIGRATION_SCAFFOLDING_TEMPLATE
8+
39
app = typer.Typer(help="MPT CLI - Migration tool for extensions.", no_args_is_help=True)
410

511

@@ -8,10 +14,53 @@ def callback() -> None:
814
"""MPT CLI - Migration tool for extensions."""
915

1016

11-
@app.command()
12-
def migrate() -> None:
13-
"""Run the migration process."""
14-
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)
1564

1665

1766
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)