Ziggy Pydust: packaging native Python extensions written in Zig
A toolkit for building Python extensions in Zig.
At a glance
- What is it?
- Ziggy Pydust is an Apache 2.0 licensed framework for writing and packaging native Python extension modules in Zig, with a pytest plugin that discovers and runs Zig tests and a comptime layer that wraps native Zig types for the Python boundary. The judgement is that the tooling is real and the packaging story is coherent, but the compatibility notes and the version numbering in the manifest still need reading carefully.
- Who is it for?
- Ziggy Pydust fits a Python project that needs one hot inner loop in native code and wants the build, the packaging and the test discovery handled together, because the extension modules are declared in the pyproject file, compiled by the build.zig step and discovered by the pytest plugin without a separate test runner.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Zig, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A framework for the boundary between Zig and CPython
Ziggy Pydust is described as a framework for writing and packaging native Python extension modules written in Zig. That is a narrow promise with a lot inside it, because a Python extension module is not just a compiled file: it is a shared object with a specific module name, a specific initialisation entry point and a specific set of exported symbols, and every one of those has to line up with what the interpreter expects at import time.
The framework is built on the CPython C API, and the compatibility statement is explicit that CPython 3.11 or newer is supported. On the Zig side, the README lists Zig 0.14.0 as the supported version.
Three capabilities are listed as the headline features. The first is packaging the extension modules themselves, so that a Zig library becomes something pip can install. The second is a pytest plugin that discovers and runs Zig tests, which removes the usual split where the Python tests run under pytest and the Zig tests run under a separate command. The third is comptime argument wrapping and unwrapping, the layer that lets a Python call arrive in a Zig function as a native Zig type rather than as a loose variant.
The README links still point at the fulcrum-so organisation on GitHub, and the project page lives at pydust.fulcrum.so.
The compatibility notes and the manifest do not agree on Zig
The version story in this project takes two readings, and the difference is easy to miss before you write any code.
The README compatibility section says Pydust supports Zig 0.14.0 and CPython 3.11 and above. The manifest tells a different story about Zig. In pyproject.toml, the ziglang dependency is pinned at ^0.15.1, and the same pin appears again in the documentation dependency group, which is where the tool that generates the Zig documentation lives.
So the prose says 0.14.0 and the dependency says 0.15.1 or newer. One of the two is out of date, and there is no note in the README saying which. For anyone building a project on top of this, that is a question to settle locally rather than to assume.
The Python side is consistent. The manifest requires python = "^3.11", the pytest configuration in the same file assumes a modern pytest, and the README badge points at the Python 3.11 release notes for the C API. The practical floor is Python 3.11.
The same manifest has a second oddity. The package version field reads 0.1.0, while the published releases listed for the project are 0.26.0, 0.25.1 and 0.25.0. The Poetry version field is not the number the project publishes under, which means the version you get from the index and the version in the source tree are tracked separately.
The pytest plugin is what makes the build testable
The most interesting configuration in the repository sits in the pyproject file, because the Python and Zig sides of a project are declared side by side there.
First, the plugin registration. Under the pytest11 entry point group, the project registers pydust.pytest_plugin, which is how pytest picks up the Zig test runner without a conftest file. The pytest configuration sets the test paths to the test directory and the example directory, so a run of pytest picks up both the project's own tests and the sample modules.
Second, the project level configuration. The root points at the example directory and the Zig build file is set to pytest.build.zig, so the test run drives a Zig build that lives beside the Python tests rather than the main build.zig at the repository root.
Third, the extension module declarations. Each entry names a module and the Zig file that provides it, for instance example.hello paired with example/hello.zig, and the same pattern for the exception, memory, module and GIL related ones. This is the declaration that turns a Zig file into an importable Python module.
The result is a project where one command covers both languages, which is the whole reason a framework like this exists: without it, the Python side of an extension project and the Zig side of it are built and tested by separate machinery that rarely agrees on what a passing run means.
The sample modules catalogue what the interop layer can carry
The example directory is the clearest statement of scope in the repository. Every entry there is a pair: a Zig file and a .pyi stub beside it. The names are the vocabulary of the boundary layer.
There is args_types.zig for how arguments are converted, buffers.zig for buffers crossing the boundary, classes.zig for exposing Zig types as Python classes, exceptions.zig for error types, functions.zig for plain function exports, gil.zig for the global interpreter lock, iterators.zig for iterator objects, memory.zig for memory management, modules.zig for module level structure, and operators.zig for operator overloads. A py.typed marker sits in the directory as well, which is the marker that tells type checkers the stubs are authoritative.
The README's snippet shows the shape a Zig extension module takes. A plain Zig function takes a struct with a named field and returns a native value, and a comptime block registers the module:
const py = @import("pydust");
pub fn fibonacci(args: struct { n: u64 }) u64 {
if (args.n < 2) return args.n;
var sum: u64 = 0;
var last: u64 = 0;
var curr: u64 = 1;
for (1..args.n) {
sum = last + curr;
last = curr;
curr = sum;
}
return sum;
}
comptime {
py.rootmodule(@This());
}The interesting detail is the argument struct. A Python argument is turned into a Zig struct with named fields, and the comptime block is what binds the module root, so the type conversions happen where the compiler can see them.
Poetry for the build, mike for the docs, renovate for the dependencies
The build tooling around the framework is conventional and, for a project that touches two languages, deliberately simple.
The package is built with Poetry, and the build system section requires poetry-core with the masonry backend. The package list includes the pydust directory, and the example directory is excluded from the distribution, which is the right call for a set of sample modules that exist to be tested rather than installed. A console script named pydust is registered against pydust.__main__:main, so the tool has a command line entry point of its own.
The documentation has its own tool chain: mkdocs with the theme called Material, a plugin for including markdown files, and mike, which is the versioned documentation tool. That combination is what lets a project publish API documentation per version, and the site is split into a general documentation section and a separate one for the Zig side of the API.
Two smaller signals point to a maintained project. renovate.json is present, which is the configuration file for automated dependency update pull requests, and the template repository is published separately with the Poetry build, pytest and publishing from GitHub Actions already wired up, so a new project starts with the same pipeline instead of rebuilding it.
The dev dependencies are lean: pytest and ruff, with a line length of 120 and a lint selection of the pyflakes, pycodestyle and isort rule families. The test group adds numpy, which is a sensible choice for a project whose boundary layer deals with buffers.
Early stage, active development, and a version number that needs explaining
The contributing guide sets expectations plainly. The project is in its early stages, and the guide says there is a lot of low hanging fruit for contributors, then lists what help looks like: answering questions in GitHub issues and discussions, suggesting or implementing features, fixing bugs and performance problems, improving the documentation, and writing articles showing how the framework has been used.
The release history shows a steady line rather than a burst. Version 0.25.0 came on 2025-05-10, 0.25.1 on 2025-05-28, and 0.26.0 on 2025-09-18. The default branch is develop, and the last push was on 2026-10-19, so the tree is being worked on steadily even though the newest release tag, 0.26.0, is older than that.
The licence is Apache 2.0, permissive enough for commercial use and for embedding the framework in a larger build system. The one caveat to keep in view is the version field mismatch already mentioned, since it affects how you record which build of the framework your artefact came from.
For a project at this stage, the documentation and the sample modules are the specification. Everything else you will need to know about the boundary between Zig and CPython is written down in the example directory, one subject per file.
Editorial conclusion
Ziggy Pydust fits a Python project that needs one hot inner loop in native code and wants the build, the packaging and the test discovery handled together, because the extension modules are declared in the pyproject file, compiled by the build.zig step and discovered by the pytest plugin without a separate test runner. It is the wrong choice if you need a wide range of Python versions, since the manifest requires Python 3.11 and above, and it is the wrong choice if you need broad Zig coverage, since the documented compatibility line and the ziglang pin in the manifest do not agree. Before you start, check which Zig version your toolchain has against both the README and the pyproject pin, read the manifest's own version field against the published release numbers, and plan on the early stage caveats the contributing guide describes.
Frequently asked questions
What does Ziggy Pydust do?
It is a framework for writing and packaging native Python extension modules written in Zig. It packages the extension modules, provides a pytest plugin that discovers and runs Zig tests, and offers comptime argument wrapping for interop with native Zig types.
Which Zig and Python versions does Ziggy Pydust support?
The README lists Zig 0.14.0 and CPython 3.11 or above, while the pyproject manifest pins the ziglang dependency at ^0.15.1 and requires python ^3.11. The Python floor is therefore 3.11, and the Zig version has to be checked against both sources.
How are Python extension modules declared in Ziggy Pydust?
Under the tool.pydust section of pyproject.toml, each tool.pydust.ext_module entry names the importable module and the Zig file that provides it, and the root option points at the directory holding them. The pytest configuration then sets the test paths to the test and example directories.
How do I start a new project with Ziggy Pydust?
There is a separate template repository, fulcrum-so/ziggy-pydust-template, that already includes a Poetry build, pytest and publishing from GitHub Actions. The main documentation lives at pydust.fulcrum.so.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/spiraldb-ziggy-pydust)Was this useful?