Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
@@ -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 }}
32 changes: 32 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# zlib-py

<p align="center">
<a href="https://github.com/Rust-for-CPython">
<img src="https://rust-for-cpython.com/r4p.svg" alt="Rust for CPython" width="160" />
</a>
</p>

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).
74 changes: 63 additions & 11 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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 —
Expand All @@ -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:
Expand Down
Loading