:py:`property.id` Property ID [4]_
:py:`property.type` Property getter return type annotation [1]_
:py:`property.summary` Doc summary
-:py:`property.is_writable` If the property is writable
+:py:`property.is_gettable` If the property is gettable
+:py:`property.is_settable` If the property is settable
:py:`property.is_deletable` If the property is deletable with :py:`del`
:py:`property.has_details` If there is enough content for the full
description block. Currently always set to
out.name = path[-1]
out.id = state.config['ID_FORMATTER'](EntryType.PROPERTY, path[-1:])
# TODO: external summary for properties
- out.summary = extract_summary(state, {}, [], property.__doc__)
+ out.is_gettable = property.fget is not None
+ if property.fget or (property.fset and property.__doc__):
+ out.summary = extract_summary(state, {}, [], property.__doc__)
+ else:
+ assert property.fset
+ out.summary = extract_summary(state, {}, [], property.fset.__doc__)
out.is_settable = property.fset is not None
out.is_deletable = property.fdel is not None
out.has_details = False
+ # For the type, if the property is gettable, get it from getters's return
+ # type. For write-only properties get it from setter's second argument
+ # annotation.
+
try:
- signature = inspect.signature(property.fget)
+ if property.fget:
+ signature = inspect.signature(property.fget)
+
+ # First try to get fully dereferenced type hints (with strings
+ # converted to actual annotations). If that fails (e.g. because a
+ # type doesn't exist), we'll take the non-dereferenced annotations
+ # from inspect instead. This is deliberately done *after*
+ # inspecting the signature because pybind11 properties would throw
+ # TypeError from typing.get_type_hints(). This way they throw
+ # ValueError from inspect and we don't need to handle TypeError in
+ # get_type_hints_or_nothing().
+ type_hints = get_type_hints_or_nothing(state, path, property.fget)
- # First try to get fully dereferenced type hints (with strings
- # converted to actual annotations). If that fails (e.g. because a type
- # doesn't exist), we'll take the non-dereferenced annotations from
- # inspect instead. This is deliberately done *after* inspecting the
- # signature because pybind11 properties would throw TypeError from
- # typing.get_type_hints(). This way they throw ValueError from inspect
- # and we don't need to handle TypeError in get_type_hints_or_nothing().
- if property.fget: type_hints = get_type_hints_or_nothing(state, path, property.fget)
-
- if 'return' in type_hints:
- out.type = extract_annotation(state, path, type_hints['return'])
+ if 'return' in type_hints:
+ out.type = extract_annotation(state, path, type_hints['return'])
+ else:
+ out.type = extract_annotation(state, path, signature.return_annotation)
else:
- out.type = extract_annotation(state, path, signature.return_annotation)
+ assert property.fset
+ signature = inspect.signature(property.fset)
+
+ # Same as the lengthy comment above
+ type_hints = get_type_hints_or_nothing(state, path, property.fset)
+
+ # Get second parameter name, then try to fetch it from type_hints
+ # and if that fails get its annotation from the non-dereferenced
+ # version
+ value_parameter = list(signature.parameters.values())[1]
+ if value_parameter.name in type_hints:
+ out.type = extract_annotation(state, path, type_hints[value_parameter.name])
+ else:
+ out.type = extract_annotation(state, path, value_parameter.annotation)
+
except ValueError:
# pybind11 properties have the type in the docstring
if state.config['PYBIND11_COMPATIBILITY']:
- out.type = parse_pybind_signature(state, path, property.fget.__doc__)[3]
+ if property.fget:
+ out.type = parse_pybind_signature(state, path, property.fget.__doc__)[3]
+ else:
+ assert property.fset
+ parsed_args = parse_pybind_signature(state, path, property.fset.__doc__)[2]
+ # If argument parsing failed, we're screwed
+ if len(parsed_args) == 1: out.type = None
+ else: out.type = parsed_args[1][2]
else:
out.type = None
<dt>
- <a href="#{{ property.id }}" {% if property.has_details %}class="m-doc"{% else %}class="m-doc-self" id="{{ property.id }}"{% endif %}>{{ property.name }}</a>{% if property.type %}: {{ property.type }}{% endif %} <span class="m-label m-flat {% if property.is_settable %}m-success{% else %}m-warning{% endif %}">get{% if property.is_settable %} set{% endif %}{% if property.is_deletable %} del{% endif %}</span>
+ <a href="#{{ property.id }}" {% if property.has_details %}class="m-doc"{% else %}class="m-doc-self" id="{{ property.id }}"{% endif %}>{{ property.name }}</a>{% if property.type %}: {{ property.type }}{% endif %} <span class="m-label m-flat {% if property.is_gettable and property.is_settable %}m-success{% elif property.is_gettable %}m-warning{% else %}m-danger{% endif %}">{% if property.is_gettable and property.is_settable %}get set{% elif property.is_gettable %}get{% else %}set{% endif %}{% if property.is_deletable %} del{% endif %}</span>
</dt>
<dd>{{ property.summary }}</dd>
<a href="#writable_property" class="m-doc-self" id="writable_property">writable_property</a> <span class="m-label m-flat m-success">get set</span>
</dt>
<dd>Writable property</dd>
+ <dt>
+ <a href="#writeonly_property" class="m-doc-self" id="writeonly_property">writeonly_property</a> <span class="m-label m-flat m-danger">set</span>
+ </dt>
+ <dd>Write-only property</dd>
</dl>
</section>
<section id="data">
def deletable_property(self):
pass
+ def writeonly_property(self, a):
+ """Write-only property"""
+ pass
+
+ writeonly_property = property(None, writeonly_property)
+
@property
def _private_property(self):
"""A private property"""
<a href="#type_property_string_nested" class="m-doc-self" id="type_property_string_nested">type_property_string_nested</a>: typing.Tuple[<a href="inspect_type_links.second.Foo.html" class="m-doc">Foo</a>, typing.List[<a href="inspect_type_links.second.html#Enum" class="m-doc">Enum</a>], typing.Any] <span class="m-label m-flat m-warning">get</span>
</dt>
<dd>A property</dd>
+ <dt>
+ <a href="#type_property_writeonly" class="m-doc-self" id="type_property_writeonly">type_property_writeonly</a>: <a href="inspect_type_links.second.html#Enum" class="m-doc">Enum</a> <span class="m-label m-flat m-danger">set</span>
+ </dt>
+ <dd>A writeonly property</dd>
+ <dt>
+ <a href="#type_property_writeonly_string_invalid" class="m-doc-self" id="type_property_writeonly_string_invalid">type_property_writeonly_string_invalid</a>: Foo.Bar <span class="m-label m-flat m-danger">set</span>
+ </dt>
+ <dd>A writeonly property with invalid string type</dd>
+ <dt>
+ <a href="#type_property_writeonly_string_nested" class="m-doc-self" id="type_property_writeonly_string_nested">type_property_writeonly_string_nested</a>: typing.Tuple[<a href="inspect_type_links.second.Foo.html" class="m-doc">Foo</a>, typing.List[<a href="inspect_type_links.second.html#Enum" class="m-doc">Enum</a>], typing.Any] <span class="m-label m-flat m-danger">set</span>
+ </dt>
+ <dd>A writeonly property with a string nested type</dd>
</dl>
</section>
<section id="data">
def type_property_string_invalid(self) -> 'FooBar':
"""A property"""
+ def type_property_writeonly(self, a: Enum):
+ """A writeonly property"""
+ type_property_writeonly = property(None, type_property_writeonly)
+
+ def type_property_writeonly_string_nested(self, a: 'Tuple[Foo, List[Enum], Any]'):
+ """A writeonly property with a string nested type"""
+ type_property_writeonly_string_nested = property(None, type_property_writeonly_string_nested)
+
+ def type_property_writeonly_string_invalid(self, a: 'Foo.Bar'):
+ """A writeonly property with invalid string type"""
+ type_property_writeonly_string_invalid = property(None, type_property_writeonly_string_invalid)
+
# Has to be here, because if it would be globally, it would prevent all
# other data annotations from being retrieved
TYPE_DATA_STRING_INVALID: 'Foo.Bar' = 3
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>pybind_signatures.MyClass23 | My Python Project</title>
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+ <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+ <div class="m-container">
+ <div class="m-row">
+ <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+ </div>
+ </div>
+</nav></header>
+<main><article>
+ <div class="m-container m-container-inflatable">
+ <div class="m-row">
+ <div class="m-col-l-10 m-push-l-1">
+ <h1>
+ <span class="m-breadcrumb"><a href="pybind_signatures.html">pybind_signatures</a>.<wbr/></span>MyClass23 <span class="m-thin">class</span>
+ </h1>
+ <p>Testing pybind 2.3 features</p>
+ <div class="m-block m-default">
+ <h3>Contents</h3>
+ <ul>
+ <li>
+ Reference
+ <ul>
+ <li><a href="#properties">Properties</a></li>
+ <li><a href="#data">Data</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ <section id="properties">
+ <h2><a href="#properties">Properties</a></h2>
+ <dl class="m-doc">
+ <dt>
+ <a href="#writeonly" class="m-doc-self" id="writeonly">writeonly</a>: float <span class="m-label m-flat m-danger">set</span>
+ </dt>
+ <dd>A write-only property</dd>
+ <dt>
+ <a href="#writeonly_crazy" class="m-doc-self" id="writeonly_crazy">writeonly_crazy</a> <span class="m-label m-flat m-danger">set</span>
+ </dt>
+ <dd>A write-only property with a type that can't be parsed</dd>
+ </dl>
+ </section>
+ <section id="data">
+ <h2><a href="#data">Data</a></h2>
+ <dl class="m-doc">
+ <dt>
+ <a href="#is_pybind23" class="m-doc-self" id="is_pybind23">is_pybind23</a> = True
+ </dt>
+ <dd></dd>
+ </dl>
+ </section>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
private: float _foo = 0.0f;
};
+struct MyClass23 {
+ void setFoo(float) {}
+
+ void setFooCrazy(const Crazy<3, int>&) {}
+};
+
void duck(py::args, py::kwargs) {}
template<class T, class U> void tenOverloads(T, U) {}
.def("instance_function_kwargs", &MyClass::instanceFunction, "Instance method with position or keyword args", py::arg("hey"), py::arg("what") = "<eh?>")
.def("another", &MyClass::another, "Instance method with no args, 'self' is thus position-only")
.def_property("foo", &MyClass::foo, &MyClass::setFoo, "A read/write property");
+
+ py::class_<MyClass23> pybind23{m, "MyClass23", "Testing pybind 2.3 features"};
+
+ /* Checker so the Python side can detect if testing pybind 2.3 features is
+ feasible */
+ pybind23.attr("is_pybind23") =
+ #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 203
+ true
+ #else
+ false
+ #endif
+ ;
+
+ #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 203
+ pybind23
+ .def_property("writeonly", nullptr, &MyClass23::setFoo, "A write-only property")
+ .def_property("writeonly_crazy", nullptr, &MyClass23::setFooCrazy, "A write-only property with a type that can't be parsed");
+ #endif
}
<dl class="m-doc">
<dt>class <a href="pybind_signatures.MyClass.html" class="m-doc">MyClass</a></dt>
<dd>My fun class!</dd>
+ <dt>class <a href="pybind_signatures.MyClass23.html" class="m-doc">MyClass23</a></dt>
+ <dd>Testing pybind 2.3 features</dd>
</dl>
</section>
<section id="functions">
self.assertEqual(*self.actual_expected_contents('pybind_signatures.html'))
self.assertEqual(*self.actual_expected_contents('pybind_signatures.MyClass.html'))
+ sys.path.append(self.path)
+ import pybind_signatures
+ if pybind_signatures.MyClass23.is_pybind23:
+ self.assertEqual(*self.actual_expected_contents('pybind_signatures.MyClass23.html'))
+
class Enums(BaseInspectTestCase):
def __init__(self, *args, **kwargs):
super().__init__(__file__, 'enums', *args, **kwargs)