Skip to main content

bausatz

tests codecov PyPI python

German: “construction kit.”

Config-driven component trees for Python — pydantic-validated classes that self-register by a class_name tag and assemble recursively from plain dict/JSON/YAML specs. Components can be pure Python or backed by C++ (via pybind11) — same config, same factory, either way. See Native (C++) components.

from typing import Literal
from bausatz import Component, ComponentFactory

class Retriever(Component):                      # an abstract family (no tag -> not registered)
    class Config(Component.Config):
        pass

class Dense(Retriever):                          # a concrete member, registered as "dense"
    class Config(Retriever.Config):
        class_name: Literal["dense"] = "dense"
        top_k: int = 50

class Pipeline(Component):                       # a container builds its children through the factory
    class Config(Component.Config):
        class_name: Literal["pipeline"] = "pipeline"
        retrievers: list[Retriever.Config]       # child specs -> concrete Configs, validated eagerly

    def _build_children(self, factory: ComponentFactory) -> None:
        self.retrievers = [factory.build(cfg) for cfg in self.config.retrievers]

pipeline = ComponentFactory.get().create(
    {"class_name": "pipeline", "retrievers": [{"class_name": "dense", "top_k": 20}]}
)

A whole system becomes a spec: swap implementations by editing config, not code. Unknown keys are rejected and fields are validated — for the whole tree in one pass, since a child field typed as a Config (here list[Retriever.Config]) resolves and validates its nested specs during the parent's validation, and rejects a child from another family — and to_dict() round-trips every component back to the spec that built it.

The pieces

piece job
Component base for buildable classes — declares a typed nested Config (pydantic) and self-registers at class-definition time by its class_name tag
ComponentFactory validate(spec) → a typed Config (children resolved), build(config) → the instance, and create = build∘validate (plus typed create_as / build_as and create_many), recursing into container children; config_adapter() builds a pydantic discriminated union over every registered Config for one-pass validation of whole trees
Registry the tag → class table; duplicate tags fail loudly
CppComponent a Component whose compute is a native (C++) component built from the validated config — see Native (C++) components

A container's child fields are typed as a Config (list[Retriever.Config], dict[str, Component.Config], an optional one, …); Component.Config resolves each nested spec into its concrete Config eagerly, in the parent's validation pass — no dedicated child-field type to import.

Scoped registries

Registration defaults to a process-global factory (zero config for a single library). Libraries that must coexist in one process scope their family instead — tags then only need to be unique per library:

my_factory = ComponentFactory()

class MyBase(Component, factory=my_factory):     # the whole subtree registers into my_factory
    class Config(Component.Config):
        pass

Native (C++) components

A component's behavior can be implemented in C++. CppComponent is a Component whose compute is a native object (typically a pybind11-exposed class): the pydantic Config still does validation, tagging, registration and to_dict, while the native component — built from the validated config — does the work. It's declarative — name the native class with component= and the framework marshals the config fields in as keyword arguments, so a C++-backed component needs no more code than a pure-Python one:

class Affine(CppComponent, component=AffineKernel):   # AffineKernel is a pybind11 class
    class Config(Component.Config):
        class_name: Literal["affine"] = "affine"
        scale: float = 1.0
        bias: float = 0.0
    def forward(self, x): return self.component.forward(x)   # self.component is built lazily

AffineKernel(scale=..., bias=...) is constructed for you from the config fields; override _create_component only when construction is custom (e.g. a container that builds child components). The library ships a C++ base bausatz::Component (header-only) so native components share a polymorphic root — enough for a C++ container to run a whole subtree natively. A downstream extension finds the headers with bausatz.get_include(). See examples/bausatz for a worked, compiled example (a declarative leaf and a native C++ composite).

Design notes

  • The tag lives on the Config (class_name: Literal["dense"] = "dense"), so it serializes with the config and validates like any other field.
  • Children are typed, not dict: typing them as a Config validates the whole config tree in one eager pass (a malformed grandchild fails validate, not later at build time), keeps the config self-describing, and — with a family base like list[Retriever.Config] — rejects a foreign child.
  • Containers own their children: _build_children(factory) turns the child Configs into components through the same factory, so a whole tree assembles against one registry in one recursive pass.
  • Import = register: a component must be imported before a spec can name it (package __init__s that import their members are the idiomatic registration point).
  • Zero dependencies beyond pydantic v2. Python ≥ 3.12. Fully typed (py.typed).

Install

pip install bausatz

MIT licensed.

Metadata

Release files for bausatz 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bausatz 0.2.0
File Size Uploaded
bausatz-0.2.0.tar.gz 25.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for bausatz 0.2.0
File Interpreter ABI Platform
bausatz-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 42.9 kB

Release files / bausatz-0.2.0.tar.gz

Download URL bausatz-0.2.0.tar.gz
Size 25.1 kB
Tags Source
SHA-256 checksum
How to use checksums
ba235547deb7660c310e95221261aae4262bdc8ce81480e1b800b1ebdb061b6c
BLAKE2b-256 checksum
How to use checksums
3e346256f93e8e0f0915bb7f1584a40e4fadad1ec6c02847cc5f5f153d03b683
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 25, 2026.

Transparency log

Release files / bausatz-0.2.0-py3-none-any.whl

Download URL bausatz-0.2.0-py3-none-any.whl
Size 17.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8422a472e59e78fd26890bb1b38b0e00ba8eb6e642636312172623d718d67c38
BLAKE2b-256 checksum
How to use checksums
0116e650ba4e3069ad9595faf93e4373b86ea53ed96db7b41eef543edd1e1c61
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page