diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
new file mode 100644
index 0000000..9fd25ff
--- /dev/null
+++ b/.github/workflows/CI.yml
@@ -0,0 +1,186 @@
+# This file is autogenerated by maturin v1.12.6
+# To update, run
+#
+# maturin generate-ci github
+#
+name: CI
+
+on:
+ push:
+ branches:
+ - main
+ - master
+ tags:
+ - '*'
+ pull_request:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ linux:
+ runs-on: ${{ matrix.platform.runner }}
+ strategy:
+ matrix:
+ platform:
+ - runner: ubuntu-22.04
+ target: x86_64
+ - runner: ubuntu-22.04
+ target: x86
+ - runner: ubuntu-22.04
+ target: aarch64
+ - runner: ubuntu-22.04
+ target: armv7
+ - runner: ubuntu-22.04
+ target: s390x
+ - runner: ubuntu-22.04
+ target: ppc64le
+ steps:
+ - uses: actions/checkout@v6
+ - uses: actions/setup-python@v6
+ with:
+ python-version: 3.x
+ - name: Build wheels
+ uses: PyO3/maturin-action@v1
+ with:
+ target: ${{ matrix.platform.target }}
+ args: --release --out dist --find-interpreter
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
+ manylinux: auto
+ - name: Upload wheels
+ uses: actions/upload-artifact@v6
+ with:
+ name: wheels-linux-${{ matrix.platform.target }}
+ path: dist
+
+ musllinux:
+ runs-on: ${{ matrix.platform.runner }}
+ strategy:
+ matrix:
+ platform:
+ - runner: ubuntu-22.04
+ target: x86_64
+ - runner: ubuntu-22.04
+ target: x86
+ - runner: ubuntu-22.04
+ target: aarch64
+ - runner: ubuntu-22.04
+ target: armv7
+ steps:
+ - uses: actions/checkout@v6
+ - uses: actions/setup-python@v6
+ with:
+ python-version: 3.x
+ - name: Build wheels
+ uses: PyO3/maturin-action@v1
+ with:
+ target: ${{ matrix.platform.target }}
+ args: --release --out dist --find-interpreter
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
+ manylinux: musllinux_1_2
+ - name: Upload wheels
+ uses: actions/upload-artifact@v6
+ with:
+ name: wheels-musllinux-${{ matrix.platform.target }}
+ path: dist
+
+ windows:
+ runs-on: ${{ matrix.platform.runner }}
+ strategy:
+ matrix:
+ platform:
+ - runner: windows-latest
+ target: x64
+ python_arch: x64
+ - runner: windows-latest
+ target: x86
+ python_arch: x86
+ - runner: windows-11-arm
+ target: aarch64
+ python_arch: arm64
+ steps:
+ - uses: actions/checkout@v6
+ - uses: actions/setup-python@v6
+ with:
+ python-version: 3.13
+ architecture: ${{ matrix.platform.python_arch }}
+ - name: Build wheels
+ uses: PyO3/maturin-action@v1
+ with:
+ target: ${{ matrix.platform.target }}
+ args: --release --out dist --find-interpreter
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
+ - name: Upload wheels
+ uses: actions/upload-artifact@v6
+ with:
+ name: wheels-windows-${{ matrix.platform.target }}
+ path: dist
+
+ macos:
+ runs-on: ${{ matrix.platform.runner }}
+ strategy:
+ matrix:
+ platform:
+ - runner: macos-15-intel
+ target: x86_64
+ - runner: macos-latest
+ target: aarch64
+ steps:
+ - uses: actions/checkout@v6
+ - uses: actions/setup-python@v6
+ with:
+ python-version: 3.x
+ - name: Build wheels
+ uses: PyO3/maturin-action@v1
+ with:
+ target: ${{ matrix.platform.target }}
+ args: --release --out dist --find-interpreter
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
+ - name: Upload wheels
+ uses: actions/upload-artifact@v6
+ with:
+ name: wheels-macos-${{ matrix.platform.target }}
+ path: dist
+
+ sdist:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - name: Build sdist
+ uses: PyO3/maturin-action@v1
+ with:
+ command: sdist
+ args: --out dist
+ - name: Upload sdist
+ uses: actions/upload-artifact@v6
+ with:
+ name: wheels-sdist
+ path: dist
+
+ release:
+ name: Release
+ runs-on: ubuntu-latest
+ if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
+ needs: [linux, musllinux, windows, macos, sdist]
+ permissions:
+ # Use to sign the release artifacts
+ id-token: write
+ # Used to upload release artifacts
+ contents: write
+ # Used to generate artifact attestation
+ attestations: write
+ steps:
+ - uses: actions/download-artifact@v7
+ - name: Generate artifact attestation
+ uses: actions/attest-build-provenance@v3
+ with:
+ subject-path: 'wheels-*/*'
+ - name: Install uv
+ if: ${{ startsWith(github.ref, 'refs/tags/') }}
+ uses: astral-sh/setup-uv@v7
+ - name: Publish to PyPI
+ if: ${{ startsWith(github.ref, 'refs/tags/') }}
+ run: uv publish 'wheels-*/*'
+ env:
+ UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..396aac3
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,32 @@
+name: test
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ pytest:
+ name: pytest (flake CPython)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Install Nix
+ uses: DeterminateSystems/nix-installer-action@main
+ with:
+ extra-conf: |
+ experimental-features = nix-command flakes
+
+ - name: Magic Nix cache
+ uses: DeterminateSystems/magic-nix-cache-action@main
+
+ - name: Build testEnv
+ run: nix build .#testEnv --print-build-logs
+
+ - name: Run tests
+ run: ./result/bin/python -m pytest tests/ -v
diff --git a/Cargo.toml b/Cargo.toml
index 266caa2..76f152a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,5 +10,5 @@ crate-type = ["cdylib"]
[dependencies]
-pyo3 = "0.27.0"
+pyo3 = { version = "0.27.0", features = ["abi3-py38"] }
zlib-rs = "0.6.3"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4ef8308
--- /dev/null
+++ b/README.md
@@ -0,0 +1,50 @@
+# zlib-py
+
+
+
+
+
+
+
+A small pyo3 extension that exposes [`zlib-rs`](https://crates.io/crates/zlib-rs) to Python.
+As a small proof of concept for the [Rust for CPython](https://github.com/Rust-for-CPython) effort.
+
+## Building
+
+### With Nix
+
+```sh
+nix build # Python env with zlib_py importable, at ./result
+nix run .#python -- -c 'import zlib_py; print(zlib_py.compress(...))'
+nix develop # cargo + rustc + maturin + the pinned python on PATH
+```
+
+The flake fetches CPython and pyo3 at the pinned revs (we'll attempt to keep
+this close to HEAD in both cases), then builds the extension with
+`maturinBuildHook`. The cargo registry is vendored in a fixed-output derivation
+so the actual build runs offline.
+
+### Without Nix
+
+```sh
+uv sync
+uv run maturin develop
+uv run python -c 'import zlib_py; print(zlib_py.compress(...))'
+```
+
+This uses whatever CPython + pyo3 your environment resolves (no grantees of working).
+
+
+## Rust for CPython — links
+
+- [Official GitHub org](https://github.com/Rust-for-CPython)
+- [Pre-PEP discussion thread](https://discuss.python.org/t/pre-pep-rust-for-cpython/104906)
+- [Latest progress update (2026-04)](https://blog.python.org/2026/04/rust-for-cpython-2026-04/)
+
+## Acknowledgements
+
+Prior art and inspiration: [`farhaanaliii/zlib-rs-python`](https://github.com/farhaanaliii/zlib-rs-python) — a separate pyo3 binding to `zlib-rs` with a broader stdlib-`zlib`-compatible surface (`compressobj`, `decompressobj`, checksums). If you want a more complete drop-in replacement today, look there.
+
+## License
+
+MIT — see [LICENSE](./LICENSE).
diff --git a/flake.nix b/flake.nix
index 0899a0c..a47b37a 100644
--- a/flake.nix
+++ b/flake.nix
@@ -19,7 +19,7 @@
owner = "python";
repo = "cpython";
rev = cpythonRev;
- sha256 = "sha256-nbOwTSb3MTICxi0d8nIlMOp2htfAkOLfNnzT2u2TK4k=";
+ sha256 = "sha256-QtzHQqVUHzTyO9WzlNSz2tZ+Pab82nxPOn6E2RrudSo=";
};
# pyo3 source fetched outside the FOD so we can use path patches —
@@ -32,22 +32,74 @@
sha256 = "sha256-nbOwTSb3MTICxi0d8nIlMOp2htfAkOLfNnzT2u2TK4k=";
};
- # Custom CPython: take a current nixpkgs python derivation as scaffolding
- # (we only reuse its configure/build wiring), swap the source for our
- # pinned git rev, and add autoreconfHook so the missing ./configure is
- # regenerated before nixpkgs' configurePhase runs.
+ # Custom CPython: take a current nixpkgs python derivation as
+ # scaffolding (we only reuse its configure/build wiring) and swap the
+ # source for our pinned git rev. The tarball-shaped source from
+ # fetchFromGitHub already contains a pregenerated `./configure`, so
+ # there's no need for autoreconfHook here.
+ #
+ # Two-stage override:
+ # 1. `.override { self = customPython; ... }` re-runs the python
+ # derivation function with `self` bound to *our* final
+ # derivation. This is the fix-point that makes passthru attrs
+ # (`pkgs`, `withPackages`, `pythonForBuild`) reference the
+ # customized interpreter rather than the unmodified scaffold.
+ # 2. `.overrideAttrs` then swaps `src` / `version` on top. Passthru
+ # is already wired to `self`, so withPackages picks up the
+ # overridden interpreter correctly.
+ #
+ # Plain `.overrideAttrs` alone leaves passthru pinned to the original
+ # python — `customPython.withPackages` silently builds a stock env.
#
- # The base attribute (`python313`) is just the scaffold — the resulting
- # interpreter's actual version comes from the cpython commit. If the
- # commit lives on a different branch, swap the scaffold accordingly.
+ # The base attribute (`python315`) determines the build *scaffolding*
+ # (configure flags, library deps); the actual interpreter version
+ # comes from the cpython commit. Pick a scaffold close to the target
+ # version so configure flags match.
cpythonShortRev = builtins.substring 0 7 cpythonRev;
- customPython = pkgs.python313.overrideAttrs (old: {
+ customPython = (pkgs.python315.override (old: {
+ self = customPython;
+ # Tell nixpkgs what version we're *actually* building so installed
+ # paths (lib/python3.16/...) line up with what cpython 3.16-alpha
+ # writes. Without this nixpkgs computes paths against the scaffold
+ # version (3.15) and postInstall steps like stripTests fail when
+ # they can't find lib/python3.15/test/__init__.py.
+ sourceVersion = {
+ major = "3";
+ minor = "16";
+ patch = "0";
+ suffix = "a-${cpythonShortRev}";
+ };
+ # Cross-compile passthru looks up `pkgsBuildTarget.${pythonAttr}`.
+ # nixpkgs has no `python316` attribute yet, so pin to the closest
+ # one (`python315`) for that lookup. Build-host side only — the
+ # actual interpreter is still our overridden 3.16 derivation.
+ pythonAttr = "python315";
+ # Unused (src is overridden below) but the scaffold function
+ # demands a non-null hash arg.
+ hash = pkgs.lib.fakeHash;
+ })).overrideAttrs (old: {
pname = "cpython-git";
version = cpythonShortRev;
src = cpythonSrc;
- nativeBuildInputs = (old.nativeBuildInputs or [])
- ++ [ pkgs.autoreconfHook ];
doCheck = false;
+
+ # nixpkgs' preConfigure does a `substituteInPlace configure
+ # --replace-fail 'libmpdec_machine=universal' …` to defeat Darwin
+ # universal-build autodetection. CPython 3.16+ rewrote the
+ # detection and the literal string is gone, so --replace-fail
+ # aborts the build. Patch our copy to use --replace-quiet, which
+ # tolerates a missing pattern; `export PYTHON_DECIMAL_WITH_MACHINE`
+ # earlier in the same script still does the heavy lifting.
+ preConfigure = builtins.replaceStrings
+ [ "--replace-fail 'libmpdec_machine=universal'" ]
+ [ "--replace-quiet 'libmpdec_machine=universal'" ]
+ (old.preConfigure or "");
+
+ # Bumping sourceVersion to 3.16 makes nixpkgs look for patches
+ # under cpython/3.16/, which doesn't exist. The 3.15 patches
+ # (no-ldconfig, virtualenv-permissions, mimetypes) apply cleanly
+ # to 3.16-alpha, so re-pin to them.
+ patches = pkgs.python315.drvAttrs.patches;
});
# Two flavours of the same [patch.crates-io] block: