This commit is contained in:
Michael Carlstrom 2024-12-05 13:26:05 -05:00
parent e7c9b90739
commit aa74fbefd7
5 changed files with 75 additions and 0 deletions

View File

@ -1329,6 +1329,15 @@ object object_or_cast(T &&o) {
return pybind11::cast(std::forward<T>(o));
}
// Declared in pytypes.h:
// Written here so make_caster<T> can be used
template <typename D>
template <typename T>
str_attr_accessor object_api<D>::attr_with_type(const char *key) const {
annotations()[key] = make_caster<T>::name.text;
return {derived(), key};
};
// Placeholder type for the unneeded (and dead code) static variable in the
// PYBIND11_OVERRIDE_OVERRIDE macro
struct override_unused {};

View File

@ -113,6 +113,10 @@ public:
/// 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 <typename T>
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 @@ public:
/// 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<D>::doc() const {
return attr("__doc__");
}
template <typename D>
str_attr_accessor object_api<D>::annotations() const {
str_attr_accessor annotations_dict = attr("__annotations__");
// Create dict automatically
if (!isinstance<dict>(annotations_dict)){
annotations_dict = dict();
}
return annotations_dict;
}
template <typename D>
handle object_api<D>::get_type() const {
return type::handle_of(derived());

View File

@ -82,6 +82,12 @@ class Optional : public object {
using object::object;
};
template <typename T>
class Final : public object {
PYBIND11_OBJECT_DEFAULT(Final, object, PyObject_Type)
using object::object;
};
template <typename T>
class TypeGuard : public bool_ {
using bool_::bool_;
@ -205,6 +211,11 @@ struct handle_type_name<typing::Optional<T>> {
static constexpr auto name = const_name("Optional[") + make_caster<T>::name + const_name("]");
};
template <typename T>
struct handle_type_name<typing::Final<T>> {
static constexpr auto name = const_name("Final[") + make_caster<T>::name + const_name("]");
};
template <typename T>
struct handle_type_name<typing::TypeGuard<T>> {
static constexpr auto name = const_name("TypeGuard[") + make_caster<T>::name + const_name("]");

View File

@ -998,4 +998,22 @@ TEST_SUBMODULE(pytypes, m) {
#else
m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = false;
#endif
m.attr_with_type<py::typing::List<int>>("list_int") = py::list();
m.attr_with_type<py::typing::Set<py::str>>("set_str") = py::set();
struct Empty {};
py::class_<Empty>(m, "EmptyAnnotationClass");
struct Point {
float x;
py::dict dict_str_int;
};
auto point = py::class_<Point>(m, "Point");
point.attr_with_type<float>("x");
point.attr_with_type<py::typing::Dict<py::str, int>>("dict_str_int") = py::dict();
m.attr_with_type<py::typing::Final<int>>("CONST_INT") = 3;
}

View File

@ -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]'