feat(cli): console entry points + the demo's stderr damping (P4 pkt. 5 og 2)
Two commands are now part of the install surface a fresh clone gets from `uv sync`: `portfolio-optimiser` (run:main) and `portfolio-optimiser-demo` (simulation:main). Deliberately two of five main()s — costsim/hitl/preflight stay module-invoked; every name here is a name the freeze has to carry. Pinned against the INSTALLED distribution's metadata, not the TOML: a [project.scripts] line that has never been synced is a claim, not a command. Measured: stdout is byte-identical across both invocation forms. stderr (P4 pkt. 2), the session's open decision, resolved by measurement rather than by preference. Damped: the round-cap notice only, via a filter on the emitting logger, keyed on the message and installed by main() — never at import, so a library consumer keeps its own logging config. NOT damped: the two ExperimentalWarnings. They fire while the package __init__ imports run -> agent_framework, always before simulation's own imports and under both invocation forms, so silencing them would mean filtering warnings inside the library package on every consumer's behalf; they are pinned in pkt. 3 instead. A console-script wrapper was rejected for a second reason: the two forms would then write different stderr, and a byte-fasit would pin the command rather than the program. stderr 6 -> 4 lines. A first implementation wrapped simulation's own agent_framework import in a scoped mute. Measurement showed it can never fire — the package __init__ has already imported agent_framework by then — so it was removed rather than left as a green-but-dead seam. Load-bearing MEASURED against the whole suite, five mutations all red + green control: remove [project.scripts] · typo the target · detach the main() call · make the filter drop everything · install the filter at import time. The typo mutation also felled a test: the resolve-assert re-checked the expected constant against itself, and now resolves what the distribution actually installs. 775 -> 785 passed / 4 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C2bxLcCRguxXzpM4priTMn
This commit is contained in:
parent
1522e2aaaa
commit
ab7f45aa95
6 changed files with 298 additions and 2 deletions
87
tests/test_console_entry_points.py
Normal file
87
tests/test_console_entry_points.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
"""P4 pkt. 5 — the console entry points are part of the FROZEN install surface.
|
||||
|
||||
The README's central claim is "download -> run". Until now every documented invocation went
|
||||
through ``uv run python -m portfolio_optimiser.<module>``, which works but is not an install
|
||||
surface: nothing in the distribution metadata promised a command. This test pins the two commands
|
||||
that the demo and the framework CLI are reached by.
|
||||
|
||||
Why read ``importlib.metadata`` and not ``pyproject.toml``: a ``[project.scripts]`` line that has
|
||||
never been ``uv sync``-ed is a claim, not a command. The distribution metadata is what a fresh
|
||||
clone materializes after ``uv sync``, so it is the only reading that can fail when the surface is
|
||||
merely *declared*. The declaration is checked too (the TOML is the source the metadata is built
|
||||
from), but the metadata assert is the load-bearing one.
|
||||
|
||||
Scope, stated so it is a decision and not an oversight: exactly TWO commands are exposed. ``run``
|
||||
is the framework CLI (three documented modes) and ``simulation`` is the offline end-to-end proof
|
||||
the README points a newcomer at. ``costsim`` / ``hitl`` / ``preflight`` keep the ``-m`` form — they
|
||||
are operator utilities, not the product's front door, and every name added here is a name the
|
||||
freeze has to carry.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import importlib.metadata
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
_DIST = "portfolio-optimiser"
|
||||
|
||||
# command name -> "module:function" target, verbatim as it must appear in the metadata.
|
||||
_EXPECTED: dict[str, str] = {
|
||||
"portfolio-optimiser": "portfolio_optimiser.run:main",
|
||||
"portfolio-optimiser-demo": "portfolio_optimiser.simulation:main",
|
||||
}
|
||||
|
||||
|
||||
def _console_scripts() -> dict[str, str]:
|
||||
"""The installed distribution's console scripts, as ``{name: "module:function"}``."""
|
||||
return {
|
||||
ep.name: ep.value
|
||||
for ep in importlib.metadata.distribution(_DIST).entry_points
|
||||
if ep.group == "console_scripts"
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("name", "target"), sorted(_EXPECTED.items()))
|
||||
def test_console_script_is_installed(name: str, target: str) -> None:
|
||||
"""T-P4.5a: the command exists in the INSTALLED distribution and points at the right target.
|
||||
|
||||
RED when the ``[project.scripts]`` entry is removed (or when it is added to the TOML without a
|
||||
re-sync — which is the same failure a fresh clone would hit for real).
|
||||
"""
|
||||
scripts = _console_scripts()
|
||||
assert name in scripts, (
|
||||
f"console script {name!r} is not installed; found {sorted(scripts)}. "
|
||||
"Declare it under [project.scripts] in pyproject.toml and re-run `uv sync`."
|
||||
)
|
||||
assert scripts[name] == target
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", sorted(_EXPECTED))
|
||||
def test_console_script_target_resolves(name: str) -> None:
|
||||
"""T-P4.5b: the INSTALLED target actually imports and is callable — a typo'd module or function
|
||||
name installs a command that only fails when the operator runs it, which on demo day is on
|
||||
stage. Resolves what the distribution says, not what this file expects: resolving ``_EXPECTED``
|
||||
would only ever re-check a constant against itself."""
|
||||
target = _console_scripts()[name]
|
||||
module_name, _, func_name = target.partition(":")
|
||||
module = importlib.import_module(module_name)
|
||||
entry = getattr(module, func_name, None)
|
||||
assert callable(entry), f"{target} (behind {name}) does not resolve to a callable"
|
||||
|
||||
|
||||
def test_pyproject_declares_exactly_these_scripts() -> None:
|
||||
"""T-P4.5c: the declaration in ``pyproject.toml`` matches the installed set exactly.
|
||||
|
||||
Guards the drift direction the metadata assert cannot see: a script installed from an older
|
||||
sync but since deleted from the TOML would leave a command that a fresh clone never gets.
|
||||
"""
|
||||
pyproject = Path(__file__).resolve().parents[1] / "pyproject.toml"
|
||||
try:
|
||||
import tomllib
|
||||
except ModuleNotFoundError: # pragma: no cover - Python 3.10 has no tomllib
|
||||
pytest.skip("tomllib is 3.11+; the installed-metadata asserts cover the same surface")
|
||||
declared = tomllib.loads(pyproject.read_text(encoding="utf-8"))["project"]["scripts"]
|
||||
assert declared == _EXPECTED
|
||||
Loading…
Add table
Add a link
Reference in a new issue