Setu 2026.11.9
static
YoSTEALTH/Setu
Edit

7. Docs and the stub#

/// above a declaration (the //! block at the top of the first from file with a source is the module docstring). Format: a description, a blank /// line, then sections, each a header on its own line with its content indented 4 spaces:

/// `path` encoded like `os.fsencode` (`py.Path`).
///
/// Type
///     return: bytes
///
/// Example
///     >>> path_bytes('/tmp/x')
///     b'/tmp/x'
///     >>> path_bytes(b'/tmp')
///     b'/tmp'
///     >>> path_bytes(3)
///     TypeError: expected str, bytes or os.PathLike object, not int
pub fn path_bytes(path: py.Path) Error!*Object {
    return py.new(py.Bytes{ .data = path.path });
}

In Python the doc is the function's __doc__ (what help() shows), the stub's docstring and the site's entry; Type changed the stub's return type from Any (a *Object) to bytes:

>>> import inspect
>>> from pkg import path_bytes
>>> path_bytes('/tmp/x'), path_bytes(b'/tmp')
(b'/tmp/x', b'/tmp')
>>> path_bytes.__doc__.splitlines()[:4]  # what help() shows
['`path` encoded like `os.fsencode` (`py.Path`).', '', 'Type', '    return: bytes']
>>> inspect.signature(path_bytes)
<Signature (path)>

The stub: def path_bytes(path: str | bytes | os.PathLike[str] | os.PathLike[bytes]) -> bytes: with that docstring.

Type entries (name: type # note or name: type - note; return: for the result) replace the Zig-derived type in the stub; bare generics get their arguments (os.PathLike -> os.PathLike[str] | os.PathLike[bytes], list -> list[Any], Coroutine -> Coroutine[Any, Any, Any]). Without one, the stub maps the Zig type: ints -> int, floats -> float, []const u8 -> str, py.Bytes -> bytes, py.Path -> str | bytes | os.PathLike[...], buffers -> Buffer, py.Sequence(T) -> Sequence[T], slices -> list[T], tuples -> tuple[T, U], void -> None, ?T -> T | None = None, *T -> the class name (Any if hidden), *Object -> Any. A method parameter that would stub as Any without a Type entry is a compile error (rename a Zig helper camelCase, or add the entry). The stub imports only the names it uses, is laid out as ruff format would, leaves out __repr__ / __str__ (object declares them), and passes mypy --strict, stubtest and ruff's flake8-pyi rules (PYI, apart from PYI021: the docstrings stay) in Setu's checks. tool/setu_doc.py turns the same docstrings into a static HTML site (and llms.txt / llms-full.txt for AI readers), and runs every Example to check that its output is what the doc says (setu_doc.py check). A repeated Example header starts another block (one per call form: Sqe(), then Sqe(2)), shown with its own Run and run on its own, in a new namespace. Setu numbers repeated headers in the docstring: help(), the stub and the site show Example #1, Example #2, ... (setu_doc.py run Sqe:2 runs Example #2); write plain Example, a hand-written Example #<n> is a compile error. A Flags section lists one name per line, optionally with a note (IORING_TIMEOUT_ABS # ts is a point in time): the site links the name and shows the note as text. A bare https:// URL anywhere in a docstring or the site's Markdown pages (outside backticks) becomes a link that opens in a new tab; a trailing ., , or ) stays text unless the ) closes a ( in the URL. In the site's Markdown pages an @@path@@ line includes a project file as a code block (an example script, as tested); the header's โ— button opens the theme menu (Nord Day, Nord Night, Tokyo Day, Tokyo Night; until one is picked, Tokyo Day, or Nord Night when the system is dark); coverage = 'zig-out/coverage.json' and profile = 'zig-out/profile.json' in [tool.setu_doc] add the Coverage and Profile pages (section 11); the Profile page is local only: setu_doc.py serve makes it from the file as it is, build (CI, the published site) leaves it out, so nothing outside your machine needs zig build profile. The site builds from setu_doc.py alone: no wrapper script, no copied CSS (a css file only for the project's own colors). Search finds the API's names and every Markdown heading. A Zig project with no Python module gets the same site from its Markdown pages: leave out module, give name, and copy Zig's autodoc output in with [[tool.setu_doc.static]] (the keys are in setu_doc.py's docstring).

Ship the extension as its package's __init__.so, with the stub as __init__.pyi and a py.typed (PEP 561 marks packages only, so a bare top-level liburing.so gets no typing): import liburing loads the extension itself, a package (__path__), on every supported interpreter; from liburing import * gives its public names, and its classes' __module__ is the import name. No __init__.py: next to an __init__.so it never runs (extensions are found first), and tool/setu_build.py refuses to ship one. Python code goes in submodules (liburing/util.py). The stub must be __init__.pyi: a liburing.pyi next to __init__.so types a liburing.liburing that is not there, and liburing itself gets no types. Moving from the submodule layout below: liburing.liburing no longer exists (use liburing), delete the old liburing.so / liburing.pyi copies in src/liburing/ (setu_build.py refuses them: import liburing.liburing would load a stale extension), and pass .package = true to addCoverageSteps.

Only a package with Python code at its top level needs the extension as a submodule (pkg/ext.so next to ext.pyi, .name = "ext") re-exported from __init__.py. To keep the submodule out of from pkg import *, give the package an __all__, not a del ext (that breaks import pkg.ext; pkg.ext.Ring). Hide the computed __all__ from type checkers: they cannot evaluate it and would then see none of the stub's names (mypy --strict reports every one name-defined):

from . import ext
from .ext import *

TYPE_CHECKING = False
if not TYPE_CHECKING:
    __all__ = [name for name in dir(ext) if not name.startswith("_")]
del TYPE_CHECKING
Generated by tool/setu_doc.py from the docstrings of Setu 2026.11.9.