Repository navigation
Expand file tree
/
Copy path_workspace.py
More file actions
148 lines (124 loc) · 7 KB
/
Copy path_workspace.py
File metadata and controls
148 lines (124 loc) · 7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""Purpose: reach this repository's extension distributions from a checkout.
Each `ext/metta-<name>/` is its own distribution and a checkout is not an
install, so nothing has written a `dist-info`: neither `import metta_pandas`
nor entry-point discovery works without help. `on_path()` puts each member's
directory on `sys.path` and adds one distribution finder to `sys.meta_path`
that answers `importlib.metadata` from the members' own `pyproject.toml`
files, so `entry_points(group="metta.extensions")` lists a checkout's members
the way it lists installed ones and the seam discovers them the same way.
The finder goes first on `sys.meta_path`, as the members go first on
`sys.path`, so metadata and imports name the same checkout; `importlib.metadata`
keeps one distribution per name, the first found [source:
https://docs.python.org/3/library/importlib.metadata.html#implementing-custom-providers].
Derived from the directory rather than a list, so a package added under `ext/`
needs no edit anywhere.
Assumes: every member's module sits directly in its distribution directory and
its `pyproject.toml` carries `[project]` with its entry points, which
`tests/checks/check_layering.py` holds it to.
Guarantees:
- `on_path()` is idempotent and answers the module names it made reachable
[tested: tests/checks/check_layering_selftest.py; commit=94057a0f073c0fab0a35c42beff2c324d8a0addd]
- after `on_path()`, `importlib.metadata.entry_points(group="metta.extensions")`
names every member and each entry point loads its module [tested:
test_a_checkout_advertises_its_members_through_importlib_metadata;
commit=58bf75947fc58ec32b2372ef0d2c14a00aa2390a]
Open Obligations:
To Do: None
Hacks: None
Future Enhancements: None
"""
from __future__ import annotations
import re
import sys
import tomllib
from collections.abc import Iterator
from importlib import metadata
from pathlib import Path
#: The workspace, DERIVED as metta._roots.workspace() derives it: the nearest
#: ancestor holding both `engine/` and `lib/`. A level count is silent when it
#: is wrong -- the glob matches nothing and every member stops being importable
#: with no error naming the cause -- and this was the last one in the seat
#: [measured 2026-09-19: the move to a top-level `ext/` left five files
#: counting to the old depth, costing 22 collection errors and 12 setup
#: errors]. It is written inline because importing metta here would run its
#: package before on_path() has made the members discoverable, and
#: `tests/checks/check_layering.py` compares EXT against the workspace roster
#: so a layout change fails a lane instead of a member's imports.
#: The distributions are a sibling of `extensions/`, not of this file: they
#: are a separate component so that deleting `ext/` leaves the seat whole,
#: which it cannot be while they sit inside it.
SEAT = Path(__file__).resolve().parent
ROOT = next(parent for parent in SEAT.parents
if (parent / "engine").is_dir() and (parent / "lib").is_dir())
EXT = ROOT / "ext"
def members() -> list[Path]:
"""Every extension distribution's directory, in name order."""
return sorted(path for path in EXT.glob("metta-*") if path.is_dir())
def module_names() -> list[str]:
"""The module each distribution ships, `metta-pandas` giving metta_pandas."""
return [path.name.replace("-", "_") for path in members()]
class CheckoutDistribution(metadata.Distribution):
"""One member's metadata, read from its `pyproject.toml` instead of a dist-info."""
def __init__(self, member: Path) -> None:
self._member = member
self._project = tomllib.loads((member / "pyproject.toml").read_text(encoding="utf-8"))["project"]
def read_text(self, filename: str) -> str | None:
"""Answer the two files `importlib.metadata` reads, rendered from the manifest."""
if filename == "METADATA":
return (
"Metadata-Version: 2.1\n"
f"Name: {self._project['name']}\n"
f"Version: {self._project.get('version', '0')}\n"
)
if filename == "entry_points.txt":
return "".join(
f"[{group}]\n" + "".join(f"{name} = {target}\n" for name, target in targets.items())
for group, targets in self._project.get("entry-points", {}).items()
)
return None
def locate_file(self, path: str | Path) -> Path:
"""A member's files are its directory's."""
return self._member / path
class CheckoutDistributions(metadata.DistributionFinder):
"""The finder that makes a checkout's members visible to `importlib.metadata`.
It finds no modules (`sys.path` carries the members already), only their
metadata, and it goes FIRST on `sys.meta_path` for the same reason the
members go first on `sys.path`: what `import metta_live` resolves to and
what `importlib.metadata` says about `metta-live` must be the same
checkout. `importlib.metadata` keeps the first distribution per name, so
a stale `*.egg-info` a wheel build left inside a member directory, which
the path finder would otherwise report ahead of the manifest, never wins.
"""
def find_distributions(
self, context: metadata.DistributionFinder.Context | None = None
) -> Iterator[metadata.Distribution]:
"""Every member, or the one `context.name` asks for, by normalised name."""
wanted = None if context is None or context.name is None else normalised(context.name)
for member in members():
if wanted is None or wanted == normalised(member.name):
yield CheckoutDistribution(member)
def normalised(name: str) -> str:
"""PEP 503's project-name normalisation: `metta_live`, `Metta-Live` and `metta-live` are one name.
The ONE home for that rule. uv, pip and `importlib.metadata` all decide
name identity this way, so anything here comparing two distribution names
raw disagrees with every tool it talks to about what a package is called.
It was private with two callers in this file while a second copy sat in
`tests/ch01_getting_started/test_packaging.py` and `check_layering.py` had
none, which is how the gate came to demand the removal of a
`[tool.uv.sources]` entry `uv lock --check` refuses to resolve without: the
workspace root declares `PyMeTTa` and keys itself `pymetta`
[measured 2026-09-21: `sh tools/check.sh layering` reported
"names pymetta, which is no longer a workspace member" against a tree uv
resolves; commit=c6ed562a1a6f964aba906206f2558489b107dc24]
[source: https://peps.python.org/pep-0503/#normalized-names].
"""
return re.sub(r"[-_.]+", "-", name).lower()
def on_path() -> list[str]:
"""Make every member importable and discoverable, answering their module names."""
for member in members():
location = str(member)
if location not in sys.path:
sys.path.insert(0, location)
if not any(isinstance(finder, CheckoutDistributions) for finder in sys.meta_path):
sys.meta_path.insert(0, CheckoutDistributions())
return module_names()