mirror of
https://github.com/pybind/pybind11.git
synced 2026-04-29 11:11:55 +00:00
fix: support Python 3.14 (#5646)
* ci: support Python 3.14
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
* fix: Python 3.14 name change
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
* tests: fix expected output to handle Python 3.14
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
* fix: tighten CLI and add color on 3.14+
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
* tests: ignore failure on 3.14.0b1
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
* fix: support Python 3.14.0b1 with interperters
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
* Update test_multiple_interpreters.py
* Update test_multiple_interpreters.py
* fix: new breakage for 3.14 fixed
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
* fix: handle empty annotations 3.14
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
* fix: Python 3.14 may not create the annotations dict
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
* fix: use PyUnstable_IsImmortal
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
* fix: use sys._is_immortal
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
* tests: ignore large values for refcount too
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
* style: pre-commit fixes
* ci: enable all free-threaded builds
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
* fix: patch for embed
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
* Revert "fix: patch for embed"
This reverts commit c4226a0671.
* ci: drop new 3.xt additions
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
* fix: logic issue, also add some comments
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
* Update include/pybind11/pytypes.h
---------
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -34,6 +34,7 @@ jobs:
|
|||||||
python:
|
python:
|
||||||
- '3.8'
|
- '3.8'
|
||||||
- '3.13'
|
- '3.13'
|
||||||
|
- '3.14'
|
||||||
- 'pypy-3.10'
|
- 'pypy-3.10'
|
||||||
- 'pypy-3.11'
|
- 'pypy-3.11'
|
||||||
- 'graalpy-24.2'
|
- 'graalpy-24.2'
|
||||||
@@ -108,6 +109,9 @@ jobs:
|
|||||||
# No NumPy for PyPy 3.10 ARM
|
# No NumPy for PyPy 3.10 ARM
|
||||||
- runs-on: macos-14
|
- runs-on: macos-14
|
||||||
python: 'pypy-3.10'
|
python: 'pypy-3.10'
|
||||||
|
# Beta 1 broken for compiling on GHA (thinks it's free-threaded)
|
||||||
|
- runs-on: windows-2022
|
||||||
|
python: '3.14'
|
||||||
|
|
||||||
|
|
||||||
name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}"
|
name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}"
|
||||||
|
|||||||
@@ -133,7 +133,12 @@ object eval_file(str fname, object global = globals(), object local = object())
|
|||||||
|
|
||||||
int closeFile = 1;
|
int closeFile = 1;
|
||||||
std::string fname_str = (std::string) fname;
|
std::string fname_str = (std::string) fname;
|
||||||
FILE *f = _Py_fopen_obj(fname.ptr(), "r");
|
FILE *f =
|
||||||
|
# if PY_VERSION_HEX >= 0x030E0000
|
||||||
|
Py_fopen(fname.ptr(), "r");
|
||||||
|
# else
|
||||||
|
_Py_fopen_obj(fname.ptr(), "r");
|
||||||
|
# endif
|
||||||
if (!f) {
|
if (!f) {
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
pybind11_fail("File \"" + fname_str + "\" could not be opened!");
|
pybind11_fail("File \"" + fname_str + "\" could not be opened!");
|
||||||
|
|||||||
@@ -2583,7 +2583,8 @@ str_attr_accessor object_api<D>::doc() const {
|
|||||||
|
|
||||||
template <typename D>
|
template <typename D>
|
||||||
object object_api<D>::annotations() const {
|
object object_api<D>::annotations() const {
|
||||||
#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9
|
// This is needed again because of the lazy annotations added in 3.14+
|
||||||
|
#if PY_VERSION_HEX < 0x030A0000 || PY_VERSION_HEX >= 0x030E0000
|
||||||
// https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
|
// https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
|
||||||
if (!hasattr(derived(), "__annotations__")) {
|
if (!hasattr(derived(), "__annotations__")) {
|
||||||
setattr(derived(), "__annotations__", dict());
|
setattr(derived(), "__annotations__", dict());
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import functools
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import sysconfig
|
import sysconfig
|
||||||
@@ -49,7 +50,10 @@ def print_includes() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
parser = argparse.ArgumentParser()
|
make_parser = functools.partial(argparse.ArgumentParser, allow_abbrev=False)
|
||||||
|
if sys.version_info >= (3, 14):
|
||||||
|
make_parser = functools.partial(make_parser, color=True, suggest_on_error=True)
|
||||||
|
parser = make_parser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--version",
|
"--version",
|
||||||
action="version",
|
action="version",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ classifiers = [
|
|||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
"Programming Language :: Python :: 3.13",
|
"Programming Language :: Python :: 3.13",
|
||||||
|
"Programming Language :: Python :: 3.14",
|
||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Programming Language :: C++",
|
"Programming Language :: C++",
|
||||||
|
|||||||
@@ -90,7 +90,6 @@ PYBIND11_MODULE(pybind11_tests, m, py::mod_gil_not_used()) {
|
|||||||
m.attr("cpp_std") = cpp_std();
|
m.attr("cpp_std") = cpp_std();
|
||||||
m.attr("PYBIND11_INTERNALS_ID") = PYBIND11_INTERNALS_ID;
|
m.attr("PYBIND11_INTERNALS_ID") = PYBIND11_INTERNALS_ID;
|
||||||
// Free threaded Python uses UINT32_MAX for immortal objects.
|
// Free threaded Python uses UINT32_MAX for immortal objects.
|
||||||
m.attr("PYBIND11_REFCNT_IMMORTAL") = UINT32_MAX;
|
|
||||||
m.attr("PYBIND11_SIMPLE_GIL_MANAGEMENT") =
|
m.attr("PYBIND11_SIMPLE_GIL_MANAGEMENT") =
|
||||||
#if defined(PYBIND11_SIMPLE_GIL_MANAGEMENT)
|
#if defined(PYBIND11_SIMPLE_GIL_MANAGEMENT)
|
||||||
true;
|
true;
|
||||||
|
|||||||
@@ -6,9 +6,17 @@ from unittest import mock
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import env
|
import env
|
||||||
from pybind11_tests import PYBIND11_REFCNT_IMMORTAL, ConstructorStats, UserType
|
from pybind11_tests import ConstructorStats, UserType
|
||||||
from pybind11_tests import class_ as m
|
from pybind11_tests import class_ as m
|
||||||
|
|
||||||
|
UINT32MAX = 2**32 - 1
|
||||||
|
|
||||||
|
|
||||||
|
def refcount_immortal(ob: object) -> int:
|
||||||
|
if _is_immortal := getattr(sys, "_is_immortal", None):
|
||||||
|
return UINT32MAX if _is_immortal(ob) else sys.getrefcount(ob)
|
||||||
|
return sys.getrefcount(ob)
|
||||||
|
|
||||||
|
|
||||||
def test_obj_class_name():
|
def test_obj_class_name():
|
||||||
expected_name = "UserType" if env.PYPY else "pybind11_tests.UserType"
|
expected_name = "UserType" if env.PYPY else "pybind11_tests.UserType"
|
||||||
@@ -382,23 +390,23 @@ def test_brace_initialization():
|
|||||||
@pytest.mark.xfail("env.PYPY or env.GRAALPY")
|
@pytest.mark.xfail("env.PYPY or env.GRAALPY")
|
||||||
def test_class_refcount():
|
def test_class_refcount():
|
||||||
"""Instances must correctly increase/decrease the reference count of their types (#1029)"""
|
"""Instances must correctly increase/decrease the reference count of their types (#1029)"""
|
||||||
from sys import getrefcount
|
|
||||||
|
|
||||||
class PyDog(m.Dog):
|
class PyDog(m.Dog):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
for cls in m.Dog, PyDog:
|
for cls in m.Dog, PyDog:
|
||||||
refcount_1 = getrefcount(cls)
|
refcount_1 = refcount_immortal(cls)
|
||||||
molly = [cls("Molly") for _ in range(10)]
|
molly = [cls("Molly") for _ in range(10)]
|
||||||
refcount_2 = getrefcount(cls)
|
refcount_2 = refcount_immortal(cls)
|
||||||
|
|
||||||
del molly
|
del molly
|
||||||
pytest.gc_collect()
|
pytest.gc_collect()
|
||||||
refcount_3 = getrefcount(cls)
|
refcount_3 = refcount_immortal(cls)
|
||||||
|
|
||||||
|
# Python may report a large value here (above 30 bits), that's also fine
|
||||||
assert refcount_1 == refcount_3
|
assert refcount_1 == refcount_3
|
||||||
assert (refcount_2 > refcount_1) or (
|
assert (refcount_2 > refcount_1) or (
|
||||||
refcount_2 == refcount_1 == PYBIND11_REFCNT_IMMORTAL
|
refcount_2 == refcount_1 and refcount_1 >= 2**29
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -305,6 +305,11 @@ def test_property_rvalue_policy():
|
|||||||
|
|
||||||
# https://foss.heptapod.net/pypy/pypy/-/issues/2447
|
# https://foss.heptapod.net/pypy/pypy/-/issues/2447
|
||||||
@pytest.mark.xfail("env.PYPY")
|
@pytest.mark.xfail("env.PYPY")
|
||||||
|
@pytest.mark.xfail(
|
||||||
|
sys.version_info == (3, 14, 0, "beta", 1),
|
||||||
|
reason="3.14.0b1 bug: https://github.com/python/cpython/issues/133912",
|
||||||
|
strict=True,
|
||||||
|
)
|
||||||
def test_dynamic_attributes():
|
def test_dynamic_attributes():
|
||||||
instance = m.DynamicClass()
|
instance = m.DynamicClass()
|
||||||
assert not hasattr(instance, "foo")
|
assert not hasattr(instance, "foo")
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ def test_independent_subinterpreters():
|
|||||||
|
|
||||||
sys.path.append(".")
|
sys.path.append(".")
|
||||||
|
|
||||||
if sys.version_info >= (3, 14):
|
# This is supposed to be added to PyPI sometime in 3.14's lifespan
|
||||||
|
if sys.version_info >= (3, 15):
|
||||||
import interpreters
|
import interpreters
|
||||||
elif sys.version_info >= (3, 13):
|
elif sys.version_info >= (3, 13):
|
||||||
import _interpreters as interpreters
|
import _interpreters as interpreters
|
||||||
@@ -86,7 +87,7 @@ def test_dependent_subinterpreters():
|
|||||||
|
|
||||||
sys.path.append(".")
|
sys.path.append(".")
|
||||||
|
|
||||||
if sys.version_info >= (3, 14):
|
if sys.version_info >= (3, 15):
|
||||||
import interpreters
|
import interpreters
|
||||||
elif sys.version_info >= (3, 13):
|
elif sys.version_info >= (3, 13):
|
||||||
import _interpreters as interpreters
|
import _interpreters as interpreters
|
||||||
|
|||||||
@@ -158,4 +158,4 @@ def test_overriding_eq_reset_hash():
|
|||||||
def test_return_set_of_unhashable():
|
def test_return_set_of_unhashable():
|
||||||
with pytest.raises(TypeError) as excinfo:
|
with pytest.raises(TypeError) as excinfo:
|
||||||
m.get_unhashable_HashMe_set()
|
m.get_unhashable_HashMe_set()
|
||||||
assert str(excinfo.value.__cause__).startswith("unhashable type:")
|
assert "unhashable type" in str(excinfo.value.__cause__)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import pickle
|
import pickle
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -62,7 +63,20 @@ def test_roundtrip(cls_name):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail("env.PYPY")
|
@pytest.mark.xfail("env.PYPY")
|
||||||
@pytest.mark.parametrize("cls_name", ["PickleableWithDict", "PickleableWithDictNew"])
|
@pytest.mark.parametrize(
|
||||||
|
"cls_name",
|
||||||
|
[
|
||||||
|
pytest.param(
|
||||||
|
"PickleableWithDict",
|
||||||
|
marks=pytest.mark.xfail(
|
||||||
|
sys.version_info == (3, 14, 0, "beta", 1),
|
||||||
|
reason="3.14.0b1 bug: https://github.com/python/cpython/issues/133912",
|
||||||
|
strict=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"PickleableWithDictNew",
|
||||||
|
],
|
||||||
|
)
|
||||||
def test_roundtrip_with_dict(cls_name):
|
def test_roundtrip_with_dict(cls_name):
|
||||||
cls = getattr(m, cls_name)
|
cls = getattr(m, cls_name)
|
||||||
p = cls("test_value")
|
p = cls("test_value")
|
||||||
|
|||||||
@@ -1143,6 +1143,10 @@ def test_dict_ranges(tested_dict, expected):
|
|||||||
|
|
||||||
# https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
|
# https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
|
||||||
def get_annotations_helper(o):
|
def get_annotations_helper(o):
|
||||||
|
if sys.version_info >= (3, 14):
|
||||||
|
import annotationlib
|
||||||
|
|
||||||
|
return annotationlib.get_annotations(o) or None
|
||||||
if isinstance(o, type):
|
if isinstance(o, type):
|
||||||
return o.__dict__.get("__annotations__", None)
|
return o.__dict__.get("__annotations__", None)
|
||||||
return getattr(o, "__annotations__", None)
|
return getattr(o, "__annotations__", None)
|
||||||
|
|||||||
Reference in New Issue
Block a user