From ab35d3ef91933d810855f8e53ac2f62f3454263c Mon Sep 17 00:00:00 2001 From: Fan Jiang Date: Sun, 23 Aug 2026 15:04:48 -0700 Subject: [PATCH] Generate pybind declarations before bindings --- README.md | 32 ++ gtwrap/pybind_wrapper.py | 314 ++++++++++++------ templates/pybind_wrapper.tpl.example | 21 +- tests/expected/python/class_pybind.cpp | 86 +++-- tests/expected/python/enum_pybind.cpp | 64 ++-- tests/expected/python/functions_pybind.cpp | 16 +- tests/expected/python/geometry_pybind.cpp | 29 +- tests/expected/python/inheritance_pybind.cpp | 48 ++- tests/expected/python/namespaces_pybind.cpp | 69 +++- tests/expected/python/operator_pybind.cpp | 29 +- .../python/pybind_lambda_adapters_pybind.cpp | 26 +- .../expected/python/special_cases_pybind.cpp | 40 ++- tests/expected/python/templates_pybind.cpp | 26 +- tests/fixtures/declare_bind_late.i | 7 + tests/fixtures/declare_bind_main.i | 8 + tests/pybind_wrapper.tpl | 15 +- tests/test_pybind_wrapper.py | 95 +++++- 17 files changed, 694 insertions(+), 231 deletions(-) create mode 100644 tests/fixtures/declare_bind_late.i create mode 100644 tests/fixtures/declare_bind_main.i diff --git a/README.md b/README.md index e049a9f9..9a6b71cd 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,38 @@ pybind_wrap(${PROJECT_NAME}_py # target For more information, please follow our [tutorial](https://github.com/borglab/gtsam-project-python). +### Pybind module templates + +Pybind templates use two generated phases so every Python type is registered +before pybind11 constructs callable signatures. Custom templates must define +both phase functions and invoke the generated module initializer: + +```cpp +{submodules} + +{declaration_module_def} {{ +// Include declaration-only specializations here. +{wrapped_declarations} +}} + +{binding_module_def} {{ +// Include methods, functions, and property specializations here. +{wrapped_bindings} +}} + +{module_def} {{ +{module_init} +}} +``` + +Declaration specializations may create modules, classes, or enums but must not +bind callables. Binding specializations can recover those objects and add their +constructors, methods, functions, and properties. See +`templates/pybind_wrapper.tpl.example` for the complete template. Declaration +calls follow the interface-file list passed to `pybind_wrap`, so an interface +which declares a base class must still precede interfaces declaring its derived +classes. + ## Documentation Documentation for wrapping C++ code can be found [here](https://github.com/borglab/wrap/blob/master/DOCS.md), including the [pybind callable-adapter annotation](https://github.com/borglab/wrap/blob/master/DOCS.md#pybind-callable-adapters). diff --git a/gtwrap/pybind_wrapper.py b/gtwrap/pybind_wrapper.py index 64fe9d63..6ee569c2 100755 --- a/gtwrap/pybind_wrapper.py +++ b/gtwrap/pybind_wrapper.py @@ -507,10 +507,13 @@ def wrap_enum(self, enum, class_name='', module=None, prefix=' ' * 4): res += ";\n\n" return res - def wrap_enums(self, enums, instantiated_class, prefix=' ' * 4): + def wrap_enums(self, + enums, + instantiated_class, + module_var, + prefix=' ' * 4): """Wrap multiple enums defined in a class.""" cpp_class = instantiated_class.to_cpp() - module_var = instantiated_class.name.lower() res = '' for enum in enums: @@ -518,68 +521,94 @@ def wrap_enums(self, enums, instantiated_class, prefix=' ' * 4): enum, class_name=cpp_class, module=module_var, prefix=prefix) return res - def wrap_instantiated_class( + def _class_binding_type(self, instantiated_class): + """Return the complete ``py::class_`` type for a wrapped class.""" + cpp_class = instantiated_class.to_cpp() + if instantiated_class.parent_class: + class_parent = f"{instantiated_class.parent_class}, " + else: + class_parent = '' + + return ('py::class_<{cpp_class}, {class_parent}' + 'std::shared_ptr<{cpp_class}>>').format( + cpp_class=cpp_class, class_parent=class_parent) + + @staticmethod + def _cpp_identifier(value): + """Convert a generated name into a valid, deterministic C++ identifier.""" + identifier = re.sub(r'[^0-9A-Za-z_]', '_', value) + if identifier and identifier[0].isdigit(): + identifier = '_' + identifier + return identifier + + def _class_var(self, instantiated_class): + """Return a class-handle variable unique within a generated file.""" + module_var = self._gen_module_var(instantiated_class.namespaces()) + return self._cpp_identifier( + f"gtwrap_class_{module_var}_{instantiated_class.name}") + + def declare_instantiated_class( self, instantiated_class: instantiator.InstantiatedClass): - """Wrap the class.""" + """Register a class without binding any of its callables.""" module_var = self._gen_module_var(instantiated_class.namespaces()) cpp_class = instantiated_class.to_cpp() if cpp_class in self.ignore_classes: return "" - if instantiated_class.parent_class: - class_parent = "{instantiated_class.parent_class}, ".format( - instantiated_class=instantiated_class) - else: - class_parent = '' + class_type = self._class_binding_type(instantiated_class) + class_var = self._class_var(instantiated_class) + # Nested enums need the owning Python class object as their scope. if instantiated_class.enums: - # If class has enums, define an instance and set module_var to the instance - instance_name = instantiated_class.name.lower() - class_declaration = ( - '\n py::class_<{cpp_class}, {class_parent}' - 'std::shared_ptr<{cpp_class}>> ' - '{instance_name}({module_var}, "{class_name}");' - '\n {instance_name}').format( - cpp_class=cpp_class, - class_name=instantiated_class.name, - class_parent=class_parent, - instance_name=instance_name, - module_var=module_var) - module_var = instance_name + declaration = ( + '\n {class_type} {class_var}({module_var}, "{class_name}");' + ).format(class_type=class_type, + class_var=class_var, + module_var=module_var, + class_name=instantiated_class.name) + declaration += self.wrap_enums(instantiated_class.enums, + instantiated_class, class_var) + return declaration + + return ('\n {class_type}({module_var}, "{class_name}");\n').format( + class_type=class_type, + module_var=module_var, + class_name=instantiated_class.name) - else: - class_declaration = ( - '\n py::class_<{cpp_class}, {class_parent}' - 'std::shared_ptr<{cpp_class}>>({module_var}, "{class_name}")' - ).format(cpp_class=cpp_class, - class_name=instantiated_class.name, - class_parent=class_parent, - module_var=module_var) - - return ('{class_declaration}' - '{wrapped_ctors}' - '{wrapped_methods}' - '{wrapped_static_methods}' - '{wrapped_dunder_methods}' - '{wrapped_properties}' - '{wrapped_operators};\n'.format( - class_declaration=class_declaration, - wrapped_ctors=self.wrap_ctors(instantiated_class), - wrapped_methods=self.wrap_methods( - instantiated_class.methods, - cpp_class), - wrapped_static_methods=self.wrap_methods( - instantiated_class.static_methods, - cpp_class), - wrapped_dunder_methods=self.wrap_dunder_methods( - instantiated_class.dunder_methods, cpp_class), - wrapped_properties=self.wrap_properties( - instantiated_class.properties, cpp_class), - wrapped_operators=self.wrap_operators( - instantiated_class.operators, cpp_class))) - - def wrap_instantiated_declaration( + def bind_instantiated_class( + self, instantiated_class: instantiator.InstantiatedClass): + """Bind a class after all generated types have been registered.""" + module_var = self._gen_module_var(instantiated_class.namespaces()) + cpp_class = instantiated_class.to_cpp() + if cpp_class in self.ignore_classes: + return "" + + wrapped_members = ( + self.wrap_ctors(instantiated_class) + + self.wrap_methods(instantiated_class.methods, cpp_class) + + self.wrap_methods(instantiated_class.static_methods, cpp_class) + + self.wrap_dunder_methods(instantiated_class.dunder_methods, + cpp_class) + + self.wrap_properties(instantiated_class.properties, cpp_class) + + self.wrap_operators(instantiated_class.operators, cpp_class)) + + if not wrapped_members: + return "" + + class_type = self._class_binding_type(instantiated_class) + class_var = self._class_var(instantiated_class) + return ( + '\n auto {class_var} = py::reinterpret_borrow<{class_type}>(' + '{module_var}.attr("{class_name}"));' + '\n {class_var}{wrapped_members};\n').format( + class_var=class_var, + class_type=class_type, + module_var=module_var, + class_name=instantiated_class.name, + wrapped_members=wrapped_members) + + def declare_instantiated_declaration( self, instantiated_decl: instantiator.InstantiatedDeclaration): - """Wrap the forward declaration.""" + """Register an instantiated forward declaration.""" module_var = self._gen_module_var(instantiated_decl.namespaces()) cpp_class = instantiated_decl.to_cpp() if cpp_class in self.ignore_classes: @@ -592,34 +621,44 @@ def wrap_instantiated_declaration( module_var=module_var) return res - def wrap_stl_class(self, stl_class): - """Wrap STL containers.""" + def declare_stl_class(self, stl_class): + """Register an STL container class without binding its members.""" module_var = self._gen_module_var(stl_class.namespaces()) cpp_class = stl_class.to_cpp() if cpp_class in self.ignore_classes: return "" - return ('\n py::class_<{cpp_class}, {class_parent}' - 'std::shared_ptr<{cpp_class}>>({module_var}, "{class_name}")' - '{wrapped_ctors}' - '{wrapped_methods}' - '{wrapped_static_methods}' - '{wrapped_properties};\n'.format( - cpp_class=cpp_class, - class_name=stl_class.name, - class_parent=str(stl_class.parent_class) + - (', ' if stl_class.parent_class else ''), - module_var=module_var, - wrapped_ctors=self.wrap_ctors(stl_class), - wrapped_methods=self.wrap_methods( - stl_class.methods, - cpp_class), - wrapped_static_methods=self.wrap_methods( - stl_class.static_methods, - cpp_class), - wrapped_properties=self.wrap_properties( - stl_class.properties, cpp_class), - )) + return ('\n {class_type}({module_var}, "{class_name}");\n').format( + class_type=self._class_binding_type(stl_class), + module_var=module_var, + class_name=stl_class.name) + + def bind_stl_class(self, stl_class): + """Bind an STL container after all generated types are registered.""" + module_var = self._gen_module_var(stl_class.namespaces()) + cpp_class = stl_class.to_cpp() + if cpp_class in self.ignore_classes: + return "" + + wrapped_members = ( + self.wrap_ctors(stl_class) + + self.wrap_methods(stl_class.methods, cpp_class) + + self.wrap_methods(stl_class.static_methods, cpp_class) + + self.wrap_properties(stl_class.properties, cpp_class)) + if not wrapped_members: + return "" + + class_type = self._class_binding_type(stl_class) + class_var = self._class_var(stl_class) + return ( + '\n auto {class_var} = py::reinterpret_borrow<{class_type}>(' + '{module_var}.attr("{class_name}"));' + '\n {class_var}{wrapped_members};\n').format( + class_var=class_var, + class_type=class_type, + module_var=module_var, + class_name=stl_class.name, + wrapped_members=wrapped_members) def wrap_functions(self, functions, @@ -703,13 +742,14 @@ def _add_namespaces(self, name, namespaces): return name def wrap_namespace(self, namespace): - """Wrap the complete `namespace`.""" - wrapped = "" + """Generate declaration and binding phases for ``namespace``.""" + declarations = "" + bindings = "" includes = "" namespaces = namespace.full_namespaces() if not self._partial_match(namespaces, self.top_module_namespaces): - return "", "" + return "", "", "" if len(namespaces) < len(self.top_module_namespaces): for element in namespace.content: @@ -720,18 +760,20 @@ def wrap_namespace(self, namespace): includes += include if isinstance(element, parser.Namespace): ( - wrapped_namespace, + namespace_declarations, + namespace_bindings, includes_namespace, ) = self.wrap_namespace( # noqa element) - wrapped += wrapped_namespace + declarations += namespace_declarations + bindings += namespace_bindings includes += includes_namespace else: module_var = self._gen_module_var(namespaces) if len(namespaces) > len(self.top_module_namespaces): - wrapped += ( - ' ' * 4 + 'pybind11::module {module_var} = ' + declarations += ( + '\n' + ' ' * 4 + 'pybind11::module {module_var} = ' '{parent_module_var}.def_submodule("{namespace}", "' '{namespace} submodule");\n'.format( module_var=module_var, @@ -739,6 +781,15 @@ def wrap_namespace(self, namespace): parent_module_var=self._gen_module_var( namespaces[:-1]), )) + bindings += ( + '\n' + ' ' * 4 + 'pybind11::module {module_var} = ' + 'py::reinterpret_borrow(' + '{parent_module_var}.attr("{namespace}"));\n'.format( + module_var=module_var, + namespace=namespace.name, + parent_module_var=self._gen_module_var( + namespaces[:-1]), + )) # Wrap an include statement, namespace, class or enum for element in namespace.content: @@ -748,27 +799,30 @@ def wrap_namespace(self, namespace): include = include.replace('<', '"').replace('>', '"') includes += include elif isinstance(element, parser.Namespace): - wrapped_namespace, includes_namespace = self.wrap_namespace( - element) - wrapped += wrapped_namespace + (namespace_declarations, namespace_bindings, + includes_namespace) = self.wrap_namespace(element) + declarations += namespace_declarations + bindings += namespace_bindings includes += includes_namespace elif isinstance(element, instantiator.InstantiatedClass): - wrapped += self.wrap_instantiated_class(element) - wrapped += self.wrap_enums(element.enums, element) + declarations += self.declare_instantiated_class(element) + bindings += self.bind_instantiated_class(element) elif isinstance(element, instantiator.InstantiatedDeclaration): - wrapped += self.wrap_instantiated_declaration(element) + declarations += self.declare_instantiated_declaration( + element) elif isinstance(element, parser.Variable): variable_namespace = self._add_namespaces('', namespaces) - wrapped += self.wrap_variable(namespace=variable_namespace, - module_var=module_var, - variable=element, - prefix='\n' + ' ' * 4) + bindings += self.wrap_variable( + namespace=variable_namespace, + module_var=module_var, + variable=element, + prefix='\n' + ' ' * 4) elif isinstance(element, parser.Enum): - wrapped += self.wrap_enum(element) + declarations += self.wrap_enum(element) # Global functions. all_funcs = [ @@ -776,14 +830,14 @@ def wrap_namespace(self, namespace): if isinstance(func, (parser.GlobalFunction, instantiator.InstantiatedGlobalFunction)) ] - wrapped += self.wrap_functions( + bindings += self.wrap_functions( all_funcs, self._add_namespaces('', namespaces)[:-2], prefix='\n' + ' ' * 4 + module_var, suffix=';', ) - return wrapped, includes + return declarations, bindings, includes def wrap_file(self, content, @@ -799,12 +853,31 @@ def wrap_file(self, submodules: List of other interface file names that should be linked to. source_name: Name of the interface file for parser diagnostics. """ + required_template_fields = ( + 'module_def', + 'submodules', + 'declaration_module_def', + 'binding_module_def', + 'wrapped_declarations', + 'wrapped_bindings', + 'module_init', + ) + missing_template_fields = [ + field for field in required_template_fields + if '{' + field + '}' not in self.module_template + ] + if missing_template_fields: + raise ValueError( + 'Pybind module template is missing declare-then-bind fields: ' + + ', '.join(missing_template_fields)) + # Parse the contents of the interface file module = parser.Module.parse_string(content, source_name=source_name) # Instantiate all templates module = instantiator.instantiate_namespace(module) - wrapped_namespace, includes = self.wrap_namespace(module) + wrapped_declarations, wrapped_bindings, includes = self.wrap_namespace( + module) if self.use_boost_serialization: includes += "#include " @@ -827,29 +900,58 @@ def wrap_file(self, # Reset the serializing classes list self._serializing_classes = [] - submodules_init = [] + phase_name = self._cpp_identifier(module_name) + declaration_function = f"gtwrap_declare_{phase_name}" + binding_function = f"gtwrap_bind_{phase_name}" + declaration_module_def = ( + f"void {declaration_function}(py::module_ &m_)") + binding_module_def = f"void {binding_function}(py::module_ &m_)" if submodules is not None: module_def = "PYBIND11_MODULE({0}, m_)".format(module_name) - - for idx, submodule in enumerate(submodules): - submodules[idx] = "void {0}(py::module_ &);".format(submodule) - submodules_init.append("{0}(m_);".format(submodule)) + submodule_declarations = [] + declare_calls = [f"{declaration_function}(m_);"] + bind_calls = [f"{binding_function}(m_);"] + + for submodule in submodules: + submodule_name = self._cpp_identifier(submodule) + declare_function = f"gtwrap_declare_{submodule_name}" + bind_function = f"gtwrap_bind_{submodule_name}" + submodule_declarations.extend([ + f"void {declare_function}(py::module_ &);", + f"void {bind_function}(py::module_ &);", + ]) + declare_calls.append(f"{declare_function}(m_);") + bind_calls.append(f"{bind_function}(m_);") + + submodules = submodule_declarations + module_initialization = "\n".join(declare_calls + bind_calls) else: module_def = "void {0}(py::module_ &m_)".format(module_name) submodules = [] + module_initialization = "\n".join([ + f"{declaration_function}(m_);", + f"{binding_function}(m_);", + ]) includes += self.ARG_POLICY_SUPPORT return self.module_template.format( module_def=module_def, + declaration_module_def=declaration_module_def, + binding_module_def=binding_module_def, module_name=module_name, includes=includes, - wrapped_namespace=wrapped_namespace, + wrapped_declarations=wrapped_declarations, + wrapped_bindings=wrapped_bindings, + module_init=module_initialization, + # Alias retained for templates transitioning from the old + # single-phase placeholder. + wrapped_namespace=module_initialization, boost_class_export=boost_class_export, submodules="\n".join(submodules), - submodules_init="\n".join(submodules_init), + submodules_init="", ) def wrap_submodule(self, source): diff --git a/templates/pybind_wrapper.tpl.example b/templates/pybind_wrapper.tpl.example index 3e251f4a..be078897 100644 --- a/templates/pybind_wrapper.tpl.example +++ b/templates/pybind_wrapper.tpl.example @@ -19,14 +19,23 @@ namespace py = pybind11; {submodules} -{module_def} {{ - m_.doc() = "pybind11 wrapper of {module_name}"; +{declaration_module_def} {{ +// Declaration specializations must only register Python types. Projects can +// replace this include with their own per-generated-file declaration hook. +#include "python/declarations/{module_name}.h" -{submodules_init} - -{wrapped_namespace} +{wrapped_declarations} +}} -#include "python/specializations.h" +{binding_module_def} {{ +// Callable and property specializations belong in the binding phase. +#include "python/specializations/{module_name}.h" +{wrapped_bindings} }} +{module_def} {{ + m_.doc() = "pybind11 wrapper of {module_name}"; + +{module_init} +}} diff --git a/tests/expected/python/class_pybind.cpp b/tests/expected/python/class_pybind.cpp index 95092ba5..6fcfdc42 100644 --- a/tests/expected/python/class_pybind.cpp +++ b/tests/expected/python/class_pybind.cpp @@ -31,23 +31,60 @@ using namespace std; namespace py = pybind11; -PYBIND11_MODULE(class_py, m_) { - m_.doc() = "pybind11 wrapper of class_py"; - py::class_>(m_, "FunRange") +void gtwrap_declare_class_py(py::module_ &m_) { + + py::class_>(m_, "FunRange"); + + py::class_, std::shared_ptr>>(m_, "FunDouble"); + + py::class_>(m_, "Test"); + + py::class_, std::shared_ptr>>(m_, "PrimitiveRefDouble"); + + py::class_, std::shared_ptr>>(m_, "MyVector3"); + + py::class_, std::shared_ptr>>(m_, "MyVector12"); + + py::class_, std::shared_ptr>>(m_, "MultipleTemplatesIntDouble"); + + py::class_, std::shared_ptr>>(m_, "MultipleTemplatesIntFloat"); + + py::class_>(m_, "ForwardKinematics"); + + py::class_>(m_, "TemplatedConstructor"); + + py::class_>(m_, "FastSet"); + + py::class_>(m_, "HessianFactor"); + + py::class_>, gtsam::SmartProjectionFactor>, std::shared_ptr>>>(m_, "SmartProjectionRigFactorPinholeCameraCal3_S2"); + + py::class_, std::shared_ptr>>(m_, "MyFactorPosePoint2"); + + py::class_, std::shared_ptr>>(m_, "SuperCoolFactorPose3"); +} + +void gtwrap_bind_class_py(py::module_ &m_) { +#include "python/specializations.h" + + auto gtwrap_class_m__FunRange = py::reinterpret_borrow>>(m_.attr("FunRange")); + gtwrap_class_m__FunRange .def(py::init<>()) .def("range",static_cast(&FunRange::range), gtwrap::internal::py_arg("d")) .def_static("create",static_cast(&FunRange::create)); - py::class_, std::shared_ptr>>(m_, "FunDouble") + auto gtwrap_class_m__FunDouble = py::reinterpret_borrow, std::shared_ptr>>>(m_.attr("FunDouble")); + gtwrap_class_m__FunDouble .def("templatedMethodString",[](Fun* self, double d, string t){return self->templatedMethod(d, t);}, gtwrap::internal::py_arg("d"), gtwrap::internal::py_arg("t")) .def("multiTemplatedMethodStringSize_t",[](Fun* self, double d, string t, size_t u){return self->multiTemplatedMethod(d, t, u);}, gtwrap::internal::py_arg("d"), gtwrap::internal::py_arg("t"), gtwrap::internal::py_arg("u")) .def("sets",static_cast::double> (Fun::*)()>(&Fun::sets)) .def_static("staticMethodWithThis",static_cast (*)()>(&Fun::staticMethodWithThis)) .def_static("templatedStaticMethodInt",[](const int& m){return Fun::templatedStaticMethod(m);}, gtwrap::internal::py_arg("m")); - py::class_>(m_, "Test") + auto gtwrap_class_m__Test = py::reinterpret_borrow>>(m_.attr("Test")); + gtwrap_class_m__Test .def(py::init<>()) .def(py::init(), gtwrap::internal::py_arg("a"), gtwrap::internal::py_arg("b")) .def("return_pair",static_cast (Test::*)(const gtsam::Vector&, const gtsam::Matrix&) const>(&Test::return_pair), gtwrap::internal::py_arg("v"), gtwrap::internal::py_arg("A")) @@ -89,42 +126,47 @@ PYBIND11_MODULE(class_py, m_) { .def_readwrite("value", &Test::value) .def_readwrite("name", &Test::name); - py::class_, std::shared_ptr>>(m_, "PrimitiveRefDouble") + auto gtwrap_class_m__PrimitiveRefDouble = py::reinterpret_borrow, std::shared_ptr>>>(m_.attr("PrimitiveRefDouble")); + gtwrap_class_m__PrimitiveRefDouble .def(py::init<>()) .def_static("Brutal",static_cast (*)(const double&)>(&PrimitiveRef::Brutal), gtwrap::internal::py_arg("t")); - py::class_, std::shared_ptr>>(m_, "MyVector3") + auto gtwrap_class_m__MyVector3 = py::reinterpret_borrow, std::shared_ptr>>>(m_.attr("MyVector3")); + gtwrap_class_m__MyVector3 .def(py::init<>()); - py::class_, std::shared_ptr>>(m_, "MyVector12") + auto gtwrap_class_m__MyVector12 = py::reinterpret_borrow, std::shared_ptr>>>(m_.attr("MyVector12")); + gtwrap_class_m__MyVector12 .def(py::init<>()); - py::class_, std::shared_ptr>>(m_, "MultipleTemplatesIntDouble"); - - py::class_, std::shared_ptr>>(m_, "MultipleTemplatesIntFloat"); - - py::class_>(m_, "ForwardKinematics") + auto gtwrap_class_m__ForwardKinematics = py::reinterpret_borrow>>(m_.attr("ForwardKinematics")); + gtwrap_class_m__ForwardKinematics .def(py::init(), gtwrap::internal::py_arg("robot"), gtwrap::internal::py_arg("start_link_name"), gtwrap::internal::py_arg("end_link_name"), gtwrap::internal::py_arg("joint_angles"), gtwrap::internal::py_arg("l2Tp") = gtsam::Pose3()); - py::class_>(m_, "TemplatedConstructor") + auto gtwrap_class_m__TemplatedConstructor = py::reinterpret_borrow>>(m_.attr("TemplatedConstructor")); + gtwrap_class_m__TemplatedConstructor .def(py::init<>()) .def(py::init(), gtwrap::internal::py_arg("arg")) .def(py::init(), gtwrap::internal::py_arg("arg")) .def(py::init(), gtwrap::internal::py_arg("arg")); - py::class_>(m_, "FastSet") + auto gtwrap_class_m__FastSet = py::reinterpret_borrow>>(m_.attr("FastSet")); + gtwrap_class_m__FastSet .def(py::init<>()) .def("__len__",[](FastSet* self){return std::distance(self->begin(), self->end());}) .def("__contains__",[](FastSet* self, size_t key){return std::find(self->begin(), self->end(), key) != self->end();}, gtwrap::internal::py_arg("key")) .def("__iter__",[](FastSet* self){return py::make_iterator(self->begin(), self->end());}); - py::class_>(m_, "HessianFactor") + auto gtwrap_class_m__HessianFactor = py::reinterpret_borrow>>(m_.attr("HessianFactor")); + gtwrap_class_m__HessianFactor .def(py::init&, const std::vector&, double>(), gtwrap::internal::py_arg("js"), gtwrap::internal::py_arg&>("Gs"), gtwrap::internal::py_arg&>("gs"), gtwrap::internal::py_arg("f")); - py::class_>, gtsam::SmartProjectionFactor>, std::shared_ptr>>>(m_, "SmartProjectionRigFactorPinholeCameraCal3_S2") + auto gtwrap_class_m__SmartProjectionRigFactorPinholeCameraCal3_S2 = py::reinterpret_borrow>, gtsam::SmartProjectionFactor>, std::shared_ptr>>>>(m_.attr("SmartProjectionRigFactorPinholeCameraCal3_S2")); + gtwrap_class_m__SmartProjectionRigFactorPinholeCameraCal3_S2 .def("add",static_cast>::*)(const gtsam::PinholeCamera::Measurement&, const gtsam::Key&, const size_t&)>(&SmartProjectionRigFactor>::add), gtwrap::internal::py_arg::Measurement&>("measured"), gtwrap::internal::py_arg("poseKey"), gtwrap::internal::py_arg("cameraId") = 0); - py::class_, std::shared_ptr>>(m_, "MyFactorPosePoint2") + auto gtwrap_class_m__MyFactorPosePoint2 = py::reinterpret_borrow, std::shared_ptr>>>(m_.attr("MyFactorPosePoint2")); + gtwrap_class_m__MyFactorPosePoint2 .def(py::init>(), gtwrap::internal::py_arg("key1"), gtwrap::internal::py_arg("key2"), gtwrap::internal::py_arg("measured"), gtwrap::internal::py_arg>("noiseModel")) .def("print",[](MyFactor* self, const string& s, const gtsam::KeyFormatter& keyFormatter){ py::scoped_ostream_redirect output; self->print(s, keyFormatter);}, gtwrap::internal::py_arg("s") = "factor: ", gtwrap::internal::py_arg("keyFormatter") = gtsam::DefaultKeyFormatter) .def("__repr__", @@ -134,9 +176,11 @@ PYBIND11_MODULE(class_py, m_) { return redirect.str(); }, gtwrap::internal::py_arg("s") = "factor: ", gtwrap::internal::py_arg("keyFormatter") = gtsam::DefaultKeyFormatter); - py::class_, std::shared_ptr>>(m_, "SuperCoolFactorPose3"); +} -#include "python/specializations.h" +PYBIND11_MODULE(class_py, m_) { + m_.doc() = "pybind11 wrapper of class_py"; +gtwrap_declare_class_py(m_); +gtwrap_bind_class_py(m_); } - diff --git a/tests/expected/python/enum_pybind.cpp b/tests/expected/python/enum_pybind.cpp index 985d1a02..e4e1c855 100644 --- a/tests/expected/python/enum_pybind.cpp +++ b/tests/expected/python/enum_pybind.cpp @@ -30,27 +30,21 @@ using namespace std; namespace py = pybind11; -PYBIND11_MODULE(enum_py, m_) { - m_.doc() = "pybind11 wrapper of enum_py"; + +void gtwrap_declare_enum_py(py::module_ &m_) { py::enum_(m_, "Color", py::arithmetic()) .value("Red", Color::Red) .value("Green", Color::Green) .value("Blue", Color::Blue); - py::class_> pet(m_, "Pet"); - pet - .def(py::init(), gtwrap::internal::py_arg("name"), gtwrap::internal::py_arg("type")) - .def("setColor",static_cast(&Pet::setColor), gtwrap::internal::py_arg("color")) - .def("getColor",static_cast(&Pet::getColor)) - .def_readwrite("name", &Pet::name) - .def_readwrite("type", &Pet::type); - - py::enum_(pet, "Kind", py::arithmetic()) + py::class_> gtwrap_class_m__Pet(m_, "Pet"); + py::enum_(gtwrap_class_m__Pet, "Kind", py::arithmetic()) .value("Dog", Pet::Kind::Dog) .value("Cat", Pet::Kind::Cat); + pybind11::module m_gtsam = m_.def_submodule("gtsam", "gtsam submodule"); py::enum_(m_gtsam, "VerbosityLM", py::arithmetic()) .value("SILENT", gtsam::VerbosityLM::SILENT) @@ -63,11 +57,8 @@ PYBIND11_MODULE(enum_py, m_) { .value("TRYDELTA", gtsam::VerbosityLM::TRYDELTA); - py::class_> mcu(m_gtsam, "MCU"); - mcu - .def(py::init<>()); - - py::enum_(mcu, "Avengers", py::arithmetic()) + py::class_> gtwrap_class_m_gtsam_MCU(m_gtsam, "MCU"); + py::enum_(gtwrap_class_m_gtsam_MCU, "Avengers", py::arithmetic()) .value("CaptainAmerica", gtsam::MCU::Avengers::CaptainAmerica) .value("IronMan", gtsam::MCU::Avengers::IronMan) .value("Hulk", gtsam::MCU::Avengers::Hulk) @@ -75,7 +66,7 @@ PYBIND11_MODULE(enum_py, m_) { .value("Thor", gtsam::MCU::Avengers::Thor); - py::enum_(mcu, "GotG", py::arithmetic()) + py::enum_(gtwrap_class_m_gtsam_MCU, "GotG", py::arithmetic()) .value("Starlord", gtsam::MCU::GotG::Starlord) .value("Gamorra", gtsam::MCU::GotG::Gamorra) .value("Rocket", gtsam::MCU::GotG::Rocket) @@ -83,21 +74,44 @@ PYBIND11_MODULE(enum_py, m_) { .value("Groot", gtsam::MCU::GotG::Groot); - py::class_, std::shared_ptr>> optimizergaussnewtonparams(m_gtsam, "OptimizerGaussNewtonParams"); - optimizergaussnewtonparams - .def(py::init::Verbosity&>(), gtwrap::internal::py_arg::Verbosity&>("verbosity")) - .def("setVerbosity",static_cast::*)(const Optimizer::Verbosity)>(>sam::Optimizer::setVerbosity), gtwrap::internal::py_arg::Verbosity>("value")) - .def("getVerbosity",static_cast::*)() const>(>sam::Optimizer::getVerbosity)) - .def("getVerbosity",static_cast::*)() const>(>sam::Optimizer::getVerbosity)); - - py::enum_::Verbosity>(optimizergaussnewtonparams, "Verbosity", py::arithmetic()) + py::class_, std::shared_ptr>> gtwrap_class_m_gtsam_OptimizerGaussNewtonParams(m_gtsam, "OptimizerGaussNewtonParams"); + py::enum_::Verbosity>(gtwrap_class_m_gtsam_OptimizerGaussNewtonParams, "Verbosity", py::arithmetic()) .value("SILENT", gtsam::Optimizer::Verbosity::SILENT) .value("SUMMARY", gtsam::Optimizer::Verbosity::SUMMARY) .value("VERBOSE", gtsam::Optimizer::Verbosity::VERBOSE); +} +void gtwrap_bind_enum_py(py::module_ &m_) { #include "python/specializations.h" + auto gtwrap_class_m__Pet = py::reinterpret_borrow>>(m_.attr("Pet")); + gtwrap_class_m__Pet + .def(py::init(), gtwrap::internal::py_arg("name"), gtwrap::internal::py_arg("type")) + .def("setColor",static_cast(&Pet::setColor), gtwrap::internal::py_arg("color")) + .def("getColor",static_cast(&Pet::getColor)) + .def_readwrite("name", &Pet::name) + .def_readwrite("type", &Pet::type); + + pybind11::module m_gtsam = py::reinterpret_borrow(m_.attr("gtsam")); + + auto gtwrap_class_m_gtsam_MCU = py::reinterpret_borrow>>(m_gtsam.attr("MCU")); + gtwrap_class_m_gtsam_MCU + .def(py::init<>()); + + auto gtwrap_class_m_gtsam_OptimizerGaussNewtonParams = py::reinterpret_borrow, std::shared_ptr>>>(m_gtsam.attr("OptimizerGaussNewtonParams")); + gtwrap_class_m_gtsam_OptimizerGaussNewtonParams + .def(py::init::Verbosity&>(), gtwrap::internal::py_arg::Verbosity&>("verbosity")) + .def("setVerbosity",static_cast::*)(const Optimizer::Verbosity)>(>sam::Optimizer::setVerbosity), gtwrap::internal::py_arg::Verbosity>("value")) + .def("getVerbosity",static_cast::*)() const>(>sam::Optimizer::getVerbosity)) + .def("getVerbosity",static_cast::*)() const>(>sam::Optimizer::getVerbosity)); + } +PYBIND11_MODULE(enum_py, m_) { + m_.doc() = "pybind11 wrapper of enum_py"; + +gtwrap_declare_enum_py(m_); +gtwrap_bind_enum_py(m_); +} diff --git a/tests/expected/python/functions_pybind.cpp b/tests/expected/python/functions_pybind.cpp index 87d53113..34533c3f 100644 --- a/tests/expected/python/functions_pybind.cpp +++ b/tests/expected/python/functions_pybind.cpp @@ -30,10 +30,15 @@ using namespace std; namespace py = pybind11; -PYBIND11_MODULE(functions_py, m_) { - m_.doc() = "pybind11 wrapper of functions_py"; +void gtwrap_declare_functions_py(py::module_ &m_) { + +} + +void gtwrap_bind_functions_py(py::module_ &m_) { +#include "python/specializations.h" + m_.def("load2D",static_cast,std::shared_ptr> (*)(string, std::shared_ptr, int, bool, bool)>(&::load2D), gtwrap::internal::py_arg("filename"), gtwrap::internal::py_arg>("model"), gtwrap::internal::py_arg("maxID"), gtwrap::internal::py_arg("addNoise"), gtwrap::internal::py_arg("smart")); m_.def("load2D",static_cast,std::shared_ptr> (*)(string, const std::shared_ptr, int, bool, bool)>(&::load2D), gtwrap::internal::py_arg("filename"), gtwrap::internal::py_arg>("model"), gtwrap::internal::py_arg("maxID"), gtwrap::internal::py_arg("addNoise"), gtwrap::internal::py_arg("smart")); m_.def("load2D",static_cast,std::shared_ptr> (*)(string, gtsam::noiseModel::Diagonal*)>(&::load2D), gtwrap::internal::py_arg("filename"), gtwrap::internal::py_arg("model")); @@ -55,8 +60,11 @@ PYBIND11_MODULE(functions_py, m_) { m_.def("FindKarcherMeanSO4",[](const std::vector& elements){return ::FindKarcherMean(elements);}, gtwrap::internal::py_arg&>("elements")); m_.def("FindKarcherMeanPose3",[](const std::vector& elements){return ::FindKarcherMean(elements);}, gtwrap::internal::py_arg&>("elements")); m_.def("TemplatedFunctionRot3",[](const gtsam::Rot3& t){ ::TemplatedFunction(t);}, gtwrap::internal::py_arg("t")); +} -#include "python/specializations.h" +PYBIND11_MODULE(functions_py, m_) { + m_.doc() = "pybind11 wrapper of functions_py"; +gtwrap_declare_functions_py(m_); +gtwrap_bind_functions_py(m_); } - diff --git a/tests/expected/python/geometry_pybind.cpp b/tests/expected/python/geometry_pybind.cpp index a3a3deaf..3c8b1faf 100644 --- a/tests/expected/python/geometry_pybind.cpp +++ b/tests/expected/python/geometry_pybind.cpp @@ -34,12 +34,25 @@ using namespace std; namespace py = pybind11; -PYBIND11_MODULE(geometry_py, m_) { - m_.doc() = "pybind11 wrapper of geometry_py"; + + +void gtwrap_declare_geometry_py(py::module_ &m_) { pybind11::module m_gtsam = m_.def_submodule("gtsam", "gtsam submodule"); - py::class_>(m_gtsam, "Point2") + py::class_>(m_gtsam, "Point2"); + + py::class_>(m_gtsam, "Point3"); + +} + +void gtwrap_bind_geometry_py(py::module_ &m_) { +#include "python/specializations.h" + + pybind11::module m_gtsam = py::reinterpret_borrow(m_.attr("gtsam")); + + auto gtwrap_class_m_gtsam_Point2 = py::reinterpret_borrow>>(m_gtsam.attr("Point2")); + gtwrap_class_m_gtsam_Point2 .def(py::init<>()) .def(py::init(), gtwrap::internal::py_arg("x"), gtwrap::internal::py_arg("y")) .def("x",static_cast(>sam::Point2::x)) @@ -62,7 +75,8 @@ PYBIND11_MODULE(geometry_py, m_) { [](const gtsam::Point2 &a){ /* __getstate__: Returns a string that encodes the state of the object */ return py::make_tuple(gtsam::serialize(a)); }, [](py::tuple t){ /* __setstate__ */ gtsam::Point2 obj; gtsam::deserialize(t[0].cast(), obj); return obj; })); - py::class_>(m_gtsam, "Point3") + auto gtwrap_class_m_gtsam_Point3 = py::reinterpret_borrow>>(m_gtsam.attr("Point3")); + gtwrap_class_m_gtsam_Point3 .def(py::init(), gtwrap::internal::py_arg("x"), gtwrap::internal::py_arg("y"), gtwrap::internal::py_arg("z")) .def("norm",static_cast(>sam::Point3::norm)) .def("serialize", [](gtsam::Point3* self){ return gtsam::serialize(*self); }) @@ -73,8 +87,11 @@ PYBIND11_MODULE(geometry_py, m_) { .def_static("staticFunction",static_cast(>sam::Point3::staticFunction)) .def_static("StaticFunctionRet",static_cast(>sam::Point3::StaticFunctionRet), gtwrap::internal::py_arg("z")); +} -#include "python/specializations.h" +PYBIND11_MODULE(geometry_py, m_) { + m_.doc() = "pybind11 wrapper of geometry_py"; +gtwrap_declare_geometry_py(m_); +gtwrap_bind_geometry_py(m_); } - diff --git a/tests/expected/python/inheritance_pybind.cpp b/tests/expected/python/inheritance_pybind.cpp index 0b522efa..d05dd636 100644 --- a/tests/expected/python/inheritance_pybind.cpp +++ b/tests/expected/python/inheritance_pybind.cpp @@ -30,13 +30,33 @@ using namespace std; namespace py = pybind11; -PYBIND11_MODULE(inheritance_py, m_) { - m_.doc() = "pybind11 wrapper of inheritance_py"; +void gtwrap_declare_inheritance_py(py::module_ &m_) { + py::class_>(m_, "MyBase"); - py::class_, MyBase, std::shared_ptr>>(m_, "MyTemplatePoint2") + py::class_, MyBase, std::shared_ptr>>(m_, "MyTemplatePoint2"); + + py::class_, MyBase, std::shared_ptr>>(m_, "MyTemplateMatrix"); + + py::class_, MyBase, std::shared_ptr>>(m_, "MyTemplateA"); + + py::class_, std::shared_ptr>(m_, "ForwardKinematicsFactor"); + + py::class_, MyTemplate, std::shared_ptr>>(m_, "ParentHasTemplateDouble"); + + py::class_>(m_, "Base"); + + py::class_>(m_, "Derived"); + +} + +void gtwrap_bind_inheritance_py(py::module_ &m_) { +#include "python/specializations.h" + + auto gtwrap_class_m__MyTemplatePoint2 = py::reinterpret_borrow, MyBase, std::shared_ptr>>>(m_.attr("MyTemplatePoint2")); + gtwrap_class_m__MyTemplatePoint2 .def(py::init<>()) .def("templatedMethodPoint2",[](MyTemplate* self, const gtsam::Point2& t){return self->templatedMethod(t);}, gtwrap::internal::py_arg("t")) .def("templatedMethodPoint3",[](MyTemplate* self, const gtsam::Point3& t){return self->templatedMethod(t);}, gtwrap::internal::py_arg("t")) @@ -51,7 +71,8 @@ PYBIND11_MODULE(inheritance_py, m_) { .def("return_ptrs",static_cast,std::shared_ptr> (MyTemplate::*)(std::shared_ptr, std::shared_ptr) const>(&MyTemplate::return_ptrs), gtwrap::internal::py_arg>("p1"), gtwrap::internal::py_arg>("p2")) .def_static("Level",static_cast (*)(const gtsam::Point2&)>(&MyTemplate::Level), gtwrap::internal::py_arg("K")); - py::class_, MyBase, std::shared_ptr>>(m_, "MyTemplateMatrix") + auto gtwrap_class_m__MyTemplateMatrix = py::reinterpret_borrow, MyBase, std::shared_ptr>>>(m_.attr("MyTemplateMatrix")); + gtwrap_class_m__MyTemplateMatrix .def(py::init<>()) .def("templatedMethodPoint2",[](MyTemplate* self, const gtsam::Point2& t){return self->templatedMethod(t);}, gtwrap::internal::py_arg("t")) .def("templatedMethodPoint3",[](MyTemplate* self, const gtsam::Point3& t){return self->templatedMethod(t);}, gtwrap::internal::py_arg("t")) @@ -66,7 +87,8 @@ PYBIND11_MODULE(inheritance_py, m_) { .def("return_ptrs",static_cast,std::shared_ptr> (MyTemplate::*)(std::shared_ptr, std::shared_ptr) const>(&MyTemplate::return_ptrs), gtwrap::internal::py_arg>("p1"), gtwrap::internal::py_arg>("p2")) .def_static("Level",static_cast (*)(const gtsam::Matrix&)>(&MyTemplate::Level), gtwrap::internal::py_arg("K")); - py::class_, MyBase, std::shared_ptr>>(m_, "MyTemplateA") + auto gtwrap_class_m__MyTemplateA = py::reinterpret_borrow, MyBase, std::shared_ptr>>>(m_.attr("MyTemplateA")); + gtwrap_class_m__MyTemplateA .def(py::init<>()) .def("templatedMethodPoint2",[](MyTemplate* self, const gtsam::Point2& t){return self->templatedMethod(t);}, gtwrap::internal::py_arg("t")) .def("templatedMethodPoint3",[](MyTemplate* self, const gtsam::Point3& t){return self->templatedMethod(t);}, gtwrap::internal::py_arg("t")) @@ -81,17 +103,15 @@ PYBIND11_MODULE(inheritance_py, m_) { .def("return_ptrs",static_cast,std::shared_ptr> (MyTemplate::*)(std::shared_ptr, std::shared_ptr) const>(&MyTemplate::return_ptrs), gtwrap::internal::py_arg>("p1"), gtwrap::internal::py_arg>("p2")) .def_static("Level",static_cast (*)(const A&)>(&MyTemplate::Level), gtwrap::internal::py_arg("K")); - py::class_, std::shared_ptr>(m_, "ForwardKinematicsFactor"); - - py::class_, MyTemplate, std::shared_ptr>>(m_, "ParentHasTemplateDouble"); - - py::class_>(m_, "Base") + auto gtwrap_class_m__Base = py::reinterpret_borrow>>(m_.attr("Base")); + gtwrap_class_m__Base .def_static("Create",static_cast (*)(double)>(&Base::Create), gtwrap::internal::py_arg("x")); - py::class_>(m_, "Derived"); - +} -#include "python/specializations.h" +PYBIND11_MODULE(inheritance_py, m_) { + m_.doc() = "pybind11 wrapper of inheritance_py"; +gtwrap_declare_inheritance_py(m_); +gtwrap_bind_inheritance_py(m_); } - diff --git a/tests/expected/python/namespaces_pybind.cpp b/tests/expected/python/namespaces_pybind.cpp index dd9a00e8..763254a3 100644 --- a/tests/expected/python/namespaces_pybind.cpp +++ b/tests/expected/python/namespaces_pybind.cpp @@ -36,43 +36,81 @@ using namespace std; namespace py = pybind11; -PYBIND11_MODULE(namespaces_py, m_) { - m_.doc() = "pybind11 wrapper of namespaces_py"; + + +void gtwrap_declare_namespaces_py(py::module_ &m_) { pybind11::module m_ns1 = m_.def_submodule("ns1", "ns1 submodule"); - py::class_>(m_ns1, "ClassA") + py::class_>(m_ns1, "ClassA"); + + py::class_>(m_ns1, "ClassB"); + + pybind11::module m_ns2 = m_.def_submodule("ns2", "ns2 submodule"); + + py::class_>(m_ns2, "ClassA"); + + pybind11::module m_ns2_ns3 = m_ns2.def_submodule("ns3", "ns3 submodule"); + + py::class_>(m_ns2_ns3, "ClassB"); + + py::class_>(m_ns2, "ClassC"); + + py::class_>(m_, "ClassD"); + + pybind11::module m_gtsam = m_.def_submodule("gtsam", "gtsam submodule"); + + py::class_>(m_gtsam, "Values"); + +} + +void gtwrap_bind_namespaces_py(py::module_ &m_) { +#include "python/specializations.h" + + pybind11::module m_ns1 = py::reinterpret_borrow(m_.attr("ns1")); + + auto gtwrap_class_m_ns1_ClassA = py::reinterpret_borrow>>(m_ns1.attr("ClassA")); + gtwrap_class_m_ns1_ClassA .def(py::init<>()); - py::class_>(m_ns1, "ClassB") + auto gtwrap_class_m_ns1_ClassB = py::reinterpret_borrow>>(m_ns1.attr("ClassB")); + gtwrap_class_m_ns1_ClassB .def(py::init<>()); - m_ns1.def("aGlobalFunction",static_cast(&ns1::aGlobalFunction)); pybind11::module m_ns2 = m_.def_submodule("ns2", "ns2 submodule"); + m_ns1.def("aGlobalFunction",static_cast(&ns1::aGlobalFunction)); + pybind11::module m_ns2 = py::reinterpret_borrow(m_.attr("ns2")); - py::class_>(m_ns2, "ClassA") + auto gtwrap_class_m_ns2_ClassA = py::reinterpret_borrow>>(m_ns2.attr("ClassA")); + gtwrap_class_m_ns2_ClassA .def(py::init<>()) .def("memberFunction",static_cast(&ns2::ClassA::memberFunction)) .def("nsArg",static_cast(&ns2::ClassA::nsArg), gtwrap::internal::py_arg("arg")) .def("nsReturn",static_cast(&ns2::ClassA::nsReturn), gtwrap::internal::py_arg("q")) .def_static("afunction",static_cast(&ns2::ClassA::afunction)); - pybind11::module m_ns2_ns3 = m_ns2.def_submodule("ns3", "ns3 submodule"); - py::class_>(m_ns2_ns3, "ClassB") + pybind11::module m_ns2_ns3 = py::reinterpret_borrow(m_ns2.attr("ns3")); + + auto gtwrap_class_m_ns2_ns3_ClassB = py::reinterpret_borrow>>(m_ns2_ns3.attr("ClassB")); + gtwrap_class_m_ns2_ns3_ClassB .def(py::init<>()); - py::class_>(m_ns2, "ClassC") + auto gtwrap_class_m_ns2_ClassC = py::reinterpret_borrow>>(m_ns2.attr("ClassC")); + gtwrap_class_m_ns2_ClassC .def(py::init<>()); m_ns2.attr("aNs2Var") = ns2::aNs2Var; m_ns2.def("aGlobalFunction",static_cast(&ns2::aGlobalFunction)); m_ns2.def("overloadedGlobalFunction",static_cast(&ns2::overloadedGlobalFunction), gtwrap::internal::py_arg("a")); m_ns2.def("overloadedGlobalFunction",static_cast(&ns2::overloadedGlobalFunction), gtwrap::internal::py_arg("a"), gtwrap::internal::py_arg("b")); - py::class_>(m_, "ClassD") + auto gtwrap_class_m__ClassD = py::reinterpret_borrow>>(m_.attr("ClassD")); + gtwrap_class_m__ClassD .def(py::init<>()); - m_.attr("aGlobalVar") = aGlobalVar; pybind11::module m_gtsam = m_.def_submodule("gtsam", "gtsam submodule"); + m_.attr("aGlobalVar") = aGlobalVar; + pybind11::module m_gtsam = py::reinterpret_borrow(m_.attr("gtsam")); - py::class_>(m_gtsam, "Values") + auto gtwrap_class_m_gtsam_Values = py::reinterpret_borrow>>(m_gtsam.attr("Values")); + gtwrap_class_m_gtsam_Values .def(py::init<>()) .def(py::init(), gtwrap::internal::py_arg("other")) .def("insert_vector",[](gtsam::Values* self, size_t j, const gtsam::Vector& vector){ self->insert(j, vector);}, gtwrap::internal::py_arg("j"), gtwrap::internal::py_arg("vector")) @@ -80,8 +118,11 @@ PYBIND11_MODULE(namespaces_py, m_) { .def("insert_matrix",[](gtsam::Values* self, size_t j, const gtsam::Matrix& matrix){ self->insert(j, matrix);}, gtwrap::internal::py_arg("j"), gtwrap::internal::py_arg("matrix")) .def("insert",static_cast(>sam::Values::insert), gtwrap::internal::py_arg("j"), gtwrap::internal::py_arg("matrix")); +} -#include "python/specializations.h" +PYBIND11_MODULE(namespaces_py, m_) { + m_.doc() = "pybind11 wrapper of namespaces_py"; +gtwrap_declare_namespaces_py(m_); +gtwrap_bind_namespaces_py(m_); } - diff --git a/tests/expected/python/operator_pybind.cpp b/tests/expected/python/operator_pybind.cpp index 672820b1..29ba2eed 100644 --- a/tests/expected/python/operator_pybind.cpp +++ b/tests/expected/python/operator_pybind.cpp @@ -31,22 +31,39 @@ using namespace std; namespace py = pybind11; -PYBIND11_MODULE(operator_py, m_) { - m_.doc() = "pybind11 wrapper of operator_py"; + + +void gtwrap_declare_operator_py(py::module_ &m_) { pybind11::module m_gtsam = m_.def_submodule("gtsam", "gtsam submodule"); - py::class_>(m_gtsam, "Pose3") + py::class_>(m_gtsam, "Pose3"); + + py::class_, std::shared_ptr>>(m_gtsam, "ContainerMatrix"); + +} + +void gtwrap_bind_operator_py(py::module_ &m_) { +#include "python/specializations.h" + + pybind11::module m_gtsam = py::reinterpret_borrow(m_.attr("gtsam")); + + auto gtwrap_class_m_gtsam_Pose3 = py::reinterpret_borrow>>(m_gtsam.attr("Pose3")); + gtwrap_class_m_gtsam_Pose3 .def(py::init<>()) .def(py::init(), gtwrap::internal::py_arg("R"), gtwrap::internal::py_arg("t")) .def(py::self * py::self); - py::class_, std::shared_ptr>>(m_gtsam, "ContainerMatrix") + auto gtwrap_class_m_gtsam_ContainerMatrix = py::reinterpret_borrow, std::shared_ptr>>>(m_gtsam.attr("ContainerMatrix")); + gtwrap_class_m_gtsam_ContainerMatrix .def("__call__", >sam::Container::operator()) .def("__getitem__", >sam::Container::operator[]); +} -#include "python/specializations.h" +PYBIND11_MODULE(operator_py, m_) { + m_.doc() = "pybind11 wrapper of operator_py"; +gtwrap_declare_operator_py(m_); +gtwrap_bind_operator_py(m_); } - diff --git a/tests/expected/python/pybind_lambda_adapters_pybind.cpp b/tests/expected/python/pybind_lambda_adapters_pybind.cpp index 683a0107..8febd308 100644 --- a/tests/expected/python/pybind_lambda_adapters_pybind.cpp +++ b/tests/expected/python/pybind_lambda_adapters_pybind.cpp @@ -24,14 +24,29 @@ pybind11::arg py_arg(const char* name) { using namespace std; namespace py = pybind11; -PYBIND11_MODULE(pybind_lambda_adapters_py, m_) { + + +void gtwrap_declare_pybind_lambda_adapters_py(py::module_ &m_) { + pybind11::module m_adapters = m_.def_submodule("adapters", "adapters submodule"); - py::class_>(m_adapters, "BaseAdapter") + py::class_>(m_adapters, "BaseAdapter"); + + py::class_, adapters::BaseAdapter, std::shared_ptr>>(m_adapters, "AdapterInt"); + +} + +void gtwrap_bind_pybind_lambda_adapters_py(py::module_ &m_) { + + pybind11::module m_adapters = py::reinterpret_borrow(m_.attr("adapters")); + + auto gtwrap_class_m_adapters_BaseAdapter = py::reinterpret_borrow>>(m_adapters.attr("BaseAdapter")); + gtwrap_class_m_adapters_BaseAdapter .def(py::init<>()) .def("inherited",[](adapters::BaseAdapter* self, int value){return self->inherited(value);}, gtwrap::internal::py_arg("value")); - py::class_, adapters::BaseAdapter, std::shared_ptr>>(m_adapters, "AdapterInt") + auto gtwrap_class_m_adapters_AdapterInt = py::reinterpret_borrow, adapters::BaseAdapter, std::shared_ptr>>>(m_adapters.attr("AdapterInt")); + gtwrap_class_m_adapters_AdapterInt .def(py::init<>()) .def("exact",static_cast::*)(int)>(&adapters::Adapter::exact), gtwrap::internal::py_arg("value")) .def("exactConst",static_cast::*)(int) const>(&adapters::Adapter::exactConst), gtwrap::internal::py_arg("value")) @@ -55,3 +70,8 @@ PYBIND11_MODULE(pybind_lambda_adapters_py, m_) { m_adapters.def("globalOverload",static_cast(&adapters::globalOverload), gtwrap::internal::py_arg("value")); m_adapters.def("globalTemplatedInt",[](int value){return adapters::globalTemplated(value);}, gtwrap::internal::py_arg("value")); } + +PYBIND11_MODULE(pybind_lambda_adapters_py, m_) { +gtwrap_declare_pybind_lambda_adapters_py(m_); +gtwrap_bind_pybind_lambda_adapters_py(m_); +} diff --git a/tests/expected/python/special_cases_pybind.cpp b/tests/expected/python/special_cases_pybind.cpp index bdbe4661..3d5f62c2 100644 --- a/tests/expected/python/special_cases_pybind.cpp +++ b/tests/expected/python/special_cases_pybind.cpp @@ -31,31 +31,49 @@ using namespace std; namespace py = pybind11; -PYBIND11_MODULE(special_cases_py, m_) { - m_.doc() = "pybind11 wrapper of special_cases_py"; + + +void gtwrap_declare_special_cases_py(py::module_ &m_) { pybind11::module m_gtsam = m_.def_submodule("gtsam", "gtsam submodule"); - py::class_>(m_gtsam, "NonlinearFactorGraph") - .def("addPriorPinholeCameraCal3Bundler",[](gtsam::NonlinearFactorGraph* self, size_t key, const gtsam::PinholeCamera& prior, const std::shared_ptr noiseModel){ self->addPrior>(key, prior, noiseModel);}, gtwrap::internal::py_arg("key"), gtwrap::internal::py_arg&>("prior"), gtwrap::internal::py_arg>("noiseModel")); + py::class_>(m_gtsam, "NonlinearFactorGraph"); - py::class_>(m_gtsam, "SfmTrack") - .def_readwrite("measurements", >sam::SfmTrack::measurements); + py::class_>(m_gtsam, "SfmTrack"); py::class_, std::shared_ptr>>(m_gtsam, "PinholeCameraCal3Bundler"); - py::class_, gtsam::Point3>, std::shared_ptr, gtsam::Point3>>> generalsfmfactorcal3bundler(m_gtsam, "GeneralSFMFactorCal3Bundler"); - generalsfmfactorcal3bundler - .def_readwrite("verbosity", >sam::GeneralSFMFactor, gtsam::Point3>::verbosity); - - py::enum_, gtsam::Point3>::Verbosity>(generalsfmfactorcal3bundler, "Verbosity", py::arithmetic()) + py::class_, gtsam::Point3>, std::shared_ptr, gtsam::Point3>>> gtwrap_class_m_gtsam_GeneralSFMFactorCal3Bundler(m_gtsam, "GeneralSFMFactorCal3Bundler"); + py::enum_, gtsam::Point3>::Verbosity>(gtwrap_class_m_gtsam_GeneralSFMFactorCal3Bundler, "Verbosity", py::arithmetic()) .value("SILENT", gtsam::GeneralSFMFactor, gtsam::Point3>::Verbosity::SILENT) .value("SUMMARY", gtsam::GeneralSFMFactor, gtsam::Point3>::Verbosity::SUMMARY) .value("VALUES", gtsam::GeneralSFMFactor, gtsam::Point3>::Verbosity::VALUES); +} +void gtwrap_bind_special_cases_py(py::module_ &m_) { #include "python/specializations.h" + pybind11::module m_gtsam = py::reinterpret_borrow(m_.attr("gtsam")); + + auto gtwrap_class_m_gtsam_NonlinearFactorGraph = py::reinterpret_borrow>>(m_gtsam.attr("NonlinearFactorGraph")); + gtwrap_class_m_gtsam_NonlinearFactorGraph + .def("addPriorPinholeCameraCal3Bundler",[](gtsam::NonlinearFactorGraph* self, size_t key, const gtsam::PinholeCamera& prior, const std::shared_ptr noiseModel){ self->addPrior>(key, prior, noiseModel);}, gtwrap::internal::py_arg("key"), gtwrap::internal::py_arg&>("prior"), gtwrap::internal::py_arg>("noiseModel")); + + auto gtwrap_class_m_gtsam_SfmTrack = py::reinterpret_borrow>>(m_gtsam.attr("SfmTrack")); + gtwrap_class_m_gtsam_SfmTrack + .def_readwrite("measurements", >sam::SfmTrack::measurements); + + auto gtwrap_class_m_gtsam_GeneralSFMFactorCal3Bundler = py::reinterpret_borrow, gtsam::Point3>, std::shared_ptr, gtsam::Point3>>>>(m_gtsam.attr("GeneralSFMFactorCal3Bundler")); + gtwrap_class_m_gtsam_GeneralSFMFactorCal3Bundler + .def_readwrite("verbosity", >sam::GeneralSFMFactor, gtsam::Point3>::verbosity); + } +PYBIND11_MODULE(special_cases_py, m_) { + m_.doc() = "pybind11 wrapper of special_cases_py"; + +gtwrap_declare_special_cases_py(m_); +gtwrap_bind_special_cases_py(m_); +} diff --git a/tests/expected/python/templates_pybind.cpp b/tests/expected/python/templates_pybind.cpp index 957d65f3..6cb0fb49 100644 --- a/tests/expected/python/templates_pybind.cpp +++ b/tests/expected/python/templates_pybind.cpp @@ -30,21 +30,35 @@ using namespace std; namespace py = pybind11; -PYBIND11_MODULE(templates_py, m_) { - m_.doc() = "pybind11 wrapper of templates_py"; - py::class_>(m_, "TemplatedConstructor") +void gtwrap_declare_templates_py(py::module_ &m_) { + + py::class_>(m_, "TemplatedConstructor"); + + py::class_, std::shared_ptr>>(m_, "ScopedTemplateResult"); + +} + +void gtwrap_bind_templates_py(py::module_ &m_) { +#include "python/specializations.h" + + auto gtwrap_class_m__TemplatedConstructor = py::reinterpret_borrow>>(m_.attr("TemplatedConstructor")); + gtwrap_class_m__TemplatedConstructor .def(py::init<>()) .def(py::init(), gtwrap::internal::py_arg("arg")) .def(py::init(), gtwrap::internal::py_arg("arg")) .def(py::init(), gtwrap::internal::py_arg("arg")); - py::class_, std::shared_ptr>>(m_, "ScopedTemplateResult") + auto gtwrap_class_m__ScopedTemplateResult = py::reinterpret_borrow, std::shared_ptr>>>(m_.attr("ScopedTemplateResult")); + gtwrap_class_m__ScopedTemplateResult .def(py::init(), gtwrap::internal::py_arg("arg")); +} -#include "python/specializations.h" +PYBIND11_MODULE(templates_py, m_) { + m_.doc() = "pybind11 wrapper of templates_py"; +gtwrap_declare_templates_py(m_); +gtwrap_bind_templates_py(m_); } - diff --git a/tests/fixtures/declare_bind_late.i b/tests/fixtures/declare_bind_late.i new file mode 100644 index 00000000..13b0dc21 --- /dev/null +++ b/tests/fixtures/declare_bind_late.i @@ -0,0 +1,7 @@ +namespace testing { + +class Later { + Later(); +}; + +} // namespace testing diff --git a/tests/fixtures/declare_bind_main.i b/tests/fixtures/declare_bind_main.i new file mode 100644 index 00000000..1882da95 --- /dev/null +++ b/tests/fixtures/declare_bind_main.i @@ -0,0 +1,8 @@ +namespace testing { + +class Consumer { + Consumer(); + void consume(const testing::Later& value); +}; + +} // namespace testing diff --git a/tests/pybind_wrapper.tpl b/tests/pybind_wrapper.tpl index debc4578..e8a2cb25 100644 --- a/tests/pybind_wrapper.tpl +++ b/tests/pybind_wrapper.tpl @@ -12,12 +12,19 @@ using namespace std; namespace py = pybind11; -PYBIND11_MODULE({module_name}, m_) {{ - m_.doc() = "pybind11 wrapper of {module_name}"; +{submodules} -{wrapped_namespace} +{declaration_module_def} {{ +{wrapped_declarations} +}} +{binding_module_def} {{ #include "python/specializations.h" - +{wrapped_bindings} }} +{module_def} {{ + m_.doc() = "pybind11 wrapper of {module_name}"; + +{module_init} +}} diff --git a/tests/test_pybind_wrapper.py b/tests/test_pybind_wrapper.py index b4eb7a00..6a0407ac 100644 --- a/tests/test_pybind_wrapper.py +++ b/tests/test_pybind_wrapper.py @@ -42,8 +42,18 @@ class TestWrap(unittest.TestCase): using namespace std; namespace py = pybind11; -PYBIND11_MODULE({module_name}, m_) {{ -{wrapped_namespace} +{submodules} + +{declaration_module_def} {{ +{wrapped_declarations} +}} + +{binding_module_def} {{ +{wrapped_bindings} +}} + +{module_def} {{ +{module_init} }} """ @@ -168,6 +178,72 @@ def test_special_cases(self): self.compare_and_diff('special_cases_pybind.cpp', output) + with open(output, 'r', encoding='UTF-8') as output_file: + content = output_file.read() + + # Typedef template instantiations are moved to the end by the + # instantiator. Their declarations must still precede methods which + # use them in generated docstring signatures. + class_declaration = ( + 'py::class_, ' + 'std::shared_ptr>>' + '(m_gtsam, "PinholeCameraCal3Bundler");') + self.assertLess(content.index(class_declaration), + content.index('.def("addPriorPinholeCameraCal3Bundler"')) + + def test_module_wide_declare_then_bind_order(self): + """Declare every interface file before binding any of them.""" + main_source = osp.join(self.INTERFACE_DIR, 'declare_bind_main.i') + late_source = osp.join(self.INTERFACE_DIR, 'declare_bind_late.i') + output = self.wrap_content([main_source, late_source], + 'declare_bind_py', + self.PYTHON_ACTUAL_DIR) + + with open(output, 'r', encoding='UTF-8') as output_file: + module_init = output_file.read().split( + 'PYBIND11_MODULE(declare_bind_py, m_)', 1)[1] + + ordered_calls = [ + 'gtwrap_declare_declare_bind_py(m_);', + 'gtwrap_declare_declare_bind_late(m_);', + 'gtwrap_bind_declare_bind_py(m_);', + 'gtwrap_bind_declare_bind_late(m_);', + ] + positions = [module_init.index(call) for call in ordered_calls] + self.assertEqual(positions, sorted(positions)) + + with open(osp.join(self.TEST_DIR, 'pybind_wrapper.tpl'), + encoding='UTF-8') as template_file: + module_template = template_file.read() + with open(late_source, encoding='UTF-8') as interface_file: + late_content = interface_file.read() + wrapper = PybindWrapper(module_name='declare_bind_py', + top_module_namespaces=[''], + ignore_classes=[''], + module_template=module_template) + generated_submodule = wrapper.wrap_file( + late_content, + module_name='declare_bind_late', + source_name=late_source) + self.assertIn( + 'void gtwrap_declare_declare_bind_late(py::module_ &m_)', + generated_submodule) + self.assertIn('void gtwrap_bind_declare_bind_late(py::module_ &m_)', + generated_submodule) + + def test_legacy_template_reports_missing_phase_fields(self): + """Reject templates that cannot express module-wide two-phase init.""" + wrapper = PybindWrapper(module_name='legacy', + top_module_namespaces=[''], + ignore_classes=[''], + module_template=( + '{module_def} {{\n' + '{module_init}\n' + '}}')) + + with self.assertRaisesRegex(ValueError, 'declare-then-bind fields'): + wrapper.wrap_file('class Example {};', module_name='legacy') + def test_enum(self): """ Test if enum generation is correct. @@ -188,10 +264,19 @@ def test_argument_policy_hook(self): namespace py = pybind11; -PYBIND11_MODULE({module_name}, m_) {{ - m_.doc() = "pybind11 wrapper of {module_name}"; +{submodules} -{wrapped_namespace} +{declaration_module_def} {{ +{wrapped_declarations} +}} + +{binding_module_def} {{ +{wrapped_bindings} +}} + +{module_def} {{ + m_.doc() = "pybind11 wrapper of {module_name}"; +{module_init} }} """