Reference¶
Config schema¶
All config files parse into a ProjectConfig
instance. The classes below correspond directly to the fields you
write in .jsonnet / .json.
- class ProjectConfig(**data)[source]¶
Top-level kiln configuration.
A project is a collection of apps plus shared infrastructure (auth, databases, framework target). Resources always live under
apps[*].config.resources; a shorthand config with top-levelmodule/resources/operationsfields is wrapped into a single implicit app withprefix=""by_wrap_shorthand(), so the scope tree (project → app → resource) is uniform across configs.Inherits
package_prefixfrom foundry and overrides its default to"_generated"so generated code lives at_generated/{module}/and is imported as_generated.{module}.routes.article. Set it to""to disable the prefix.- auth: AuthConfig | None¶
- databases: list[DatabaseConfig]¶
- framework: str¶
Target framework profile. Selects which renderer set runs; each renderer is tagged with the framework it implements, and only those matching this value are used.
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- resolve_database(db_key)[source]¶
Return the
DatabaseConfigselected by db_key.When db_key is
None, returns the database markeddefault=True.- Raises:
ValueError – If db_key does not match any configured database, or if no database has
default=Trueand db_key isNone.- Return type:
- class AppConfig(**data)[source]¶
One app within a project: a module of related resources.
An app owns its own Python package (
module) and a list of resources.- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- resources: Annotated[list[ResourceConfig], Scoped(name=resource)]¶
- class App(**data)[source]¶
An app mounted at a URL prefix in the project router.
- class ResourceConfig(**data)[source]¶
A resource: a consumer-defined Python model plus its operations.
modelis a dotted import path to any SQLAlchemy selectable class (table, mapped view, etc.) defined by the consumer, e.g."myapp.models.Article".operationsis a scoped list ofOperationConfigentries — each entry becomes an"operation"scope instance that the engine visits independently.require_authsets the default authentication requirement for all operations. Individual operations can override this via their ownrequire_authfield.- generate_tests: bool¶
When
True, emit a pytest test file for this resource’s generated routes and serializers.
- model: str¶
Dotted import path to the consumer’s SQLAlchemy model class, e.g.
"myapp.models.Article".
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- operations: Annotated[list[OperationConfig], Scoped(name=operation)]¶
Ordered list of operations to run — each becomes a scope instance of
"operation"that the engine visits in turn.
- pk_type: Literal['uuid', 'str', 'email', 'int', 'float', 'bool', 'datetime', 'date', 'json']¶
Type of the primary key, used to generate the correct path parameter.
- class OperationConfig(**data)[source]¶
Configuration for a single operation.
Known fields (
name,require_auth) are parsed normally. All other keys are collected intooptionsvia Pydantic’sextra="allow"setting and passed to the operation’sOptionsmodel (seefoundry.operation.operation()).Each
OperationConfigis a scope instance of the"operation"scope: the engine descends intoResourceConfig.operationsand visits each entry independently. Every@operationclass withdispatch_on="name"matches at most one entry per resource — the one whosenameequals the op’s own.Examples:
# Built-in operation {"name": "get"} # With extra options (go into ``options`` via model_extra) {"name": "create", "fields": [...]} # Action operation {"name": "publish", "fn": "blog.actions.publish", "params": [...]} # Custom third-party operation {"name": "bulk_create", "class": "my_pkg.ops.BulkOp", "max": 100}
- class FieldSpec(**data)[source]¶
A named, typed field — used in operation schemas and action params.
- class AuthConfig(**data)[source]¶
JWT authentication configuration.
- get_current_user_fn: str | None¶
Dotted import path to a custom
get_current_userdependency, e.g."myapp.auth.custom.get_current_user". When set, the generatedauth/dependencies.pyre-exports this function instead of containing the default JWT implementation.
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- verify_credentials_fn: str | None¶
Dotted import path to a credential-verification function, e.g.
"myapp.auth.verify_credentials". The function must accept(username: str, password: str)and return adict(the JWT payload) on success orNoneon failure.Required when using the default JWT auth flow (
get_current_user_fnis not set).
- class DatabaseConfig(**data)[source]¶
Configuration for a single database connection.
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
Field types¶
The type field on FieldSpec accepts:
Type |
Python annotation |
Used in |
Notes |
|---|---|---|---|
|
|
request/response schemas, pk |
Default for primary keys. |
|
|
schemas, action params |
|
|
|
schemas |
Added |
|
|
schemas, pk |
|
|
|
schemas |
|
|
|
schemas |
|
|
|
schemas |
|
|
|
schemas |
|
|
|
schemas |
Built-in operations¶
Every built-in operation is registered under the foundry.operations
entry-point group in kiln’s own pyproject.toml. See Usage
for what each one generates and Extending kiln for the operation
protocol.
Name |
Module |
Scope |
Description |
|---|---|---|---|
|
project |
Emits |
|
|
resource |
The five CRUD endpoints. Each op lives in its own module alongside the FastAPI renderer for its output. |
|
|
resource |
Custom action endpoints: |
|
|
resource |
Cross-cutting augmenter. Appends |
|
|
app |
Emits |
|
|
project |
Multi-app projects only. Emits the top-level
|
Generated file layout¶
The table below summarises every file kiln 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 |
|---|---|---|
|
|
Yes |
|
|
Yes |
|
|
Yes |
|
|
Yes |
|
|
Yes |
|
|
Yes |
|
|
Yes |
|
CRUD + |
Yes |
|
|
Yes |
|
CRUD + |
Yes |
|
|
Yes |
Every file is overwritten on every generation run.
foundry API¶
Targets¶
- class Target(name, language, schema, template_dir, jsonnet_stdlib_dir=None)[source]¶
A concrete code-generation target.
- name¶
Short identifier, used for
--targetdispatch when multiple targets are installed and as the jsonnet stdlib import prefix.
- language¶
Language-identifier the target generates for (e.g.
"python"). Passed tofoundry.imports.format_imports()so the assembler renders import blocks in the right syntax. Targets declare their formatter under thefoundry.import_formattersentry-point group.
- schema¶
FoundryConfigsubclass the target’s config files validate against. Foundry’s loader instantiates this.
- template_dir¶
Directory of Jinja templates the target’s renderers reference. Foundry builds the Jinja environment rooted here.
- jsonnet_stdlib_dir¶
Optional directory of jsonnet
.libsonnetfiles exposed to configs as<name>/...imports.Nonewhen the target ships no stdlib.
Engine¶
- class Engine(registry=<factory>, package_prefix='')[source]¶
Orchestrates the build phase of code generation.
- registry¶
OperationRegistryholding the ops to run. Defaults to the populatedDEFAULT_REGISTRY; tests pass an isolated registry to keep their ops out of the global one.
- 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:
- Returns:
An
BuildStorecontaining all objects produced by operations.
- class BuildContext(config, scope, instance, instance_id, store, package_prefix='')[source]¶
Context passed to every operation’s
buildmethod.Parameterized on the scope instance type so operations can annotate e.g.
ctx: BuildContext[ResourceConfig]and get typed access toctx.instance.*. The engine itself buildsBuildContext[Any]since it’s scope-agnostic.- 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=OperationRegistry(entries=[OperationEntry(meta=OperationMeta(name='get', scope='operation', requires=(), after_children=False, dispatch_on='name'), cls=<class 'kiln.operations.get.Get'>), OperationEntry(meta=OperationMeta(name='list', scope='operation', requires=('get', ), after_children=False, dispatch_on='name'), cls=<class 'kiln.operations.list.List'>), OperationEntry(meta=OperationMeta(name='create', scope='operation', requires=('list', ), after_children=False, dispatch_on='name'), cls=<class 'kiln.operations.create.Create'>), OperationEntry(meta=OperationMeta(name='update', scope='operation', requires=('create', ), after_children=False, dispatch_on='name'), cls=<class 'kiln.operations.update.Update'>), OperationEntry(meta=OperationMeta(name='delete', scope='operation', requires=('update', ), after_children=False, dispatch_on='name'), cls=<class 'kiln.operations.delete.Delete'>), OperationEntry(meta=OperationMeta(name='action', scope='operation', requires=(), after_children=False, dispatch_on=None), cls=<class 'kiln.operations.action.Action'>), OperationEntry(meta=OperationMeta(name='auth', scope='resource', requires=(), after_children=True, dispatch_on=None), cls=<class 'kiln.operations.auth.Auth'>), OperationEntry(meta=OperationMeta(name='scaffold', scope='project', requires=(), after_children=False, dispatch_on=None), cls=<class 'kiln.operations.scaffold.Scaffold'>), OperationEntry(meta=OperationMeta(name='auth_scaffold', scope='project', requires=(), after_children=False, dispatch_on=None), cls=<class 'kiln.operations.scaffold.AuthScaffold'>), OperationEntry(meta=OperationMeta(name='router', scope='app', requires=(), after_children=True, dispatch_on=None), cls=<class 'kiln.operations.routing.Router'>), OperationEntry(meta=OperationMeta(name='project_router', scope='project', requires=(), after_children=False, dispatch_on=None), cls=<class 'kiln.operations.routing.ProjectRouter'>)]))[source]¶
Decorate a class as a kiln operation.
The decorated class must define:
Options: apydantic.BaseModelsubclass (defaults toEmptyOptionsif 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 returningFalse, 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
BuildContext.storeand mutating the objects returned byBuildStore.outputs_under()in place. Combined withrequiresfor ordering andwhenfor activation, a single operation mechanism covers both “produce” and “augment” roles.- 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) – WhenTrue(project scope only), defer this operation until every child scope has executed sobuildcan 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 unlessgetattr(ctx.instance, dispatch_on) == name. Designed for scopes whose instance is a discriminated-union config (e.g.OperationConfigentries under a resource), where multiple ops share one scope and each matches a single entry.registry (
OperationRegistry) – Registry to register into. Defaults to the process-wideDEFAULT_REGISTRY; tests may pass an isolated registry to keep their ops out of the global namespace.
- Return type:
- 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(...)]
- 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 itsbuildmethod 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.instancewhose value must equalnamefor 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.
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
Nonefor 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.
- discover_scopes(config_cls)[source]¶
Derive scopes from a Pydantic model’s
Scopedmarkers.The top-level config is always the
"project"scope. Each field declaredAnnotated[list[T], Scoped()]becomes a child scope of the current level; the item typeTis then itself descended into to discover grandchild scopes.Non-list
BaseModelfields (e.g.App.config) are traversed transparently: their nestedScopedfields become scopes rooted at the enclosing level, withresolve_pathreflecting 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:
ScopeTreecontaining every discovered scope, project first.
- foundry.scope.PROJECT¶
The root scope – always present in every generation run.
Typed outputs¶
Every operation’s build method returns instances of the types
below. All are mutable dataclasses.
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 aFileFragmentdeclaring the output file plus one or moreSnippetFragmentcontributions into its slots.- Parameters:
- Return type:
- 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='', 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 thefoundry.import_formattersentry-point group.
- 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")).
- class BuildStore(scope_tree=<factory>, _items=<factory>, _instances=<factory>, _children=<factory>, _parent_of=<factory>)[source]¶
Accumulator for objects produced during the build phase.
Objects are keyed by
(instance_id, op_name). Instance ids are dot-path strings produced by the engine (e.g."project.apps.0.resources.2") — the leaf scope, the ancestor chain, and every index are all recoverable from the id viafoundry.scope.scope_for(), so the store never needs a separate scope field.Ancestry is tracked in
_children— the engine records each instance’s parent id on registration sochildren()can walk the tree without callers reconstructing store keys.- scope_tree¶
ScopeTreefor the build’s config. Required for thescope_of()derivation (and therefore forchild_scope=filtering onchildren()). 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_idto the scope-instance config object.
- _children¶
Map from a parent instance id to its registered child instance ids, in insertion order.
- ancestor_of(instance_id, scope_name)[source]¶
Return the enclosing instance at scope_name, if any.
Walks
_parent_ofedges 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’smodel).
- 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
scopesto be populated).
- 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.
- 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).
- 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.
Output¶
- class GeneratedFile(path, content)[source]¶
Immutable final output – a path and its content.
- path¶
Output path relative to the output directory.
- content¶
File contents as a string.
- write_files(files, out_dir)[source]¶
Write generated files to disk.
Each file’s
pathis joined with out_dir to determine the target path. Parent directories are created as needed. Existing files are always overwritten.- Parameters:
files (
Sequence[GeneratedFile]) – Sequence ofGeneratedFileobjects.out_dir (
Path) – Root directory for output paths.
- Return type:
- Returns:
Number of files written.
Naming and imports¶
- class Name(raw)[source]¶
Derives conventional identifiers from a base string.
Accepts either a
PascalCaseclass name (e.g."Article") or asnake_caseidentifier (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"
- prefix_import(prefix, *parts)[source]¶
Build a Python import path under prefix (which may be empty).
- split_dotted_class(dotted_path)[source]¶
Split a dotted import path into
(module, class_name).- Parameters:
dotted_path (
str) – A fully-qualified class path such as"myapp.models.Article".- Return type:
- Returns:
A
(module, class_name)tuple, e.g.("myapp.models", "Article").- Raises:
ValueError – If dotted_path contains fewer than two parts.
- class ImportCollector(*others)[source]¶
Accumulates imports as
(module, name)pairs.A bare import is
(module, None)(e.g. Pythonimport uuid). A from-import is(module, name)(e.g. Pythonfrom 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
foundry.import_formattersentry-point group.- add_from(module, *names)[source]¶
Register an import of names from module.
Multiple calls with the same module are merged.
- Return type:
Jinja environment¶
- create_jinja_env(*template_dirs)[source]¶
Create a Jinja2 environment for code generation.
The returned environment has
trim_blocksandlstrip_blocksenabled 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 foundry/kiln 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.
Stdlib reference¶
The following .libsonnet files ship inside the kiln package and
are importable from any config file using the kiln/ prefix.
kiln/auth/jwt.libsonnet¶
Configures JWT authentication.
local auth = import 'kiln/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.
kiln/db/databases.libsonnet¶
Configures async PostgreSQL connections.
local db = import 'kiln/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.