muscles-data 0.2.1__tar.gz → 0.2.2__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.
- {muscles_data-0.2.1 → muscles_data-0.2.2}/PKG-INFO +1 -1
- {muscles_data-0.2.1 → muscles_data-0.2.2}/pyproject.toml +1 -1
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/migrations.py +42 -5
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles_data.egg-info/PKG-INFO +1 -1
- {muscles_data-0.2.1 → muscles_data-0.2.2}/tests/test_migrations.py +49 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/README.md +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/setup.cfg +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/__init__.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/actions.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/adapters/__init__.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/adapters/memory.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/catalog.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/config.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/contracts.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/errors.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/migration_identity.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/models.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/operation_journal.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/package.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/policies.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/policy_search.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/ports.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/runtime.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/versioned_migrations.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles_data.egg-info/SOURCES.txt +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles_data.egg-info/dependency_links.txt +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles_data.egg-info/requires.txt +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles_data.egg-info/top_level.txt +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/tests/test_contracts.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/tests/test_data_runtime.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/tests/test_event_contracts.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/tests/test_operation_journal.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/tests/test_policies.py +0 -0
- {muscles_data-0.2.1 → muscles_data-0.2.2}/tests/test_policy_search.py +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"""Storage-neutral migration source registration and dependency ordering."""
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from contextlib import nullcontext
|
|
4
|
+
from fnmatch import fnmatchcase
|
|
4
5
|
from importlib import import_module
|
|
5
6
|
from importlib.util import find_spec, module_from_spec
|
|
6
7
|
import hashlib
|
|
7
8
|
from pathlib import Path
|
|
9
|
+
from pathlib import PurePosixPath
|
|
8
10
|
import pkgutil
|
|
9
|
-
import re
|
|
10
11
|
import sys
|
|
11
12
|
from types import ModuleType
|
|
12
13
|
from typing import Callable, ContextManager, Protocol
|
|
@@ -16,14 +17,50 @@ class MigrationBackend(Protocol):
|
|
|
16
17
|
def upgrade(self, target: str = "head") -> None: ...
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
def discover_migration_modules(
|
|
20
|
-
|
|
20
|
+
def discover_migration_modules(
|
|
21
|
+
package_name: str | None = None,
|
|
22
|
+
*,
|
|
23
|
+
pattern: str = "m[0-9]*.py",
|
|
24
|
+
) -> tuple[ModuleType, ...]:
|
|
25
|
+
"""Load matching Python migration files in name order, binding code to its checksum.
|
|
26
|
+
|
|
27
|
+
When ``package_name`` is omitted, the calling package is used. A filename
|
|
28
|
+
pattern without a directory searches its ``migrations`` subpackage; a
|
|
29
|
+
directory in ``pattern`` selects that subpackage, e.g. ``migrate/000[0-9]_*.py``.
|
|
30
|
+
``package_name`` remains available for callers outside an owning package.
|
|
31
|
+
"""
|
|
32
|
+
if not isinstance(pattern, str) or not pattern:
|
|
33
|
+
raise ValueError("A migration filename pattern is required")
|
|
34
|
+
relative_pattern = PurePosixPath(pattern)
|
|
35
|
+
if relative_pattern.is_absolute() or ".." in relative_pattern.parts:
|
|
36
|
+
raise ValueError("Migration pattern must be a relative package path")
|
|
37
|
+
filename_pattern = relative_pattern.name
|
|
38
|
+
module_pattern = filename_pattern[:-3] if filename_pattern.endswith(".py") else filename_pattern
|
|
39
|
+
if not module_pattern:
|
|
40
|
+
raise ValueError("A migration filename pattern is required")
|
|
41
|
+
|
|
42
|
+
if package_name is None:
|
|
43
|
+
caller_package = sys._getframe(1).f_globals.get("__package__")
|
|
44
|
+
if not isinstance(caller_package, str) or not caller_package:
|
|
45
|
+
raise ValueError("Could not infer the calling package for migration discovery")
|
|
46
|
+
package_name = caller_package
|
|
47
|
+
if not relative_pattern.parent.parts:
|
|
48
|
+
package_name = f"{package_name}.migrations"
|
|
49
|
+
if relative_pattern.parent.parts:
|
|
50
|
+
subpackage = ".".join(relative_pattern.parent.parts)
|
|
51
|
+
if any(not part.isidentifier() for part in relative_pattern.parent.parts):
|
|
52
|
+
raise ValueError("Migration pattern directories must be Python package names")
|
|
53
|
+
package_name = f"{package_name}.{subpackage}"
|
|
54
|
+
|
|
21
55
|
package = import_module(package_name)
|
|
22
56
|
paths = getattr(package, "__path__", None)
|
|
23
57
|
if paths is None:
|
|
24
58
|
raise ValueError("Migration module discovery requires a Python package")
|
|
25
|
-
names = sorted(
|
|
26
|
-
|
|
59
|
+
names = sorted(
|
|
60
|
+
item.name
|
|
61
|
+
for item in pkgutil.iter_modules(paths)
|
|
62
|
+
if not item.ispkg and fnmatchcase(item.name, module_pattern)
|
|
63
|
+
)
|
|
27
64
|
modules = []
|
|
28
65
|
for name in names:
|
|
29
66
|
qualified = f"{package_name}.{name}"
|
|
@@ -147,3 +147,52 @@ def test_discovery_loads_only_ordered_migration_modules(monkeypatch, tmp_path):
|
|
|
147
147
|
(package / "m001_first.py").write_text("revision = 'changed'\n", encoding="utf-8")
|
|
148
148
|
with pytest.raises(ValueError, match="changed"):
|
|
149
149
|
discover_migration_modules("sample_migrations")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_discovery_accepts_custom_filename_pattern(monkeypatch, tmp_path):
|
|
153
|
+
package = tmp_path / "custom_migrations"
|
|
154
|
+
migration_package = package / "migrate"
|
|
155
|
+
migration_package.mkdir(parents=True)
|
|
156
|
+
(package / "__init__.py").write_text("", encoding="utf-8")
|
|
157
|
+
(migration_package / "__init__.py").write_text("", encoding="utf-8")
|
|
158
|
+
(migration_package / "0002_second.py").write_text("revision = '002'\n", encoding="utf-8")
|
|
159
|
+
(migration_package / "0001_first.py").write_text("revision = '001'\n", encoding="utf-8")
|
|
160
|
+
(migration_package / "helper.py").write_text(
|
|
161
|
+
"raise RuntimeError('must not be loaded')\n", encoding="utf-8"
|
|
162
|
+
)
|
|
163
|
+
monkeypatch.syspath_prepend(str(tmp_path))
|
|
164
|
+
|
|
165
|
+
modules = discover_migration_modules(
|
|
166
|
+
"custom_migrations.migrate", pattern="000[0-9]_*.py"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
assert [module.revision for module in modules] == ["001", "002"]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_discovery_infers_calling_package_and_migrations_directory(monkeypatch, tmp_path):
|
|
173
|
+
package = tmp_path / "inferred_migrations"
|
|
174
|
+
migrations = package / "migrations"
|
|
175
|
+
migrations.mkdir(parents=True)
|
|
176
|
+
custom_migrations = package / "migrate"
|
|
177
|
+
custom_migrations.mkdir()
|
|
178
|
+
(package / "__init__.py").write_text("", encoding="utf-8")
|
|
179
|
+
(package / "loader.py").write_text(
|
|
180
|
+
"from muscles.data.migrations import discover_migration_modules\n"
|
|
181
|
+
"def load_default():\n"
|
|
182
|
+
" return discover_migration_modules(pattern='m000[0-9]_*.py')\n"
|
|
183
|
+
"def load_custom():\n"
|
|
184
|
+
" return discover_migration_modules(pattern='migrate/000[0-9]_*.py')\n",
|
|
185
|
+
encoding="utf-8",
|
|
186
|
+
)
|
|
187
|
+
(migrations / "__init__.py").write_text("", encoding="utf-8")
|
|
188
|
+
(migrations / "m0001_initial.py").write_text("revision = '001'\n", encoding="utf-8")
|
|
189
|
+
(custom_migrations / "__init__.py").write_text("", encoding="utf-8")
|
|
190
|
+
(custom_migrations / "0001_initial.py").write_text("revision = '001-custom'\n", encoding="utf-8")
|
|
191
|
+
monkeypatch.syspath_prepend(str(tmp_path))
|
|
192
|
+
|
|
193
|
+
from importlib import import_module
|
|
194
|
+
|
|
195
|
+
loader = import_module("inferred_migrations.loader")
|
|
196
|
+
|
|
197
|
+
assert [module.revision for module in loader.load_default()] == ["001"]
|
|
198
|
+
assert [module.revision for module in loader.load_custom()] == ["001-custom"]
|
|
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
|
|
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
|
|
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
|