PyBonsai (pybonsai-lang)
PyBonsai is a restricted subset of Python 3.12 for writing configuration that must give the same result on every machine. A PyBonsai file is ordinary Python syntax, but it is never run by CPython: PyBonsai parses it, rejects every construct outside an allow list, and interprets the syntax tree itself. A file cannot import modules, read files or the environment, loop without a bound or mutate shared state. It plays the part Starlark plays for Bazel, for any Python program that wants a hermetic configuration language with its own functions and types.
pip install pybonsai-lang
from pybonsai import evaluate_source
names = evaluate_source("xs = [i * i for i in range(4)]")
assert names["xs"] == (0, 1, 4, 9)
PyPI (1.2.0) ↗
Source repository ↗
Changelog ↗
PyrixOS documentation →
Cloche (cloche)
Cloche runs a program in an unprivileged Linux sandbox. It needs no root, no daemon and no container image: the program sees only the host paths bound into the sandbox, read-only, and a few empty writable directories, under a seccomp system call filter and resource limits. When the program succeeds, the declared outputs are copied out. The same sandbox can run under a rootless OCI runtime, or in a small virtual machine with its own kernel (Firecracker or Cloud Hypervisor). Version 1.2 adds the system call filters for riscv64 and aarch64 builders.
pip install cloche
import tempfile
from cloche import Bind, Command, Limits, run
command = Command(
name="hello",
program="/usr/bin/sh",
args=("-c", "echo hello > /build/greeting"),
binds=tuple(Bind(source=p, destination=p) for p in ("/usr", "/lib", "/lib64")),
outputs={"greeting": "/build/greeting"},
)
outcome = run(command, tempfile.mkdtemp(), Limits(timeout=60))
print(open(outcome.outputs["greeting"]).read()) # hello
PyPI (1.2.0) ↗
Source repository ↗
Changelog ↗
PyrixOS documentation →
Trellis (trellis-kconfig)
Trellis configures a build from a tree of options written in a subset of the Linux Kconfig language. The user's choices are kept in a defconfig, and a SAT solver resolves them into one value for every option. When choices cannot all hold, Trellis names the smallest set of choices and rules in conflict and lists the changes that would remove it, instead of quietly turning an option off. It is a Python library with a command, a line prompter and a full-screen menu on top.
pip install trellis-kconfig
from trellis import Configurator
configurator = Configurator.load("Kconfig", "defconfig")
configurator.set("WIFI", "m")
print(configurator.resolve().values)
conflict = configurator.conflict("NET", "n")
if conflict is not None:
print(conflict.describe())
for fix in configurator.fixes("NET", "n"):
print("drop", fix.dropped, "changes", fix.changes)
configurator.save("defconfig", "config.pyb")
PyPI (1.1.0) ↗
Source repository ↗
Changelog ↗
Lattice (pyrixos-lattice)
Lattice is the package manager of PyrixOS. It reads package and system descriptions written in PyBonsai, turns them into derivations (exact build recipes identified by a hash of their contents), builds them in the Cloche sandbox, and keeps the results in an immutable, content-addressed store. It switches whole machines between generations with rollback, and fetches signed binaries from caches. Everything the lattice command does is available from Python through the Lattice class, and through a daemon with a local and a remote API. Lattice stays below 1.0 until PyrixOS 1.0.
pip install pyrixos-lattice
pip install "pyrixos-lattice[config]" # adds lattice config, through Trellis
from lattice import Lattice, LatticeError, Settings
lattice = Lattice(Settings(sandbox_backend="namespaces"))
try:
result = lattice.evaluate("hello.pyb")
except LatticeError as error:
print(error)
else:
for name, drv in result.derivations.items():
print(name, drv.store_path)
PyPI (0.13.0) ↗
Source repository ↗
Changelog ↗
PyrixOS documentation →
Espalier (pyrixos-espalier)
Espalier gives Python classes validation from their type annotations, with the interface of Pydantic 2 (BaseModel, Field, ConfigDict, validators, TypeAdapter, model_validate, model_dump_json and the rest that PyrixOS uses), implemented in pure Python. Pydantic's validation core is written in Rust; Espalier lets a project that wants an all-Python dependency tree keep the same model code. It supports strict and lax modes, frozen models, forbidden extra fields, aliases, discriminated unions and JSON round trips. It is slower than Pydantic, so it suits configuration and API models more than bulk data.
pip install pyrixos-espalier
from typing import Annotated, Literal
from espalier import BaseModel, ConfigDict, Field, ValidationError
class Mount(BaseModel):
model_config = ConfigDict(frozen=True, extra="forbid", strict=True)
source: str = Field(min_length=1)
target: str
mode: Literal["ro", "rw"] = "ro"
size: Annotated[int, Field(ge=0)] = 0
mount = Mount.model_validate_json('{"source": "/a", "target": "/b"}')
print(mount.model_dump_json()) # {"source":"/a","target":"/b","mode":"ro","size":0}
try:
Mount(source="", target="/b", size=-1)
except ValidationError as error:
print(error)
PyPI (1.0.0) ↗
Source repository ↗
Changelog ↗
pyrixos-ed25519
pyrixos-ed25519 creates and checks Ed25519 signatures as RFC 8032 specifies, in Python with no compiled code, and is tested against the RFC 8032 test vectors. Lattice uses it to sign and verify its binary caches. Keys are the 32-byte seeds and public keys of the RFC, so they interoperate with other Ed25519 implementations. Python integers give no constant-time arithmetic, so signing time depends on the secret key: the package suits places where an attacker cannot time many signatures, such as signing build outputs on a build host. Signing takes about 2 ms and verification about 10 ms on a current x86_64 core.
pip install pyrixos-ed25519
from pyrixos_ed25519 import BadSignatureError, SigningKey, VerifyingKey
key = SigningKey.generate() # or SigningKey(seed), 32 bytes
signature = key.sign(b"message") # 64 bytes
public = key.verifying_key.encoded # 32 bytes
try:
VerifyingKey(public).verify(signature, b"message")
except BadSignatureError:
...
PyPI (1.0.0) ↗
Source repository ↗
Changelog ↗