Author-side pre-publish gate for free-threaded Python (3.14t): catch the silently re-enabled GIL, a missing cp314t wheel, and a stale Free Threading trove classifier before you ship.
Project description
ft-readiness
Catch a broken free-threading build before you publish it, not after your users file the bug.
Python 3.14 made free-threading officially supported. If you ship a C extension there are three ways to break it silently — and none of them fail your build:
- Your extension omits the
Py_mod_gilslot. CPython re-enables the GIL the moment someone imports your module on a free-threaded interpreter. Every 3.14t user loses parallelism; the only signal is a warning buried in stderr. - You ship wheels for regular CPython but no
cp314twheel. Free-threaded users fall back to an sdist build, or to the GIL build, with nothing telling you they got the slow path. - Your
Free Threading ::trove classifier says one thing and your wheels say another — you advertise support you didn't ship, or ship support nobody can discover on PyPI.
ft-readiness is a pre-publish gate for all three. Run it in CI after python -m build; it exits non-zero when your artifacts and your claims don't line up.
$ ft-readiness .
✗ FT001 [error] Declares 'Programming Language :: Python :: Free Threading :: 2 - Beta' but ships no free-threaded (cp3XXt) wheel for its compiled extension.
→ Build a cp3XXt wheel (cibuildwheel with CIBW_ENABLE=cpython-freethreading, or CPython 3.14t + your build backend), or drop the Free Threading classifier until you ship one.
! FT004 [warning] Ships abi3 (limited-API) wheels but no cp3XXt wheel. The stable ABI is unsupported on the free-threaded build, so abi3 wheels do NOT cover 3.14t users.
→ Build a separate free-threaded wheel; you can keep abi3 for the GIL build (e.g. py_limited_api=not sysconfig.get_config_var('Py_GIL_DISABLED')).
ft-readiness 0.1.0: 1 error(s), 1 warning(s).
Install
pip install ft-readiness
Python 3.9+. Pure Python, one runtime dependency (packaging).
What it checks
The static checks read your built dist/ and your pyproject.toml (or a wheel's METADATA). They run on any interpreter — you do not need a free-threaded build to run them.
| Code | Level | Fires when |
|---|---|---|
| FT001 | error | A Free Threading :: classifier is declared, the package has a compiled extension, but no cp3XXt wheel ships. You advertise support your artifacts don't deliver. |
| FT002 | warning | A cp3XXt wheel ships but no Free Threading :: classifier is declared. Support is real but undiscoverable on PyPI. |
| FT003 | warning | The package ships compiled-extension wheels but none for free-threaded Python. The silent missing-wheel case. |
| FT004 | warning | Only abi3 (limited-API) wheels ship, no cp3XXt. The stable ABI is unsupported on the free-threaded build, so abi3 does not cover 3.14t. |
| FT005 | warning | The package advertises free-threading but requires-python excludes the versions (3.13+) that have a free-threaded build. |
| FT006 | info | A pure-Python package claims 3 - Stable/4 - Resilient — a reminder to back the claim with thread-safety tests. |
The runtime check is the one that catches a re-enabled GIL, and it needs a free-threaded interpreter (it imports your module and watches the GIL state):
| Code | Level | Fires when |
|---|---|---|
| FT010 | error | Importing a module you named re-enabled the GIL — the Py_mod_gil slot is missing. |
| FT011 | warning | A module named for the runtime check couldn't be imported. |
# on a python3.14t interpreter, in CI:
ft-readiness . --import mypkg._core --import mypkg._fast
On a non-free-threaded interpreter the --import checks are skipped with a note, so the same command is safe everywhere in your matrix.
pytest plugin
If you already run a 3.14t leg in CI, wire the runtime check into pytest instead:
pytest --ft-import mypkg._core --ft-import mypkg._fast
Each module is imported in a fresh subprocess and reported as its own test. On a non-free-threaded build the checks skip.
GitHub Action
- uses: fernforge/ft-readiness@v1
with:
project: . # optional, default '.'
strict: 'false' # optional, treat warnings as errors
The action installs ft-readiness and runs the static checks against your dist/. Run it after your build step. For the runtime GIL check, add a job on actions/setup-python with a 3.14t interpreter and call ft-readiness --import ... there.
Why these three failures and not others
The GIL re-enable prints a warning, so an attentive porter can catch it by reading stderr. The two failures with no signal at all are the missing cp314t wheel and the classifier that doesn't match the wheels — a user just gets the slow path and never tells you. ft-readiness turns all three into a build failure you see before the release goes out.
Options
ft-readiness [PROJECT] [--dist DIR] [--import MODULE ...] [--strict] [--format text|json]
PROJECT— project directory (default.); itsdist/is inspected unless--distis given.--dist DIR— directory of built wheels/sdists.--import MODULE— runtime GIL check for a module (repeatable or comma-separated).--strict— exit 1 on warnings too.--format json— machine-readable output for CI dashboards.
Exit code is 1 if there are any errors (or any warnings under --strict), else 0.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ft_readiness-0.1.0.tar.gz.
File metadata
- Download URL: ft_readiness-0.1.0.tar.gz
- Upload date:
- Size: 37.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cf94389e5c60e1293f9ec1b65b4bf3ebf6dedcd3506fa27773a1fbdb41b7ae03
|
|
| MD5 |
5599d2d0661e2d15b3a23b9b7d2f8454
|
|
| BLAKE2b-256 |
5b0d607253be1253e082d28f90e0c3883763035df626f7fd011ce160865f4dea
|
File details
Details for the file ft_readiness-0.1.0-py3-none-any.whl.
File metadata
- Download URL: ft_readiness-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
51e20a01d8d08bde74b0ffd18995ec6e923f43873e6a90dd5b1dc443d2c140ce
|
|
| MD5 |
c02daab84b3266467a5f9ccfad8a8eff
|
|
| BLAKE2b-256 |
24d9cc40ffb64298510de99a555da0251de6c173befcea5cb5cc488f011476ce
|