Skip to main content

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:

  1. Your extension omits the Py_mod_gil slot. 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.
  2. You ship wheels for regular CPython but no cp314t wheel. Free-threaded users fall back to an sdist build, or to the GIL build, with nothing telling you they got the slow path.
  3. 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 .); its dist/ is inspected unless --dist is 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ft_readiness-0.1.0.tar.gz (37.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ft_readiness-0.1.0-py3-none-any.whl (15.0 kB view details)

Uploaded Python 3

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

Hashes for ft_readiness-0.1.0.tar.gz
Algorithm Hash digest
SHA256 cf94389e5c60e1293f9ec1b65b4bf3ebf6dedcd3506fa27773a1fbdb41b7ae03
MD5 5599d2d0661e2d15b3a23b9b7d2f8454
BLAKE2b-256 5b0d607253be1253e082d28f90e0c3883763035df626f7fd011ce160865f4dea

See more details on using hashes here.

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

Hashes for ft_readiness-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 51e20a01d8d08bde74b0ffd18995ec6e923f43873e6a90dd5b1dc443d2c140ce
MD5 c02daab84b3266467a5f9ccfad8a8eff
BLAKE2b-256 24d9cc40ffb64298510de99a555da0251de6c173befcea5cb5cc488f011476ce

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page