Setu 2026.11.9
static
YoSTEALTH/Setu

Setu#

ci docs tag coverage zig python abi platform license

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.

Documentation: https://yostealth.github.io/Setu/ (the guide to writing an extension, the test extension's API).

Note: Setu is not on PyPI and has no Python package: it is a Zig dependency (build.zig.zon), used at build time.

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_class, a test extension with a class per hook family (the model root file)
test/wheel/              # setu_class as a project, for the tool/setu_build.py wheel checks
test/doc/                # setu_class as a docs site, for the tool/setu_doc.py checks
test/doc_md/             # a site of Markdown pages only (no Python module), for the same checks
tool/setu_build.py       # the wheel build backend (PEP 517) projects copy into their tool/
tool/setu_doc.py         # the docs site builder projects copy into their tool/
tool/setu_doc_web.zig    # the site's behaviour (WebAssembly, zig build doc-web); app.js only runs it
tool/check_coverage.py   # line coverage under pytest (kcov), run by zig build coverage (addCoverageSteps)
                         # and of Zig unit tests (addZigTestCoverage: zig build coverage-zig)
tool/check_api.py        # every public runtime function compiled into setu_class (after zig build coverage)
tool/check_profile.py    # instructions per call, per Zig line (callgrind), run by zig build profile
                         # (addProfileSteps)
tool/check_symbol.py     # CPython symbols exported by an interpreter, abi3t on 3.15+, run by
                         # zig build check-symbol (addSymbolCheck)

Commands:

zig build test                        # type-check every declaration + comptime and doc-web unit tests
zig build develop                     # test extension (setu_class.so) + stubs into test/
python3 tool/check_doc.py             # the docs site of test/doc/ (after develop)
tool/check.py                         # every check, on 3.15t, 3.15, 3.14t and 3.14
python3 tool/bench.py --ref HEAD      # per-call cost, A/B against a commit (`-Doptimize=fast`)
zig build coverage -Dpython=python3   # line coverage of the test extension (kcov), zig-out/coverage.json;
                                      # then tool/check_api.py
zig build profile -Doptimize=fast -Dcpu=x86_64_v3   # instructions per call (callgrind), zig-out/profile.json
python3 tool/setu_doc.py serve        # Setu's docs site (zig-out/site): guide, API, coverage, profile

For AI sessions (and other readers in a hurry)#

llms.txt is the index to read first; doc/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 doc/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. tool/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 tool/setu_doc.py.

An extension: one module declaration#

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_class.zig an example of each filter (.only, .exclude, .prefixes, .functions = false).

Naming#

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__).

Wheels: tool/setu_build.py#

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 tool/, 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: tool/check.py builds the glibc and musl wheels of test/wheel/ and pip-installs them on every interpreter (tool/check_wheel.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 tool/setu_build.py into tool/ (run it after bumping Setu); zig build setu-build-check fails if the copy differs (run it in the project's checks).

Docs site: tool/setu_doc.py#

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_doc] 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_doc.py check; project fixtures in a hooks file). Everything the pages do (search, Run, the theme menu, Copy, the source views) is Zig compiled to WebAssembly (tool/setu_doc_web.zig); setu_doc.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_doc.py in tool/, like setu_build.py, in step with addSetuDocSteps (zig build setu-doc-sync / setu-doc-check), which also adds zig build doc-web (the Wasm module, from the pinned Setu; setu_doc.py runs it). A Zig project with no Python module gets the same site from its Markdown pages (no module, a name; a static folder such as Zig's autodoc output copied in). test/doc/ is a working model, test/doc_md/ a module-less one (tool/check_doc.py). The workflow runs zig build develop (the extension and its stub), then python tool/setu_doc.py (writes site/), and uploads site/ to GitHub Pages.

Releases#

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 tool/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> -- tool/setu_build.py tells (exit 1: changed).

Generated by tool/setu_doc.py from the docstrings of Setu 2026.11.9.