mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
builder.py imported utils.env.handlers.infinito.ip_range directly, while ip_range imports EnvBuilder from builder for its annotations. CodeQL reported that as py/unsafe-cyclic-import on PR #999. The import only runs under TYPE_CHECKING, so nothing breaks at runtime, but builder now takes ip_range from utils.env.handlers like ORDERED_HANDLERS, the path every other handler module already uses. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
utils/env/handlers 🧩
Per-variable mapping logic for the .env generator.
For the package's overall role see README.md.
For general README conventions see 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 iterates the registry in __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.pyINFINITO_GHCR_MIRROR_PREFIX->infinito/ghcr_mirror_prefix.py
- The
infinito_prefix in the filename MUST be present iff the owned key carries theINFINITO_prefix. - Aggregate handlers (one helper call resolves several keys at once) MUST take the singular topic name without a key suffix.
Example:
infinito/inventory.pyresolvesINFINITO_INVENTORY_DIR,INFINITO_INVENTORY_FILE, andINFINITO_INVENTORY_HOST_VARS_FILEfrom onescripts/inventory/resolve.shinvocation. - The trivial static passthroughs live in two collector files:
passthrough.py(always emitted) andgha_passthrough.py(emitted only whenctx.on_ghais true).
Module Shape 📐
Every handler module MUST expose the same surface so the registry can call it uniformly:
"""<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: ...
KEYMUST be the owned variable's name.COMMENTMUST be a single line, no em dashes, that ends up above the key in the generated.env.applyMUST take(eb, ctx)and returnNone.- A handler that targets a conditional branch (GHA-only, NIX-only, ...) MUST self-check the relevant
ctxflag 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, 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.filesand yaml_bootstrap.py qualify;utils.cache.yamldoes not, because it imports PyYAML. Read SPOT files withutils.cache.files.read_textplusutils.yaml_bootstrap.load_blockor a stdlib line parse.load_blockaccepts block mappings, block lists and string scalars only, and raisesBootstrapYamlErroron everything else, so a SPOT file read this way MUST stay inside that subset. test_bare_bootstrap.py enforces this by running the generator with every non-stdlib import blocked. - A handler MUST NOT import from writer.py or builder.py at runtime.
The
EnvBuilderandBuildContexttypes MAY be referenced underTYPE_CHECKINGfor annotations only.
Registry 📋
- __init__.py MUST list every handler module in
ORDERED_HANDLERS. - The order in
ORDERED_HANDLERSMUST respect data dependencies. Example:infinito_containerruns afterpassthroughso it can read the resolvedINFINITO_DISTRO. - Adding a handler MUST also bump the relevant baseline test in test_dotenv_generator.py when the new key is a static-default in default.env, so the drift test stays honest.