What's New in v0.11.0
Added
pyoz init --package-- Python package directory layout - New--packageflag forpyoz initthat scaffolds a project with a proper Python package directory. Instead of installing a flat.sodirectly into site-packages, the extension is placed inside a package directory with an__init__.pythat re-exports all native symbols. The native module is automatically prefixed with an underscore (e.g.,_myproject.so) to avoid name collisions with the package directory.pyoz buildandpyoz developautomatically detect package mode whenmodule-namestarts with_and apy-packagesentry matches the project name, placing the.soand.pyiinside the package directory in wheels and development installs. This enables combining native extensions with pure Python code in the same importable package.pyoz.Owned(T)-- allocator-backed return types - New generic wrapper for returning heap-allocated data from Zig functions and methods.Owned(T)pairs a value with its allocator; PyOZ converts the inner value to a Python object then automatically frees the backing memory. This eliminates the need for fixed-size stack buffers when building dynamic strings or data. Thepyoz.owned(allocator, value)constructor auto-coerces mutable slices ([]u8) to const ([]const u8), sostd.fmt.allocPrintresults can be returned directly without@ascasts. Supports all return type wrappers:!Owned(T)(error union),?Owned(T)(optional). Works with any slice type thattoPyhandles.pyoz.fmt()-- inline string formatter - New utility function for formatting strings using Zig'sstd.fmtsyntax. Returns a[*:0]const u8suitable for passing toPyErr_SetString, raise functions, or any API that copies the string immediately. The 4096-byte buffer lives in the caller's stack frame (the function isinline), so it is safe to use in one-liners likereturn pyoz.raiseValueError(pyoz.fmt("value {d} exceeds limit {d}", .{ val, limit })). Eliminates the need for manualbufPrintZboilerplate when building dynamic error messages.pyoz.base(Parent)-- single inheritance between PyOZ classes - New function for declaring that one PyOZ-defined Zig struct inherits from another. The child struct declarespub const __base__ = pyoz.base(Animal);and embeds the parent as_parent: Animal(must be the first field). PyOZ setstp_baseto the parent's type object soisinstance(), Python's MRO, and method/property inheritance all work automatically. The child's__init__accepts a flattened argument list (parent fields first, then child fields). Parent methods and properties are inherited via MRO — no duplication needed. Works in both non-ABI3 (static type object) and ABI3 (PyType_FromSpecWithBases) modes. Comptime validation ensures correct struct layout and parent registration order. Stub generation emitsclass Dog(Animal):with the correct flattened__init__signature.pyoz test-- inline embedded tests - New CLI command that builds the module, extracts embedded Python test code from the compiled.so, and runs it withunittest(stdlib, zero dependencies). Tests are defined inline in the Zig module definition usingpyoz.@"test"("name", \\body)for assertion tests andpyoz.testRaises("name", "ExceptionType", \\body)for exception tests. The generated Python file usesunittest.TestCasewith properassertRaisescontext managers. Supports--verbose/-vfor detailed output and--release/-rto build in release mode before testing.pyoz bench-- inline embedded benchmarks - New CLI command that builds the module in release mode, extracts embedded Python benchmark code, and runs it withtimeit(stdlib). Benchmarks are defined inline usingpyoz.bench("name", \\body). The generated script times each benchmark over 100,000 iterations and prints a formatted results table with ops/s. Both commands are available in the Zig CLI (src/cli) and Python wrapper (pyoz test/pyoz bench).pyoz.TestDefandpyoz.BenchDeftypes - New struct types for defining inline tests and benchmarks.pyoz.@"test"()creates assertion-based tests,pyoz.testRaises()creates exception-checking tests, andpyoz.bench()creates benchmarks. These are passed topyoz.module()via the new.testsand.benchmarksoptional config fields.- Binary section embedding for tests and benchmarks - Test and benchmark Python code is generated at comptime and embedded into the compiled
.soas named sections (.pyoztest/.pyozbencon ELF/PE,__DATA,__pyoztest/__DATA,__pyozbencon Mach-O), using the same magic-header pattern as stubs (PYOZTEST/PYOZBENC+ 8-byte LE length + content). - Generic section extraction in
symreader.zig- NewextractNamedSection()infrastructure that parameterizes section name and magic string across ELF/PE/Mach-O formats.extractTests()andextractBenchmarks()are thin wrappers. ExistingextractStubs()is unchanged. - Syntax checking before test/bench execution -
pyoz testandpyoz benchnow runpython3 -m py_compileon the generated Python file before executing it. If the user's inline test/benchmark code has syntax errors, a clear error message with line numbers is shown instead of a confusing runtime traceback.
Fixed
__hash__correctness for classes defining__eq__- When a class defines__eq__(or any comparison dunder) without explicitly defining__hash__, PyOZ now setstp_hash = PyObject_HashNotImplemented, making instances correctly unhashable (raisesTypeErroronhash(), cannot be added to sets or used as dict keys). Previously, these classes silently retained the default id-based hash, violating Python semantics. This fix works for both ABI3 and non-ABI3 modes. Classes that define both__eq__and__hash__continue to work as before.- Computed property setters returning
?voidor!voidcaused compile error - When aset_Xcomputed property setter returned an optional (?void) or error union (!void) instead of plainvoid, the generated wrapper inproperties.zigdiscarded the return value, which Zig rejects for non-void types. This prevented using thereturn pyoz.raiseValueError("msg")one-liner pattern in property setters. All three setter code paths (generateSetterfor field-based custom setters,generateComputedSetterfor computed properties, andgeneratePyozPropertySetterforpyoz.property()API setters) now handle?void,!void, and plainvoidreturn types using the same three-branch dispatch pattern used throughout the rest of the codebase (attributes.zig,descriptor.zig,sequence.zig, etc.). Also fixedgenerateSetter's existing error union branch to preserve already-set Python exceptions instead of overwriting them. - Zig errors now map to correct Python exception types - Previously, all Zig errors (including
error.TypeError,error.IndexOutOfBounds,error.DivisionByZero, etc.) were incorrectly raised asRuntimeErrorin Python. NowsetError()inwrappers.zigandsetErrorFromMapping()inerrors.ziguse a newmapWellKnownError()function that first tries an exact match against allExcBaseenum variants (covering all 50+ standard Python exceptions likeTypeError,ValueError,IndexError,KeyError,ZeroDivisionError,AttributeError,FileNotFoundError,PermissionError,MemoryError,NotImplementedError,StopIteration, etc.), then checks common Zig-idiomatic aliases (DivisionByZero->ZeroDivisionError,OutOfMemory->MemoryError,IndexOutOfBounds->IndexError,KeyNotFound->KeyError,FileNotFound->FileNotFoundError,PermissionDenied->PermissionError, etc.), and falls back toRuntimeErroronly for truly unrecognized errors.
Installation
Download the binary for your platform and add it to your PATH:
| Platform | Binary |
|---|---|
| Linux x86_64 | pyoz-x86_64-linux
|
| Linux ARM64 | pyoz-aarch64-linux
|
| macOS x86_64 | pyoz-x86_64-macos
|
| macOS ARM64 (Apple Silicon) | pyoz-aarch64-macos
|
| Windows x86_64 | pyoz-x86_64-windows.exe
|
| Windows ARM64 | pyoz-aarch64-windows.exe
|
Source
Download PyOZ-0.11.0.tar.gz for the source code.
Quick Start
pyoz init mymodule
cd mymodule
pyoz build
pip install dist/*.whl