Setu (सेतु, "bridge"): a generic Zig <-> CPython binding layer. One build loads on
CPython 3.14, 3.14t, 3.15, 3.15t and later: a single <name>.so in one
cp314.cp315-abi3.abi3t.cp314t.cp315t wheel per platform (the 3.15 stable ABI for free-threading,
PEP 803, plus a 3.14 entry point). Any pip installs it on 3.14, 3.14t, 3.15 and 3.15t; 3.16t and later
need pip 26.2+ (abi3t). PEP 697 type data, critical sections. No Python.h: the C API is declared by hand in
src/capi.zig.
Zig 0.17.0, Linux.
Setu is mainly for the Shakti and Liburing projects, which use it as a dependency (it never imports either). For your own extensions, use PyOZ instead: it fills the same role and is documented and maintained for public use.
src/root.zig the module root (@import("setu"))
src/capi.zig hand-declared C API
src/py.zig functions, conversions, errors, the module (exportModule)
src/scan.zig setu.From: which declarations of a Module.from namespace Python gets
src/class.zig setu.Class: a struct becomes a Python type
src/exception.zig setu.Exception: an exception class of the extension
src/stub.zig .pyi text (stub.module writes an extension's whole .pyi)
src/stubgen.zig the stub generator addStubs builds (no libpython)
test/ setu_classes, a test extension with a class per hook family (the model root file)
test/wheel/ setu_classes as a project, for the tools/setu_build.py wheel checks
test/docs/ setu_classes as a docs site, for the tools/setu_docs.py checks
tools/setu_build.py the wheel build backend (PEP 517) projects copy into their tools/
tools/setu_docs.py the docs site builder projects copy into their tools/
tools/setu_docs_web.zig the site's search and Run logic (WebAssembly, zig build docs-web)
Commands:
zig build test # type-check every declaration + comptime and docs-web unit tests
zig build develop # test extension (setu_classes.so) + stubs into test/
python3 tools/check_docs.py # the docs site of test/docs/ (after develop)
tools/check.sh # every check, on 3.15t, 3.15, 3.14t and 3.14
python3 tools/bench.py --ref HEAD # per-call cost, A/B against a commit (ReleaseFast)llms.txt is the index to read first; docs/setu-guide.md is the guide to writing an extension: the rules,
every parameter type, every class hook signature, every compile error Setu raises. A project using Setu keeps a
copy of the guide at docs/setu-guide.md (zig build setu-guide-sync / setu-guide-check, see
addSetuGuideSteps), so a session working on its bindings finds it inside the project.
tools/check_guide.py keeps the guide in step with the source. A project's own docs site gets llms.txt and
llms-full.txt for its Python API from tools/setu_docs.py.
The extension's root file lists its Zig files once. Setu finds their lowercase pub fns (functions),
setu.Classes, setu.Exceptions, UPPER_CASE constants and enums, creates them in its Py_mod_exec
(classes bases first), adds __version__, runs init, and writes the .pyi from the same declaration:
const setu = @import("setu");
pub const module: setu.Module = .{
.name = "point",
.version = "1.0",
.from = &.{
.{ .ns = @import("point.zig"), .source = @embedFile("point.zig") },
.{ .ns = @import("kernel.zig"), .prefixes = &.{"O_"} }, // constants only: no `source`
.{ .ns = @import("enum.zig"), .enums = .int_enum }, // `enum.IntEnum` classes
},
.init = init, // after Setu's part
};
fn init(_: *setu.c.Object) setu.py.Error!void {}
comptime {
setu.py.exportModule(module);
}// build.zig: the .pyi, written by Setu's stub generator (built for the host, no libpython)
const pyi = @import("setu").addStubs(b, setu_dep, .{ .root_source_file = b.path("src/root.zig") });_-named declarations, camelCase pub fns, plain structs and lowercase values are skipped; a name
found twice is a compile error. src/scan.zig (top) has the rules, test/setu_classes.zig an example of
each filter (.only, .exclude, .prefixes, .functions = false).
Setu's own code follows Zig style. In a file that Module.from, py.exportFns or setu.Class exports, a lowercase
pub fn, snake_case included (prep_recv, get_label), is a Python function, method or property under
that name; camelCase pub fns are Zig-only helpers. So snake_case function names there are on purpose,
not a slip from Zig's camelCase rule: they are the Python names. Hooks keep their Python names too
(__init__, __repr__).
A PEP 517 build backend (no setuptools) that builds a Zig + Setu extension's wheels with zig build.
A backend must sit in the project's own tree, so each project keeps a byte-identical copy in its
tools/, never edited there: what differs per project goes in [tool.setu_build] in its
pyproject.toml (the keys are listed in the script's docstring). Setu tests the script itself:
tools/check.sh builds the glibc and musl wheels of test/wheel/ and pip-installs them on every
interpreter (tools/check_wheels.py).
A project keeps its copy in step with the Setu it pins with two zig build steps, from its
build.zig (setu must be in its build.zig.zon):
const setu_dep = b.dependency("setu", .{});
@import("setu").addSetuBuildSteps(b, setu_dep);zig build setu-build-sync copies the pinned Setu's tools/setu_build.py into tools/ (run it after
bumping Setu); zig build setu-build-check fails if the copy differs (run it in the project's checks).
A static HTML site built by Setu's own builder, Python's standard library only: the API pages from the imported
module's docstrings (the /// docs; typed signatures from the .pyi), grouped by a [tool.setu_docs] table in
the project's pyproject.toml (the keys are in the script's docstring), the project's Markdown pages, the
constants, its example scripts, llms.txt / llms-full.txt, and a runner for the >>> examples
(setu_docs.py check; project fixtures in a hooks file). Search and the Run transcript are Zig compiled to
WebAssembly (tools/setu_docs_web.zig); setu_docs.py serve serves the site on 127.0.0.1 with Run buttons
that run an example and show its session (only on examples that can run: none that read a name nothing defines;
the published site has no Run buttons). A Markdown table after a <!-- chart --> line becomes a colored bar
chart (GitHub shows the table). The project keeps a byte-identical copy of setu_docs.py in tools/,
like setu_build.py, in step with addSetuDocsSteps (zig build setu-docs-sync / setu-docs-check), which
also adds zig build docs-web (the Wasm module, from the pinned Setu; setu_docs.py runs it). test/docs/ is
a working model (tools/check_docs.py). The workflow runs zig build develop (the extension and its stub),
then python tools/setu_docs.py (writes site/), and uploads site/ to GitHub Pages.
Versions are <year>.<month>.<day>[-<n>], no zero padding: 2026.10.20 for a day's first release, then
2026.10.20-2, 2026.10.20-3, ... Tags are v<version> (the build.zig.zon version). Each tag's notes say whether
tools/setu_build.py changed since the previous tag: if it did, projects run zig build setu-build-sync
after bumping to it. git diff --quiet <previous tag> <tag> -- tools/setu_build.py tells (exit 1: changed).