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.
Files changed (34) hide show
  1. {muscles_data-0.2.1 → muscles_data-0.2.2}/PKG-INFO +1 -1
  2. {muscles_data-0.2.1 → muscles_data-0.2.2}/pyproject.toml +1 -1
  3. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/migrations.py +42 -5
  4. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles_data.egg-info/PKG-INFO +1 -1
  5. {muscles_data-0.2.1 → muscles_data-0.2.2}/tests/test_migrations.py +49 -0
  6. {muscles_data-0.2.1 → muscles_data-0.2.2}/README.md +0 -0
  7. {muscles_data-0.2.1 → muscles_data-0.2.2}/setup.cfg +0 -0
  8. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/__init__.py +0 -0
  9. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/actions.py +0 -0
  10. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/adapters/__init__.py +0 -0
  11. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/adapters/memory.py +0 -0
  12. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/catalog.py +0 -0
  13. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/config.py +0 -0
  14. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/contracts.py +0 -0
  15. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/errors.py +0 -0
  16. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/migration_identity.py +0 -0
  17. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/models.py +0 -0
  18. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/operation_journal.py +0 -0
  19. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/package.py +0 -0
  20. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/policies.py +0 -0
  21. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/policy_search.py +0 -0
  22. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/ports.py +0 -0
  23. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/runtime.py +0 -0
  24. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles/data/versioned_migrations.py +0 -0
  25. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles_data.egg-info/SOURCES.txt +0 -0
  26. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles_data.egg-info/dependency_links.txt +0 -0
  27. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles_data.egg-info/requires.txt +0 -0
  28. {muscles_data-0.2.1 → muscles_data-0.2.2}/src/muscles_data.egg-info/top_level.txt +0 -0
  29. {muscles_data-0.2.1 → muscles_data-0.2.2}/tests/test_contracts.py +0 -0
  30. {muscles_data-0.2.1 → muscles_data-0.2.2}/tests/test_data_runtime.py +0 -0
  31. {muscles_data-0.2.1 → muscles_data-0.2.2}/tests/test_event_contracts.py +0 -0
  32. {muscles_data-0.2.1 → muscles_data-0.2.2}/tests/test_operation_journal.py +0 -0
  33. {muscles_data-0.2.1 → muscles_data-0.2.2}/tests/test_policies.py +0 -0
  34. {muscles_data-0.2.1 → muscles_data-0.2.2}/tests/test_policy_search.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: muscles-data
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Framework-level named data resources and typed ports for Muscles
5
5
  Author-email: "Denis B." <denis@butko.info>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "muscles-data"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "Framework-level named data resources and typed ports for Muscles"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -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(package_name: str) -> tuple[ModuleType, ...]:
20
- """Load owned Python migration files in name order, binding code to its checksum."""
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(item.name for item in pkgutil.iter_modules(paths)
26
- if not item.ispkg and re.match(r"^m[0-9]", item.name))
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}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: muscles-data
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Framework-level named data resources and typed ports for Muscles
5
5
  Author-email: "Denis B." <denis@butko.info>
6
6
  License: MIT
@@ -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