mpt-tool 5.0.0__tar.gz → 5.1.0__tar.gz

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.
Files changed (50) hide show
  1. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/PKG-INFO +34 -7
  2. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/docs/PROJECT_DESCRIPTION.md +33 -6
  3. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/cli.py +3 -0
  4. mpt_tool-5.1.0/mpt_tool/commands/check.py +22 -0
  5. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/commands/factory.py +4 -1
  6. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/config.py +4 -4
  7. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/migration/mixins/airtable_client.py +4 -2
  8. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/use_cases/__init__.py +2 -0
  9. mpt_tool-5.1.0/mpt_tool/use_cases/check_migrations.py +42 -0
  10. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/use_cases/errors.py +8 -4
  11. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/pyproject.toml +1 -1
  12. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/.gitignore +0 -0
  13. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/LICENSE +0 -0
  14. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/__init__.py +0 -0
  15. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/commands/__init__.py +0 -0
  16. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/commands/base.py +0 -0
  17. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/commands/data.py +0 -0
  18. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/commands/errors.py +0 -0
  19. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/commands/fake.py +0 -0
  20. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/commands/list.py +0 -0
  21. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/commands/new_data.py +0 -0
  22. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/commands/new_schema.py +0 -0
  23. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/commands/schema.py +0 -0
  24. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/commands/validators.py +0 -0
  25. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/constants.py +0 -0
  26. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/enums.py +0 -0
  27. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/errors.py +0 -0
  28. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/managers/__init__.py +0 -0
  29. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/managers/encoders.py +0 -0
  30. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/managers/errors.py +0 -0
  31. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/managers/file_migration.py +0 -0
  32. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/managers/state/__init__.py +0 -0
  33. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/managers/state/airtable.py +0 -0
  34. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/managers/state/base.py +0 -0
  35. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/managers/state/factory.py +0 -0
  36. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/managers/state/file.py +0 -0
  37. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/migration/__init__.py +0 -0
  38. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/migration/base.py +0 -0
  39. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/migration/data_base.py +0 -0
  40. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/migration/mixins/__init__.py +0 -0
  41. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/migration/mixins/mpt_client.py +0 -0
  42. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/migration/schema_base.py +0 -0
  43. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/models.py +0 -0
  44. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/py.typed +0 -0
  45. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/renders.py +0 -0
  46. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/templates.py +0 -0
  47. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/use_cases/apply_migration.py +0 -0
  48. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/use_cases/list_migrations.py +0 -0
  49. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/use_cases/new_migration.py +0 -0
  50. {mpt_tool-5.0.0 → mpt_tool-5.1.0}/mpt_tool/use_cases/run_migrations.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mpt-tool
3
- Version: 5.0.0
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
- - `AIRTABLE_API_KEY`: Your Airtable API key (required when using `AirtableAPIClientMixin` or when `STORAGE_TYPE=airtable`)
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
- - `AIRTABLE_API_KEY`: Your Airtable API key
77
- - `STORAGE_AIRTABLE_BASE_ID`: Your Airtable base ID
78
- - `STORAGE_AIRTABLE_TABLE_NAME`: The name of the table to store migration state
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
@@ -38,10 +38,10 @@ Install with pip or your favorite PyPI package manager:
38
38
  ## Environment Variables
39
39
 
40
40
  The tool uses the following environment variables:
41
- - `STORAGE_TYPE`: Storage backend for migration state (`local` or `airtable`, default: `local`). See [Storage Configuration](#storage)
42
- - `MPT_API_TOKEN`: Your MPT API key (required when using `MPTAPIClientMixin`)
43
41
  - `MPT_API_BASE_URL`: The MPT API base url (required when using `MPTAPIClientMixin`)
44
- - `AIRTABLE_API_KEY`: Your Airtable API key (required when using `AirtableAPIClientMixin` or when `STORAGE_TYPE=airtable`)
42
+ - `MPT_API_TOKEN`: Your MPT API key (required when using `MPTAPIClientMixin`)
43
+ - `MPT_TOOL_STORAGE_TYPE`: Storage backend for migration state (`local` or `airtable`, default: `local`). See [Storage Configuration](#storage)
44
+ - `MPT_TOOL_STORAGE_AIRTABLE_API_KEY`: Your Airtable API key (required when using `AirtableAPIClientMixin` or when `MPT_TOOL_STORAGE_TYPE=airtable`)
45
45
 
46
46
  ## Configuration
47
47
 
@@ -60,9 +60,9 @@ No additional configuration is required.
60
60
  #### Airtable Storage
61
61
 
62
62
  Airtable configuration is done via environment variables:
63
- - `AIRTABLE_API_KEY`: Your Airtable API key
64
- - `STORAGE_AIRTABLE_BASE_ID`: Your Airtable base ID
65
- - `STORAGE_AIRTABLE_TABLE_NAME`: The name of the table to store migration state
63
+ - `MPT_TOOL_STORAGE_AIRTABLE_API_KEY`: Your Airtable API key
64
+ - `MPT_TOOL_STORAGE_AIRTABLE_BASE_ID`: Your Airtable base ID
65
+ - `MPT_TOOL_STORAGE_AIRTABLE_TABLE_NAME`: The name of the table to store migration state
66
66
 
67
67
  Your Airtable table must have the following columns:
68
68
 
@@ -135,6 +135,28 @@ class Migration(DataBaseMigration, MPTAPIClientMixin, AirtableAPIClientMixin):
135
135
  self.log.info(f"Processed {len(records)} records")
136
136
  ```
137
137
 
138
+ ### Checking Migrations
139
+ Before running migrations, you can validate your migration folder for issues:
140
+
141
+ ```bash
142
+ mpt-tool migrate --check
143
+ ```
144
+
145
+ This command:
146
+ - Verifies the migration folder structure
147
+ - Detects duplicate migration_id values (which could happen if migrations were created with the same name)
148
+ - Exits with code 0 if all checks pass
149
+ - Exits with code 1 and shows a detailed error message if duplicates are found
150
+
151
+ **Example output when duplicates are found:**
152
+
153
+ ```bash
154
+ Checking migrations...
155
+ Error running check command: Duplicate migration_id found in migrations: 20260113180013_duplicate_name.py, 20260114190014_duplicate_name.py
156
+ ```
157
+
158
+ **Best Practice:** Run `--check` as part of your CI/CD pipeline to catch migration issues before deployment.
159
+
138
160
  ### Running Migrations
139
161
  - **Run all pending data migrations:**
140
162
  ```bash
@@ -240,6 +262,11 @@ Run `mpt-tool --help` to see all available commands and params:
240
262
 
241
263
  ## Best Practices
242
264
 
265
+ ### Migration Validation
266
+ - Run `mpt-tool migrate --check` before committing migration files
267
+ - Include `--check` in your CI/CD pipeline to catch issues early
268
+ - Verify there are no duplicate migration_id values before deployment
269
+
243
270
  ### Migration Naming
244
271
  - Use descriptive, snake_case names (e.g., `add_user_table`, `fix_null_emails`, `sync_agreements_from_api`)
245
272
  - Keep names concise but meaningful
@@ -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()
@@ -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}:
@@ -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("AIRTABLE_API_KEY"),
8
- "base_id": os.getenv("STORAGE_AIRTABLE_BASE_ID"),
9
- "table_name": os.getenv("STORAGE_AIRTABLE_TABLE_NAME", "Migrations"),
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("STORAGE_TYPE", "local")
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 AIRTABLE_API_KEY.
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("Airtable API key must be set in env variable AIRTABLE_API_KEY")
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)
@@ -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)
@@ -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
  [project]
2
2
  name = "mpt-tool"
3
- version = "5.0.0"
3
+ version = "5.1.0"
4
4
  description = "Migration tool for extensions"
5
5
  authors = [{ name = "SoftwareOne AG" }]
6
6
  requires-python = ">=3.12,<4"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes