From aa74fbefd7b7b006caaab317e65c41ebd571735c Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 13:26:05 -0500 Subject: [PATCH 01/59] init --- include/pybind11/cast.h | 9 +++++++++ include/pybind11/pytypes.h | 17 +++++++++++++++++ include/pybind11/typing.h | 11 +++++++++++ tests/test_pytypes.cpp | 18 ++++++++++++++++++ tests/test_pytypes.py | 20 ++++++++++++++++++++ 5 files changed, 75 insertions(+) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 2ae25c2ebf..ebc1d1596a 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1329,6 +1329,15 @@ object object_or_cast(T &&o) { return pybind11::cast(std::forward(o)); } +// Declared in pytypes.h: +// Written here so make_caster can be used +template +template +str_attr_accessor object_api::attr_with_type(const char *key) const { + annotations()[key] = make_caster::name.text; + return {derived(), key}; +}; + // Placeholder type for the unneeded (and dead code) static variable in the // PYBIND11_OVERRIDE_OVERRIDE macro struct override_unused {}; diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 60d51fdcff..93c703326c 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -113,6 +113,10 @@ class object_api : public pyobject_tag { /// See above (the only difference is that the key is provided as a string literal) str_attr_accessor attr(const char *key) const; + // attr_with_type is implemented in cast.h: + template + str_attr_accessor attr_with_type(const char *key) const; + /** \rst Matches * unpacking in Python, e.g. to unpack arguments out of a ``tuple`` or ``list`` for a function call. Applying another * to the result yields @@ -182,6 +186,9 @@ class object_api : public pyobject_tag { /// Get or set the object's docstring, i.e. ``obj.__doc__``. str_attr_accessor doc() const; + // TODO: Make read only? + str_attr_accessor annotations() const; + /// Return the object's current reference count ssize_t ref_count() const { #ifdef PYPY_VERSION @@ -2558,6 +2565,16 @@ str_attr_accessor object_api::doc() const { return attr("__doc__"); } +template +str_attr_accessor object_api::annotations() const { + str_attr_accessor annotations_dict = attr("__annotations__"); + // Create dict automatically + if (!isinstance(annotations_dict)){ + annotations_dict = dict(); + } + return annotations_dict; +} + template handle object_api::get_type() const { return type::handle_of(derived()); diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 84aaf9f702..d8ed68d234 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -82,6 +82,12 @@ class Optional : public object { using object::object; }; +template +class Final : public object { + PYBIND11_OBJECT_DEFAULT(Final, object, PyObject_Type) + using object::object; +}; + template class TypeGuard : public bool_ { using bool_::bool_; @@ -205,6 +211,11 @@ struct handle_type_name> { static constexpr auto name = const_name("Optional[") + make_caster::name + const_name("]"); }; +template +struct handle_type_name> { + static constexpr auto name = const_name("Final[") + make_caster::name + const_name("]"); +}; + template struct handle_type_name> { static constexpr auto name = const_name("TypeGuard[") + make_caster::name + const_name("]"); diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 8df4cdd3f6..18d327f877 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -998,4 +998,22 @@ TEST_SUBMODULE(pytypes, m) { #else m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = false; #endif + + m.attr_with_type>("list_int") = py::list(); + m.attr_with_type>("set_str") = py::set(); + + + struct Empty {}; + py::class_(m, "EmptyAnnotationClass"); + + struct Point { + float x; + py::dict dict_str_int; + }; + auto point = py::class_(m, "Point"); + point.attr_with_type("x"); + point.attr_with_type>("dict_str_int") = py::dict(); + + m.attr_with_type>("CONST_INT") = 3; + } diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 9fd24b34f1..741a345704 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1101,3 +1101,23 @@ def test_list_ranges(tested_list, expected): def test_dict_ranges(tested_dict, expected): assert m.dict_iterator_default_initialization() assert m.transform_dict_plus_one(tested_dict) == expected + + +def test_module_attribute_types() -> None: + module_annotations = m.__annotations__ + + assert module_annotations['list_int'] == 'list[int]' + assert module_annotations['set_str'] == 'set[str]' + + +def test_class_attribute_types() -> None: + empty_annotations = m.EmptyAnnotationClass.__annotations__ + annotations = m.Point.__annotations__ + + assert empty_annotations == {} + assert annotations['x'] == 'float' + assert annotations['dict_str_int'] == 'dict[str, int]' + +def test_final_annotation() -> None: + module_annotations = m.__annotations__ + assert module_annotations['CONST_INT'] == 'Final[int]' From 4c263c13360849a69d18747f0c2f84547ef8bd42 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 13:32:53 -0500 Subject: [PATCH 02/59] add comment --- include/pybind11/pytypes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 93c703326c..965b12c7e3 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -186,7 +186,7 @@ class object_api : public pyobject_tag { /// Get or set the object's docstring, i.e. ``obj.__doc__``. str_attr_accessor doc() const; - // TODO: Make read only? + /// Get or set the object's annotations, i.e. ``obj.__annotations``. str_attr_accessor annotations() const; /// Return the object's current reference count From 4788d8cf3233fd06671d9d4758d908770e3667ea Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2024 18:35:02 +0000 Subject: [PATCH 03/59] style: pre-commit fixes --- include/pybind11/pytypes.h | 4 ++-- tests/test_pytypes.cpp | 2 -- tests/test_pytypes.py | 13 +++++++------ 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 965b12c7e3..6606b7c03b 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -2567,9 +2567,9 @@ str_attr_accessor object_api::doc() const { template str_attr_accessor object_api::annotations() const { - str_attr_accessor annotations_dict = attr("__annotations__"); + str_attr_accessor annotations_dict = attr("__annotations__"); // Create dict automatically - if (!isinstance(annotations_dict)){ + if (!isinstance(annotations_dict)) { annotations_dict = dict(); } return annotations_dict; diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 18d327f877..82c11468e2 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -1002,7 +1002,6 @@ TEST_SUBMODULE(pytypes, m) { m.attr_with_type>("list_int") = py::list(); m.attr_with_type>("set_str") = py::set(); - struct Empty {}; py::class_(m, "EmptyAnnotationClass"); @@ -1015,5 +1014,4 @@ TEST_SUBMODULE(pytypes, m) { point.attr_with_type>("dict_str_int") = py::dict(); m.attr_with_type>("CONST_INT") = 3; - } diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 741a345704..9f9cadeb2f 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1106,18 +1106,19 @@ def test_dict_ranges(tested_dict, expected): def test_module_attribute_types() -> None: module_annotations = m.__annotations__ - assert module_annotations['list_int'] == 'list[int]' - assert module_annotations['set_str'] == 'set[str]' + assert module_annotations["list_int"] == "list[int]" + assert module_annotations["set_str"] == "set[str]" def test_class_attribute_types() -> None: empty_annotations = m.EmptyAnnotationClass.__annotations__ annotations = m.Point.__annotations__ - + assert empty_annotations == {} - assert annotations['x'] == 'float' - assert annotations['dict_str_int'] == 'dict[str, int]' + assert annotations["x"] == "float" + assert annotations["dict_str_int"] == "dict[str, int]" + def test_final_annotation() -> None: module_annotations = m.__annotations__ - assert module_annotations['CONST_INT'] == 'Final[int]' + assert module_annotations["CONST_INT"] == "Final[int]" From bc8f095f1cb0dfe96eb6060aac87c82961cca930 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 13:38:13 -0500 Subject: [PATCH 04/59] fix extra ; --- include/pybind11/cast.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index ebc1d1596a..9bb27d7ac2 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1336,7 +1336,7 @@ template str_attr_accessor object_api::attr_with_type(const char *key) const { annotations()[key] = make_caster::name.text; return {derived(), key}; -}; +} // Placeholder type for the unneeded (and dead code) static variable in the // PYBIND11_OVERRIDE_OVERRIDE macro From c38836a803cda404777625b8371e4db9af25962a Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 14:02:17 -0500 Subject: [PATCH 05/59] Add annotation helper for older python versions --- tests/test_pytypes.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 9f9cadeb2f..53d5b03cfe 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1102,23 +1102,32 @@ def test_dict_ranges(tested_dict, expected): assert m.dict_iterator_default_initialization() assert m.transform_dict_plus_one(tested_dict) == expected +def get_annotations_helper(o): + # Taken from __annotations__ docs + # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older + if isinstance(o, type): + ann = o.__dict__.get('__annotations__', None) + else: + ann = getattr(o, '__annotations__', None) + return ann + def test_module_attribute_types() -> None: - module_annotations = m.__annotations__ + module_annotations = get_annotations_helper(m) assert module_annotations["list_int"] == "list[int]" assert module_annotations["set_str"] == "set[str]" def test_class_attribute_types() -> None: - empty_annotations = m.EmptyAnnotationClass.__annotations__ - annotations = m.Point.__annotations__ + empty_annotations = get_annotations_helper(m.EmptyAnnotationClass) + annotations = get_annotations_helper(m.Point) - assert empty_annotations == {} + assert empty_annotations is None assert annotations["x"] == "float" assert annotations["dict_str_int"] == "dict[str, int]" def test_final_annotation() -> None: - module_annotations = m.__annotations__ + module_annotations = get_annotations_helper(m) assert module_annotations["CONST_INT"] == "Final[int]" From 9e439860187567bd7544a97ecc287c9aec39b949 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2024 19:02:54 +0000 Subject: [PATCH 06/59] style: pre-commit fixes --- tests/test_pytypes.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 53d5b03cfe..2a3ac32513 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1102,13 +1102,14 @@ def test_dict_ranges(tested_dict, expected): assert m.dict_iterator_default_initialization() assert m.transform_dict_plus_one(tested_dict) == expected + def get_annotations_helper(o): # Taken from __annotations__ docs # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older if isinstance(o, type): - ann = o.__dict__.get('__annotations__', None) + ann = o.__dict__.get("__annotations__", None) else: - ann = getattr(o, '__annotations__', None) + ann = getattr(o, "__annotations__", None) return ann From 9f34dccdb843362af2c981a3fa66463e96e3027e Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 14:20:43 -0500 Subject: [PATCH 07/59] test writing __annotations__ to __dict__ --- include/pybind11/pytypes.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 6606b7c03b..ee8cc82bf4 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -2567,7 +2567,13 @@ str_attr_accessor object_api::doc() const { template str_attr_accessor object_api::annotations() const { - str_attr_accessor annotations_dict = attr("__annotations__"); + // Create dict automatically + + #if !defined(PYPY_VERSION) && PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9 + str_attr_accessor annotations_dict = attr("__dict__").attr("__annotations__"); + #else + str_attr_accessor annotations_dict = attr("__annotations__"); + #endif // Create dict automatically if (!isinstance(annotations_dict)) { annotations_dict = dict(); From 10d7b05ec4c826bf69e69cd1264f45631fa64733 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2024 19:21:14 +0000 Subject: [PATCH 08/59] style: pre-commit fixes --- include/pybind11/pytypes.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index ee8cc82bf4..3c543044de 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -2569,11 +2569,11 @@ template str_attr_accessor object_api::annotations() const { // Create dict automatically - #if !defined(PYPY_VERSION) && PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9 - str_attr_accessor annotations_dict = attr("__dict__").attr("__annotations__"); - #else - str_attr_accessor annotations_dict = attr("__annotations__"); - #endif +#if !defined(PYPY_VERSION) && PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9 + str_attr_accessor annotations_dict = attr("__dict__").attr("__annotations__"); +#else + str_attr_accessor annotations_dict = attr("__annotations__"); +#endif // Create dict automatically if (!isinstance(annotations_dict)) { annotations_dict = dict(); From 6dff59f0c964324392e9ab7bb04dceaa0ef23ae1 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 15:13:13 -0500 Subject: [PATCH 09/59] revert bac to __annotations__ --- include/pybind11/pytypes.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 3c543044de..6606b7c03b 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -2567,13 +2567,7 @@ str_attr_accessor object_api::doc() const { template str_attr_accessor object_api::annotations() const { - // Create dict automatically - -#if !defined(PYPY_VERSION) && PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9 - str_attr_accessor annotations_dict = attr("__dict__").attr("__annotations__"); -#else str_attr_accessor annotations_dict = attr("__annotations__"); -#endif // Create dict automatically if (!isinstance(annotations_dict)) { annotations_dict = dict(); From c8edd09f060f0e1316061dff1ca5ee84fccd2198 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 15:43:39 -0500 Subject: [PATCH 10/59] use getattr for automatic init --- include/pybind11/pytypes.h | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 6606b7c03b..2b9688e059 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -186,8 +186,8 @@ class object_api : public pyobject_tag { /// Get or set the object's docstring, i.e. ``obj.__doc__``. str_attr_accessor doc() const; - /// Get or set the object's annotations, i.e. ``obj.__annotations``. - str_attr_accessor annotations() const; + /// Get or set the object's annotations, i.e. ``obj.__annotations__``. + object annotations() const; /// Return the object's current reference count ssize_t ref_count() const { @@ -2566,12 +2566,9 @@ str_attr_accessor object_api::doc() const { } template -str_attr_accessor object_api::annotations() const { - str_attr_accessor annotations_dict = attr("__annotations__"); - // Create dict automatically - if (!isinstance(annotations_dict)) { - annotations_dict = dict(); - } +// Always a dict +object object_api::annotations() const { + dict annotations_dict = getattr(derived(), "__annotations__", dict()); return annotations_dict; } From 259ce9378294a0e54f6a7261c92cfaa684ade25a Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 15:46:44 -0500 Subject: [PATCH 11/59] use std::move --- include/pybind11/pytypes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 2b9688e059..cb77cc8184 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -2569,7 +2569,7 @@ template // Always a dict object object_api::annotations() const { dict annotations_dict = getattr(derived(), "__annotations__", dict()); - return annotations_dict; + return std::move(annotations_dict); } template From 7e380e284853559bb468fde524d530830bbc845a Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 15:59:52 -0500 Subject: [PATCH 12/59] update helper --- tests/test_pytypes.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 2a3ac32513..9db6a36fe3 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1104,13 +1104,7 @@ def test_dict_ranges(tested_dict, expected): def get_annotations_helper(o): - # Taken from __annotations__ docs - # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older - if isinstance(o, type): - ann = o.__dict__.get("__annotations__", None) - else: - ann = getattr(o, "__annotations__", None) - return ann + return getattr(o, "__annotations__", None) def test_module_attribute_types() -> None: @@ -1124,7 +1118,7 @@ def test_class_attribute_types() -> None: empty_annotations = get_annotations_helper(m.EmptyAnnotationClass) annotations = get_annotations_helper(m.Point) - assert empty_annotations is None + assert empty_annotations == {} assert annotations["x"] == "float" assert annotations["dict_str_int"] == "dict[str, int]" From b8ad03da76ea64e9fc37ff1ff399f486f2484ad1 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 16:11:24 -0500 Subject: [PATCH 13/59] remove stdmove --- include/pybind11/pytypes.h | 3 +-- tests/test_pytypes.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index cb77cc8184..2e871ed4b3 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -2568,8 +2568,7 @@ str_attr_accessor object_api::doc() const { template // Always a dict object object_api::annotations() const { - dict annotations_dict = getattr(derived(), "__annotations__", dict()); - return std::move(annotations_dict); + return getattr(derived(), "__annotations__", dict()); } template diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 9db6a36fe3..40cba7c8e7 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1104,6 +1104,7 @@ def test_dict_ranges(tested_dict, expected): def get_annotations_helper(o): + print(help(o)) return getattr(o, "__annotations__", None) From e5235f00f7993a9e0c453f23fa8a857351f58b2e Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 19:03:31 -0500 Subject: [PATCH 14/59] test isinstance --- include/pybind11/pytypes.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 2e871ed4b3..7fed214d43 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -2567,8 +2567,14 @@ str_attr_accessor object_api::doc() const { template // Always a dict +// https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older object object_api::annotations() const { - return getattr(derived(), "__annotations__", dict()); + if (isinstance(derived())){ + return getattr(getattr(derived(), "__dict__"), "__annotations__", dict()); + } + else{ + return getattr(derived(), "__annotations__", dict()); + } } template From 639b192658a364a2ec89925230ebf439a7a01796 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 00:04:21 +0000 Subject: [PATCH 15/59] style: pre-commit fixes --- include/pybind11/pytypes.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 7fed214d43..b3996f9cf0 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -2569,10 +2569,9 @@ template // Always a dict // https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older object object_api::annotations() const { - if (isinstance(derived())){ + if (isinstance(derived())) { return getattr(getattr(derived(), "__dict__"), "__annotations__", dict()); - } - else{ + } else { return getattr(derived(), "__annotations__", dict()); } } From 8da0ce0350fbbfc261303c9eb7413336e269f121 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 19:40:10 -0500 Subject: [PATCH 16/59] add #if 3.9 --- include/pybind11/pytypes.h | 16 +++++++++++----- tests/test_pytypes.py | 8 +++++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 7fed214d43..7d8e137560 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -2569,12 +2569,18 @@ template // Always a dict // https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older object object_api::annotations() const { - if (isinstance(derived())){ - return getattr(getattr(derived(), "__dict__"), "__annotations__", dict()); - } - else{ + // Python 3.8, 3.9 + #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9 + if (isinstance(derived())){ + return getattr(derived(), "__dict__").get("__annotations__", dict()); + } + else{ + return getattr(derived(), "__annotations__", dict()); + } + // Python 3.10+ + #else return getattr(derived(), "__annotations__", dict()); - } + #endif } template diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 40cba7c8e7..c59a094a73 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1102,10 +1102,12 @@ def test_dict_ranges(tested_dict, expected): assert m.dict_iterator_default_initialization() assert m.transform_dict_plus_one(tested_dict) == expected - +# 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): - print(help(o)) - return getattr(o, "__annotations__", None) + if isinstance(o, type): + return o.__dict__.get('__annotations__', {}) + else: + return getattr(o, '__annotations__', {}) def test_module_attribute_types() -> None: From 31cc64f414f4ed7cc50df8d667fa2de5d0b65de1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 00:43:30 +0000 Subject: [PATCH 17/59] style: pre-commit fixes --- include/pybind11/pytypes.h | 21 ++++++++++----------- tests/test_pytypes.py | 6 +++--- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 7d8e137560..2b0f522e06 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -2569,18 +2569,17 @@ template // Always a dict // https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older object object_api::annotations() const { - // Python 3.8, 3.9 - #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9 - if (isinstance(derived())){ - return getattr(derived(), "__dict__").get("__annotations__", dict()); - } - else{ - return getattr(derived(), "__annotations__", dict()); - } - // Python 3.10+ - #else +// Python 3.8, 3.9 +#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9 + if (isinstance(derived())) { + return getattr(derived(), "__dict__").get("__annotations__", dict()); + } else { return getattr(derived(), "__annotations__", dict()); - #endif + } +// Python 3.10+ +#else + return getattr(derived(), "__annotations__", dict()); +#endif } template diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index c59a094a73..6d9f1e28ba 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1102,12 +1102,12 @@ def test_dict_ranges(tested_dict, expected): assert m.dict_iterator_default_initialization() assert m.transform_dict_plus_one(tested_dict) == expected + # 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): if isinstance(o, type): - return o.__dict__.get('__annotations__', {}) - else: - return getattr(o, '__annotations__', {}) + return o.__dict__.get("__annotations__", {}) + return getattr(o, "__annotations__", {}) def test_module_attribute_types() -> None: From 4d0968dea85116888ac5b1162a446b4c90c41791 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 19:50:22 -0500 Subject: [PATCH 18/59] use getattr --- include/pybind11/pytypes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 2b0f522e06..7ba991e06c 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -2572,7 +2572,7 @@ object object_api::annotations() const { // Python 3.8, 3.9 #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9 if (isinstance(derived())) { - return getattr(derived(), "__dict__").get("__annotations__", dict()); + return getattr(getattr(derived(), "__dict__"), "__annotations__", dict()); } else { return getattr(derived(), "__annotations__", dict()); } From e84df95bf2f8eda776d34ae7a3eaf8f03ffda4c1 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 20:01:33 -0500 Subject: [PATCH 19/59] add dir --- tests/test_pytypes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 6d9f1e28ba..8545fac5c2 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1105,6 +1105,7 @@ 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 def get_annotations_helper(o): + dir(o) if isinstance(o, type): return o.__dict__.get("__annotations__", {}) return getattr(o, "__annotations__", {}) From d6601774090c3e96ccf45faf82039e34a64c7f70 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 20:09:33 -0500 Subject: [PATCH 20/59] use hasattr --- include/pybind11/pytypes.h | 2 +- tests/test_pytypes.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 7ba991e06c..d7482927b2 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -2571,7 +2571,7 @@ template object object_api::annotations() const { // Python 3.8, 3.9 #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9 - if (isinstance(derived())) { + if (hasattr(derived(), "__dict__")) { return getattr(getattr(derived(), "__dict__"), "__annotations__", dict()); } else { return getattr(derived(), "__annotations__", dict()); diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 8545fac5c2..332f2125e1 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1105,7 +1105,7 @@ 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 def get_annotations_helper(o): - dir(o) + print(dir(o)) if isinstance(o, type): return o.__dict__.get("__annotations__", {}) return getattr(o, "__annotations__", {}) @@ -1125,6 +1125,7 @@ def test_class_attribute_types() -> None: assert empty_annotations == {} assert annotations["x"] == "float" assert annotations["dict_str_int"] == "dict[str, int]" + assert False def test_final_annotation() -> None: From fe21e0f8d702df81123eea1062a53684fbb91c20 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 20:28:09 -0500 Subject: [PATCH 21/59] try setattr --- include/pybind11/pytypes.h | 7 +++---- tests/test_pytypes.py | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index d7482927b2..feb18ac638 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -2571,11 +2571,10 @@ template object object_api::annotations() const { // Python 3.8, 3.9 #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9 - if (hasattr(derived(), "__dict__")) { - return getattr(getattr(derived(), "__dict__"), "__annotations__", dict()); - } else { - return getattr(derived(), "__annotations__", dict()); + if (!hasattr(derived(), "__annotations__")) { + setattr(derived(), "__annotations__", dict()); } + return attr("__annotations__"); // Python 3.10+ #else return getattr(derived(), "__annotations__", dict()); diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 332f2125e1..44f161a147 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1125,7 +1125,6 @@ def test_class_attribute_types() -> None: assert empty_annotations == {} assert annotations["x"] == "float" assert annotations["dict_str_int"] == "dict[str, int]" - assert False def test_final_annotation() -> None: From 4a443a7adfea2f243207e2e7628929ee305c9deb Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 21:06:24 -0500 Subject: [PATCH 22/59] add c++17 guard --- include/pybind11/cast.h | 6 +++++- include/pybind11/pytypes.h | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 9bb27d7ac2..7753d8f0d2 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1329,14 +1329,18 @@ object object_or_cast(T &&o) { return pybind11::cast(std::forward(o)); } + +#if defined(PYBIND11_CPP17) // Declared in pytypes.h: // Written here so make_caster can be used template template str_attr_accessor object_api::attr_with_type(const char *key) const { - annotations()[key] = make_caster::name.text; + static constexpr auto name = make_caster::name; + annotations()[key] = name.text; return {derived(), key}; } +#endif // Placeholder type for the unneeded (and dead code) static variable in the // PYBIND11_OVERRIDE_OVERRIDE macro diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index feb18ac638..89afe22136 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -113,10 +113,11 @@ class object_api : public pyobject_tag { /// See above (the only difference is that the key is provided as a string literal) str_attr_accessor attr(const char *key) const; +#if defined(PYBIND11_CPP17) // attr_with_type is implemented in cast.h: template str_attr_accessor attr_with_type(const char *key) const; - +#endif /** \rst Matches * unpacking in Python, e.g. to unpack arguments out of a ``tuple`` or ``list`` for a function call. Applying another * to the result yields From 008c370ba47d03f98b7e72774646b09bf1f1ed4d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 02:06:52 +0000 Subject: [PATCH 23/59] style: pre-commit fixes --- include/pybind11/cast.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 7753d8f0d2..a2040ad860 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1329,7 +1329,6 @@ object object_or_cast(T &&o) { return pybind11::cast(std::forward(o)); } - #if defined(PYBIND11_CPP17) // Declared in pytypes.h: // Written here so make_caster can be used From b318d0220c53fada926580b28976cf380e2e0385 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 21:37:47 -0500 Subject: [PATCH 24/59] add compile guard --- tests/test_pytypes.cpp | 5 +++++ tests/test_pytypes.py | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 82c11468e2..e2cca75603 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -999,6 +999,7 @@ TEST_SUBMODULE(pytypes, m) { m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = false; #endif +#if defined(PYBIND11_CPP17) m.attr_with_type>("list_int") = py::list(); m.attr_with_type>("set_str") = py::set(); @@ -1014,4 +1015,8 @@ TEST_SUBMODULE(pytypes, m) { point.attr_with_type>("dict_str_int") = py::dict(); m.attr_with_type>("CONST_INT") = 3; + m.attr("defined_PYBIND11_CPP17") = true; +#else + m.attr("defined_PYBIND11_CPP17") = false; +#endif } diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 44f161a147..9ae2f4ff9a 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1105,19 +1105,24 @@ 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 def get_annotations_helper(o): - print(dir(o)) if isinstance(o, type): return o.__dict__.get("__annotations__", {}) return getattr(o, "__annotations__", {}) - +@pytest.mark.skipif( + not m.defined_PYBIND11_CPP17, + reason="C++17 Position Independent Code not available", +) def test_module_attribute_types() -> None: module_annotations = get_annotations_helper(m) assert module_annotations["list_int"] == "list[int]" assert module_annotations["set_str"] == "set[str]" - +@pytest.mark.skipif( + not m.defined_PYBIND11_CPP17, + reason="C++17 Position Independent Code not available", +) def test_class_attribute_types() -> None: empty_annotations = get_annotations_helper(m.EmptyAnnotationClass) annotations = get_annotations_helper(m.Point) @@ -1126,7 +1131,10 @@ def test_class_attribute_types() -> None: assert annotations["x"] == "float" assert annotations["dict_str_int"] == "dict[str, int]" - +@pytest.mark.skipif( + not m.defined_PYBIND11_CPP17, + reason="C++17 Position Independent Code not available", +) def test_final_annotation() -> None: module_annotations = get_annotations_helper(m) assert module_annotations["CONST_INT"] == "Final[int]" From a53bf0efc888f67a6643304fb837ffb2ca476329 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 02:38:10 +0000 Subject: [PATCH 25/59] style: pre-commit fixes --- tests/test_pytypes.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 9ae2f4ff9a..fd5a0f80ad 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1109,6 +1109,7 @@ def get_annotations_helper(o): return o.__dict__.get("__annotations__", {}) return getattr(o, "__annotations__", {}) + @pytest.mark.skipif( not m.defined_PYBIND11_CPP17, reason="C++17 Position Independent Code not available", @@ -1119,6 +1120,7 @@ def test_module_attribute_types() -> None: assert module_annotations["list_int"] == "list[int]" assert module_annotations["set_str"] == "set[str]" + @pytest.mark.skipif( not m.defined_PYBIND11_CPP17, reason="C++17 Position Independent Code not available", @@ -1131,6 +1133,7 @@ def test_class_attribute_types() -> None: assert annotations["x"] == "float" assert annotations["dict_str_int"] == "dict[str, int]" + @pytest.mark.skipif( not m.defined_PYBIND11_CPP17, reason="C++17 Position Independent Code not available", From adfed511e2959600ed956cf0baa8786a118f7360 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 5 Dec 2024 22:20:34 -0500 Subject: [PATCH 26/59] cleanup --- include/pybind11/cast.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index a2040ad860..162e1884e3 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1335,8 +1335,7 @@ object object_or_cast(T &&o) { template template str_attr_accessor object_api::attr_with_type(const char *key) const { - static constexpr auto name = make_caster::name; - annotations()[key] = name.text; + annotations()[key] = make_caster::name.text; return {derived(), key}; } #endif From 9160cb1af5bae144c9865c366cd58b0eb13e0196 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 8 Dec 2024 14:32:59 -0500 Subject: [PATCH 27/59] comments and function name cleanup --- include/pybind11/cast.h | 2 +- include/pybind11/pytypes.h | 9 +++------ tests/test_pytypes.cpp | 10 +++++----- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 162e1884e3..1274eb89f4 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1334,7 +1334,7 @@ object object_or_cast(T &&o) { // Written here so make_caster can be used template template -str_attr_accessor object_api::attr_with_type(const char *key) const { +str_attr_accessor object_api::attr_with_type_hint(const char *key) const { annotations()[key] = make_caster::name.text; return {derived(), key}; } diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 89afe22136..4435ecf986 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -114,9 +114,9 @@ class object_api : public pyobject_tag { str_attr_accessor attr(const char *key) const; #if defined(PYBIND11_CPP17) - // attr_with_type is implemented in cast.h: + // attr_with_type_hint is implemented in cast.h: template - str_attr_accessor attr_with_type(const char *key) const; + str_attr_accessor attr_with_type_hint(const char *key) const; #endif /** \rst Matches * unpacking in Python, e.g. to unpack arguments out of a ``tuple`` @@ -2567,16 +2567,13 @@ str_attr_accessor object_api::doc() const { } template -// Always a dict -// https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older object object_api::annotations() const { -// Python 3.8, 3.9 #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9 +// 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__")) { setattr(derived(), "__annotations__", dict()); } return attr("__annotations__"); -// Python 3.10+ #else return getattr(derived(), "__annotations__", dict()); #endif diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index e2cca75603..02ad90559d 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -1000,8 +1000,8 @@ TEST_SUBMODULE(pytypes, m) { #endif #if defined(PYBIND11_CPP17) - m.attr_with_type>("list_int") = py::list(); - m.attr_with_type>("set_str") = py::set(); + m.attr_with_type_hint>("list_int") = py::list(); + m.attr_with_type_hint>("set_str") = py::set(); struct Empty {}; py::class_(m, "EmptyAnnotationClass"); @@ -1011,10 +1011,10 @@ TEST_SUBMODULE(pytypes, m) { py::dict dict_str_int; }; auto point = py::class_(m, "Point"); - point.attr_with_type("x"); - point.attr_with_type>("dict_str_int") = py::dict(); + point.attr_with_type_hint("x"); + point.attr_with_type_hint>("dict_str_int") = py::dict(); - m.attr_with_type>("CONST_INT") = 3; + m.attr_with_type_hint>("CONST_INT") = 3; m.attr("defined_PYBIND11_CPP17") = true; #else m.attr("defined_PYBIND11_CPP17") = false; From 07f861382ea49f37f36cca711e49ae6b20519630 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 8 Dec 2024 19:36:56 +0000 Subject: [PATCH 28/59] style: pre-commit fixes --- include/pybind11/pytypes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 4435ecf986..6ece5edbfa 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -2569,7 +2569,7 @@ str_attr_accessor object_api::doc() const { template object object_api::annotations() const { #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9 -// 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__")) { setattr(derived(), "__annotations__", dict()); } From 14d2dda5eba15591123832cefb359a851095dbbe Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 15 Dec 2024 15:46:10 -0500 Subject: [PATCH 29/59] update test case --- include/pybind11/typing.h | 17 ++++++++++++++--- tests/test_pytypes.cpp | 10 ++++------ tests/test_pytypes.py | 20 +++++++++++++++----- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index ee8748c2b7..005279058b 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -88,6 +88,12 @@ class Final : public object { using object::object; }; +template +class ClassVar : public object { + PYBIND11_OBJECT_DEFAULT(ClassVar, object, PyObject_Type) + using object::object; +}; + template class TypeGuard : public bool_ { using bool_::bool_; @@ -257,14 +263,19 @@ struct handle_type_name> { = const_name("Optional[") + as_return_type>::name + const_name("]"); }; -// TypeGuard and TypeIs use as_return_type to use the return type if available, which is usually -// the narrower type. - template struct handle_type_name> { static constexpr auto name = const_name("Final[") + make_caster::name + const_name("]"); }; +template +struct handle_type_name> { + static constexpr auto name = const_name("ClassVar[") + make_caster::name + const_name("]"); +}; + +// TypeGuard and TypeIs use as_return_type to use the return type if available, which is usually +// the narrower type. + template struct handle_type_name> { static constexpr auto name diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index b9051f82ba..819ed18014 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -1045,13 +1045,11 @@ TEST_SUBMODULE(pytypes, m) { struct Empty {}; py::class_(m, "EmptyAnnotationClass"); - struct Point { - float x; - py::dict dict_str_int; - }; + struct Point {}; auto point = py::class_(m, "Point"); - point.attr_with_type_hint("x"); - point.attr_with_type_hint>("dict_str_int") = py::dict(); + point.def(py::init()); + point.attr_with_type_hint>("x"); + point.attr_with_type_hint>>("dict_str_int") = py::dict(); m.attr_with_type_hint>("CONST_INT") = 3; m.attr("defined_PYBIND11_CPP17") = true; diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 84f1b66fc0..748aaa9fdf 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1106,8 +1106,8 @@ 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 def get_annotations_helper(o): if isinstance(o, type): - return o.__dict__.get("__annotations__", {}) - return getattr(o, "__annotations__", {}) + return o.__dict__.get("__annotations__", None) + return getattr(o, "__annotations__", None) @pytest.mark.skipif( @@ -1129,9 +1129,19 @@ def test_class_attribute_types() -> None: empty_annotations = get_annotations_helper(m.EmptyAnnotationClass) annotations = get_annotations_helper(m.Point) - assert empty_annotations == {} - assert annotations["x"] == "float" - assert annotations["dict_str_int"] == "dict[str, int]" + assert empty_annotations is None + assert annotations["x"] == "ClassVar[float]" + assert annotations["dict_str_int"] == "ClassVar[dict[str, int]]" + + m.Point.x = 1.0 + assert m.Point.x == 1.0 + + point = m.Point() + assert point.x == 1.0 + + point.dict_str_int["hi"] = 3 + assert m.Point().dict_str_int == {"hi": 3} + @pytest.mark.skipif( From 881b356ce8eee860d714898e564f55a6688ad8d5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 15 Dec 2024 20:47:10 +0000 Subject: [PATCH 30/59] style: pre-commit fixes --- tests/test_pytypes.cpp | 3 ++- tests/test_pytypes.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 819ed18014..4bb6d0c251 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -1049,7 +1049,8 @@ TEST_SUBMODULE(pytypes, m) { auto point = py::class_(m, "Point"); point.def(py::init()); point.attr_with_type_hint>("x"); - point.attr_with_type_hint>>("dict_str_int") = py::dict(); + point.attr_with_type_hint>>("dict_str_int") + = py::dict(); m.attr_with_type_hint>("CONST_INT") = 3; m.attr("defined_PYBIND11_CPP17") = true; diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 748aaa9fdf..edf6e43d6b 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1143,7 +1143,6 @@ def test_class_attribute_types() -> None: assert m.Point().dict_str_int == {"hi": 3} - @pytest.mark.skipif( not m.defined_PYBIND11_CPP17, reason="C++17 Position Independent Code not available", From 731745ae0fd7b3acab32dbdb147d4dfe1e39ef2a Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 15 Dec 2024 15:49:52 -0500 Subject: [PATCH 31/59] add write --- tests/test_pytypes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index edf6e43d6b..6d6fd717e4 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1136,8 +1136,9 @@ def test_class_attribute_types() -> None: m.Point.x = 1.0 assert m.Point.x == 1.0 + m.Point.x = 3.0 point = m.Point() - assert point.x == 1.0 + assert point.x == 3.0 point.dict_str_int["hi"] = 3 assert m.Point().dict_str_int == {"hi": 3} From 890fcae7d83b03a224304cc6e878da987a6efebd Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 15 Dec 2024 16:51:06 -0500 Subject: [PATCH 32/59] added instance check --- tests/test_pytypes.cpp | 15 ++++++++++----- tests/test_pytypes.py | 29 +++++++++++++++++++---------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 4bb6d0c251..40faad17c1 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -1045,13 +1045,18 @@ TEST_SUBMODULE(pytypes, m) { struct Empty {}; py::class_(m, "EmptyAnnotationClass"); - struct Point {}; - auto point = py::class_(m, "Point"); - point.def(py::init()); - point.attr_with_type_hint>("x"); - point.attr_with_type_hint>>("dict_str_int") + struct Static {}; + auto static_class = py::class_(m, "Static"); + static_class.def(py::init()); + static_class.attr_with_type_hint>("x") = 1.0; + static_class.attr_with_type_hint>>("dict_str_int") = py::dict(); + struct Instance {}; + auto instance = py::class_(m, "Instance", py::dynamic_attr()); + instance.def(py::init()); + instance.attr_with_type_hint("y"); + m.attr_with_type_hint>("CONST_INT") = 3; m.attr("defined_PYBIND11_CPP17") = true; #else diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 6d6fd717e4..554376ff9f 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1127,21 +1127,30 @@ def test_module_attribute_types() -> None: ) def test_class_attribute_types() -> None: empty_annotations = get_annotations_helper(m.EmptyAnnotationClass) - annotations = get_annotations_helper(m.Point) + static_annotations = get_annotations_helper(m.Static) + instance_annotations = get_annotations_helper(m.Instance) assert empty_annotations is None - assert annotations["x"] == "ClassVar[float]" - assert annotations["dict_str_int"] == "ClassVar[dict[str, int]]" + assert static_annotations["x"] == "ClassVar[float]" + assert static_annotations["dict_str_int"] == "ClassVar[dict[str, int]]" - m.Point.x = 1.0 - assert m.Point.x == 1.0 + assert m.Static.x == 1.0 - m.Point.x = 3.0 - point = m.Point() - assert point.x == 3.0 + m.Static.x = 3.0 + static = m.Static() + assert static.x == 3.0 - point.dict_str_int["hi"] = 3 - assert m.Point().dict_str_int == {"hi": 3} + static.dict_str_int["hi"] = 3 + assert m.Static().dict_str_int == {"hi": 3} + + assert instance_annotations["y"] == "float" + instance1 = m.Instance() + instance1.y = 4.0 + + instance2 = m.Instance() + instance2.y = 5.0 + + assert instance1.y != instance2.y @pytest.mark.skipif( From 28d2a6635014a59f08166d953b729128aadd9ba2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 15 Dec 2024 21:51:35 +0000 Subject: [PATCH 33/59] style: pre-commit fixes --- tests/test_pytypes.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 40faad17c1..792785baaf 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -1049,7 +1049,8 @@ TEST_SUBMODULE(pytypes, m) { auto static_class = py::class_(m, "Static"); static_class.def(py::init()); static_class.attr_with_type_hint>("x") = 1.0; - static_class.attr_with_type_hint>>("dict_str_int") + static_class.attr_with_type_hint>>( + "dict_str_int") = py::dict(); struct Instance {}; From 3cbaafdb8afb4a3053c25be926518585cc75d926 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Tue, 17 Dec 2024 12:19:20 -0800 Subject: [PATCH 34/59] Test for `__cpp_inline_variables` and use `static_assert()` --- include/pybind11/cast.h | 7 +++++-- include/pybind11/pytypes.h | 3 +-- tests/test_pytypes.cpp | 6 +++--- tests/test_pytypes.py | 12 ++++++------ 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index f676955194..4a10dcbbf0 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1366,16 +1366,19 @@ object object_or_cast(T &&o) { return pybind11::cast(std::forward(o)); } -#if defined(PYBIND11_CPP17) // Declared in pytypes.h: // Written here so make_caster can be used template template str_attr_accessor object_api::attr_with_type_hint(const char *key) const { +#if !defined(__cpp_inline_variables) + static_assert(false, + "C++17 feature __cpp_inline_variables not available: " + "https://en.cppreference.com/w/cpp/language/static#Static_data_members"); +#endif annotations()[key] = make_caster::name.text; return {derived(), key}; } -#endif // Placeholder type for the unneeded (and dead code) static variable in the // PYBIND11_OVERRIDE_OVERRIDE macro diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 6ece5edbfa..a30ecf4b86 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -113,11 +113,10 @@ class object_api : public pyobject_tag { /// See above (the only difference is that the key is provided as a string literal) str_attr_accessor attr(const char *key) const; -#if defined(PYBIND11_CPP17) // attr_with_type_hint is implemented in cast.h: template str_attr_accessor attr_with_type_hint(const char *key) const; -#endif + /** \rst Matches * unpacking in Python, e.g. to unpack arguments out of a ``tuple`` or ``list`` for a function call. Applying another * to the result yields diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 792785baaf..761d6cfaaa 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -1038,7 +1038,7 @@ TEST_SUBMODULE(pytypes, m) { m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = false; #endif -#if defined(PYBIND11_CPP17) +#if defined(__cpp_inline_variables) m.attr_with_type_hint>("list_int") = py::list(); m.attr_with_type_hint>("set_str") = py::set(); @@ -1059,9 +1059,9 @@ TEST_SUBMODULE(pytypes, m) { instance.attr_with_type_hint("y"); m.attr_with_type_hint>("CONST_INT") = 3; - m.attr("defined_PYBIND11_CPP17") = true; + m.attr("defined___cpp_inline_variables") = true; #else - m.attr("defined_PYBIND11_CPP17") = false; + m.attr("defined___cpp_inline_variables") = false; #endif m.def("half_of_number", [](const RealNumber &x) { return RealNumber{x.value / 2}; }); // std::vector diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 554376ff9f..24c5f2f0ea 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1111,8 +1111,8 @@ def get_annotations_helper(o): @pytest.mark.skipif( - not m.defined_PYBIND11_CPP17, - reason="C++17 Position Independent Code not available", + not m.defined___cpp_inline_variables, + reason="C++17 feature __cpp_inline_variables not available.", ) def test_module_attribute_types() -> None: module_annotations = get_annotations_helper(m) @@ -1122,8 +1122,8 @@ def test_module_attribute_types() -> None: @pytest.mark.skipif( - not m.defined_PYBIND11_CPP17, - reason="C++17 Position Independent Code not available", + not m.defined___cpp_inline_variables, + reason="C++17 feature __cpp_inline_variables not available.", ) def test_class_attribute_types() -> None: empty_annotations = get_annotations_helper(m.EmptyAnnotationClass) @@ -1154,8 +1154,8 @@ def test_class_attribute_types() -> None: @pytest.mark.skipif( - not m.defined_PYBIND11_CPP17, - reason="C++17 Position Independent Code not available", + not m.defined___cpp_inline_variables, + reason="C++17 feature __cpp_inline_variables not available.", ) def test_final_annotation() -> None: module_annotations = get_annotations_helper(m) From 58e5a0967034ce5806f05772597a9f3a44768e28 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Tue, 17 Dec 2024 13:01:50 -0800 Subject: [PATCH 35/59] Add guard: __annotations__["list_int"] was set already. --- include/pybind11/cast.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 4a10dcbbf0..ffd3d4dd34 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1376,7 +1376,11 @@ str_attr_accessor object_api::attr_with_type_hint(const char *key) const { "C++17 feature __cpp_inline_variables not available: " "https://en.cppreference.com/w/cpp/language/static#Static_data_members"); #endif - annotations()[key] = make_caster::name.text; + object ann = annotations(); + if (ann.contains(key)) { + throw std::runtime_error("__annotations__[\"" + std::string(key) + "\"] was set already."); + } + ann[key] = make_caster::name.text; return {derived(), key}; } From 882d20f57fa54cb9499073d52e5b3ae20425905d Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Tue, 17 Dec 2024 13:13:34 -0800 Subject: [PATCH 36/59] Avoid explicit `false` in `static_assert()`. --- include/pybind11/cast.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index ffd3d4dd34..952bda090c 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1372,7 +1372,7 @@ template template str_attr_accessor object_api::attr_with_type_hint(const char *key) const { #if !defined(__cpp_inline_variables) - static_assert(false, + static_assert(!std::is_same::value, "C++17 feature __cpp_inline_variables not available: " "https://en.cppreference.com/w/cpp/language/static#Static_data_members"); #endif From b296137950e992c5fb6498e0d28bea37067d7e66 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Wed, 18 Dec 2024 17:09:27 -0800 Subject: [PATCH 37/59] add redeclaration test --- tests/test_pytypes.cpp | 5 +++++ tests/test_pytypes.py | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 761d6cfaaa..9380c8bfb5 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -1058,7 +1058,12 @@ TEST_SUBMODULE(pytypes, m) { instance.def(py::init()); instance.attr_with_type_hint("y"); + m.def("attr_with_type_hint_float_x", [](py::handle obj) { + obj.attr_with_type_hint("x"); + }); + m.attr_with_type_hint>("CONST_INT") = 3; + m.attr("defined___cpp_inline_variables") = true; #else m.attr("defined___cpp_inline_variables") = false; diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 24c5f2f0ea..4c6ee8da97 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1153,6 +1153,18 @@ def test_class_attribute_types() -> None: assert instance1.y != instance2.y +@pytest.mark.skipif( + not m.defined___cpp_inline_variables, + reason="C++17 feature __cpp_inline_variables not available.", +) +def test_redeclaration_attr_with_type_hint() -> None: + obj = m.Instance() + m.attr_with_type_hint_float_x(obj) + help(obj) + assert get_annotations_helper(obj)["x"] == "float" + with pytest.raises(RuntimeError, match=r'^__annotations__\["x"\] was set already\.$'): + m.attr_with_type_hint_float_x(obj) + @pytest.mark.skipif( not m.defined___cpp_inline_variables, reason="C++17 feature __cpp_inline_variables not available.", From 981e0a3c832cbef4928c21bd55d840671a2250d7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 01:09:58 +0000 Subject: [PATCH 38/59] style: pre-commit fixes --- tests/test_pytypes.cpp | 7 +++---- tests/test_pytypes.py | 5 ++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 9380c8bfb5..311aa50b55 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -1058,12 +1058,11 @@ TEST_SUBMODULE(pytypes, m) { instance.def(py::init()); instance.attr_with_type_hint("y"); - m.def("attr_with_type_hint_float_x", [](py::handle obj) { - obj.attr_with_type_hint("x"); - }); + m.def("attr_with_type_hint_float_x", + [](py::handle obj) { obj.attr_with_type_hint("x"); }); m.attr_with_type_hint>("CONST_INT") = 3; - + m.attr("defined___cpp_inline_variables") = true; #else m.attr("defined___cpp_inline_variables") = false; diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 4c6ee8da97..9303b94017 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1162,9 +1162,12 @@ def test_redeclaration_attr_with_type_hint() -> None: m.attr_with_type_hint_float_x(obj) help(obj) assert get_annotations_helper(obj)["x"] == "float" - with pytest.raises(RuntimeError, match=r'^__annotations__\["x"\] was set already\.$'): + with pytest.raises( + RuntimeError, match=r'^__annotations__\["x"\] was set already\.$' + ): m.attr_with_type_hint_float_x(obj) + @pytest.mark.skipif( not m.defined___cpp_inline_variables, reason="C++17 feature __cpp_inline_variables not available.", From 7f7b70b943b5003230babf9495d956c27992cd09 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 19 Dec 2024 12:57:37 -0800 Subject: [PATCH 39/59] add handle support to attr_with_type_hint --- include/pybind11/cast.h | 16 ++++++++++++++++ include/pybind11/pytypes.h | 3 +++ tests/test_pytypes.py | 1 - 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 952bda090c..6ef405f1ed 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1368,6 +1368,22 @@ object object_or_cast(T &&o) { // Declared in pytypes.h: // Written here so make_caster can be used +template +template +obj_attr_accessor object_api::attr_with_type_hint(handle key) const { +#if !defined(__cpp_inline_variables) + static_assert(!std::is_same::value, + "C++17 feature __cpp_inline_variables not available: " + "https://en.cppreference.com/w/cpp/language/static#Static_data_members"); +#endif + object ann = annotations(); + if (ann.contains(key)) { + throw std::runtime_error("__annotations__[\"" + std::string(key) + "\"] was set already."); + } + ann[key] = make_caster::name.text; + return {derived(), reinterpret_borrow(key)}; +} + template template str_attr_accessor object_api::attr_with_type_hint(const char *key) const { diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index a30ecf4b86..d8d158b952 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -114,6 +114,9 @@ class object_api : public pyobject_tag { str_attr_accessor attr(const char *key) const; // attr_with_type_hint is implemented in cast.h: + template + obj_attr_accessor attr_with_type_hint(handle key) const; + template str_attr_accessor attr_with_type_hint(const char *key) const; diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 9303b94017..c0d1598fad 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1160,7 +1160,6 @@ def test_class_attribute_types() -> None: def test_redeclaration_attr_with_type_hint() -> None: obj = m.Instance() m.attr_with_type_hint_float_x(obj) - help(obj) assert get_annotations_helper(obj)["x"] == "float" with pytest.raises( RuntimeError, match=r'^__annotations__\["x"\] was set already\.$' From 19e033c4815504ce8c4e0ff81ed2f64fc31c58aa Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 19 Dec 2024 13:08:32 -0800 Subject: [PATCH 40/59] store reintpreted_key --- include/pybind11/cast.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 6ef405f1ed..3d30ff37a8 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1377,11 +1377,12 @@ obj_attr_accessor object_api::attr_with_type_hint(handle key) const { "https://en.cppreference.com/w/cpp/language/static#Static_data_members"); #endif object ann = annotations(); - if (ann.contains(key)) { - throw std::runtime_error("__annotations__[\"" + std::string(key) + "\"] was set already."); + object reinterpreted_key = reinterpret_borrow(key); + if (ann.contains(reinterpreted_key)) { + throw std::runtime_error("__annotations__[\"" + std::string(py::str(reinterpreted_key)) + "\"] was set already."); } ann[key] = make_caster::name.text; - return {derived(), reinterpret_borrow(key)}; + return {derived(), reinterpreted_key}; } template From 375ea304e8c22f9743ca0cdf0f522a551940933d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 21:10:52 +0000 Subject: [PATCH 41/59] style: pre-commit fixes --- include/pybind11/cast.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 3d30ff37a8..6a5ef1543c 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1379,7 +1379,8 @@ obj_attr_accessor object_api::attr_with_type_hint(handle key) const { object ann = annotations(); object reinterpreted_key = reinterpret_borrow(key); if (ann.contains(reinterpreted_key)) { - throw std::runtime_error("__annotations__[\"" + std::string(py::str(reinterpreted_key)) + "\"] was set already."); + throw std::runtime_error("__annotations__[\"" + std::string(py::str(reinterpreted_key)) + + "\"] was set already."); } ann[key] = make_caster::name.text; return {derived(), reinterpreted_key}; From 9f532cfc466fc31450b984246336acc85933cf35 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 19 Dec 2024 13:11:29 -0800 Subject: [PATCH 42/59] fix str namespace --- include/pybind11/cast.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 6a5ef1543c..f556c5ede0 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1379,7 +1379,7 @@ obj_attr_accessor object_api::attr_with_type_hint(handle key) const { object ann = annotations(); object reinterpreted_key = reinterpret_borrow(key); if (ann.contains(reinterpreted_key)) { - throw std::runtime_error("__annotations__[\"" + std::string(py::str(reinterpreted_key)) + throw std::runtime_error("__annotations__[\"" + std::string(str(reinterpreted_key)) + "\"] was set already."); } ann[key] = make_caster::name.text; From 8b241f1727d54a6bbd0a4b61237aedafd0b5593f Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 19 Dec 2024 13:31:09 -0800 Subject: [PATCH 43/59] fix scope? --- include/pybind11/cast.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index f556c5ede0..650fcac9ea 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1366,6 +1366,9 @@ object object_or_cast(T &&o) { return pybind11::cast(std::forward(o)); } +// This is being used to get around the conflict with the deprecated str() function on object_api +typedef str py_str; + // Declared in pytypes.h: // Written here so make_caster can be used template @@ -1379,7 +1382,7 @@ obj_attr_accessor object_api::attr_with_type_hint(handle key) const { object ann = annotations(); object reinterpreted_key = reinterpret_borrow(key); if (ann.contains(reinterpreted_key)) { - throw std::runtime_error("__annotations__[\"" + std::string(str(reinterpreted_key)) + throw std::runtime_error("__annotations__[\"" + py_str(reinterpreted_key) + "\"] was set already."); } ann[key] = make_caster::name.text; From f205bb4840ba8d6df2982f064cd4d4bef0879847 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 19 Dec 2024 13:33:20 -0800 Subject: [PATCH 44/59] string wrap --- include/pybind11/cast.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 650fcac9ea..d4ae36c8f7 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1382,7 +1382,7 @@ obj_attr_accessor object_api::attr_with_type_hint(handle key) const { object ann = annotations(); object reinterpreted_key = reinterpret_borrow(key); if (ann.contains(reinterpreted_key)) { - throw std::runtime_error("__annotations__[\"" + py_str(reinterpreted_key) + throw std::runtime_error("__annotations__[\"" + std::string(py_str(reinterpreted_key)) + "\"] was set already."); } ann[key] = make_caster::name.text; From a6ffbe35c4856782d7b54ac38aafb99975d96c32 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 19 Dec 2024 13:48:17 -0800 Subject: [PATCH 45/59] clang tidy --- include/pybind11/cast.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index d4ae36c8f7..69fc0bada7 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1367,7 +1367,7 @@ object object_or_cast(T &&o) { } // This is being used to get around the conflict with the deprecated str() function on object_api -typedef str py_str; +using py_str = str; // Declared in pytypes.h: // Written here so make_caster can be used @@ -1380,7 +1380,7 @@ obj_attr_accessor object_api::attr_with_type_hint(handle key) const { "https://en.cppreference.com/w/cpp/language/static#Static_data_members"); #endif object ann = annotations(); - object reinterpreted_key = reinterpret_borrow(key); + auto reinterpreted_key = reinterpret_borrow(key); if (ann.contains(reinterpreted_key)) { throw std::runtime_error("__annotations__[\"" + std::string(py_str(reinterpreted_key)) + "\"] was set already."); From cd4f771b124022608129d3874701e0ccb3e7b106 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 20 Dec 2024 12:30:27 -0800 Subject: [PATCH 46/59] Swap order of attr_with_type_hint implementation in cast.h (so that the `const char *` overload appears first). --- include/pybind11/cast.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 69fc0bada7..82ec2b6d09 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1373,36 +1373,36 @@ using py_str = str; // Written here so make_caster can be used template template -obj_attr_accessor object_api::attr_with_type_hint(handle key) const { +str_attr_accessor object_api::attr_with_type_hint(const char *key) const { #if !defined(__cpp_inline_variables) static_assert(!std::is_same::value, "C++17 feature __cpp_inline_variables not available: " "https://en.cppreference.com/w/cpp/language/static#Static_data_members"); #endif object ann = annotations(); - auto reinterpreted_key = reinterpret_borrow(key); - if (ann.contains(reinterpreted_key)) { - throw std::runtime_error("__annotations__[\"" + std::string(py_str(reinterpreted_key)) - + "\"] was set already."); + if (ann.contains(key)) { + throw std::runtime_error("__annotations__[\"" + std::string(key) + "\"] was set already."); } ann[key] = make_caster::name.text; - return {derived(), reinterpreted_key}; + return {derived(), key}; } template template -str_attr_accessor object_api::attr_with_type_hint(const char *key) const { +obj_attr_accessor object_api::attr_with_type_hint(handle key) const { #if !defined(__cpp_inline_variables) static_assert(!std::is_same::value, "C++17 feature __cpp_inline_variables not available: " "https://en.cppreference.com/w/cpp/language/static#Static_data_members"); #endif object ann = annotations(); - if (ann.contains(key)) { - throw std::runtime_error("__annotations__[\"" + std::string(key) + "\"] was set already."); + auto reinterpreted_key = reinterpret_borrow(key); + if (ann.contains(reinterpreted_key)) { + throw std::runtime_error("__annotations__[\"" + std::string(py_str(reinterpreted_key)) + + "\"] was set already."); } ann[key] = make_caster::name.text; - return {derived(), key}; + return {derived(), reinterpreted_key}; } // Placeholder type for the unneeded (and dead code) static variable in the From 08d477417ba9fe91d528c173af7bd5b3113971c5 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 20 Dec 2024 12:51:15 -0800 Subject: [PATCH 47/59] Reuse `const char *` overload from `handle` overload. --- include/pybind11/cast.h | 20 +++----------------- include/pybind11/pytypes.h | 1 - tests/test_pytypes.cpp | 4 +++- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 82ec2b6d09..df18157dbb 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1366,11 +1366,8 @@ object object_or_cast(T &&o) { return pybind11::cast(std::forward(o)); } -// This is being used to get around the conflict with the deprecated str() function on object_api -using py_str = str; - // Declared in pytypes.h: -// Written here so make_caster can be used +// Implemented here so that make_caster can be used. template template str_attr_accessor object_api::attr_with_type_hint(const char *key) const { @@ -1390,19 +1387,8 @@ str_attr_accessor object_api::attr_with_type_hint(const char *key) const { template template obj_attr_accessor object_api::attr_with_type_hint(handle key) const { -#if !defined(__cpp_inline_variables) - static_assert(!std::is_same::value, - "C++17 feature __cpp_inline_variables not available: " - "https://en.cppreference.com/w/cpp/language/static#Static_data_members"); -#endif - object ann = annotations(); - auto reinterpreted_key = reinterpret_borrow(key); - if (ann.contains(reinterpreted_key)) { - throw std::runtime_error("__annotations__[\"" + std::string(py_str(reinterpreted_key)) - + "\"] was set already."); - } - ann[key] = make_caster::name.text; - return {derived(), reinterpreted_key}; + (void) attr_with_type_hint(key.cast().c_str()); + return {derived(), reinterpret_borrow(key)}; } // Placeholder type for the unneeded (and dead code) static variable in the diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index d8d158b952..def7a122fc 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -116,7 +116,6 @@ class object_api : public pyobject_tag { // attr_with_type_hint is implemented in cast.h: template obj_attr_accessor attr_with_type_hint(handle key) const; - template str_attr_accessor attr_with_type_hint(const char *key) const; diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 311aa50b55..b4fa99192b 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -1039,8 +1039,10 @@ TEST_SUBMODULE(pytypes, m) { #endif #if defined(__cpp_inline_variables) + // Exercises const char* overload: m.attr_with_type_hint>("list_int") = py::list(); - m.attr_with_type_hint>("set_str") = py::set(); + // Exercises py::handle overload: + m.attr_with_type_hint>(py::str("set_str")) = py::set(); struct Empty {}; py::class_(m, "EmptyAnnotationClass"); From 15829719259b2a90dad85bfccca3084a10c3caa3 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Fri, 20 Dec 2024 14:22:42 -0800 Subject: [PATCH 48/59] Added comment --- include/pybind11/pytypes.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index def7a122fc..edcee299b8 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -113,7 +113,11 @@ class object_api : public pyobject_tag { /// See above (the only difference is that the key is provided as a string literal) str_attr_accessor attr(const char *key) const; - // attr_with_type_hint is implemented in cast.h: + /** \rst + Similar to the above attr functions with the difference that the templated Type + is used to set the `__annotations__` dict value to the corresponding key. Worth nothing that + attr_with_type_hint is implemented in cast.h + \endrst */ template obj_attr_accessor attr_with_type_hint(handle key) const; template From fe04344bb00cb879cb44e927d69bbdec90c58a8b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 22:23:15 +0000 Subject: [PATCH 49/59] style: pre-commit fixes --- include/pybind11/pytypes.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index edcee299b8..b1f865d85d 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -113,11 +113,11 @@ class object_api : public pyobject_tag { /// See above (the only difference is that the key is provided as a string literal) str_attr_accessor attr(const char *key) const; - /** \rst + /** \rst Similar to the above attr functions with the difference that the templated Type - is used to set the `__annotations__` dict value to the corresponding key. Worth nothing that - attr_with_type_hint is implemented in cast.h - \endrst */ + is used to set the `__annotations__` dict value to the corresponding key. Worth nothing + that attr_with_type_hint is implemented in cast.h + \endrst */ template obj_attr_accessor attr_with_type_hint(handle key) const; template From 988c0396ffa16f048a95dc2290877b2151b160f6 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Fri, 20 Dec 2024 14:23:58 -0800 Subject: [PATCH 50/59] line up comment --- include/pybind11/pytypes.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index b1f865d85d..6c06f0a78b 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -116,8 +116,8 @@ class object_api : public pyobject_tag { /** \rst Similar to the above attr functions with the difference that the templated Type is used to set the `__annotations__` dict value to the corresponding key. Worth nothing - that attr_with_type_hint is implemented in cast.h - \endrst */ + that attr_with_type_hint is implemented in cast.h + \endrst */ template obj_attr_accessor attr_with_type_hint(handle key) const; template From 224189283bb67574492b2ee0d9f7b14dd2e6d6be Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Fri, 20 Dec 2024 14:25:38 -0800 Subject: [PATCH 51/59] fixed spelling error --- include/pybind11/pytypes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 6c06f0a78b..ba4bba0054 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -115,7 +115,7 @@ class object_api : public pyobject_tag { /** \rst Similar to the above attr functions with the difference that the templated Type - is used to set the `__annotations__` dict value to the corresponding key. Worth nothing + is used to set the `__annotations__` dict value to the corresponding key. Worth noting that attr_with_type_hint is implemented in cast.h \endrst */ template From 4be50fe1a93ee6cd317bd13c57d05ffb3f9f70f1 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Fri, 20 Dec 2024 14:26:05 -0800 Subject: [PATCH 52/59] Add missing period --- include/pybind11/pytypes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index ba4bba0054..3d43c83dd9 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -116,7 +116,7 @@ class object_api : public pyobject_tag { /** \rst Similar to the above attr functions with the difference that the templated Type is used to set the `__annotations__` dict value to the corresponding key. Worth noting - that attr_with_type_hint is implemented in cast.h + that attr_with_type_hint is implemented in cast.h. \endrst */ template obj_attr_accessor attr_with_type_hint(handle key) const; From 8f87e21291c785dfa4f22889c8923f6050d8cfb7 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 20 Dec 2024 17:28:41 -0800 Subject: [PATCH 53/59] Introduce `detail::always_false<>` to make the new `static_assert()` more readable. --- include/pybind11/cast.h | 2 +- include/pybind11/detail/common.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index df18157dbb..853165a498 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1372,7 +1372,7 @@ template template str_attr_accessor object_api::attr_with_type_hint(const char *key) const { #if !defined(__cpp_inline_variables) - static_assert(!std::is_same::value, + static_assert(always_false::value, "C++17 feature __cpp_inline_variables not available: " "https://en.cppreference.com/w/cpp/language/static#Static_data_members"); #endif diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 78173cad30..fc825f36ae 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -627,6 +627,9 @@ struct instance { static_assert(std::is_standard_layout::value, "Internal error: `pybind11::detail::instance` is not standard layout!"); +template +struct always_false : std::false_type {}; + /// from __cpp_future__ import (convenient aliases from C++14/17) #if defined(PYBIND11_CPP14) using std::conditional_t; From e6b1370085655b0e8bf993c85217b5c9e43b5594 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 20 Dec 2024 17:30:43 -0800 Subject: [PATCH 54/59] Copy `attr` comment for `const char *` overload, for consistency. --- include/pybind11/pytypes.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 3d43c83dd9..92e0a81f44 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -120,6 +120,7 @@ class object_api : public pyobject_tag { \endrst */ template obj_attr_accessor attr_with_type_hint(handle key) const; + /// See above (the only difference is that the key is provided as a string literal) template str_attr_accessor attr_with_type_hint(const char *key) const; From 999b668688b5b965a6f929a5a7f8af5176c8a21d Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sat, 21 Dec 2024 08:20:46 -0800 Subject: [PATCH 55/59] static_assert(std::false_type::value, ...) --- include/pybind11/cast.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 853165a498..ad8fb0face 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1372,7 +1372,7 @@ template template str_attr_accessor object_api::attr_with_type_hint(const char *key) const { #if !defined(__cpp_inline_variables) - static_assert(always_false::value, + static_assert(std::false_type::value, "C++17 feature __cpp_inline_variables not available: " "https://en.cppreference.com/w/cpp/language/static#Static_data_members"); #endif From 9d46b4dfd3cb38a3028c9d61822873c116ff6fc3 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sat, 21 Dec 2024 12:12:18 -0800 Subject: [PATCH 56/59] Revert "static_assert(std::false_type::value, ...)" This reverts commit 999b668688b5b965a6f929a5a7f8af5176c8a21d. --- include/pybind11/cast.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index ad8fb0face..853165a498 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1372,7 +1372,7 @@ template template str_attr_accessor object_api::attr_with_type_hint(const char *key) const { #if !defined(__cpp_inline_variables) - static_assert(std::false_type::value, + static_assert(always_false::value, "C++17 feature __cpp_inline_variables not available: " "https://en.cppreference.com/w/cpp/language/static#Static_data_members"); #endif From 921657adf251580f8c53862506d6c715d077131b Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sat, 21 Dec 2024 12:27:26 -0800 Subject: [PATCH 57/59] Add comment for `always_false` --- include/pybind11/detail/common.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index fc825f36ae..c05db0e7da 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -627,6 +627,11 @@ struct instance { static_assert(std::is_standard_layout::value, "Internal error: `pybind11::detail::instance` is not standard layout!"); +// Some older compilers (e.g. gcc 9.4.0) require +// static_assert(always_false::value, "..."); +// instead of +// static_assert(false, "..."); +// to trigger the static_assert() in a template only if it is actually instantiated. template struct always_false : std::false_type {}; From e1e2c9a4fe8e377b12d48fb44061b7f8259788fd Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 21 Dec 2024 15:22:54 -0800 Subject: [PATCH 58/59] add test for inspect.get_annotations() --- tests/test_pytypes.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index c0d1598fad..3f93d56c2b 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1120,6 +1120,18 @@ def test_module_attribute_types() -> None: assert module_annotations["list_int"] == "list[int]" assert module_annotations["set_str"] == "set[str]" +@pytest.mark.skipif( + not m.defined___cpp_inline_variables, + reason="C++17 feature __cpp_inline_variables not available.", +) +@pytest.mark.skipif(sys.version_info < (3, 10), + reason="get_annotations function does not exist until Python3.10") +def test_get_annotations_compliance() -> None: + from inspect import get_annotations + module_annotations = get_annotations(m) + + assert module_annotations["list_int"] == "list[int]" + assert module_annotations["set_str"] == "set[str]" @pytest.mark.skipif( not m.defined___cpp_inline_variables, From 2774a8dfb169a9af4a30f599558d7c5179301f59 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 23:24:07 +0000 Subject: [PATCH 59/59] style: pre-commit fixes --- tests/test_pytypes.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 3f93d56c2b..448bfa6a83 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1120,19 +1120,24 @@ def test_module_attribute_types() -> None: assert module_annotations["list_int"] == "list[int]" assert module_annotations["set_str"] == "set[str]" + @pytest.mark.skipif( not m.defined___cpp_inline_variables, reason="C++17 feature __cpp_inline_variables not available.", ) -@pytest.mark.skipif(sys.version_info < (3, 10), - reason="get_annotations function does not exist until Python3.10") +@pytest.mark.skipif( + sys.version_info < (3, 10), + reason="get_annotations function does not exist until Python3.10", +) def test_get_annotations_compliance() -> None: from inspect import get_annotations + module_annotations = get_annotations(m) assert module_annotations["list_int"] == "list[int]" assert module_annotations["set_str"] == "set[str]" + @pytest.mark.skipif( not m.defined___cpp_inline_variables, reason="C++17 feature __cpp_inline_variables not available.",