mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
The env generator runs on the bare bootstrap python, before any dependency is installed. utils/distros.py reached PyYAML through utils.cache.yaml, so `python -m cli.meta.env` died with ModuleNotFoundError on every dev-runtime image that ships python3 without PyYAML. utils/yaml_bootstrap.load_block reads the block-YAML subset the SPOT files use with stdlib only. It raises BootstrapYamlError on anything it cannot parse identically to yaml.safe_load, so meta/distros.yml fails loud instead of drifting. The per-line regex lint meant to guard this is gone: one hop of indirection defeated it and it stayed green while the invariant was broken. It is replaced by a test that runs the real generator with every non-stdlib import blocked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
67 lines
3.9 KiB
Markdown
67 lines
3.9 KiB
Markdown
# utils/env/handlers 🧩
|
|
|
|
Per-variable mapping logic for the `.env` generator.
|
|
For the package's overall role see [README.md](../README.md).
|
|
For general README conventions see [documentation.md](../../../docs/contributing/documentation.md).
|
|
|
|
## Purpose 🎯
|
|
|
|
Each module in this directory owns exactly one variable, or one tightly-coupled group resolved by a single helper call.
|
|
The orchestrator in [builder.py](../builder.py) iterates the registry in [__init__.py](__init__.py) and calls each handler's `apply(eb, ctx)` in order.
|
|
|
|
## File Naming 🏷️
|
|
|
|
- A handler module name MUST match the lowercase of the variable it owns:
|
|
- `INFINITO_CONTAINER` -> `infinito/container.py`
|
|
- `INFINITO_GHCR_MIRROR_PREFIX` -> `infinito/ghcr_mirror_prefix.py`
|
|
- The `infinito_` prefix in the filename MUST be present iff the owned key carries the `INFINITO_` prefix.
|
|
- Aggregate handlers (one helper call resolves several keys at once) MUST take the singular topic name without a key suffix.
|
|
Example: `infinito/inventory.py` resolves `INFINITO_INVENTORY_DIR`, `INFINITO_INVENTORY_FILE`, and `INFINITO_INVENTORY_HOST_VARS_FILE` from one `scripts/inventory/resolve.sh` invocation.
|
|
- The trivial static passthroughs live in two collector files: `passthrough.py` (always emitted) and `gha_passthrough.py` (emitted only when `ctx.on_gha` is true).
|
|
|
|
## Module Shape 📐
|
|
|
|
Every handler module MUST expose the same surface so the registry can call it uniformly:
|
|
|
|
```python
|
|
"""<one-line docstring: what variable, what it derives from>."""
|
|
|
|
from __future__ import annotations
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from utils.env.builder import BuildContext, EnvBuilder
|
|
|
|
KEY = "INFINITO_..."
|
|
COMMENT = "<single-line per-key comment, mirrors default.env style>"
|
|
|
|
|
|
def apply(eb: EnvBuilder, ctx: BuildContext) -> None: ...
|
|
```
|
|
|
|
- `KEY` MUST be the owned variable's name.
|
|
- `COMMENT` MUST be a single line, no em dashes, that ends up above the key in the generated `.env`.
|
|
- `apply` MUST take `(eb, ctx)` and return `None`.
|
|
- A handler that targets a conditional branch (GHA-only, NIX-only, ...) MUST self-check the relevant `ctx` flag and return early.
|
|
The registry calls every handler unconditionally.
|
|
|
|
## Import Rules 🔗
|
|
|
|
- A handler MUST NOT import another handler.
|
|
Cross-handler data flows through `eb.get(...)` / `eb.set(...)` only.
|
|
- A handler MAY import stdlib, [parser.py](../parser.py), [runtime.py](../runtime.py), and any repo module whose own import closure is stdlib-only.
|
|
The whole closure MUST import and run on the bare bootstrap python, before any dependency is installed.
|
|
`utils.cache.files` and [yaml_bootstrap.py](../../yaml_bootstrap.py) qualify; `utils.cache.yaml` does not, because it imports PyYAML.
|
|
Read SPOT files with `utils.cache.files.read_text` plus `utils.yaml_bootstrap.load_block` or a stdlib line parse.
|
|
`load_block` accepts block mappings, block lists and string scalars only, and raises `BootstrapYamlError` on everything else, so a SPOT file read this way MUST stay inside that subset.
|
|
[test_bare_bootstrap.py](../../../tests/integration/meta/env/test_bare_bootstrap.py) enforces this by running the generator with every non-stdlib import blocked.
|
|
- A handler MUST NOT import from [writer.py](../writer.py) or [builder.py](../builder.py) at runtime.
|
|
The `EnvBuilder` and `BuildContext` types MAY be referenced under `TYPE_CHECKING` for annotations only.
|
|
|
|
## Registry 📋
|
|
|
|
- [__init__.py](__init__.py) MUST list every handler module in `ORDERED_HANDLERS`.
|
|
- The order in `ORDERED_HANDLERS` MUST respect data dependencies.
|
|
Example: `infinito_container` runs after `passthrough` so it can read the resolved `INFINITO_DISTRO`.
|
|
- Adding a handler MUST also bump the relevant baseline test in [test_dotenv_generator.py](../../../tests/integration/meta/env/test_dotenv_generator.py) when the new key is a static-default in [default.env](../../../default.env), so the drift test stays honest.
|