Source code for codegen.config

"""Load and validate target config files.

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

from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, ConfigDict, ValidationError

from codegen import jsonnet
from codegen.errors import ConfigError

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


[docs] class CodegenConfig(BaseModel): """Base class for target-level config schemas. Targets registering with codegen should subclass this so their config model carries the codegen-recognized meta fields. Today that is just :attr:`package_prefix`; more may be added as codegen 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:`~codegen.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 (be uses ``"_generated"``). """ package_prefix: str = ""
[docs] class ExtensibleConfig(BaseModel): """Base for config nodes whose extra keys feed an op's options. Targets that dispatch ops by a field value (``name``, ``type``, ...) and pass every other key through to the op's own ``Options`` model can subclass this to inherit ``extra="allow"`` plus the :attr:`options` accessor instead of re-spelling them per config class. be uses it for :class:`be.config.schema.OperationConfig` and :class:`be.config.schema.ModifierConfig`; other targets that need the same dispatch shape can do the same. """ model_config = ConfigDict(extra="allow") @property def options(self) -> dict[str, Any]: """Op-specific options (every key not declared on the model).""" return self.model_extra or {}
[docs] def load_config( path: Path, schema: type[CodegenConfig], stdlibs: Mapping[str, Path] | None = None, ) -> CodegenConfig: """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:`codegen.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:`codegen.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)