Reference

Config schema

Each target has its own pydantic schema that its config files parse into:

The classes below correspond directly to the fields you write in the matching .jsonnet / .json file for that target.

be config schema

Field types

The type field on FieldSpec accepts:

Type

Python annotation

Used in

Notes

uuid

uuid.UUID

request/response schemas, pk

Default for primary keys.

str

str

schemas, action params

email

str

schemas

Added pydantic.EmailStr validation.

int

int

schemas, pk

float

float

schemas

bool

bool

schemas

datetime

datetime.datetime

schemas

date

datetime.date

schemas

json

dict[str, Any]

schemas

nested

generated sub-schema class

read-op schemas (get / list)

Dumps a related model inline. Requires model and fields; see Nested (related-model) fields below.

be_root config schema

fe config schema

fe_root config schema

Built-in operations

Every built-in operation is registered under its target’s own entry-point group in pyproject.toml: be.operations for the be target, be_root.operations for be_root, and so on. See Usage for what each one generates and Extending kiln for the operation protocol.

The table below covers be’s built-in ops; be_root, fe, and fe_root each ship a single project-scope RootScaffold / OpenApiTsConfig op (see be_root config schema, fe config schema, fe_root config schema for the configs that drive them).

Name

Module

Scope

Description

scaffold

be.operations.scaffold

project

Two project-scope ops live here. Scaffold always emits db/*_session.py. AuthScaffold emits the auth/ package when config.auth is set.

get / list / create / update / delete

be.operations.get, list, create, update, delete

resource

The five CRUD endpoints. Each op lives in its own module alongside the FastAPI renderer for its output.

action

be.operations.action

resource

Custom action endpoints: POST /{pk}/{slug} for per-instance actions, POST /{slug} for collection-level actions.

auth

be.operations.auth

resource

Cross-cutting augmenter. Appends current_user dependency to every CRUD / action handler when config.auth is set.

router

be.operations.routing

app

Emits routes/__init__.py for one app, aggregating every resource router via include_router.

project_router

be.operations.routing

project

Multi-app projects only. Emits the top-level routes/__init__.py that mounts each app at its prefix.

Generated file layout

The table below summarises every file be can produce. Paths are relative to the --out directory (or to the config’s package_prefix when --out is omitted). {module} is the app’s module config field. {name} is the lowercase, snake-cased model name.

Path

Produced by

Overwrite

db/__init__.py

scaffold

Yes

db/{db_key}_session.py (or db/session.py)

scaffold

Yes

auth/__init__.py

scaffold

Yes

auth/dependencies.py

scaffold

Yes

auth/router.py

scaffold

Yes

{module}/schemas/{name}.py

get / list / create / update

Yes

{module}/serializers/{name}.py

get / list

Yes

{module}/routes/{name}.py

CRUD + action

Yes

{module}/routes/__init__.py

router

Yes

{module}/tests/test_{name}.py

CRUD + action (when generate_tests: true)

Yes

routes/__init__.py

project_router

Yes

Every file is overwritten on every generation run.

codegen API

Targets

class Target(name, language, schema, template_dir, operations_entry_point, jsonnet_stdlib_dir=None)[source]

A concrete code-generation target.

name

Short identifier, used for --target dispatch when multiple targets are installed and as the jsonnet stdlib import prefix.

language

Language-identifier the target generates for (e.g. "python"). Passed to codegen.imports.format_imports() so the assembler renders import blocks in the right syntax. Targets declare their formatter under the codegen.import_formatters entry-point group.

schema

CodegenConfig subclass the target’s config files validate against. Codegen’s loader instantiates this.

template_dir

Directory of Jinja templates the target’s renderers reference. Codegen builds the Jinja environment rooted here.

operations_entry_point

Entry-point group name where the target’s @operation-decorated classes are registered, e.g. "be.operations" or "be_root.operations". At build time the pipeline calls load_registry() against this group to assemble a fresh, target-private registry; targets installed side-by-side never see each other’s ops.

jsonnet_stdlib_dir

Optional directory of jsonnet .libsonnet files exposed to configs as <name>/... imports. None when the target ships no stdlib.

discover_targets()[source]

Load every Target registered under codegen.targets.

Return type:

list[Target]

Returns:

All installed targets, in entry-point discovery order.

exception CLIError[source]

Base class for errors the CLI should render cleanly.

Subclasses set prefix to control how the error is labelled when rendered at the CLI boundary.

prefix: str = 'Error'
exception ConfigError[source]

Raised when a config file can’t be loaded or is invalid.

prefix: str = 'Error loading config'
exception GenerationError[source]

Raised when file generation fails due to bad config semantics.

prefix: str = 'Error'

Engine

class Engine(registry=<factory>, package_prefix='')[source]

Orchestrates the build phase of code generation.

registry

OperationRegistry holding the ops to run. Defaults to an empty registry – production callers always pass one populated by codegen.operation.load_registry(); tests pass their own isolated registry.

package_prefix

Dotted prefix for generated imports, forwarded to every BuildContext.

build(config)[source]

Run the build phase over all scopes and operations.

Walks the scope tree depth-first. At each scope instance, pre-phase operations (after_children=False) run before descending into children; post-phase operations (after_children=True) run after every child scope instance completes, so they can aggregate earlier output from the store.

Parameters:

config (BaseModel) – The project config model instance.

Return type:

BuildStore

Returns:

An BuildStore containing all objects produced by operations.

class BuildContext(config, scope, instance, instance_id, store, package_prefix='')[source]

Context passed to every operation’s build method.

Parameterized on two types:

  • InstanceT – the scope’s per-instance config (e.g. ResourceConfig for resource-scope ops).

  • ConfigT – the project root config. The engine itself uses BuildContext[Any, BaseModel] since it’s target-agnostic; target-specific ops (e.g. all of be) annotate BuildContext[X, ProjectConfig] and get typed access to ctx.config.* without casting.

config

The full project config (top-level model).

scope

The scope this operation is running in.

instance

The config object for the current scope instance (e.g. one resource’s config dict).

instance_id

Human-readable identifier for the instance within its scope.

store

The build store for querying earlier operations’ output.

package_prefix

Dotted prefix for generated imports (e.g. "_generated"). Extensions use this to resolve their own import paths.

Operations

operation(name, *, scope, requires=None, after_children=False, dispatch_on=None, registry=None)[source]

Decorate a class as a be operation.

The decorated class must define:

  • Options: a pydantic.BaseModel subclass (defaults to EmptyOptions if absent).

  • build(self, ctx, options) -> list: produces output objects for the engine to collect.

Optionally it may define:

  • when(self, ctx) -> bool: when present and returning False, the engine skips this operation for the current build context. Use this for conditional operations (e.g. auth, which only runs when the project has auth configured).

Operations can also modify earlier operations’ outputs by inspecting store and mutating the objects returned by outputs_under() in place. Combined with requires for ordering and when for activation, a single operation mechanism covers both “produce” and “augment” roles.

The decorator stashes the captured OperationMeta on the class under _operation_meta; load_registry() reads it back at entry-point load time. When registry is supplied (the test path) the decorator also pushes the entry into that registry directly, so unit tests can keep ops out of the entry-point flow entirely.

Parameters:
  • name (str) – Unique operation name.

  • scope (str) – Scope name (e.g. "resource", "app", "project").

  • requires (list[str] | None) – Operation names that must run first.

  • after_children (bool) – When True (project scope only), defer this operation until every child scope has executed so build can walk child output in the store. The engine rejects this flag at any other scope.

  • dispatch_on (str | None) – Attribute name on the scope instance to compare against name. When set, the engine skips the op unless getattr(ctx.instance, dispatch_on) == name. Designed for scopes whose instance is a discriminated-union config (e.g. OperationConfig entries under a resource), where multiple ops share one scope and each matches a single entry.

  • registry (OperationRegistry | None) – Optional registry to push into directly. None (the production path) means the decorator only stashes meta on the class; the pipeline picks it up later via load_registry(). Tests pass an isolated registry to keep their ops out of the entry-point flow.

Return type:

Any

Returns:

Class decorator.

Example:

@operation("get", scope="resource")
class Get:
    class Options(BaseModel):
        fields: list[FieldSpec] | None = None

    def build(self, ctx, options):
        return [RouteHandler(...)]
load_registry(entry_point_group)[source]

Build a fresh OperationRegistry from an entry-point group.

Walks entry_point_group, loads each declared class, and registers it via the OperationMeta stashed on the class by the operation() decorator. Each call returns a brand-new registry, so two targets sharing a process never see each other’s ops.

Parameters:

entry_point_group (str) – Dotted entry-point group name, e.g. "be.operations" or "be_root.operations".

Return type:

OperationRegistry

Returns:

A populated OperationRegistry.

Raises:

TypeError – If a discovered class is missing the _operation_meta attribute – typically because the class wasn’t decorated with operation().

class OperationMeta(name, scope, requires=(), after_children=False, dispatch_on=None)[source]

Metadata attached to a decorated operation class.

name

Unique operation name (e.g. "get").

scope

Scope name this operation runs in (e.g. "resource").

requires

Names of operations that must run before this one within the same scope.

after_children

When True, this project-scope operation runs after all child scopes have executed, so its build method can inspect objects produced at the resource/app scopes via the build store. Ignored outside the project scope (the engine raises if set).

dispatch_on

Attribute name on ctx.instance whose value must equal name for this op to run. Engine skips the op silently when the attribute is absent or mismatched. Use it at scopes where the instance is a discriminated union — every registered op at the scope shares the scope walk and each dispatches to its own entry by name.

class EmptyOptions(**data)[source]

Default options model for operations with no config.

model_config: ClassVar[ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class OperationRegistry(entries=<factory>)[source]

Collection of (meta, cls) entries with query helpers.

Populated by the operation() decorator at decoration time. The engine reads entries from the registry to walk scopes, group by scope, and topo-sort within a scope — it never needs to look up metadata on individual classes.

entries

(meta, cls) pairs, in registration order.

register(meta, cls)[source]

Append an OperationEntry to entries.

Return type:

None

sorted_by_scope()[source]

Group entries by scope and topo-sort each bucket.

Phase (pre vs post) is encoded on meta.after_children and split out at runtime by the engine; a single sorted list per scope is enough. Scopes with no registered ops are omitted — the engine uses dict.get(scope, []).

Return type:

dict[str, list[OperationEntry]]

Returns:

Mapping from scope name to topo-sorted (meta, cls) entries.

validate_scopes(known)[source]

Raise if any operation targets a scope outside known.

Parameters:

known (set[str]) – Set of scope names discovered from the config model.

Raises:

ValueError – If an op’s declared scope is not in known.

Return type:

None

Scopes

class Scope(name, config_key, parent=None, resolve_path=())[source]

A named level in the config tree.

name

Human-readable scope name, e.g. "resource".

config_key

The config field name that produced this scope, e.g. "resources". Empty string for the root (project) scope.

parent

The parent scope, or None for the root.

resolve_path

Dotted attribute path from parent’s scope instance to this scope’s list of items. Empty for the root scope. Defaults to (config_key,) for a direct child.

class ScopeTree(iterable=(), /)[source]

Flat collection of scopes with convenience lookups.

Subclassing tuple gives callers the usual iteration/index/len ergonomics for free, while the two methods below handle the recurring “children of X” and “scope for id Y” patterns so the engine, store, and assembler don’t each reinvent the search.

Construct from discover_scopes() output:

tree = ScopeTree(discover_scopes(MyConfig))
children_of(parent)[source]

Return direct children of parent, in discovery order.

Return type:

list[Scope]

scope_for(instance_id)[source]

Return the Scope an instance_id belongs to.

Instance ids produced by the engine are dot-joined paths of the form "project.<config_key>.<index>...". Each config_key maps to exactly one child scope at the current level, so the scope is recovered by walking the tree from PROJECT using segment pairs.

Parameters:

instance_id (str) – A dot-path instance id (e.g. "project.apps.0.resources.2").

Return type:

Scope

Returns:

The Scope the id terminates at.

Raises:

ValueError – If the id doesn’t start with "project" or references a config_key not present in this tree.

class Scoped(name)[source]

Annotated marker tagging a list field as a codegen scope.

Attach to a list[SomeBaseModel] field on a CodegenConfig subclass to declare that the field defines a scope level. Only marked fields produce scopes; unmarked lists are plain data.

name

The scope’s name, e.g. "app" or "resource". Required — field names on configs are conventionally plural (apps, resources), but a scope refers to one instance, so the name is spelled out explicitly rather than derived by a heuristic.

discover_scopes(config_cls)[source]

Derive scopes from a Pydantic model’s Scoped markers.

The top-level config is always the "project" scope. Each field declared Annotated[list[T], Scoped()] becomes a child scope of the current level; the item type T is then itself descended into to discover grandchild scopes.

Non-list BaseModel fields (e.g. App.config) are traversed transparently: their nested Scoped fields become scopes rooted at the enclosing level, with resolve_path reflecting the full attribute walk.

Each scoped item type is descended into at most once, so discovery always terminates. If the same type appears in multiple scoped lists only the first occurrence is descended — subsequent ones still produce their own scope but no grandchildren.

Parameters:

config_cls (type[BaseModel]) – The Pydantic model class to inspect.

Return type:

ScopeTree

Returns:

ScopeTree containing every discovered scope, project first.

codegen.scope.PROJECT

The root scope – always present in every generation run.

Typed outputs

Every operation’s build method returns instances of the types below. Framework-agnostic types live in codegen.outputs; FastAPI-specific output dataclasses live in be.operations.types.

class StaticFile(path, template, context=<factory>, if_exists='overwrite', executable=False, banner=True)[source]

A file rendered directly from a template.

Used for scaffold files (auth, db sessions), utils, and other files that don’t need the assembler’s multi-contributor merging.

if_exists defaults to "overwrite" (be’s regenerated scaffold behaviour); "skip" makes codegen.output.write_files() leave existing files alone – right for one-shot bootstraps like be_root, where --force / --force-paths is the explicit opt-in to clobber.

executable sets the user/group/other +x bits on the emitted file after write – use for shell scripts the surrounding justfile / package.json invokes via a bare ./scripts/foo.sh rather than bash ./scripts/foo.sh.

banner defaults to True; set False to suppress the engine-emitted autogenerated header for files that must not carry one.

banner: bool = True
context: dict[str, Any]
executable: bool = False
if_exists: Literal['overwrite', 'skip'] = 'overwrite'
path: str
template: str

Render registry

class RenderRegistry(_entries=<factory>)[source]

Maps output types to renderer functions.

Example:

registry = RenderRegistry()

@registry.renders(RouteHandler)
def render_route(handler, ctx):
    return Fragment(...)
render(obj, ctx)[source]

Produce fragments for a build output.

Every registered renderer returns an iterable of fragments (typically as a generator via yield). Renderers usually yield a FileFragment declaring the output file plus one or more SnippetFragment contributions into its slots.

Parameters:
  • obj (object) – The build output to render.

  • ctx (RenderCtx) – Render context.

Return type:

list[FileFragment | SnippetFragment]

Returns:

A list of fragments. May be empty if the renderer decides not to contribute.

Raises:

LookupError – No renderer registered for the type.

renders(output_type)[source]

Register a renderer for output_type.

Parameters:

output_type (type) – The output class this renderer handles.

Return type:

Callable[[Callable[[Any, RenderCtx], Iterable[FileFragment | SnippetFragment]]], Callable[[Any, RenderCtx], Iterable[FileFragment | SnippetFragment]]]

Returns:

The original function, unmodified.

class RenderCtx(env, config, package_prefix='', language='', target_name='', store=<factory>, instance_id='')[source]

Context passed to every renderer function.

env

Jinja2 environment for template lookups.

config

The full project config dict (or model).

package_prefix

Dotted prefix for generated imports, e.g. "_generated".

language

Target language identifier used to render import blocks (e.g. "python"). Must match a formatter declared in the codegen.import_formatters entry-point group.

target_name

The target’s short name (e.g. "be"), used by the engine-emitted file banner to name the codegen generate --target <name> command that produced the file.

store

The build store. Renderers reach ancestor scope instances through it (e.g. a handler rendered at operation scope looks up its resource via store.ancestor_of(instance_id, "resource")).

instance_id

Id of the scope instance whose output is being rendered. Paired with store for ancestor and self lookups.

class BuildStore(scope_tree=<factory>, _items=<factory>, _instances=<factory>, _children=<factory>, _parent_of=<factory>)[source]

Accumulator for objects produced during the build phase.

Outputs are keyed by (instance_id, op_name). Ancestry between instances is tracked separately so tree walks don’t have to parse dot-path ids.

Query methods at a glance

Scope-instance lookup (return config objects, not outputs):

  • ancestor_of() — walk up to find an enclosing scope’s config instance.

  • ancestor_id_of() — same walk, but return the id. Useful when you need the id to pass into the output-query methods below.

  • children() — direct children of an instance, optionally filtered by child scope.

  • scope_of() — resolve an id’s Scope.

Output lookup (return objects emitted by ops):

  • outputs_under() — every output of a type at or below a given instance id. Good for aggregate passes (Auth at resource scope sweeping handlers).

  • outputs_under_ancestor() — walk up to a named scope first, then collect. Good for ops that need to reach sideways via a shared ancestor.

  • output_under_ancestor() — singular form; raises if nothing matches. For ops that expect exactly one target (e.g. a modifier op finding its parent op’s ListResult).

  • entries() — raw (instance_id, op_name, items) tuples. The assembler uses this; ops rarely need to.

Mutation:

  • add() — engine calls this with an op’s yielded outputs. Ops normally don’t call it directly.

  • register_instance() — engine calls this before invoking build() at a scope instance. Ops never call it.

Typical extension recipes

Read an ancestor’s config (e.g. resource model from operation scope):

resource = ctx.store.ancestor_of(ctx.instance_id, "resource")

Augment every handler in your subtree (e.g. Auth):

for handler in ctx.store.outputs_under(
    ctx.instance_id, RouteHandler
):
    handler.extra_deps.append(...)

Reach a specific output your parent scope produced (e.g. a modifier finding its parent op’s bundle):

bundle = ctx.store.output_under_ancestor(
    ctx.instance_id, "operation", ListResult
)
bundle.search_request.body_context["has_filter"] = True
scope_tree

ScopeTree for the build’s config. Required for the scope_of() derivation (and therefore for child_scope= filtering on children()). Defaults to empty so ad-hoc store-level tests can skip it when they don’t care.

_items

Internal storage mapping (instance_id, op_name) keys to object lists.

_instances

Map from instance_id to the scope-instance config object.

_children

Map from a parent instance id to its registered child instance ids, in insertion order.

_parent_of

Map from an instance id to its parent id; drives ancestor_of() / ancestor_id_of().

add(instance_id, op_name, *objects)[source]

Store build outputs for a build step.

Parameters:
  • instance_id (str) – Dot-path id produced by the engine.

  • op_name (str) – Operation name that produced these objects.

  • *objects (object) – The build outputs to store.

Return type:

None

ancestor_id_of(instance_id, scope_name)[source]

Return the enclosing instance id at scope_name, if any.

Mirrors ancestor_of() but returns the ancestor’s id instead of its instance. Ops that need to scan outputs under a higher scope use this to get the id outputs_under() wants.

Return type:

str | None

ancestor_of(instance_id, scope_name)[source]

Return the enclosing instance at scope_name, if any.

Walks _parent_of edges from instance_id toward the root and returns the first instance whose scope name matches. Used by descendant ops that need data from a higher scope (e.g. an operation-scope op reading its enclosing resource’s model).

Parameters:
  • instance_id (str) – Id whose ancestor to find.

  • scope_name (str) – Scope name of the wanted ancestor.

Return type:

object | None

Returns:

The ancestor instance, or None if no ancestor at that scope is registered.

children(parent_id, *, child_scope=None)[source]

Return child instances of parent_id.

Children come back in registration (config) order. When child_scope is given, only children in that scope are returned (requires scope_tree to be populated).

Parameters:
  • parent_id (str) – Parent instance id.

  • child_scope (str | None) – Optional scope-name filter.

Return type:

list[tuple[str, object]]

Returns:

List of (child_id, child_instance) pairs.

entries()[source]

Iterate stored entries as (instance_id, op_name, items).

Used by the assembler to walk the store and dispatch each item to the correct renderer.

Return type:

Iterator[tuple[str, str, list[object]]]

instance_at(instance_id)[source]

Return the instance registered at instance_id, if any.

Renderers occasionally need the registered instance for the current dispatch entry (e.g. an op that yields outputs at its own scope rather than at a child scope). ancestor_of() walks parents only, so a renderer dispatched at the same scope as the wanted instance cannot recover it that way – this accessor closes the gap.

Return type:

object | None

output_under_ancestor(instance_id, scope_name, output_type)[source]

Return the sole output_type output under the named ancestor.

Singular form of outputs_under_ancestor() — raises LookupError when no ancestor is registered at scope_name or when the ancestor produced no output of output_type. Returns the first match when more than one exists; callers that care about multiplicity should use the plural form.

Return type:

TypeVar(T)

outputs_under(ancestor_id, output_type)[source]

Return every output_type output at or below ancestor_id.

Walks the store by path prefix, so output produced at any depth under ancestor_id surfaces — useful for ops that aggregate or mutate outputs from deeper scopes (e.g. auth adding dependencies to every handler under a resource).

Return type:

list[TypeVar(T)]

outputs_under_ancestor(instance_id, scope_name, output_type)[source]

Return outputs under the ancestor of instance_id at scope_name.

Convenience for the common “walk up to a named scope, then look for outputs there” pattern — used by ops that need to reach outputs an ancestor (or sibling via a shared ancestor) produced. Returns [] when no ancestor at that scope is registered.

Return type:

list[TypeVar(T)]

register_instance(instance_id, instance, *, parent=None)[source]

Remember the scope-instance object for instance_id.

Called by the engine before operations run at each scope instance. Renderers access these via ancestor_of() when they need a higher scope’s config.

Parameters:
  • instance_id (str) – Dot-path id.

  • instance (object) – The scope-instance config object.

  • parent (str | None) – Id of the enclosing scope instance. When given, children() will surface this instance under parent. Omit for the project root.

Return type:

None

scope_of(instance_id)[source]

Resolve the Scope of instance_id.

Return type:

Scope

class FileFragment(path, template, context=<factory>, imports=<factory>, if_exists='overwrite', executable=False, banner=True)[source]

Declares an output file’s wrapper template and scalar context.

One FileFragment per output path describes the template the assembler wraps the file in and the non-slot context passed to it. Every SnippetFragment sharing that path contributes a slot-list item that the assembler folds into context before the wrapper is rendered.

Multiple renderers may emit a FileFragment for the same path (e.g. every route handler at the resource declares the route file) — the assembler requires them to agree on template and unifies their context dicts, raising if two disagree on a shared key.

A blank template is a convention for an empty-content file (e.g. __init__.py).

path

Output path relative to the output directory.

template

Jinja2 template name that wraps the file.

context

Non-slot template variables. Merged across all FileFragments at this path (shared keys must agree).

imports

Imports the wrapper itself needs, on top of any contributed by snippets.

if_exists

Write policy for the assembled output. Set to "skip" to make codegen.output.write_files() leave existing files alone (be_root’s re-bootstrap-safe path); defaults to "overwrite". When two fragments at the same path disagree, the assembler picks the stricter "overwrite" – a single contributor that wants the file regenerated wins over any contributor that would have been happy to skip.

executable

When True, codegen.output.write_files() chmods the emitted file +x (user/group/other). Use for shell scripts that the rendered justfile / package.json invokes via a bare ./scripts/foo.sh. Merge: True wins – any contributor that wants the bit set wins over a peer that doesn’t care.

banner

When True (default), the engine prepends the autogenerated header banner (see codegen.banner) to the rendered content. Set False to suppress it for files that must not carry one. Merge: False wins – any contributor that opts out suppresses it.

class SnippetFragment(path, slot, template=None, context=<factory>, value=None, imports=<factory>)[source]

A contribution slotted into a file’s context list.

Each snippet becomes one entry in file.context[slot] — a list the wrapper template iterates over. Snippets at the same path may target different slots.

Supply exactly one of template (rendered by the assembler into a string) or value (used as-is, may be any type — useful for dict slots the wrapper iterates over itself).

path

Output path; must match a FileFragment.

slot

Key in the file’s context this snippet appends to.

template

Jinja2 template the assembler renders against context to produce a string slot item. Mutually exclusive with value.

context

Template variables for template.

value

Raw slot item — any type, used as-is. Mutually exclusive with template.

imports

Imports this contribution needs in the output file’s import block.

render_slot_item(env)[source]

Return the slot-list item this snippet contributes.

When template is set the assembler renders it against context and strips surrounding whitespace, so the surrounding file template can join items with its own separators without fighting jinja’s trailing newline. Otherwise value is passed through unchanged.

Return type:

object

Fragment

Union of fragment types a renderer may yield.

codegen.render.registry

Process-wide RenderRegistry populated at import time.

Output

class GeneratedFile(path, content, if_exists='overwrite', executable=False)[source]

Immutable final output – a path and its content.

path

Output path relative to the output directory.

content

File contents as a string.

if_exists

Per-file write policy honored by codegen.output.write_files(). "overwrite" (the default) always replaces the target on disk – the historical behaviour every be scaffold output relied on. "skip" writes the file only if it does not yet exist; right for one-shot scaffolding (e.g. be_root’s bootstrap) where users edit the file post-generation and a re-run should be non-destructive. --force / --force-paths on the CLI override "skip" back to "overwrite" for the affected files.

executable

When True, codegen.output.write_files() chmods the emitted file +x (user/group/other) so it can be invoked as ./path/to/file rather than bash path/to/file. Pairs with shebang-bearing shell scripts emitted alongside a justfile or package.json.

write_files(files, out_dir, *, force=False, force_paths=None)[source]

Write generated files to disk, honoring per-file write policy.

Each file’s path is joined with out_dir to determine the target path. Parent directories are created as needed.

The if_exists policy decides what happens when the target already exists:

  • "overwrite" (default for be output) – replace unconditionally.

  • "skip" (be_root’s bootstrap files) – leave the existing file untouched.

force and force_paths let the caller override "skip" back to "overwrite" from the CLI without changing the file declarations themselves:

  • force=True clobbers every "skip" file.

  • force_paths={"main.py", "pyproject.toml"} clobbers only those paths – handy for resetting a single bootstrapped file without touching the rest. Each entry is a glob (fnmatch), so force_paths={"*.just"} resets every justfile module and force_paths={"config/*"} the config tree; a plain filename with no wildcard is an exact match.

Parameters:
  • files (Sequence[GeneratedFile]) – Sequence of GeneratedFile objects.

  • out_dir (Path) – Root directory for output paths.

  • force (bool) – When True, treat every file as "overwrite" regardless of its declared policy.

  • force_paths (Iterable[str] | None) – Optional collection of glob patterns (matched against path, which is relative to out_dir) whose "skip" declaration should be overridden to "overwrite". Ignored when force is True.

Return type:

int

Returns:

Number of files written (skipped files do not count).

Naming and imports

class Name(raw)[source]

Derives conventional identifiers from a base string.

Accepts either a PascalCase class name (e.g. "Article") or a snake_case identifier (e.g. "publish_article") and exposes the common derived forms used by code generators.

Examples:

model = Name("Article")
model.pascal              # "Article"
model.lower               # "article"
model.suffixed("Resource")  # "ArticleResource"

action = Name("publish_article")
action.pascal             # "PublishArticle"
action.slug               # "publish-article"
action.suffixed("Request")  # "PublishArticleRequest"
classmethod from_dotted(dotted_path)[source]

Create a Name from a dotted import path.

Parameters:

dotted_path (str) – A fully-qualified class path such as "myapp.models.Article".

Return type:

tuple[str, Name]

Returns:

A (module, Name) tuple, e.g. ("myapp.models", Name("Article")). Callers that need the raw class-name string can read it from Name.raw.

Raises:

ValueError – If dotted_path contains fewer than two parts.

property lower: str

Fully lowercased form, no separator inserted.

"Article""article", "publish_article""publish_article".

Use snake instead when the result will become a file/module/function name and the input is PascalCase – Name("NotificationPreference").lower collapses to "notificationpreference" whereas snake returns "notification_preference".

static parent_path(dotted, *, levels=1)[source]

Return dotted’s ancestor module path.

Drops the last levels dot-separated segments. When the path runs out of segments before levels are stripped, it stops and returns whatever remains rather than producing an empty string — so callers can chain the op without guarding for short paths:

>>> Name.parent_path("blog.models.Article")
'blog.models'
>>> Name.parent_path("blog.models.Article", levels=2)
'blog'
>>> Name.parent_path("single", levels=2)
'single'

levels=2 is the common idiom for “app module from model dotted path” – "blog.models.Article" -> "blog".

Return type:

str

property pascal: str

PascalCase form of the name.

If the raw string contains no underscores and already starts with an uppercase letter it is returned as-is (assumed to already be PascalCase, e.g. "StockMovement" from a dotted import path).

property slug: str

Hyphenated slug form (for URL segments).

"publish_article""publish-article", "NotificationPreference""notification-preference" (PascalCase boundaries are split first via snake).

property snake: str

snake_case form (for file/module/function names).

"Article""article", "NotificationPreference""notification_preference", "XMLParser""xml_parser", "publish_article""publish_article".

PascalCase / camelCase boundaries become underscores so multi-word model classes produce readable identifiers. Strings already containing underscores pass through unchanged (only lowercased) on the assumption that the caller chose their own boundaries.

suffixed(suffix)[source]

PascalCase name with suffix appended.

Parameters:

suffix (str) – Class-name suffix, e.g. "CreateRequest".

Return type:

str

Returns:

Combined string, e.g. "ArticleCreateRequest".

prefix_import(prefix, *parts)[source]

Build a Python import path under prefix (which may be empty).

Parameters:
  • prefix (str) – Optional package prefix, e.g. "_generated".

  • *parts (str) – Module name segments to join with ..

Return type:

str

Returns:

A .-joined import path, with prefix prepended when non-empty.

class ImportCollector(*others)[source]

Accumulates imports as (module, name) pairs.

A bare import is (module, None) (e.g. Python import uuid). A from-import is (module, name) (e.g. Python from datetime import datetime). Multiple calls for the same module are merged; duplicates are deduplicated.

Examples:

collector = ImportCollector()
collector.add("uuid")
collector.add_from("datetime", "datetime", "date")
collector.format("python")
# "import uuid\nfrom datetime import date, datetime\n"

Rendering is delegated to a language formatter looked up via the codegen.import_formatters entry-point group.

add(module)[source]

Register a bare <module> import.

Return type:

None

add_from(module, *names)[source]

Register an import of names from module.

Multiple calls with the same module are merged.

Return type:

None

property bare_modules: list[str]

Bare-imported modules (e.g. ["uuid"]).

format(language)[source]

Render the imports as a string in language’s syntax.

Raises:

KeyError – No formatter registered for language.

Return type:

str

property from_imports: dict[str, list[str]]

{module: [name, ...]} of from-imports, preserving order.

property sorted_from_imports: list[tuple[str, list[str]]]

Deterministically-ordered from-imports.

Returns [(module, [name, ...]), ...] sorted by module and by name so templates can iterate without re-sorting.

update(other)[source]

Merge imports from other into this collector.

Return type:

None

format_imports(collector, language)[source]

Render collector using the formatter registered for language.

Empty language returns the empty string; callers that do not configure a language simply get no import block.

Return type:

str

Jinja environment

create_jinja_env(*template_dirs)[source]

Create a Jinja2 environment for code generation.

The returned environment has trim_blocks and lstrip_blocks enabled so that block tags ({% if %}, {% for %}, etc.) do not add extra blank lines to the rendered output.

Parameters:

*template_dirs (Path) – One or more directories to search for templates. Earlier directories take priority.

Return type:

Environment

Returns:

A configured jinja2.Environment.

render_template(env, template_name, **context)[source]

Render template_name against context and return the raw result.

Every jinja call in codegen/be flows through this helper so whitespace policy lives at the call site, not hidden inside a render wrapper. Callers handle trimming themselves:

  • Inline code snippets typically want .strip().

  • Whole-file output wants .rstrip() + "\n".

  • Slot contributions that the outer template controls typically want the raw output, unmodified.

Parameters:
  • env (Environment) – The Jinja2 environment to use.

  • template_name (str) – Template path relative to the environment’s template directories.

  • **context (object) – Template context variables.

Return type:

str

Returns:

The rendered template, exactly as jinja produced it.

Stdlib reference

The following .libsonnet files ship inside the be package and are importable from any config file using the be/ prefix.

be/auth/jwt.libsonnet

Configures JWT authentication.

local auth = import 'be/auth/jwt.libsonnet';

auth.jwt({
  secret_env:            "JWT_SECRET",
  algorithm:             "HS256",
  token_url:             "/auth/token",
  exclude_paths:         ["/docs", "/openapi.json", "/health"],
  verify_credentials_fn: "myapp.auth.verify_credentials",
})

To supply a custom get_current_user dependency instead of the generated JWT flow, set get_current_user_fn to a dotted import path. In that case verify_credentials_fn is not required.

be/db/databases.libsonnet

Configures async PostgreSQL connections.

local db = import 'be/db/databases.libsonnet';

db.postgres("primary", {
  url_env:       "DATABASE_URL",
  default:       true,
  echo:          false,
  pool_size:     5,
  max_overflow:  10,
  pool_timeout:  30,
  pool_recycle:  -1,
  pool_pre_ping: true,
})

Resources that omit db_key use the database with default: true.

pgqueuer integration

be does not scaffold pgqueuer wiring. See Background tasks (pgqueuer) for the full guide — the two helpers in ingot.queue (ingot.queue.get_queue() for transactional-outbox enqueue, ingot.queue.open_worker_driver() for the SQLAlchemy→asyncpg DSN bridge), the worker-factory pattern, and how to run the worker with pgqueuer’s own CLI.