return '<a href="{}" class="{}">{}</a>'.format(entry.url, ' '.join(entry.css_classes), relative_name)
_pybind_name_rx = re.compile('[a-zA-Z0-9_]*')
-_pybind_arg_name_rx = re.compile('[*a-zA-Z0-9_]+')
+_pybind_arg_name_rx = re.compile('[/*a-zA-Z0-9_]+')
_pybind_type_rx = re.compile('[a-zA-Z0-9_.]+')
def _pybind11_extract_default_argument(string):
else:
out.is_staticmethod = False
- # Guesstimate whether the arguments are positional-only or
- # position-or-keyword. It's either all or none. This is a brown
- # magic, sorry.
-
- # For instance methods positional-only argument names are either
- # self (for the first argument) or arg(I-1) (for second
- # argument and further). Also, the `self` argument is
- # positional-or-keyword only if there are positional-or-keyword
- # arguments afgter it, otherwise it's positional-only.
- if inspect.isclass(parent) and not out.is_staticmethod:
- assert args and args[0][0] == 'self'
-
- positional_only = True
- for i, arg in enumerate(args[1:]):
- if arg[0] != 'arg{}'.format(i):
- positional_only = False
- break
-
- # For static methods or free functions positional-only arguments
- # are argI.
+ # If the arguments contain a literal * or / (which is only if
+ # py::pos_only{} or py::kw_only{} got explicitly used), it's
+ # following the usual logic:
+ for arg in args:
+ # If / is among the arguments, everything until the / is
+ # positional-only
+ if arg[0] == '/':
+ param_kind = 'POSITIONAL_ONLY'
+ break
+ # Otherwise, if * is among the arguments, everythign until the
+ # * is positional-or-keyword. Assuming pybind11 sanity, so
+ # not handling cases where * would be before / and such.
+ if arg[0] == '*':
+ param_kind = 'POSITIONAL_OR_KEYWORD'
+ break
+
+ # If they don't contain either, guesstimate whether the arguments
+ # are positional-only or position-or-keyword. It's either all or none.
+ # This is a brown magic, sorry.
else:
- positional_only = True
- for i, arg in enumerate(args):
- if arg[0] != 'arg{}'.format(i):
- positional_only = False
- break
+ # For instance methods positional-only argument names are
+ # either self (for the first argument) or arg(I-1) (for second
+ # argument and further). Also, the `self` argument is
+ # positional-or-keyword only if there are positional-or-keyword
+ # arguments afgter it, otherwise it's positional-only.
+ if inspect.isclass(parent) and not out.is_staticmethod:
+ assert args and args[0][0] == 'self'
+
+ param_kind = 'POSITIONAL_ONLY'
+ for i, arg in enumerate(args[1:]):
+ if arg[0] != 'arg{}'.format(i):
+ param_kind = 'POSITIONAL_OR_KEYWORD'
+ break
+
+ # For static methods or free functions positional-only
+ # arguments are argI.
+ else:
+ param_kind = 'POSITIONAL_ONLY'
+ for i, arg in enumerate(args):
+ if arg[0] != 'arg{}'.format(i):
+ param_kind = 'POSITIONAL_OR_KEYWORD'
+ break
param_names = []
param_types = []
param = Empty()
param.name = arg_name
param_names += [arg_name]
+
+ # Skip * and / placeholders, update the param_kind instead
+ if arg_name == '/':
+ assert param_kind == 'POSITIONAL_ONLY'
+ param_kind = 'POSITIONAL_OR_KEYWORD'
+ continue
+ if arg_name == '*':
+ assert param_kind == 'POSITIONAL_OR_KEYWORD'
+ param_kind = 'KEYWORD_ONLY'
+ continue
+
# Don't include redundant type for the self argument
if i == 0 and arg_name == 'self':
param.type, param.type_link = None, None
param.type, param.type_link = arg_type, arg_type_link
param_types += [arg_type]
signature += ['{}: {}'.format(arg_name, arg_type)]
+
if arg_default:
# If the type is a registered enum, try to make a link to
# the value -- for an enum of type `module.EnumType`,
param.name = 'kwargs'
param.kind = 'VAR_KEYWORD'
else:
- param.kind = 'POSITIONAL_ONLY' if positional_only else 'POSITIONAL_OR_KEYWORD'
+ param.kind = param_kind
out.params += [param]
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>pybind_signatures.MyClass26 | 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>MyClass26 <span class="m-thin">class</span>
+ </h1>
+ <p>Testing pybind 2.6 features</p>
+ <nav class="m-block m-default">
+ <h3>Contents</h3>
+ <ul>
+ <li>
+ Reference
+ <ul>
+ <li><a href="#staticmethods">Static methods</a></li>
+ <li><a href="#data">Data</a></li>
+ </ul>
+ </li>
+ </ul>
+ </nav>
+ <section id="staticmethods">
+ <h2><a href="#staticmethods">Static methods</a></h2>
+ <dl class="m-doc">
+ <dt id="keyword_only">
+ <span class="m-doc-wrap-bumper">def <a href="#keyword_only" class="m-doc-self">keyword_only</a>(</span><span class="m-doc-wrap">b: float,<span class="m-text m-dim"> *,</span>
+ keyword: str = 'no') -> int</span>
+ </dt>
+ <dd>Keyword-only arguments</dd>
+ <dt id="positional_keyword_only">
+ <span class="m-doc-wrap-bumper">def <a href="#positional_keyword_only" class="m-doc-self">positional_keyword_only</a>(</span><span class="m-doc-wrap">a: int<span class="m-text m-dim">, /</span>,
+ b: float,<span class="m-text m-dim"> *,</span>
+ keyword: str = 'no') -> int</span>
+ </dt>
+ <dd>Positional and keyword-only arguments</dd>
+ <dt id="positional_only">
+ <span class="m-doc-wrap-bumper">def <a href="#positional_only" class="m-doc-self">positional_only</a>(</span><span class="m-doc-wrap">a: int<span class="m-text m-dim">, /</span>,
+ b: float) -> int</span>
+ </dt>
+ <dd>Positional-only arguments</dd>
+ </dl>
+ </section>
+ <section id="data">
+ <h2><a href="#data">Data</a></h2>
+ <dl class="m-doc">
+ <dt id="is_pybind26">
+ <a href="#is_pybind26" class="m-doc-self">is_pybind26</a> = True
+ </dt>
+ <dd></dd>
+ </dl>
+ </section>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
void setFooCrazy(const Crazy<3, int>&) {}
};
+struct MyClass26 {
+ static int positionalOnly(int, float) { return 1; }
+ static int keywordOnly(float, const std::string&) { return 2; }
+ static int positionalKeywordOnly(int, float, const std::string&) { return 3; }
+};
+
void duck(py::args, py::kwargs) {}
template<class T, class U> void tenOverloads(T, U) {}
.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
+
+ py::class_<MyClass26> pybind26{m, "MyClass26", "Testing pybind 2.6 features"};
+
+ /* Checker so the Python side can detect if testing pybind 2.6 features is
+ feasible */
+ pybind26.attr("is_pybind26") =
+ #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206
+ true
+ #else
+ false
+ #endif
+ ;
+
+ #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206
+ pybind26
+ .def_static("positional_only", &MyClass26::positionalOnly, "Positional-only arguments", py::arg("a"), py::pos_only{}, py::arg("b"))
+ .def_static("keyword_only", &MyClass26::keywordOnly, "Keyword-only arguments", py::arg("b"), py::kw_only{}, py::arg("keyword") = "no")
+ .def_static("positional_keyword_only", &MyClass26::positionalKeywordOnly, "Positional and keyword-only arguments", py::arg("a"), py::pos_only{}, py::arg("b"), py::kw_only{}, py::arg("keyword") = "no");
+ #endif
}
<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>
+ <dt>class <a href="pybind_signatures.MyClass26.html" class="m-doc">MyClass26</a></dt>
+ <dd>Testing pybind 2.6 features</dd>
</dl>
</section>
<section id="functions">
('**kwargs', None, None, None),
], None, None))
+ def test_keyword_positional_only(self):
+ self.assertEqual(parse_pybind_signature(self.state, [],
+ 'foo(a: int, /, b: float, *, keyword: str)'),
+ ('foo', '', [
+ ('a', 'int', 'int', None),
+ ('/', None, None, None),
+ ('b', 'float', 'float', None),
+ ('*', None, None, None),
+ ('keyword', 'str', 'str', None),
+ ], None, None))
+
def test_default_values(self):
self.assertEqual(parse_pybind_signature(self.state, [],
'foo(a: float = 1.0, b: str = \'hello\')'),
with self.assertRaises(TypeError):
pybind_signatures.MyClass.another(self=a)
+ def test_explicit_positional_args(self):
+ sys.path.append(self.path)
+ import pybind_signatures
+
+ # Similar to above, but these functions have explicit py::pos_only and
+ # py::kw_only placeholders
+
+ if not pybind_signatures.MyClass26.is_pybind26:
+ self.skipTest("only on pybind 2.6+")
+
+ # The a: int argument is always before the / and thus shouldn't be
+ # callable with a keyword
+ self.assertEqual(pybind_signatures.MyClass26.positional_only(1, 3.0), 1)
+ self.assertEqual(pybind_signatures.MyClass26.positional_keyword_only(1, 3.0), 3)
+ with self.assertRaises(TypeError):
+ pybind_signatures.MyClass26.positional_only(a=1, b=3.0)
+ with self.assertRaises(TypeError):
+ pybind_signatures.MyClass26.positional_keyword_only(a=1, b=3.0)
+
+ # The b argument is always between / and * and thus should be callable
+ # both without (done above/below) and with
+ self.assertEqual(pybind_signatures.MyClass26.positional_only(1, b=3.0), 1)
+ self.assertEqual(pybind_signatures.MyClass26.keyword_only(b=3.0), 2)
+ self.assertEqual(pybind_signatures.MyClass26.positional_keyword_only(1, b=3.0), 3)
+
+ # The keyword: str argument is always after the / and thus shouldn't be
+ # callable without a keyword
+ self.assertEqual(pybind_signatures.MyClass26.keyword_only(3.0, keyword='yes'), 2)
+ self.assertEqual(pybind_signatures.MyClass26.positional_keyword_only(1, 3.0, keyword='yes'), 3)
+ with self.assertRaises(TypeError):
+ pybind_signatures.MyClass26.keyword_only(3.0, 'yes')
+ with self.assertRaises(TypeError):
+ pybind_signatures.MyClass26.positional_keyword_only(1, 3.0, 'yes')
+
def test(self):
sys.path.append(self.path)
import pybind_signatures
self.assertEqual(*self.actual_expected_contents('pybind_signatures.MyClass.html'))
self.assertEqual(*self.actual_expected_contents('false_positives.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'))
+ if pybind_signatures.MyClass26.is_pybind26:
+ self.assertEqual(*self.actual_expected_contents('pybind_signatures.MyClass26.html'))
class Enums(BaseInspectTestCase):
def test(self):