Pyzig Module
pyzig is the Zig↔Python interoperability layer used by Faebryk’s native modules (graph, sexp, faebryk typegraph, …).
There are three distinct layers to keep straight:
- •Python loader/glue:
src/faebryk/core/zig/__init__.py(build-on-import +.pyisyncing) - •Zig build:
src/faebryk/core/zig/build.zig(buildspyzig.so+pyzig_sexp.so, generates stubs) - •Zig binding utilities:
src/faebryk/core/zig/src/pyzig/*(wrapper generation + minimal C-API surface)
Quick Start
bash
ato dev compile python -c "import faebryk.core.zig; import faebryk.core.graph"
Relevant Files
- •Python-side loader/build glue:
- •
src/faebryk/core/zig/__init__.py(ZIG_NORECOMPILE,ZIG_RELEASEMODE, lock, stub syncing)
- •
- •Zig build + stub generation:
- •
src/faebryk/core/zig/build.zig(builds extensions + runs.pyigenerator)
- •
- •Core pyzig utilities:
- •
src/faebryk/core/zig/src/pyzig/pybindings.zig(minimal CPython C-API declarations) - •
src/faebryk/core/zig/src/pyzig/pyzig.zig(wrapper generation helpers) - •
src/faebryk/core/zig/src/pyzig/type_registry.zig(global type-object registry) - •
src/faebryk/core/zig/src/pyzig/pyi.zig(stub generation helpers)
- •
- •Example consumers:
- •
src/faebryk/core/zig/src/python/graph/graph_py.zig - •
src/faebryk/core/zig/src/python/sexp/sexp_py.zig
- •
Dependants (Call Sites)
- •Graph bindings:
src/faebryk/core/zig/src/python/graph/* - •Sexp bindings:
src/faebryk/core/zig/src/python/sexp/* - •TypeGraph bindings:
src/faebryk/core/zig/src/python/faebryk/*(and friends)
How to Work With / Develop / Test
Core Concepts
- •Direct binding: pyzig calls the CPython C-API directly (no cffi/ctypes).
- •Wrapper types: most exposed Zig structs become Python heap types via
wrap_in_python(...)/wrap_in_python_simple(...). - •Global type registry: prevents re-creating Python
PyTypeObjects for the same Zig type (type_registry). - •No direct
__init__(by default): many “reference” types are not meant to be user-constructed;pyzigoften installs an init that raises. - •Debug handle: generated wrappers include
__zig_address__()to help debug pointer identity.
Development Workflow
- •Edit Zig:
- •binding helpers:
src/faebryk/core/zig/src/pyzig/* - •module wrappers:
src/faebryk/core/zig/src/python/**
- •binding helpers:
- •Rebuild native modules:
- •
ato dev compile(importsfaebryk.core.zig; editable installs compile-on-import) - •set
ZIG_RELEASEMODE=ReleaseFast|ReleaseSafe|Debugas needed
- •
- •If you changed stubs/output:
- •ensure
src/faebryk/core/zig/gen/**gets updated (this is driven bysrc/faebryk/core/zig/__init__.py)
- •ensure
Testing
- •Smoke tests are usually through downstream modules:
- •
python -m faebryk.core.graph(GraphView allocation/cleanup stress) - •
ato dev test --llm test/core/solver(heavy user of graph + bindings via many subsystems)
- •
Best Practices
- •Assume mistakes segfault: treat changes here like unsafe systems programming.
- •Be explicit about ownership:
- •if a wrapper allocates Zig memory, define how it is freed (explicit
.destroy()vstp_dealloccalling.deinit()). - •if you duplicate input buffers (sexp does), expose a
free(...)path and document it.
- •if a wrapper allocates Zig memory, define how it is freed (explicit
- •Don’t rely on Python GC for Zig arenas unless you intentionally installed a
tp_deallocthat callsdeinit. - •Stub hygiene matters: keep the
.pyisurface accurate; many callers rely on types for navigation.
Build-on-import behavior (important)
src/faebryk/core/zig/__init__.py is responsible for:
- •compiling extensions in editable installs (unless
ZIG_NORECOMPILE=1) - •loading
pyzig.soandpyzig_sexp.sofromsrc/faebryk/core/zig/zig-out/lib/ - •copying + formatting generated
.pyifiles intosrc/faebryk/core/zig/gen/**(black + ruff)