pyverge 0.2.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.
- pyverge/__init__.py +9 -0
- pyverge/_version.py +24 -0
- pyverge/cli/__init__.py +1 -0
- pyverge/cli/_helpers.py +206 -0
- pyverge/cli/config.py +64 -0
- pyverge/cli/main.py +175 -0
- pyverge/migration/__init__.py +86 -0
- pyverge/migration/adapters.py +164 -0
- pyverge/migration/diff.py +243 -0
- pyverge/migration/engine.py +518 -0
- pyverge/migration/exceptions.py +244 -0
- pyverge/migration/executor.py +242 -0
- pyverge/migration/graph.py +331 -0
- pyverge/migration/hooks.py +119 -0
- pyverge/migration/manager.py +696 -0
- pyverge/migration/models.py +120 -0
- pyverge/migration/policy.py +125 -0
- pyverge/migration/registry.py +434 -0
- pyverge/migration/steps.py +51 -0
- pyverge/migration/strategy.py +171 -0
- pyverge/migration/types.py +391 -0
- pyverge/migration/versioning.py +300 -0
- pyverge/migration/walker.py +282 -0
- pyverge/py.typed +0 -0
- pyverge-0.2.0.dist-info/METADATA +149 -0
- pyverge-0.2.0.dist-info/RECORD +29 -0
- pyverge-0.2.0.dist-info/WHEEL +4 -0
- pyverge-0.2.0.dist-info/entry_points.txt +2 -0
- pyverge-0.2.0.dist-info/licenses/LICENSE +201 -0
pyverge/__init__.py
ADDED
pyverge/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.2.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
pyverge/cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""The pyverge CLI subpackage."""
|
pyverge/cli/_helpers.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Helper functions for the CLI."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def load_json_file(path: Path) -> Any:
|
|
11
|
+
"""Load JSON file."""
|
|
12
|
+
try:
|
|
13
|
+
with open(path) as f:
|
|
14
|
+
return json.load(f)
|
|
15
|
+
except FileNotFoundError as e:
|
|
16
|
+
raise FileNotFoundError(f"Data file not found: {path}") from e
|
|
17
|
+
except json.JSONDecodeError as e:
|
|
18
|
+
raise json.JSONDecodeError(f"Invalid JSON in {path}", e.doc, e.pos) from e
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def write_json_file(path: Path, data: dict[str, Any]) -> None:
|
|
22
|
+
"""Write JSON file."""
|
|
23
|
+
try:
|
|
24
|
+
with open(path, "w") as f:
|
|
25
|
+
json.dump(data, f, indent=2)
|
|
26
|
+
f.write("\n")
|
|
27
|
+
except PermissionError as e:
|
|
28
|
+
raise PermissionError(f"Cannot write to {path}: Permission denied") from e
|
|
29
|
+
except OSError as e:
|
|
30
|
+
raise OSError(f"Failed to write {path}: {e}") from e
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def create_example_models_file(path: Path) -> None:
|
|
34
|
+
"""Create example models.py file."""
|
|
35
|
+
if path.exists():
|
|
36
|
+
typer.secho(f"Skipping: {path} already exists", fg=typer.colors.YELLOW)
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
path.write_text('''"""Versioned models managed by pyverge."""
|
|
41
|
+
|
|
42
|
+
from typing import Literal
|
|
43
|
+
|
|
44
|
+
import semver
|
|
45
|
+
from pydantic import BaseModel
|
|
46
|
+
|
|
47
|
+
from pyverge.migration import (
|
|
48
|
+
MigrationSettings,
|
|
49
|
+
ModelManager,
|
|
50
|
+
PydanticModelAdapter,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Create a scoped manager class
|
|
54
|
+
UserManager = ModelManager[semver.Version].scoped(
|
|
55
|
+
PydanticModelAdapter(),
|
|
56
|
+
settings=MigrationSettings(),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Version 1: Initial user model
|
|
61
|
+
@UserManager.model()
|
|
62
|
+
class UserV1(BaseModel):
|
|
63
|
+
"""User model v1.0.0"""
|
|
64
|
+
kind: Literal["User"] = "User"
|
|
65
|
+
version: Literal["1.0.0"] = "1.0.0"
|
|
66
|
+
name: str
|
|
67
|
+
email: str
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Version 2: Add age field
|
|
71
|
+
@UserManager.model()
|
|
72
|
+
class UserV2(BaseModel):
|
|
73
|
+
"""User model v2.0.0"""
|
|
74
|
+
kind: Literal["User"] = "User"
|
|
75
|
+
version: Literal["2.0.0"] = "2.0.0"
|
|
76
|
+
name: str
|
|
77
|
+
email: str
|
|
78
|
+
age: int | None = None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Define migration
|
|
82
|
+
@UserManager.migration("User", "1.0.0", "2.0.0")
|
|
83
|
+
def add_age_field(data: dict) -> dict:
|
|
84
|
+
"""Add optional age field."""
|
|
85
|
+
return {**data, "age": None}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# Export manager instance for CLI discovery
|
|
89
|
+
__manager__ = UserManager()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Alternative: Factory function pattern
|
|
93
|
+
# def create_manager():
|
|
94
|
+
# """Factory function for creating manager with custom setup."""
|
|
95
|
+
# return UserManager()
|
|
96
|
+
#
|
|
97
|
+
# __manager__ = create_manager
|
|
98
|
+
''')
|
|
99
|
+
typer.secho(f"Created {path}", fg=typer.colors.GREEN)
|
|
100
|
+
except OSError as e:
|
|
101
|
+
typer.secho(f"Failed to create {path}: {e}", fg=typer.colors.RED)
|
|
102
|
+
raise
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def create_single_manager_config(project_dir: Path, use_pyproject: bool) -> None:
|
|
106
|
+
"""Create single manager configuration."""
|
|
107
|
+
try:
|
|
108
|
+
if use_pyproject:
|
|
109
|
+
config_file = project_dir / "pyproject.toml"
|
|
110
|
+
config_content = """[tool.pyverge]
|
|
111
|
+
manager = "models"
|
|
112
|
+
"""
|
|
113
|
+
if config_file.exists():
|
|
114
|
+
typer.echo(f"\nAdd this to {config_file}:")
|
|
115
|
+
typer.echo(config_content)
|
|
116
|
+
else:
|
|
117
|
+
config_file.write_text(f"""[project]
|
|
118
|
+
name = "{project_dir.name}"
|
|
119
|
+
version = "0.1.0"
|
|
120
|
+
|
|
121
|
+
{config_content}""")
|
|
122
|
+
typer.secho(f"Created {config_file}", fg=typer.colors.GREEN)
|
|
123
|
+
else:
|
|
124
|
+
config_file = project_dir / "pyverge.toml"
|
|
125
|
+
if not config_file.exists():
|
|
126
|
+
config_file.write_text("""[pyverge]
|
|
127
|
+
manager = "models"
|
|
128
|
+
|
|
129
|
+
# Optional: Use factory function
|
|
130
|
+
# manager = "models:create_manager"
|
|
131
|
+
|
|
132
|
+
# Optional: Pass initialization arguments
|
|
133
|
+
# init_args = []
|
|
134
|
+
# [pyverge.init_kwargs]
|
|
135
|
+
# debug = false
|
|
136
|
+
""")
|
|
137
|
+
typer.secho(f"Created {config_file}", fg=typer.colors.GREEN)
|
|
138
|
+
except OSError as e:
|
|
139
|
+
typer.secho(f"Failed to create config: {e}", fg=typer.colors.RED)
|
|
140
|
+
raise
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def create_multi_manager_config(project_dir: Path, use_pyproject: bool) -> None:
|
|
144
|
+
"""Create multi-manager configuration."""
|
|
145
|
+
try:
|
|
146
|
+
if use_pyproject:
|
|
147
|
+
config_content = """[tool.pyverge.managers]
|
|
148
|
+
default = "models"
|
|
149
|
+
api_v1 = "api.v1.models"
|
|
150
|
+
api_v2 = "api.v2.models"
|
|
151
|
+
|
|
152
|
+
# Optional: Configure specific manager with init args
|
|
153
|
+
# [tool.pyverge.managers.api_v1]
|
|
154
|
+
# manager = "api.v1.models:create_manager"
|
|
155
|
+
# init_args = ["production"]
|
|
156
|
+
"""
|
|
157
|
+
else:
|
|
158
|
+
config_content = """[pyverge.managers]
|
|
159
|
+
default = "models"
|
|
160
|
+
api_v1 = "api.v1.models"
|
|
161
|
+
api_v2 = "api.v2.models"
|
|
162
|
+
|
|
163
|
+
# Optional: Configure specific manager with init args
|
|
164
|
+
# [pyverge.managers.api_v1]
|
|
165
|
+
# manager = "api.v1.models:create_manager"
|
|
166
|
+
# init_args = ["production"]
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
config_file = project_dir / (
|
|
170
|
+
"pyproject.toml" if use_pyproject else "pyverge.toml"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
if config_file.exists() and use_pyproject:
|
|
174
|
+
typer.echo(f"\nAdd this to {config_file}:")
|
|
175
|
+
typer.echo(config_content)
|
|
176
|
+
else:
|
|
177
|
+
if use_pyproject:
|
|
178
|
+
config_file.write_text(f"""[project]
|
|
179
|
+
name = "{project_dir.name}"
|
|
180
|
+
version = "0.1.0"
|
|
181
|
+
|
|
182
|
+
{config_content}""")
|
|
183
|
+
else:
|
|
184
|
+
config_file.write_text(config_content)
|
|
185
|
+
typer.secho(f"Created {config_file}", fg=typer.colors.GREEN)
|
|
186
|
+
except OSError as e:
|
|
187
|
+
typer.secho(f"Failed to create config: {e}", fg=typer.colors.RED)
|
|
188
|
+
raise
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def print_next_steps(multiple: bool) -> None:
|
|
192
|
+
"""Print next steps after initialization."""
|
|
193
|
+
if multiple:
|
|
194
|
+
typer.echo("\nMultiple managers configured:")
|
|
195
|
+
typer.echo(" • default (models)")
|
|
196
|
+
typer.echo(" • api_v1 (api.v1.models)")
|
|
197
|
+
typer.echo(" • api_v2 (api.v2.models)")
|
|
198
|
+
typer.echo("\nCommands:")
|
|
199
|
+
typer.echo(" pyverge managers - List all managers")
|
|
200
|
+
typer.echo(" pyverge info api_v1 - Show manager details")
|
|
201
|
+
typer.echo(" pyverge validate -M api_v1 ... - Use specific manager")
|
|
202
|
+
else:
|
|
203
|
+
typer.echo("\nNext steps:")
|
|
204
|
+
typer.echo(" 1. Edit models.py to add your models")
|
|
205
|
+
typer.echo(" 2. Run: pyverge info")
|
|
206
|
+
typer.echo(" 3. Run: pyverge validate -d data.json -s User -v 1.0.0")
|
pyverge/cli/config.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from types import ModuleType
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pyverge.migration import ModelManager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ConfigError(Exception):
|
|
11
|
+
"""Configuration loading error."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def resolve_manager(spec: str) -> ModelManager:
|
|
15
|
+
"""Resolve a manager from a ``module_path:object_path`` spec."""
|
|
16
|
+
|
|
17
|
+
if ":" not in spec:
|
|
18
|
+
raise ConfigError(f"Manager spec must be 'module:object_path', got '{spec}'")
|
|
19
|
+
|
|
20
|
+
module_path, object_path = spec.split(":", 1)
|
|
21
|
+
cwd = Path.cwd()
|
|
22
|
+
module = _import_module(cwd, module_path)
|
|
23
|
+
obj: Any = module
|
|
24
|
+
for part in object_path.split("."):
|
|
25
|
+
obj = getattr(obj, part)
|
|
26
|
+
if not isinstance(obj, ModelManager):
|
|
27
|
+
raise ConfigError(
|
|
28
|
+
f"Manager '{spec}' resolved to {type(obj).__name__}, "
|
|
29
|
+
"expected a ModelManager"
|
|
30
|
+
)
|
|
31
|
+
return obj
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _import_module(cwd: Path, module_path: str) -> ModuleType:
|
|
35
|
+
"""Import a module, falling back to a local file when not on the path."""
|
|
36
|
+
try:
|
|
37
|
+
return importlib.import_module(module_path)
|
|
38
|
+
except ImportError as e:
|
|
39
|
+
module_file = cwd / module_path.replace(".", "/")
|
|
40
|
+
if not module_file.suffix:
|
|
41
|
+
module_file = module_file.with_suffix(".py")
|
|
42
|
+
|
|
43
|
+
if not module_file.exists():
|
|
44
|
+
raise ConfigError(f"Cannot import module '{module_path}': {e}") from e
|
|
45
|
+
|
|
46
|
+
spec = importlib.util.spec_from_file_location(module_path, module_file)
|
|
47
|
+
if spec is None or spec.loader is None:
|
|
48
|
+
raise ConfigError(f"Cannot load module from {module_file}") from e
|
|
49
|
+
|
|
50
|
+
module = importlib.util.module_from_spec(spec)
|
|
51
|
+
sys.modules[module_path] = module
|
|
52
|
+
spec.loader.exec_module(module)
|
|
53
|
+
return module
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def list_managers_from_module(module_path: str) -> list[str]:
|
|
57
|
+
"""Return the names of ``ModelManager`` instances/subclasses in *module_path*."""
|
|
58
|
+
module = importlib.import_module(module_path)
|
|
59
|
+
return [
|
|
60
|
+
name
|
|
61
|
+
for name, value in vars(module).items()
|
|
62
|
+
if isinstance(value, ModelManager)
|
|
63
|
+
or (isinstance(value, type) and issubclass(value, ModelManager))
|
|
64
|
+
]
|
pyverge/cli/main.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from pydantic import ValidationError
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from ._helpers import (
|
|
11
|
+
load_json_file,
|
|
12
|
+
)
|
|
13
|
+
from .config import (
|
|
14
|
+
ConfigError,
|
|
15
|
+
list_managers_from_module,
|
|
16
|
+
resolve_manager,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
app = typer.Typer(help="Schema evolution and migrations for versioned models")
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
ManagerOption = Annotated[
|
|
23
|
+
str,
|
|
24
|
+
typer.Option(
|
|
25
|
+
...,
|
|
26
|
+
"--manager",
|
|
27
|
+
"-m",
|
|
28
|
+
help="Manager import path (module:object_path)",
|
|
29
|
+
),
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.command()
|
|
34
|
+
def check(
|
|
35
|
+
data: Annotated[
|
|
36
|
+
Path, typer.Option(..., "--data", "-d", help="Path to data file (JSON)")
|
|
37
|
+
],
|
|
38
|
+
schema: Annotated[str, typer.Option(..., "--schema", "-s", help="Schema name")],
|
|
39
|
+
version: Annotated[
|
|
40
|
+
str, typer.Option(..., "--version", "-v", help="Schema version")
|
|
41
|
+
],
|
|
42
|
+
manager: ManagerOption,
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Check a payload against a schema version."""
|
|
45
|
+
try:
|
|
46
|
+
mgr = resolve_manager(manager)
|
|
47
|
+
data_dict = load_json_file(data)
|
|
48
|
+
|
|
49
|
+
mgr.validate(data_dict, schema, version)
|
|
50
|
+
|
|
51
|
+
typer.secho(f"✓ Valid against {schema} v{version}", fg=typer.colors.GREEN)
|
|
52
|
+
raise typer.Exit(0)
|
|
53
|
+
|
|
54
|
+
except ValidationError as e:
|
|
55
|
+
typer.secho("✗ Validation failed", fg=typer.colors.RED)
|
|
56
|
+
typer.secho("\nValidation errors:", fg=typer.colors.RED)
|
|
57
|
+
for error in e.errors():
|
|
58
|
+
field = ".".join(str(loc) for loc in error["loc"])
|
|
59
|
+
typer.echo(f" • {field}: {error['msg']}")
|
|
60
|
+
raise typer.Exit(1) from e
|
|
61
|
+
except ConfigError as e:
|
|
62
|
+
typer.secho(str(e), fg=typer.colors.RED)
|
|
63
|
+
raise typer.Exit(1) from e
|
|
64
|
+
except FileNotFoundError as e:
|
|
65
|
+
typer.secho(f"File not found: {e}", fg=typer.colors.RED)
|
|
66
|
+
raise typer.Exit(1) from e
|
|
67
|
+
except json.JSONDecodeError as e:
|
|
68
|
+
typer.secho(f"Invalid JSON in {data}: {e}", fg=typer.colors.RED)
|
|
69
|
+
raise typer.Exit(1) from e
|
|
70
|
+
except typer.Exit:
|
|
71
|
+
raise
|
|
72
|
+
except Exception as e:
|
|
73
|
+
typer.secho(f"Validation error: {e}", fg=typer.colors.RED)
|
|
74
|
+
raise typer.Exit(1) from e
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@app.command()
|
|
78
|
+
def managers(
|
|
79
|
+
module: Annotated[
|
|
80
|
+
str, typer.Argument(..., help="Module path to inspect for managers")
|
|
81
|
+
],
|
|
82
|
+
) -> None:
|
|
83
|
+
"""List ModelManagers defined in a module."""
|
|
84
|
+
try:
|
|
85
|
+
names = list_managers_from_module(module)
|
|
86
|
+
|
|
87
|
+
table = Table(title=f"Managers in {module}")
|
|
88
|
+
table.add_column("Name", style="cyan")
|
|
89
|
+
|
|
90
|
+
for name in sorted(names):
|
|
91
|
+
table.add_row(name)
|
|
92
|
+
|
|
93
|
+
console.print(table)
|
|
94
|
+
|
|
95
|
+
except ImportError as e:
|
|
96
|
+
typer.secho(f"Cannot import module '{module}': {e}", fg=typer.colors.RED)
|
|
97
|
+
raise typer.Exit(1) from e
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@app.command()
|
|
101
|
+
def info(
|
|
102
|
+
manager: Annotated[
|
|
103
|
+
str, typer.Argument(..., help="Manager import path (module:object_path)")
|
|
104
|
+
],
|
|
105
|
+
) -> None:
|
|
106
|
+
"""Show information about a specific manager."""
|
|
107
|
+
try:
|
|
108
|
+
mgr = resolve_manager(manager)
|
|
109
|
+
|
|
110
|
+
typer.secho(f"Manager: {manager}", bold=True)
|
|
111
|
+
typer.echo("")
|
|
112
|
+
|
|
113
|
+
versions = mgr.list_versions()
|
|
114
|
+
models = sorted({v.model.__name__ for v in versions})
|
|
115
|
+
|
|
116
|
+
if not models:
|
|
117
|
+
typer.secho("No models registered", fg=typer.colors.YELLOW)
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
typer.secho("Registered Models:", bold=True)
|
|
121
|
+
typer.echo("")
|
|
122
|
+
|
|
123
|
+
for model_name in models:
|
|
124
|
+
typer.secho(f" {model_name}", bold=True)
|
|
125
|
+
for v in versions:
|
|
126
|
+
if v.model.__name__ == model_name:
|
|
127
|
+
typer.echo(f" • v{v.version[1]}")
|
|
128
|
+
|
|
129
|
+
typer.echo(f"\nTotal: {len(models)} models")
|
|
130
|
+
|
|
131
|
+
except ConfigError as e:
|
|
132
|
+
typer.secho(str(e), fg=typer.colors.RED)
|
|
133
|
+
raise typer.Exit(1) from e
|
|
134
|
+
except typer.Exit:
|
|
135
|
+
raise
|
|
136
|
+
except Exception as e:
|
|
137
|
+
typer.secho(str(e), fg=typer.colors.RED)
|
|
138
|
+
raise typer.Exit(1) from e
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@app.command()
|
|
142
|
+
def diff(
|
|
143
|
+
schema: Annotated[str, typer.Option(..., "--schema", "-s", help="Schema name")],
|
|
144
|
+
from_version: Annotated[
|
|
145
|
+
str, typer.Option(..., "--from", "-f", help="Source version")
|
|
146
|
+
],
|
|
147
|
+
to_version: Annotated[str, typer.Option(..., "--to", "-t", help="Target version")],
|
|
148
|
+
manager: ManagerOption,
|
|
149
|
+
format: Annotated[
|
|
150
|
+
str, typer.Option(..., "--format", help="Output format (json)")
|
|
151
|
+
] = "json",
|
|
152
|
+
) -> None:
|
|
153
|
+
"""Show differences between schema versions."""
|
|
154
|
+
try:
|
|
155
|
+
mgr = resolve_manager(manager)
|
|
156
|
+
diff_result = mgr.diff(schema, from_version, to_version)
|
|
157
|
+
|
|
158
|
+
if format == "json":
|
|
159
|
+
typer.echo(json.dumps(diff_result.render(), indent=2, default=str))
|
|
160
|
+
else:
|
|
161
|
+
typer.secho(f"Unknown format: {format}", fg=typer.colors.RED)
|
|
162
|
+
raise typer.Exit(1)
|
|
163
|
+
|
|
164
|
+
except ConfigError as e:
|
|
165
|
+
typer.secho(str(e), fg=typer.colors.RED)
|
|
166
|
+
raise typer.Exit(1) from e
|
|
167
|
+
except typer.Exit:
|
|
168
|
+
raise
|
|
169
|
+
except Exception as e:
|
|
170
|
+
typer.secho(f"Diff error: {e}", fg=typer.colors.RED)
|
|
171
|
+
raise typer.Exit(1) from e
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if __name__ == "__main__":
|
|
175
|
+
app()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from . import types
|
|
2
|
+
from .adapters import PydanticModelAdapter
|
|
3
|
+
from .diff import PydanticDiff
|
|
4
|
+
from .engine import Engine
|
|
5
|
+
from .exceptions import (
|
|
6
|
+
DiscoveryError,
|
|
7
|
+
DiscoveryValidationError,
|
|
8
|
+
EngineError,
|
|
9
|
+
MaxDepthExceededError,
|
|
10
|
+
MigrationAlreadyRegisteredError,
|
|
11
|
+
MigrationError,
|
|
12
|
+
MigrationMissingFieldError,
|
|
13
|
+
MigrationNotFoundError,
|
|
14
|
+
MigrationPathIntegrityError,
|
|
15
|
+
ModelAlreadyRegisteredError,
|
|
16
|
+
ModelNotFoundError,
|
|
17
|
+
RegistryError,
|
|
18
|
+
VersionedModelError,
|
|
19
|
+
)
|
|
20
|
+
from .executor import LevelParallelExecutor, SequentialExecutor, StepExecutor
|
|
21
|
+
from .graph import GraphBuilder, GraphEntry, MigrationGraph
|
|
22
|
+
from .hooks import MigrationHook, OTELHook
|
|
23
|
+
from .manager import ModelManager
|
|
24
|
+
from .models import (
|
|
25
|
+
DiscoverySettings,
|
|
26
|
+
MigrationSettings,
|
|
27
|
+
VersioningSettings,
|
|
28
|
+
)
|
|
29
|
+
from .policy import (
|
|
30
|
+
earliest_target_resolver,
|
|
31
|
+
fixed_target_resolver,
|
|
32
|
+
latest_target_resolver,
|
|
33
|
+
multi_target_resolver,
|
|
34
|
+
skip_target_resolver,
|
|
35
|
+
)
|
|
36
|
+
from .registry import Registry
|
|
37
|
+
from .strategy import DefaultEntryMigration, EntryMigration
|
|
38
|
+
from .versioning import SentinelEdge, SentinelNode, VersionEdge, VersionNode
|
|
39
|
+
from .walker import CompoundKeyWalker, PydanticWalker
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"CompoundKeyWalker",
|
|
43
|
+
"DefaultEntryMigration",
|
|
44
|
+
"DiscoveryError",
|
|
45
|
+
"DiscoverySettings",
|
|
46
|
+
"DiscoveryValidationError",
|
|
47
|
+
"Engine",
|
|
48
|
+
"EngineError",
|
|
49
|
+
"EntryMigration",
|
|
50
|
+
"GraphBuilder",
|
|
51
|
+
"GraphEntry",
|
|
52
|
+
"LevelParallelExecutor",
|
|
53
|
+
"MaxDepthExceededError",
|
|
54
|
+
"MigrationAlreadyRegisteredError",
|
|
55
|
+
"MigrationError",
|
|
56
|
+
"MigrationGraph",
|
|
57
|
+
"MigrationHook",
|
|
58
|
+
"MigrationMissingFieldError",
|
|
59
|
+
"MigrationNotFoundError",
|
|
60
|
+
"MigrationPathIntegrityError",
|
|
61
|
+
"MigrationSettings",
|
|
62
|
+
"ModelAlreadyRegisteredError",
|
|
63
|
+
"ModelManager",
|
|
64
|
+
"ModelNotFoundError",
|
|
65
|
+
"OTELHook",
|
|
66
|
+
"PydanticDiff",
|
|
67
|
+
"PydanticModelAdapter",
|
|
68
|
+
"PydanticWalker",
|
|
69
|
+
"Registry",
|
|
70
|
+
"RegistryError",
|
|
71
|
+
"SentinelEdge",
|
|
72
|
+
"SentinelNode",
|
|
73
|
+
"SequentialExecutor",
|
|
74
|
+
"StepExecutor",
|
|
75
|
+
"VersionEdge",
|
|
76
|
+
"VersionNode",
|
|
77
|
+
"VersionedModelError",
|
|
78
|
+
"VersioningSettings",
|
|
79
|
+
"compile_target_spec",
|
|
80
|
+
"earliest_target_resolver",
|
|
81
|
+
"fixed_target_resolver",
|
|
82
|
+
"latest_target_resolver",
|
|
83
|
+
"multi_target_resolver",
|
|
84
|
+
"skip_target_resolver",
|
|
85
|
+
"types",
|
|
86
|
+
]
|