What's New in v0.13.0
Breaking: requires Zig 0.16.0 and Python 3.10+; existing projects need a few build.zig edits (including one line for macOS); pyoz.fmt returns pyoz.Formatted (see Changed); wheels are now portable and tagged manylinux_2_17_* / macosx_13_0_*. Step-by-step instructions: Upgrading to 0.13.
Added
- Upgrading to 0.13 guide, verified by migrating a project generated by the 0.12 CLI.
- Editable installs (PEP 660).
pip install -e .works throughpyoz.backend(newbuild_editablehook), andpyoz developnow builds and pip-installs a standard editable wheel (__editable__.*.pth+ dist-info) instead of symlinking into site-packages. Rebuilds are picked up without reinstalling;pip uninstallremoves it. - Portable wheels, tagged from the binary.
pyoz buildbuilds wheels for a baseline CPU, glibc 2.17 on Linux (manylinux_2_17_*, accepted by PyPI) and macOS 13.0, and reads the platform tag from the built module (highest glibc symbol version, allowed shared libraries, minimum macOS version, architecture), like auditwheel.--nativebuilds for the exact machine.linux-platform-tagselects the glibc floor (manylinux_2_28_x86_64builds against glibc 2.28). (#58) - Cross-building wheels:
pyoz build --target <targets>|all --python <3.X[t]>. One machine builds wheels for Linux, macOS and Windows on x86_64 and aarch64, for any CPython 3.10–3.14 including free-threaded 3.13t/3.14t. Headers and Windows import libraries for other platforms or versions come from python-build-standalone, verified against its SHA256SUMS and cached. - Complete wheel metadata from
[project](PEP 621/639): readme (file or text, any content type), SPDXlicenseexpressions and legacy license tables,license-filesshipped in.dist-info/licenses/(by defaultLICEN[CS]E*,COPYING*,NOTICE*,AUTHORS*), classifiers, keywords, authors, maintainers, URLs,dependencies,optional-dependencies, andentry_points.txtfrom[project.scripts]/gui-scripts/entry-points.pyoz publishsends the same metadata, so PyPI shows the classifiers and license. Parsing uses a real TOML reader (multi-line arrays, inline tables). (#58) - Source installs without Zig: when no Zig 0.16 is on
PATH,pyoz.backendrequests theziglangpackage in pip's isolated build environment and builds with its compiler, sopip install git+...and sdists work anywhere.pyoz buildwarns whenzigis a different minor release. (#60) pyoz.Args(...)keyword arguments on class methods (instance, static and class methods), with defaults shown inhelp()and fields expanded in stubs. Contributed by @marselester. (#59).withParams("a, b")onpyoz.funcentries names Python-visible parameters in stubs andhelp()(previously alwaysarg0, arg1). (#61)- Async protocols on classes:
__aiter__,__anext__,__await__,__aenter__,__aexit__. Instances work withasync for,await objandasync with, andanext(it, default)works as well. Plain Zig return values become awaitables that complete immediately and need no running event loop, so they work under asyncio, trio or a barecoro.send(None). Returning apyoz.asyncFnresult runs the work on astd.Iotask, and in__anext__anullresult from the task ends the iteration. The newpyoz.Future(f)names that result type. The slots take the per-object lock on free-threaded builds, work in ABI3 mode, and generateasync def/Generator[...]stubs. pyoz.asyncMethod: async instance methods. Theselfparameter type is the safety contract, enforced at compile time:self: Truns on a copy taken at call time (pointer-free fields);self: *const Tborrows the object, which is kept alive until the Zig task is joined, and is only allowed on__frozen__classes with no*Tmethods;self: *Tis rejected.- Async functions on
std.Io:pyoz.asyncFn(f). Calling the function from Python returns anasyncio.Future;fruns on its ownstd.Iotask without the GIL. Optional leadingstd.Ioandstd.mem.Allocator(per-call arena) parameters. Cancelling the Python task cancels the Zig task (error.Canceledat its nextIocall). Bounded thread use (pyoz.setAsyncConcurrency, default 256) with unbounded queued concurrency. About 3x the throughput ofrun_in_executorfor bulkgather. Works on regular, free-threaded and ABI3 builds. - Free-threading stress tests in the suite: on free-threaded interpreters, many threads hammer one object (ArrayList appends that race on reallocation, plus read-modify-write), share one iterator through its
__next__slot and one async iterator through__anext__, and six event loops run async jobs in parallel; they run on multi-core CI runners in Debug and ReleaseSafe and skip on GIL builds. - Free-threaded CPython (PEP 703) support.
.gil_used = falsemodule option declares GIL-free operation (previously every PyOZ import re-enabled the GIL). Every class method, property and protocol slot runs in a per-object critical section on free-threaded builds (__lock__ = falseopts out); zero cost on regular builds.pyoz buildproducescpXY-cpXYtwheels. CI tests 3.14t. pyoz.io(),pyoz.asyncLiveJobs(),pyoz.Formatted.- Async functions can return PyOZ class instances and take pointer-free structs (including PyOZ classes) by value; up to 8 parameters; module
.error_mappingsapply to async errors; stubs emitAwaitable[T]. Tested on asyncio and uvloop, and cross-linked for Windows (x86_64/ARM64), macOS (x86_64/arm64) and Linux (x86_64/aarch64).
Changed
- Python 3.10 is now the minimum (3.8 and 3.9 are end-of-life). ABI3 builds use
Py_LIMITED_API = 0x030A0000and producecp310-abi3wheels; generated projects declarerequires-python = ">=3.10"; CI tests 3.10–3.14 and 3.14t.abi.zig's version checks now derive from the same constant (they previously read a build option no build script defined). - Zig 0.16.0 is now required. The library, build scripts, CLI,
pypi/backend and the project templates generated bypyoz initall target Zig 0.16 (std.Io,std.process.Init, module-levellink_libc/linkSystemLibrary). Projects created with an olderpyoz initneed the samebuild.zigchanges:.link_libc = trueon the module instead oflib.linkLibC(), andaddLibraryPath/linkSystemLibrary(name, .{})called on the module. - Pure-Zig wheel compression. The vendored miniz C library is removed; DEFLATE now uses
std.compress.flate. The CLI is a single static binary with no libc dependency. pyoz initno longer runszig buildtwice. The package fingerprint is generated directly and the dependency hash is pinned withzig fetch --save. Generatedbuild.zig.zonfiles declareminimum_zig_version = "0.16.0".pyoz testandpyoz benchshare one implementation.- PyPI upload errors show the server's message instead of only the HTTP status, with a hint for common causes; they no longer claim "the version already exists" for every HTTP 400. The Python
pyozcommand exits with the error instead of a traceback. (#58) - Keyword-argument errors follow CPython:
f() got an unexpected keyword argument 'x'(previously unknown keywords were silently ignored),got multiple values for argument 'x',missing required argument 'x',takes at most N positional arguments. A specific conversion error (e.g.OverflowError) is no longer replaced by a genericTypeError,help()shows the real defaults (exponent=2.0, notexponent=None), andPath/buffer arguments passed throughpyoz.Argsare released after the call. (#59) - Fixed-size array parameters accept tuples as well as lists, and stubs say
list[T] | tuple[T, ...]. A wrong length raisesValueError: expected N items, got M(previously aTypeErrorwhose message was the Zig error name). (#61) - The Zig version is defined once (
version.zig); a CLI test checks every other copy (build.zig.zonfiles, the pip backend, CI workflows, installation docs). - Python 3.14 supported and tested.
pyoz.fmtis now lazy and lifetime-safe (breaking). It returns apyoz.Formattedvalue that is rendered by its consumer (everyraise*function, and any return-value conversion tostr). Code that declared a[*:0]const u8return type forpyoz.fmtresults must usepyoz.Formatted("fmt", struct { ... })(andreturn .{ .args = .{ ... } }) instead; this is a compile error, not a silent change.
Fixed
- Examples returned pointers into dead stack frames (
join_strings,decimal_doublein both examples), and one returned a shared static buffer that races under free-threading (iter_join). They passed in Debug but failed in release builds with Zig 0.16's optimizer. They now return heap memory viapyoz.Owned;Owned(pyoz.Decimal)is supported. CI now also runs every test suite in ReleaseSafe. pip install .failed for every project usingpyoz.backend(ModuleNotFoundError: No module named '_pyoz': the backend imported the native module by the wrong name). CI now builds the pyoz wheel and runspip install .andpip install -e .on a generated project.pyoz publishonly uploads wheels for the current name and version; leftovers from earlier builds indist/are skipped instead of failing with HTTP 400.- Windows ReleaseSafe builds failed inside Zig 0.16's translation of MinGW's
_FORTIFY_SOURCEwrappers;_FORTIFY_SOURCEis now undefined for the Python header import on Windows. pyoz.fmtreturned a pointer into a dead stack frame when used as a__repr__/__str__return value. Messages longer than 4 KB were replaced by "fmt: message too long"; they are now rendered in full.reprviapyoz.fmtis ~12% faster.- Free-threaded builds: static object headers wrote
1intoob_tid; the class freelist raced (now disabled on free-threaded builds); lazy datetime/decimal/pathlib caches raced (now lock-free), including an ABI3 datetime ordering bug where a class could be used before it was published; Python library name missed thetABI flag. - Wheel RECORD is now spec-compliant. Every file is listed with its
sha256=digest and size; previously entries had no hashes. - Wheel and
.dist-infonames are normalized (My-Pkg→my_pkg) as the binary distribution format requires. - Reproducible wheels: ZIP timestamps honor
SOURCE_DATE_EPOCH. - Cross-compiling generated projects to Windows now installs
name.pyd(the template chose the extension from the host OS instead of the target). - Windows wheels with nested Python packages use
/in ZIP entry names instead of\. - ABI3 builds under Zig 0.16: built-in type objects (
PyLong_Type, ...) are referenced via@extern, since 0.16's C translator rejects extern variables of opaque type underPy_LIMITED_API. - ABI3 classes defining
__eq__without__hash__failed to compile (PyObject_HashNotImplementedwas cast as a function body rather than a pointer). - Double free in Python detection when
sysconfigreturned an empty include path. - Child processes killed by a signal no longer trip a union-field safety check in
pyoz build/test/bench/develop. - TestPyPI uploads prefer
TEST_PYPI_TOKENoverPYPI_TOKENwhen both are set. pyoz initwith an invalid name no longer leaves an empty directory behind.- Generated README referenced a nonexistent
pyoz build-wheelcommand. - macOS builds of generated projects failed to link (
undefined symbol: _PyErr_Occurred, ...): the template never allowed the interpreter-provided C API symbols to be undefined. New projects setlinker_allow_shlib_undefinedon macOS; existing ones need the one-line change in the upgrading guide. - macOS wheels were uninstallable: the tag used the build machine's full macOS version (e.g.
macosx_14_5_arm64), but pip only generatesmacosx_N_0tags for macOS 11 and later. - Linux wheels were not portable: built for the build machine's CPU features and glibc, tagged
linux_*(rejected by PyPI), and the documentedmanylinuxoverride only relabelled them. Thepyozpackage's own wheels were taggedmanylinux2014/macosx_11_0while built against glibc 2.28/2.34 and macOS 13. - Windows non-ABI3 modules linked
python3.lib, which only exports the Stable ABI; they now linkpython3XY[t].lib. - Free-threaded builds: protocol slots were not locked.
__repr__,__iter__/__next__, operators,__getitem__/__setitem__,__call__, rich comparison and the buffer protocol ran without the object's critical section on free-threaded CPython (only methods and properties took it). A new stress test drives__next__from many threads. - Explicit-target builds on Debian/Ubuntu could not find the multiarch
pyconfig.h; PyOZ'sbuild.zignow provides it. - sdists include
py-packages, README and license files, skip build output, and use the normalized file name (PEP 625).
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.13.0.tar.gz for the source code.
Quick Start
pyoz init mymodule
cd mymodule
pyoz build
pip install dist/*.whl