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.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:

"""<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, 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 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 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 EnvBuilder and BuildContext types MAY be referenced under TYPE_CHECKING for annotations only.

Registry πŸ“‹ΒΆ

  • 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 when the new key is a static-default in default.env, so the drift test stays honest.