Source code for foundry.config

"""Load and validate target config files.

Supports ``.json`` files directly and ``.jsonnet`` files via
:mod:`foundry.jsonnet`, which adds prefix-based stdlib imports so
targets can ship their own libsonnet helpers under a registered
prefix (e.g. ``import 'kiln/auth/jwt.libsonnet'``).
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from pydantic import BaseModel, ValidationError

from foundry import jsonnet
from foundry.errors import ConfigError

if TYPE_CHECKING:
    from collections.abc import Mapping
    from pathlib import Path


[docs] class FoundryConfig(BaseModel): """Base class for target-level config schemas. Targets registering with foundry should subclass this so their config model carries the foundry-recognized meta fields. Today that is just :attr:`package_prefix`; more may be added as foundry grows. Target-specific fields (auth, databases, apps, routes, whatever) are declared by the subclass. Attributes: package_prefix: Dotted prefix prepended to the dotted import path of every :class:`~foundry.spec.GeneratedFile` (and used as the on-disk directory prefix). Empty string disables the prefix. Targets that generate Python (or another language with package semantics) should override the default to something sensible (kiln uses ``"_generated"``). """ package_prefix: str = ""
[docs] def load_config( path: Path, schema: type[FoundryConfig], stdlibs: Mapping[str, Path] | None = None, ) -> FoundryConfig: """Load and validate a config file against *schema*. Args: path: Path to a ``.json`` or ``.jsonnet`` file. schema: Pydantic model used to validate the parsed data. stdlibs: Optional mapping of jsonnet import prefix to stdlib directory. See :func:`foundry.jsonnet.evaluate`. Returns: Validated model instance of *schema*. Raises: ConfigError: If the file is missing, has an unsupported extension, fails to parse, or fails schema validation. """ raw = _read_source(path, stdlibs or {}) try: return schema.model_validate_json(raw) except ValidationError as exc: msg = f"Invalid config in {path}: {exc}" raise ConfigError(msg) from exc
def _read_source(path: Path, stdlibs: Mapping[str, Path]) -> str: """Read *path* as JSON or Jsonnet source, returning JSON text. ``.jsonnet`` is evaluated via :func:`foundry.jsonnet.evaluate` with *stdlibs* wired in; ``.json`` is returned verbatim. """ suffix = path.suffix.lower() try: if suffix == ".jsonnet": return jsonnet.evaluate(path, stdlibs) if suffix == ".json": return path.read_text() except RuntimeError as exc: msg = f"Jsonnet evaluation failed for {path}: {exc}" raise ConfigError(msg) from exc except OSError as exc: msg = f"Could not read {path}: {exc}" raise ConfigError(msg) from exc msg = f"Unsupported config format: {suffix!r} (use .json or .jsonnet)" raise ConfigError(msg)