python-zstandard ships three backends for the same zstd library
Python bindings to the Zstandard (zstd) compression library
At a glance
- What is it?
- python-zstandard is a BSD-3-Clause binding to the Zstandard compression library that offers a C extension, a CFFI interface and a Rust backend through PyO3, and its packaging is where most of the decisions a user actually hits are made.
- Who is it for?
- python-zstandard fits a Python service that needs zstd compression with dictionaries, streaming or frame parameters, and the fact that it carries no runtime dependencies makes it easy to add to an environment where a compiled dependency would be unwelcome. It does not fit a project that has to support Python below 3.10, and the CFFI path is harder to justify now that a C extension and a Rust backend both exist.
- Can I use it commercially?
- Yes. BSD-3-Clause 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 6 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One library, three ways to reach the C API
python-zstandard provides Python bindings for interfacing with the Zstandard compression library, and it does so through a C extension and a CFFI interface. The repository shows a third path as well, with a rust-ext/ directory alongside c-ext/ and the pure Python zstandard/ package directory.
The stated goal is a rich interface to the underlying C API through a Pythonic interface while not sacrificing performance, which the documentation phrases as exposing most of the features and flexibility of the C API while keeping the usability and safety Python provides. That is a specific ambition. Most compression bindings in Python offer a compress and a decompress and leave the interesting parts, dictionaries, frame parameters, pledged sizes, streaming with flushes, to the underlying library's own API.
The Cargo manifest names the Rust library backend_rust with crate type cdylib and path rust-ext/src/lib.rs, so the Rust build is a genuine third implementation rather than a wrapper around the C one. It links zstd-safe 7.2.4 and zstd-sys 2.0.16, and it enables the experimental, legacy and zstdmt features on both.
The two feature flags deserve a second look. zstdmt is the multi-threading support in the reference library, legacy keeps older frame formats readable, and experimental is what you would expect. A binding that exposes the experimental surface is exposing an API the upstream library itself marks as unstable, so the version of zstd-sys you build against starts to matter.
The package metadata says Python 3.10 and no dependencies
The project metadata is minimal and modern:
[project]
name = "zstandard"
dynamic = ["version"]
description = "Zstandard bindings for Python"
license = "BSD-3-Clause"
requires-python = ">=3.10"The distribution name on the index is zstandard, not python-zstandard, which trips people up once. The version is dynamic, which means it is read from the source rather than written in the metadata.
The runtime dependency list is empty, which is the line that decides whether this fits your deployment. There is nothing to resolve at install time beyond the package itself, and the optional cffi extra is the only place a dependency appears.
The classifiers list the supported Python versions from 3.10 through 3.14, and there is a free-threading classifier marked 1 - Unstable, which tells you that free-threaded builds are shipped and that the project does not consider them stable yet. The build system section requires setuptools>=77.0.0, packaging and cffi, so building from source needs a compiler toolchain.
The URL section points at the GitHub repository and at python-zstandard.readthedocs.io for the usage documentation, and the README says the same thing in prose.
CFFI versions that change with your Python
The setup.py is where the packaging subtleties live, and its comments are unusually informative.
The script refuses to run on anything older than Python 3.10, printing a message that Python 3.10+ is required and exiting. Then it picks a minimum CFFI version at runtime:
MINIMUM_CFFI_VERSION = "2.0" if sys.version_info[0:2] >= (3, 14) else "1.17"The comments above it explain why each version is required. CFFI 1.10 is needed for ffi.from_buffer() to handle all buffer types such as memoryview, CFFI 1.11 is needed for ffi.gc() to declare the size of objects so garbage collection pitfalls are avoided, and 1.17 is required everywhere so that older versions do not have to be supported. Version 2.0 is required for Python 3.14 and later, to add improved free-threading support.
The same rule appears in the pyproject, twice, once in the optional cffi extra and once in the build requirements, with environment markers that select cffi~=1.17 for Python below 3.14 and cffi>=2.0.0b for 3.14 and above, both excluding PyPy.
The other thing setup.py handles is cross-compilation. It reads SETUPTOOLS_EXT_SUFFIX from the environment, patches sysconfig so that a cross-compiled build does not get the host interpreter's extension suffix overwritten, and imports a compat shim from setuptools._distutils.compat.py39 when it is available.
How the project is built and tested
The CI configuration is visible as a row of badges in the README, and each one names a workflow: test, wheel, typing, sdist, anaconda and sphinx. That is the whole build matrix in one line, and it tells you what the project promises to produce: a source distribution, wheels, a Conda package, a documentation build, and a typing workflow that checks the type annotations.
The repository layout matches a project that keeps several packaging paths alive at once. There is a c-ext/ directory for the C extension, a rust-ext/ directory for the Rust backend, a zstd/ directory for the bundled library sources, setup_zstd.py for the zstd build step, and make_cffi.py for the CFFI build. There is also a Justfile, a Cargo.toml and Cargo.lock, a .cargo/ directory, pyproject.toml, setup.py, MANIFEST.in, a debian/ directory for packaging, a bench.py for benchmarks, and both uv.lock and a pyproject dependency-groups section for the dev environment.
The dev dependency group tells you how the project is tested: hypothesis 6.111.0 for property based testing, mypy for typing, pytest with pytest-xdist for parallel test runs, plus the cffi markers again. The pytest configuration lives in the pyproject under tool.pytest.ini_options.
For a user, the practical consequence is that the choice of interface is made by the wheel you get, not by an import statement. Whichever backend your platform received, the Python API is the same, and the CFFI extra exists for the platforms where a compiled extension is not available.
The alternative is shelling out to the zstd binary
There are two realistic alternatives to this binding, and they suit different situations.
The first is the zstd command line tool. A script can pipe data through it or compress a file, and the result is identical compressed data, since the format is the format. The difference in approach is that the shell approach gives you one-shot compression with the tool's own defaults, while the library gives you the C API surface, which means dictionaries, frame parameters, streaming with explicit flushes, pledged source sizes, and the ability to compress a stream incrementally without materialising it.
The second is a different compression library entirely. Python ships gzip, bz2 and lzma in the standard library, and the choice between them and zstd is usually a decision about ratio versus speed and about how well the format is supported by the other tools in your pipeline. If the consumer of the compressed data is a Python program that can use lzma, then lzma needs no extra dependency, and that is a real argument against adding a compiled extension to a service that has none.
The case for the binding is when zstd is already the format in play. When a database, a message queue or another service in the system speaks zstd, then the Python side has to speak zstd, and the question becomes only whether you want the C API or a subprocess. The binding wins on that comparison, mostly because a subprocess has to serialise your data through a pipe and keeps the library's finer controls, like frame parameters and streaming flushes, out of reach.
BSD-3-Clause terms, the 0.x line, and where the project is now
The licence is BSD-3-Clause, stated in the pyproject, in the Cargo manifest and in the setup.py header, with the LICENSE file at the repository root and copyright to Gregory Szorc. The same terms apply to the Python distribution and to the Rust crate, which is convenient for anyone who needs to vendor one without the other.
The version history shows a project that releases when there is something to release. The recent releases are 0.25.0 on 2025-09-14, 0.24.0 on 2025-08-17 and 0.22.0 on 2023-11-01, and the Cargo manifest still carries version 0.25.0, so the crate version and the last published distribution agree.
The 0.x numbering is the thing to be deliberate about. A zero major version is the convention for saying the interface may still change, and for a binding that mirrors a C API that upstream also marks as experimental in places, that is a reasonable stance. The documentation is hosted on Read the Docs, and the project also keeps a SECURITY.md at the repository root, which is worth knowing about before you file an issue about a malformed frame.
The repository is not archived and the last push was on 2026-10-17, so the work continues, and the zstd-sys version pinned in the manifest, 2.0.16, tells you which upstream release the current build targets.
Editorial conclusion
python-zstandard fits a Python service that needs zstd compression with dictionaries, streaming or frame parameters, and the fact that it carries no runtime dependencies makes it easy to add to an environment where a compiled dependency would be unwelcome. It does not fit a project that has to support Python below 3.10, and the CFFI path is harder to justify now that a C extension and a Rust backend both exist. Before adopting it, read the build requirements in setup.py for your Python version, because the minimum CFFI version changes at 3.14, and check whether the wheel for your platform is published before assuming a source build is quick.
Frequently asked questions
What is the pip name of python-zstandard?
The distribution is named zstandard, as declared in the pyproject metadata, while the repository is python-zstandard. The package has no runtime dependencies and requires Python 3.10 or later.
Does python-zstandard need cffi installed?
Only if you use the CFFI interface, which is an optional extra. The setup script selects a minimum CFFI version of 1.17, or 2.0 for Python 3.14 and later to add improved free-threading support, and the extra is excluded on PyPy.
What backends does python-zstandard provide?
The README states that a C extension and a CFFI interface are provided, and the repository also contains a rust-ext/ directory built as a cdylib named backend_rust using pyo3 0.25.1 on top of zstd-safe 7.2.4 and zstd-sys 2.0.16.
Is the zstandard package usable on free-threaded Python?
The project classifiers include Programming Language :: Python :: Free Threading :: 1 - Unstable, and the setup script requires CFFI 2.0 for Python 3.14 and later to add improved free-threading support, so builds exist but are not marked stable.
Which zstd features does the binding enable?
The Cargo manifest enables the experimental, legacy and zstdmt features on both zstd-safe 7.2.4 and zstd-sys 2.0.16, which covers multi-threaded compression, older frame formats and the experimental parts of the upstream API.
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/indygreg-python-zstandard)Was this useful?