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
13 changes: 12 additions & 1 deletion docs/using.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,16 @@ You can also set this option in ``pyproject.toml``, using::
although note that this option is not formally documented/supported by the Python
packaging infrastructure and may change in future.

The ``get_extensions()`` functions will automatically detect this option and
Alternatively, if you use setuptools 65.4 or later, you can dynamically opt in
to limited API builds by setting the ``EXTENSION_HELPERS_PY_LIMITED_API``
environment variable, e.g.::

EXTENSION_HELPERS_PY_LIMITED_API='cp311' python -m build

If you define ``py_limited_api`` in ``setup.cfg``, you can use
``EXTENSION_HELPERS_PY_LIMITED_API`` to opt **out** of the limited API builds
by setting ``EXTENSION_HELPERS_PY_LIMITED_API`` to an empty string. There is however
no way to opt out if you use ``py-limited-api`` in ``pyproject.toml``.

The ``get_extensions()`` functions will automatically detect these options and
add the necessary compiler flags to build your extension modules.
16 changes: 16 additions & 0 deletions extension_helpers/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import re
import sys
import tempfile
from configparser import ConfigParser
from importlib import machinery as import_machinery
from importlib.util import module_from_spec, spec_from_file_location
Expand Down Expand Up @@ -154,6 +155,21 @@
for the py_limited_api setting
"""

py_limited_api = os.environ.get("EXTENSION_HELPERS_PY_LIMITED_API")

if py_limited_api is not None:

if "DIST_EXTRA_CONFIG" in os.environ:
raise ValueError(

Check warning on line 163 in extension_helpers/_utils.py

View check run for this annotation

Codecov / codecov/patch

extension_helpers/_utils.py#L163

Added line #L163 was not covered by tests
"Cannot use EXTENSION_HELPERS_PY_LIMITED_API if DIST_EXTRA_CONFIG is already defined"
)

dist_extra_config_filename = tempfile.mktemp()
with open(dist_extra_config_filename, "w") as f:
f.write(f"[bdist_wheel]\npy_limited_api={py_limited_api}")
os.environ["DIST_EXTRA_CONFIG"] = dist_extra_config_filename
return py_limited_api

srcdir = Path(srcdir)

setup_cfg = srcdir / "setup.cfg"
Expand Down
30 changes: 25 additions & 5 deletions extension_helpers/tests/test_setup_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,9 +469,15 @@ def test():


@pytest.mark.parametrize("config", ("setup.cfg", "pyproject.toml"))
@pytest.mark.parametrize("envvar", (False, True))
@pytest.mark.parametrize("limited_api", (None, "cp310"))
@pytest.mark.parametrize("extension_type", ("c", "pyx", "both"))
def test_limited_api(tmp_path, config, limited_api, extension_type):
def test_limited_api(tmp_path, config, envvar, limited_api, extension_type):

if sys.version_info < (3, 11):
pytest.skip(
"This test requires setuptools>=65.4 which is only available for Python 3.11 and later"
)
Comment on lines +477 to +480
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could be a @pytest.mark.skipif

Copy link
Copy Markdown
Contributor

@neutrinoceros neutrinoceros Jul 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming back to see I'm not sure this comment is correct actually; setuptools 80.9 appears to still support Python 3.9, so how would 65.4 require 3.11 ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strange, when I tried running this on Python 3.10 older versions of setuptools would get installed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in #117


package = _extension_test_package(
tmp_path, extension_type=extension_type, include_numpy=True, include_setup_py=False
Expand All @@ -493,8 +499,12 @@ def test_limited_api(tmp_path, config, limited_api, extension_type):
"""
)

if limited_api:
if limited_api and not envvar:
setup_cfg += f"\n[bdist_wheel]\npy_limited_api={limited_api}"
elif envvar:
# Make sure if we are using the environment variable that it takes
# precedence over this setting (this only works for setup.cfg)
setup_cfg += "\n[bdist_wheel]\npy_limited_api=cp35"

(package / "setup.cfg").write_text(setup_cfg)

Expand Down Expand Up @@ -524,7 +534,7 @@ def test_limited_api(tmp_path, config, limited_api, extension_type):
build-backend = 'setuptools.build_meta'

[project]
name = "hehlpers_test_package"
name = "helpers_test_package"
version = "0.1"

[tool.setuptools.packages]
Expand All @@ -535,13 +545,23 @@ def test_limited_api(tmp_path, config, limited_api, extension_type):
"""
)

if limited_api:
if limited_api and not envvar:
pyproject_toml += f'\n[tool.distutils.bdist_wheel]\npy-limited-api = "{limited_api}"'

(package / "pyproject.toml").write_text(pyproject_toml)

env = os.environ.copy()

if envvar:
if limited_api:
env["EXTENSION_HELPERS_PY_LIMITED_API"] = limited_api
else:
env["EXTENSION_HELPERS_PY_LIMITED_API"] = ""

with chdir(package):
subprocess.run([sys.executable, "-m", "build", "--wheel", "--no-isolation"], check=True)
subprocess.run(
[sys.executable, "-m", "build", "--wheel", "--no-isolation"], env=env, check=True
)

wheels = os.listdir(package / "dist")

Expand Down
Loading