mpt-tool 5.0.0__py3-none-any.whl → 5.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mpt_tool/cli.py +3 -0
- mpt_tool/commands/check.py +22 -0
- mpt_tool/commands/factory.py +4 -1
- mpt_tool/config.py +4 -4
- mpt_tool/migration/mixins/airtable_client.py +4 -2
- mpt_tool/use_cases/__init__.py +2 -0
- mpt_tool/use_cases/check_migrations.py +42 -0
- mpt_tool/use_cases/errors.py +8 -4
- {mpt_tool-5.0.0.dist-info → mpt_tool-5.1.0.dist-info}/METADATA +34 -7
- {mpt_tool-5.0.0.dist-info → mpt_tool-5.1.0.dist-info}/RECORD +13 -11
- {mpt_tool-5.0.0.dist-info → mpt_tool-5.1.0.dist-info}/WHEEL +0 -0
- {mpt_tool-5.0.0.dist-info → mpt_tool-5.1.0.dist-info}/entry_points.txt +0 -0
- {mpt_tool-5.0.0.dist-info → mpt_tool-5.1.0.dist-info}/licenses/LICENSE +0 -0
mpt_tool/cli.py
CHANGED
|
@@ -18,6 +18,9 @@ def callback() -> None:
|
|
|
18
18
|
@app.command("migrate")
|
|
19
19
|
def migrate( # noqa: WPS211
|
|
20
20
|
ctx: typer.Context,
|
|
21
|
+
check: Annotated[ # noqa: FBT002
|
|
22
|
+
bool, typer.Option("--check", help="Check for duplicate migration_id in migrations.")
|
|
23
|
+
] = False,
|
|
21
24
|
data: Annotated[bool, typer.Option("--data", help="Run data migrations.")] = False, # noqa: FBT002
|
|
22
25
|
schema: Annotated[bool, typer.Option("--schema", help="Run schema migrations.")] = False, # noqa: FBT002
|
|
23
26
|
fake: Annotated[
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from mpt_tool.commands.base import BaseCommand
|
|
4
|
+
from mpt_tool.use_cases import CheckMigrationsUseCase
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CheckCommand(BaseCommand):
|
|
8
|
+
"""Checks migrations for duplicate migration_id."""
|
|
9
|
+
|
|
10
|
+
@override
|
|
11
|
+
@property
|
|
12
|
+
def start_message(self) -> str:
|
|
13
|
+
return "Checking migrations..."
|
|
14
|
+
|
|
15
|
+
@override
|
|
16
|
+
@property
|
|
17
|
+
def success_message(self) -> str:
|
|
18
|
+
return "Migrations check passed successfully."
|
|
19
|
+
|
|
20
|
+
@override
|
|
21
|
+
def run(self) -> None:
|
|
22
|
+
CheckMigrationsUseCase().execute()
|
mpt_tool/commands/factory.py
CHANGED
|
@@ -3,6 +3,7 @@ from typing import cast
|
|
|
3
3
|
from mpt_tool.commands.base import (
|
|
4
4
|
BaseCommand,
|
|
5
5
|
)
|
|
6
|
+
from mpt_tool.commands.check import CheckCommand
|
|
6
7
|
from mpt_tool.commands.data import DataCommand
|
|
7
8
|
from mpt_tool.commands.errors import CommandNotFoundError
|
|
8
9
|
from mpt_tool.commands.fake import FakeCommand
|
|
@@ -28,7 +29,9 @@ class CommandFactory:
|
|
|
28
29
|
Raises:
|
|
29
30
|
CommandNotFoundError: If no command is found.
|
|
30
31
|
"""
|
|
31
|
-
match param_data:
|
|
32
|
+
match param_data: # noqa: WPS242
|
|
33
|
+
case {"check": True}:
|
|
34
|
+
return CheckCommand()
|
|
32
35
|
case {"data": True}:
|
|
33
36
|
return DataCommand()
|
|
34
37
|
case {"schema": True}:
|
mpt_tool/config.py
CHANGED
|
@@ -4,9 +4,9 @@ import os
|
|
|
4
4
|
def get_airtable_config(config_key: str) -> str | None:
|
|
5
5
|
"""Get Airtable configuration."""
|
|
6
6
|
config = {
|
|
7
|
-
"api_key": os.getenv("
|
|
8
|
-
"base_id": os.getenv("
|
|
9
|
-
"table_name": os.getenv("
|
|
7
|
+
"api_key": os.getenv("MPT_TOOL_STORAGE_AIRTABLE_API_KEY"),
|
|
8
|
+
"base_id": os.getenv("MPT_TOOL_STORAGE_AIRTABLE_BASE_ID"),
|
|
9
|
+
"table_name": os.getenv("MPT_TOOL_STORAGE_AIRTABLE_TABLE_NAME", "Migrations"),
|
|
10
10
|
}
|
|
11
11
|
return config.get(config_key)
|
|
12
12
|
|
|
@@ -19,4 +19,4 @@ def get_mpt_config(config_key: str) -> str | None:
|
|
|
19
19
|
|
|
20
20
|
def get_storage_type() -> str:
|
|
21
21
|
"""Get storage type."""
|
|
22
|
-
return os.getenv("
|
|
22
|
+
return os.getenv("MPT_TOOL_STORAGE_TYPE", "local")
|
|
@@ -8,7 +8,7 @@ from mpt_tool.config import get_airtable_config
|
|
|
8
8
|
class AirtableAPIClientMixin:
|
|
9
9
|
"""Mixin to add Airtable API client to commands.
|
|
10
10
|
|
|
11
|
-
The API key is read from the environment variable
|
|
11
|
+
The API key is read from the environment variable MPT_TOOL_STORAGE_AIRTABLE_API_KEY.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
@cached_property
|
|
@@ -24,6 +24,8 @@ class AirtableAPIClientMixin:
|
|
|
24
24
|
"""
|
|
25
25
|
airtable_api_key = get_airtable_config("api_key")
|
|
26
26
|
if not airtable_api_key:
|
|
27
|
-
raise ValueError(
|
|
27
|
+
raise ValueError(
|
|
28
|
+
"Airtable API key must be set in env variable MPT_TOOL_STORAGE_AIRTABLE_API_KEY"
|
|
29
|
+
)
|
|
28
30
|
|
|
29
31
|
return AirtableClient(airtable_api_key)
|
mpt_tool/use_cases/__init__.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from mpt_tool.use_cases.apply_migration import ApplyMigrationUseCase
|
|
2
|
+
from mpt_tool.use_cases.check_migrations import CheckMigrationsUseCase
|
|
2
3
|
from mpt_tool.use_cases.list_migrations import ListMigrationsUseCase
|
|
3
4
|
from mpt_tool.use_cases.new_migration import NewMigrationUseCase
|
|
4
5
|
from mpt_tool.use_cases.run_migrations import RunMigrationsUseCase
|
|
5
6
|
|
|
6
7
|
__all__ = [
|
|
7
8
|
"ApplyMigrationUseCase",
|
|
9
|
+
"CheckMigrationsUseCase",
|
|
8
10
|
"ListMigrationsUseCase",
|
|
9
11
|
"NewMigrationUseCase",
|
|
10
12
|
"RunMigrationsUseCase",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from collections import Counter
|
|
2
|
+
|
|
3
|
+
from mpt_tool.managers import FileMigrationManager
|
|
4
|
+
from mpt_tool.managers.errors import MigrationFolderError
|
|
5
|
+
from mpt_tool.use_cases.errors import CheckMigrationError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CheckMigrationsUseCase:
|
|
9
|
+
"""Use case for checking migrations for duplicate migration_id."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, file_migration_manager: FileMigrationManager | None = None):
|
|
12
|
+
self.file_migration_manager = file_migration_manager or FileMigrationManager()
|
|
13
|
+
|
|
14
|
+
def execute(self) -> None: # noqa: WPS210
|
|
15
|
+
"""Check for duplicate migration_id in migration files.
|
|
16
|
+
|
|
17
|
+
Raises:
|
|
18
|
+
CheckMigrationError: If duplicate migration_id is found or a migration folder
|
|
19
|
+
error occurs.
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
migration_files = self.file_migration_manager.retrieve_migration_files()
|
|
23
|
+
except MigrationFolderError as error:
|
|
24
|
+
raise CheckMigrationError(str(error)) from error
|
|
25
|
+
|
|
26
|
+
if not migration_files:
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
migration_id_counter = Counter(migration.migration_id for migration in migration_files)
|
|
30
|
+
duplicated_migration_ids = [
|
|
31
|
+
migration_id for migration_id, count in migration_id_counter.items() if count > 1
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
if duplicated_migration_ids:
|
|
35
|
+
duplicate_migrations = [
|
|
36
|
+
migration.file_name
|
|
37
|
+
for migration in migration_files
|
|
38
|
+
if migration.migration_id in duplicated_migration_ids
|
|
39
|
+
]
|
|
40
|
+
migrations_list = ", ".join(duplicate_migrations)
|
|
41
|
+
error_message = f"Duplicate migration_id found in migrations: {migrations_list}"
|
|
42
|
+
raise CheckMigrationError(error_message)
|
mpt_tool/use_cases/errors.py
CHANGED
|
@@ -5,13 +5,17 @@ class UseCaseError(BaseError):
|
|
|
5
5
|
"""Base error for use cases."""
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
class ApplyMigrationError(UseCaseError):
|
|
9
|
+
"""Error applying migration."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CheckMigrationError(UseCaseError):
|
|
13
|
+
"""Error checking migrations."""
|
|
14
|
+
|
|
15
|
+
|
|
8
16
|
class NewMigrationError(UseCaseError):
|
|
9
17
|
"""Error creating new migration."""
|
|
10
18
|
|
|
11
19
|
|
|
12
20
|
class RunMigrationError(UseCaseError):
|
|
13
21
|
"""Error running migration."""
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class ApplyMigrationError(UseCaseError):
|
|
17
|
-
"""Error applying migration."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mpt-tool
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.1.0
|
|
4
4
|
Summary: Migration tool for extensions
|
|
5
5
|
Author: SoftwareOne AG
|
|
6
6
|
License: Apache-2.0 license
|
|
@@ -51,10 +51,10 @@ Install with pip or your favorite PyPI package manager:
|
|
|
51
51
|
## Environment Variables
|
|
52
52
|
|
|
53
53
|
The tool uses the following environment variables:
|
|
54
|
-
- `STORAGE_TYPE`: Storage backend for migration state (`local` or `airtable`, default: `local`). See [Storage Configuration](#storage)
|
|
55
|
-
- `MPT_API_TOKEN`: Your MPT API key (required when using `MPTAPIClientMixin`)
|
|
56
54
|
- `MPT_API_BASE_URL`: The MPT API base url (required when using `MPTAPIClientMixin`)
|
|
57
|
-
- `
|
|
55
|
+
- `MPT_API_TOKEN`: Your MPT API key (required when using `MPTAPIClientMixin`)
|
|
56
|
+
- `MPT_TOOL_STORAGE_TYPE`: Storage backend for migration state (`local` or `airtable`, default: `local`). See [Storage Configuration](#storage)
|
|
57
|
+
- `MPT_TOOL_STORAGE_AIRTABLE_API_KEY`: Your Airtable API key (required when using `AirtableAPIClientMixin` or when `MPT_TOOL_STORAGE_TYPE=airtable`)
|
|
58
58
|
|
|
59
59
|
## Configuration
|
|
60
60
|
|
|
@@ -73,9 +73,9 @@ No additional configuration is required.
|
|
|
73
73
|
#### Airtable Storage
|
|
74
74
|
|
|
75
75
|
Airtable configuration is done via environment variables:
|
|
76
|
-
- `
|
|
77
|
-
- `
|
|
78
|
-
- `
|
|
76
|
+
- `MPT_TOOL_STORAGE_AIRTABLE_API_KEY`: Your Airtable API key
|
|
77
|
+
- `MPT_TOOL_STORAGE_AIRTABLE_BASE_ID`: Your Airtable base ID
|
|
78
|
+
- `MPT_TOOL_STORAGE_AIRTABLE_TABLE_NAME`: The name of the table to store migration state
|
|
79
79
|
|
|
80
80
|
Your Airtable table must have the following columns:
|
|
81
81
|
|
|
@@ -148,6 +148,28 @@ class Migration(DataBaseMigration, MPTAPIClientMixin, AirtableAPIClientMixin):
|
|
|
148
148
|
self.log.info(f"Processed {len(records)} records")
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
+
### Checking Migrations
|
|
152
|
+
Before running migrations, you can validate your migration folder for issues:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
mpt-tool migrate --check
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
This command:
|
|
159
|
+
- Verifies the migration folder structure
|
|
160
|
+
- Detects duplicate migration_id values (which could happen if migrations were created with the same name)
|
|
161
|
+
- Exits with code 0 if all checks pass
|
|
162
|
+
- Exits with code 1 and shows a detailed error message if duplicates are found
|
|
163
|
+
|
|
164
|
+
**Example output when duplicates are found:**
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
Checking migrations...
|
|
168
|
+
Error running check command: Duplicate migration_id found in migrations: 20260113180013_duplicate_name.py, 20260114190014_duplicate_name.py
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Best Practice:** Run `--check` as part of your CI/CD pipeline to catch migration issues before deployment.
|
|
172
|
+
|
|
151
173
|
### Running Migrations
|
|
152
174
|
- **Run all pending data migrations:**
|
|
153
175
|
```bash
|
|
@@ -253,6 +275,11 @@ Run `mpt-tool --help` to see all available commands and params:
|
|
|
253
275
|
|
|
254
276
|
## Best Practices
|
|
255
277
|
|
|
278
|
+
### Migration Validation
|
|
279
|
+
- Run `mpt-tool migrate --check` before committing migration files
|
|
280
|
+
- Include `--check` in your CI/CD pipeline to catch issues early
|
|
281
|
+
- Verify there are no duplicate migration_id values before deployment
|
|
282
|
+
|
|
256
283
|
### Migration Naming
|
|
257
284
|
- Use descriptive, snake_case names (e.g., `add_user_table`, `fix_null_emails`, `sync_agreements_from_api`)
|
|
258
285
|
- Keep names concise but meaningful
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
mpt_tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
mpt_tool/cli.py,sha256=
|
|
3
|
-
mpt_tool/config.py,sha256=
|
|
2
|
+
mpt_tool/cli.py,sha256=zJMFNsRWaaJDav2-sQbbA6Ydf0nvMLpMKyKVh_8I-dE,2611
|
|
3
|
+
mpt_tool/config.py,sha256=U9YWajFcG36EGvFpYiOecmVqMGUYZb246NgHT_xQGEw,716
|
|
4
4
|
mpt_tool/constants.py,sha256=i3jnIAFT8XXS2Z7iw8VbyWxrmnRq3nMklMfwDgmOkxI,147
|
|
5
5
|
mpt_tool/enums.py,sha256=ElHflGP0c0Jq4PqxD3YISgdWa-hgkkd8aqD7cv5nUBc,1143
|
|
6
6
|
mpt_tool/errors.py,sha256=_u1vgUwTNj-IF7b4VJiYOkv4vgD48jMHpMYpOgGkugQ,167
|
|
@@ -10,9 +10,10 @@ mpt_tool/renders.py,sha256=RLAQyxoLH7zCCJO62J90oAAurTMIVeyR_NNQLrb6sLk,1234
|
|
|
10
10
|
mpt_tool/templates.py,sha256=9-_M7Diy50D5Io0sC1Ez9ez4fNxFRMXSkIQ_7XcK95Y,308
|
|
11
11
|
mpt_tool/commands/__init__.py,sha256=UNBsxkGf4MC7tIsOHHP4leo1exBDv8MAJZY_VizZGAY,175
|
|
12
12
|
mpt_tool/commands/base.py,sha256=EfhyJm-37NMN4yu9gXFAbWuU2_7gsEKEYpKewegT0o8,752
|
|
13
|
+
mpt_tool/commands/check.py,sha256=jSQWkDoc7Z7Jg3zmRvJicUzuWL64zcWaY9xXMDMiwfU,533
|
|
13
14
|
mpt_tool/commands/data.py,sha256=Q_1t0QqUNrBe9h13UXkvkh12ld2EYAXVf0E_oWC36Qk,597
|
|
14
15
|
mpt_tool/commands/errors.py,sha256=yYsGZovbm-HN9dyuAcYXZhSPixwdiytUzqN18De-k3I,183
|
|
15
|
-
mpt_tool/commands/factory.py,sha256
|
|
16
|
+
mpt_tool/commands/factory.py,sha256=rBvEY1vWG5LpEJXgtKy9W37rp5P3x1W-MJ7Pkbk9U1M,1836
|
|
16
17
|
mpt_tool/commands/fake.py,sha256=O3ASK5b7Henwh9AcIujzDM84ZUzkOEuQ_HuACe_94CI,711
|
|
17
18
|
mpt_tool/commands/list.py,sha256=V_rBSfpfcRNCDry-3xvj-joWu0S3VVsmm3n4nQwueDA,874
|
|
18
19
|
mpt_tool/commands/new_data.py,sha256=Ls9S3s4eCNmkpieAlXSWNGDTlIuE3gZ-pynWGMN0sHM,820
|
|
@@ -33,16 +34,17 @@ mpt_tool/migration/base.py,sha256=gbvfJwLnvQsOG_gWXTMGIvkxwrEFTKFAPovZeAz89jA,57
|
|
|
33
34
|
mpt_tool/migration/data_base.py,sha256=EZIAXhriXr61Gu-6ZbRbvhAnRv5zysQDX9Gco3BNP6E,243
|
|
34
35
|
mpt_tool/migration/schema_base.py,sha256=u3XH-OKii1p4jwMjdI92w5Q71YZ07s2FI4bDDC_FtfQ,249
|
|
35
36
|
mpt_tool/migration/mixins/__init__.py,sha256=qvWJfFqTzphAgR5vrTC1qzzWvrDTWKhtiWGpWS4kq1I,203
|
|
36
|
-
mpt_tool/migration/mixins/airtable_client.py,sha256=
|
|
37
|
+
mpt_tool/migration/mixins/airtable_client.py,sha256=EWsZi3SlUvHm3gUEeU-lZ6cIFa3nWAttN2JqMrCG1-Q,869
|
|
37
38
|
mpt_tool/migration/mixins/mpt_client.py,sha256=oUQUi0usC85Z9aCzaFM8UzRrxnGGrcf3cj0DfYzm24E,909
|
|
38
|
-
mpt_tool/use_cases/__init__.py,sha256=
|
|
39
|
+
mpt_tool/use_cases/__init__.py,sha256=vujepn0mgQj9nmNPI9o2yTC4Ok2xjXmDIqCiks14G-U,499
|
|
39
40
|
mpt_tool/use_cases/apply_migration.py,sha256=nZXdESTQYKGbBbfA2CoKb_d_x8K8bMker7-DJ1rvklc,1745
|
|
40
|
-
mpt_tool/use_cases/
|
|
41
|
+
mpt_tool/use_cases/check_migrations.py,sha256=k3kbAp9ipiBNDcZ3J3poMSRGQlgsZv4SdX7sG3gg5tE,1683
|
|
42
|
+
mpt_tool/use_cases/errors.py,sha256=qcuI2Waeqi0Pwn1CyVTO2gN6lWWLLNHMob5YQi-V5gY,423
|
|
41
43
|
mpt_tool/use_cases/list_migrations.py,sha256=a4Otx6IL5EkqU-iwWjYEaFdD8eR4Zha7GDmQMRjVhuw,1427
|
|
42
44
|
mpt_tool/use_cases/new_migration.py,sha256=pNHrOamGPupyGb7s9_P1B0-r_ooNDqcYpU0oiGNt2HU,860
|
|
43
45
|
mpt_tool/use_cases/run_migrations.py,sha256=iHGnUEuUKRWo_ZgvUQFfsUIaAtCAZvWW0pbf60j8dzM,3798
|
|
44
|
-
mpt_tool-5.
|
|
45
|
-
mpt_tool-5.
|
|
46
|
-
mpt_tool-5.
|
|
47
|
-
mpt_tool-5.
|
|
48
|
-
mpt_tool-5.
|
|
46
|
+
mpt_tool-5.1.0.dist-info/METADATA,sha256=MGjMyvJzgAKJiCONy5-HqlbZmXFXUDzAEBco42MBLGE,11167
|
|
47
|
+
mpt_tool-5.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
48
|
+
mpt_tool-5.1.0.dist-info/entry_points.txt,sha256=ouNlQh2WYlerppVMgMkkTQKoEDlgAczOhCMhDQMhu-0,47
|
|
49
|
+
mpt_tool-5.1.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
50
|
+
mpt_tool-5.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|