pybind_wrapper.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 """
3 GTSAM Copyright 2010-2020, Georgia Tech Research Corporation,
4 Atlanta, Georgia 30332-0415
5 All Rights Reserved
6 
7 See LICENSE for the license information
8 
9 Code generator for wrapping a C++ module with Pybind11
10 Author: Duy Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal, and Frank Dellaert
11 """
12 
13 # pylint: disable=too-many-arguments, too-many-instance-attributes, no-self-use, no-else-return, too-many-arguments, unused-format-string-argument, line-too-long, consider-using-f-string
14 
15 import re
16 from pathlib import Path
17 from typing import List
18 
19 import gtwrap.interface_parser as parser
20 import gtwrap.template_instantiator as instantiator
21 
22 from gtwrap.xml_parser.xml_parser import XMLDocParser
23 
25  """
26  Class to generate binding code for Pybind11 specifically.
27  """
28 
29  def __init__(self,
30  module_name,
31  top_module_namespaces='',
32  use_boost_serialization=False,
33  ignore_classes=(),
34  module_template="",
35  xml_source=""):
36  self.module_name = module_name
37  self.top_module_namespaces = top_module_namespaces
38  self.use_boost_serialization = use_boost_serialization
39  self.ignore_classes = ignore_classes
40  self._serializing_classes = []
41  self.module_template = module_template
42  self.python_keywords = [
43  'lambda', 'False', 'def', 'if', 'raise', 'None', 'del', 'import',
44  'return', 'True', 'elif', 'in', 'try', 'and', 'else', 'is',
45  'while', 'as', 'except', 'lambda', 'with', 'assert', 'finally',
46  'nonlocal', 'yield', 'break', 'for', 'not', 'class', 'from', 'or',
47  'continue', 'global', 'pass'
48  ]
49  self.xml_source = xml_source
50  self.xml_parser = XMLDocParser()
51 
52  self.dunder_methods = ('len', 'contains', 'iter')
53 
54  # amount of indentation to add before each function/method declaration.
55  self.method_indent = '\n' + (' ' * 8)
56 
57  # Special methods which are leveraged by ipython/jupyter notebooks
59  "svg", "png", "jpeg", "html", "javascript", "markdown", "latex"
60  ]
61 
62  def _py_args_names(self, args):
63  """Set the argument names in Pybind11 format."""
64  names = args.names()
65  if names:
66  py_args = []
67  for arg in args.list():
68  if arg.default is not None:
69  default = ' = {arg.default}'.format(arg=arg)
70  else:
71  default = ''
72  argument = 'py::arg("{name}"){default}'.format(
73  name=arg.name, default='{0}'.format(default))
74  py_args.append(argument)
75  return ", " + ", ".join(py_args)
76  else:
77  return ''
78 
79  def _method_args_signature(self, args):
80  """Generate the argument types and names as per the method signature."""
81  cpp_types = args.to_cpp()
82  names = args.names()
83  types_names = [
84  "{} {}".format(ctype, name)
85  for ctype, name in zip(cpp_types, names)
86  ]
87 
88  return ', '.join(types_names)
89 
90  def wrap_ctors(self, my_class):
91  """Wrap the constructors."""
92  res = ""
93  for ctor in my_class.ctors:
94  res += (self.method_indent + '.def(py::init<{args_cpp_types}>()'
95  '{py_args_names})'.format(
96  args_cpp_types=", ".join(ctor.args.to_cpp()),
97  py_args_names=self._py_args_names(ctor.args),
98  ))
99  return res
100 
101  def _wrap_serialization(self, cpp_class):
102  """Helper method to add serialize, deserialize and pickle methods to the wrapped class."""
103  if not cpp_class in self._serializing_classes:
104  self._serializing_classes.append(cpp_class)
105 
106  serialize_method = self.method_indent + \
107  ".def(\"serialize\", []({class_inst} self){{ return gtsam::serialize(*self); }})".format(class_inst=cpp_class + '*')
108 
109  deserialize_method = self.method_indent + \
110  '.def("deserialize", []({class_inst} self, string serialized)' \
111  '{{ gtsam::deserialize(serialized, *self); }}, py::arg("serialized"))' \
112  .format(class_inst=cpp_class + '*')
113 
114  # Since this class supports serialization, we also add the pickle method.
115  pickle_method = self.method_indent + \
116  ".def(py::pickle({indent} [](const {cpp_class} &a){{ /* __getstate__: Returns a string that encodes the state of the object */ return py::make_tuple(gtsam::serialize(a)); }},{indent} [](py::tuple t){{ /* __setstate__ */ {cpp_class} obj; gtsam::deserialize(t[0].cast<std::string>(), obj); return obj; }}))"
117 
118  return serialize_method + deserialize_method + \
119  pickle_method.format(cpp_class=cpp_class, indent=self.method_indent)
120 
121  def _wrap_print(self, ret: str, method: parser.Method, cpp_class: str,
122  args_names: List[str], args_signature_with_names: str,
123  py_args_names: str, prefix: str, suffix: str):
124  """
125  Update the print method to print to the output stream and append a __repr__ method.
126 
127  Args:
128  ret (str): The result of the parser.
129  method (parser.Method): The method to be wrapped.
130  cpp_class (str): The C++ name of the class to which the method belongs.
131  args_names (List[str]): List of argument variable names passed to the method.
132  args_signature_with_names (str): C++ arguments containing their names and type signatures.
133  py_args_names (str): The pybind11 formatted version of the argument list.
134  prefix (str): Prefix to add to the wrapped method when writing to the cpp file.
135  suffix (str): Suffix to add to the wrapped method when writing to the cpp file.
136 
137  Returns:
138  str: The wrapped print method.
139  """
140  # Redirect stdout - see pybind docs for why this is a good idea:
141  # https://pybind11.readthedocs.io/en/stable/advanced/pycpp/utilities.html#capturing-standard-output-from-ostream
142  ret = ret.replace('self->print',
143  'py::scoped_ostream_redirect output; self->print')
144 
145  # Make __repr__() call .print() internally
146  ret += '''{prefix}.def("__repr__",
147  [](const {cpp_class}& self{opt_comma}{args_signature_with_names}){{
148  gtsam::RedirectCout redirect;
149  self.{method_name}({method_args});
150  return redirect.str();
151  }}{py_args_names}){suffix}'''.format(
152  prefix=prefix,
153  cpp_class=cpp_class,
154  opt_comma=', ' if args_names else '',
155  args_signature_with_names=args_signature_with_names,
156  method_name=method.name,
157  method_args=", ".join(args_names) if args_names else '',
158  py_args_names=py_args_names,
159  suffix=suffix)
160  return ret
161 
162  def _wrap_dunder(self,
163  method,
164  cpp_class,
165  prefix,
166  suffix,
167  method_suffix=""):
168  """
169  Wrap a Python double-underscore (dunder) method.
170 
171  E.g. __len__() gets wrapped as `.def("__len__", [](gtsam::KeySet* self) {return self->size();})`
172 
173  Supported methods are:
174  - __contains__(T x)
175  - __len__()
176  - __iter__()
177  """
178  py_method = method.name + method_suffix
179  args_names = method.args.names()
180  py_args_names = self._py_args_names(method.args)
181  args_signature_with_names = self._method_args_signature(method.args)
182 
183  if method.name == 'len':
184  function_call = "return std::distance(self->begin(), self->end());"
185  elif method.name == 'contains':
186  function_call = f"return std::find(self->begin(), self->end(), {method.args.args_list[0].name}) != self->end();"
187  elif method.name == 'iter':
188  function_call = "return py::make_iterator(self->begin(), self->end());"
189 
190  ret = ('{prefix}.def("__{py_method}__",'
191  '[]({self}{opt_comma}{args_signature_with_names}){{'
192  '{function_call}'
193  '}}'
194  '{py_args_names}){suffix}'.format(
195  prefix=prefix,
196  py_method=py_method,
197  self=f"{cpp_class}* self",
198  opt_comma=', ' if args_names else '',
199  args_signature_with_names=args_signature_with_names,
200  function_call=function_call,
201  py_args_names=py_args_names,
202  suffix=suffix,
203  ))
204 
205  return ret
206 
207  def _wrap_method(self,
208  method,
209  cpp_class,
210  prefix,
211  suffix,
212  method_suffix=""):
213  """
214  Wrap the `method` for the class specified by `cpp_class`.
215 
216  Args:
217  method: The method to wrap.
218  cpp_class: The C++ name of the class to which the method belongs.
219  prefix: Prefix to add to the wrapped method when writing to the cpp file.
220  suffix: Suffix to add to the wrapped method when writing to the cpp file.
221  method_suffix: A string to append to the wrapped method name.
222  """
223  py_method = method.name + method_suffix
224  cpp_method = method.to_cpp()
225 
226  args_names = method.args.names()
227  py_args_names = self._py_args_names(method.args)
228  args_signature_with_names = self._method_args_signature(method.args)
229 
230  # Special handling for the serialize/serializable method
231  if cpp_method in ["serialize", "serializable"]:
232  if self.use_boost_serialization:
233  return self._wrap_serialization(cpp_class)
234  else:
235  return ""
236 
237  # Special handling of ipython specific methods
238  # https://ipython.readthedocs.io/en/stable/config/integrating.html
239  if cpp_method in self._ipython_special_methods:
240  idx = self._ipython_special_methods.index(cpp_method)
241  py_method = f"_repr_{self._ipython_special_methods[idx]}_"
242 
243  # Add underscore to disambiguate if the method name matches a python keyword
244  if py_method in self.python_keywords:
245  py_method = py_method + "_"
246 
247  is_method = isinstance(
248  method, (parser.Method, instantiator.InstantiatedMethod))
249  is_static = isinstance(
250  method,
251  (parser.StaticMethod, instantiator.InstantiatedStaticMethod))
252  return_void = method.return_type.is_void()
253 
254  caller = cpp_class + "::" if not is_method else "self->"
255  function_call = ('{opt_return} {caller}{method_name}'
256  '({args_names});'.format(
257  opt_return='return' if not return_void else '',
258  caller=caller,
259  method_name=cpp_method,
260  args_names=', '.join(args_names),
261  ))
262 
263  ret = ('{prefix}.{cdef}("{py_method}",'
264  '[]({opt_self}{opt_comma}{args_signature_with_names}){{'
265  '{function_call}'
266  '}}'
267  '{py_args_names}{docstring}){suffix}'.format(
268  prefix=prefix,
269  cdef="def_static" if is_static else "def",
270  py_method=py_method,
271  opt_self="{cpp_class}* self".format(
272  cpp_class=cpp_class) if is_method else "",
273  opt_comma=', ' if is_method and args_names else '',
274  args_signature_with_names=args_signature_with_names,
275  function_call=function_call,
276  py_args_names=py_args_names,
277  suffix=suffix,
278  # Try to get the function's docstring from the Doxygen XML.
279  # If extract_docstring errors or fails to find a docstring, it just prints a warning.
280  # The incantation repr(...)[1:-1].replace('"', r'\"') replaces newlines with \n
281  # and " with \" so that the docstring can be put into a C++ string on a single line.
282  docstring=', "' + repr(self.xml_parser.extract_docstring(self.xml_source, cpp_class, cpp_method, method.args.names()))[1:-1].replace('"', r'\"') + '"'
283  if self.xml_source != "" else "",
284  ))
285 
286  # Create __repr__ override
287  # We allow all arguments to .print() and let the compiler handle type mismatches.
288  if method.name == 'print':
289  ret = self._wrap_print(ret, method, cpp_class, args_names,
290  args_signature_with_names, py_args_names,
291  prefix, suffix)
292 
293  return ret
294 
296  methods,
297  cpp_class,
298  prefix='\n' + ' ' * 8,
299  suffix=''):
300  res = ""
301  for method in methods:
302  res += self._wrap_dunder(method=method,
303  cpp_class=cpp_class,
304  prefix=prefix,
305  suffix=suffix)
306 
307  return res
308 
309  def wrap_methods(self,
310  methods,
311  cpp_class,
312  prefix='\n' + ' ' * 8,
313  suffix=''):
314  """
315  Wrap all the methods in the `cpp_class`.
316  """
317  res = ""
318  for method in methods:
319 
320  # To avoid type confusion for insert
321  if method.name == 'insert' and cpp_class == 'gtsam::Values':
322  name_list = method.args.names()
323  type_list = method.args.to_cpp()
324  # inserting non-wrapped value types
325  if type_list[0].strip() == 'size_t':
326  method_suffix = '_' + name_list[1].strip()
327  res += self._wrap_method(method=method,
328  cpp_class=cpp_class,
329  prefix=prefix,
330  suffix=suffix,
331  method_suffix=method_suffix)
332 
333  res += self._wrap_method(
334  method=method,
335  cpp_class=cpp_class,
336  prefix=prefix,
337  suffix=suffix,
338  )
339 
340  return res
341 
342  def wrap_variable(self,
343  namespace,
344  module_var,
345  variable,
346  prefix='\n' + ' ' * 8):
347  """
348  Wrap a variable that's not part of a class (i.e. global)
349  """
350  variable_value = ""
351  if variable.default is None:
352  variable_value = variable.name
353  else:
354  variable_value = variable.default
355 
356  return '{prefix}{module_var}.attr("{variable_name}") = {namespace}{variable_value};'.format(
357  prefix=prefix,
358  module_var=module_var,
359  variable_name=variable.name,
360  namespace=namespace,
361  variable_value=variable_value)
362 
363  def wrap_properties(self, properties, cpp_class, prefix='\n' + ' ' * 8):
364  """Wrap all the properties in the `cpp_class`."""
365  res = ""
366  for prop in properties:
367  res += ('{prefix}.def_{property}("{property_name}", '
368  '&{cpp_class}::{property_name})'.format(
369  prefix=prefix,
370  property="readonly"
371  if prop.ctype.is_const else "readwrite",
372  cpp_class=cpp_class,
373  property_name=prop.name,
374  ))
375  return res
376 
377  def wrap_operators(self, operators, cpp_class, prefix='\n' + ' ' * 8):
378  """Wrap all the overloaded operators in the `cpp_class`."""
379  res = ""
380  template = "{prefix}.def({{0}})".format(prefix=prefix)
381  for op in operators:
382  if op.operator == "[]": # __getitem__
383  res += "{prefix}.def(\"__getitem__\", &{cpp_class}::operator[])".format(
384  prefix=prefix, cpp_class=cpp_class)
385  elif op.operator == "()": # __call__
386  res += "{prefix}.def(\"__call__\", &{cpp_class}::operator())".format(
387  prefix=prefix, cpp_class=cpp_class)
388  elif op.is_unary:
389  res += template.format("{0}py::self".format(op.operator))
390  else:
391  res += template.format("py::self {0} py::self".format(
392  op.operator))
393  return res
394 
395  def wrap_enum(self, enum, class_name='', module=None, prefix=' ' * 4):
396  """
397  Wrap an enum.
398 
399  Args:
400  enum: The parsed enum to wrap.
401  class_name: The class under which the enum is defined.
402  prefix: The amount of indentation.
403  """
404  if module is None:
405  module = self._gen_module_var(enum.namespaces())
406 
407  cpp_class = enum.cpp_typename().to_cpp()
408  if class_name:
409  # If class_name is provided, add that as the namespace
410  cpp_class = class_name + "::" + cpp_class
411 
412  res = '{prefix}py::enum_<{cpp_class}>({module}, "{enum.name}", py::arithmetic())'.format(
413  prefix=prefix, module=module, enum=enum, cpp_class=cpp_class)
414  for enumerator in enum.enumerators:
415  res += '\n{prefix} .value("{enumerator.name}", {cpp_class}::{enumerator.name})'.format(
416  prefix=prefix, enumerator=enumerator, cpp_class=cpp_class)
417  res += ";\n\n"
418  return res
419 
420  def wrap_enums(self, enums, instantiated_class, prefix=' ' * 4):
421  """Wrap multiple enums defined in a class."""
422  cpp_class = instantiated_class.to_cpp()
423  module_var = instantiated_class.name.lower()
424  res = ''
425 
426  for enum in enums:
427  res += "\n" + self.wrap_enum(
428  enum, class_name=cpp_class, module=module_var, prefix=prefix)
429  return res
430 
432  self, instantiated_class: instantiator.InstantiatedClass):
433  """Wrap the class."""
434  module_var = self._gen_module_var(instantiated_class.namespaces())
435  cpp_class = instantiated_class.to_cpp()
436  if cpp_class in self.ignore_classes:
437  return ""
438  if instantiated_class.parent_class:
439  class_parent = "{instantiated_class.parent_class}, ".format(
440  instantiated_class=instantiated_class)
441  else:
442  class_parent = ''
443 
444  if instantiated_class.enums:
445  # If class has enums, define an instance and set module_var to the instance
446  instance_name = instantiated_class.name.lower()
447  class_declaration = (
448  '\n py::class_<{cpp_class}, {class_parent}'
449  'std::shared_ptr<{cpp_class}>> '
450  '{instance_name}({module_var}, "{class_name}");'
451  '\n {instance_name}').format(
452  cpp_class=cpp_class,
453  class_name=instantiated_class.name,
454  class_parent=class_parent,
455  instance_name=instance_name,
456  module_var=module_var)
457  module_var = instance_name
458 
459  else:
460  class_declaration = (
461  '\n py::class_<{cpp_class}, {class_parent}'
462  'std::shared_ptr<{cpp_class}>>({module_var}, "{class_name}")'
463  ).format(cpp_class=cpp_class,
464  class_name=instantiated_class.name,
465  class_parent=class_parent,
466  module_var=module_var)
467 
468  return ('{class_declaration}'
469  '{wrapped_ctors}'
470  '{wrapped_methods}'
471  '{wrapped_static_methods}'
472  '{wrapped_dunder_methods}'
473  '{wrapped_properties}'
474  '{wrapped_operators};\n'.format(
475  class_declaration=class_declaration,
476  wrapped_ctors=self.wrap_ctors(instantiated_class),
477  wrapped_methods=self.wrap_methods(
478  instantiated_class.methods, cpp_class),
479  wrapped_static_methods=self.wrap_methods(
480  instantiated_class.static_methods, cpp_class),
481  wrapped_dunder_methods=self.wrap_dunder_methods(
482  instantiated_class.dunder_methods, cpp_class),
483  wrapped_properties=self.wrap_properties(
484  instantiated_class.properties, cpp_class),
485  wrapped_operators=self.wrap_operators(
486  instantiated_class.operators, cpp_class)))
487 
489  self, instantiated_decl: instantiator.InstantiatedDeclaration):
490  """Wrap the forward declaration."""
491  module_var = self._gen_module_var(instantiated_decl.namespaces())
492  cpp_class = instantiated_decl.to_cpp()
493  if cpp_class in self.ignore_classes:
494  return ""
495 
496  res = ('\n py::class_<{cpp_class}, '
497  'std::shared_ptr<{cpp_class}>>({module_var}, "{class_name}");'
498  ).format(cpp_class=cpp_class,
499  class_name=instantiated_decl.name,
500  module_var=module_var)
501  return res
502 
503  def wrap_stl_class(self, stl_class):
504  """Wrap STL containers."""
505  module_var = self._gen_module_var(stl_class.namespaces())
506  cpp_class = stl_class.to_cpp()
507  if cpp_class in self.ignore_classes:
508  return ""
509 
510  return ('\n py::class_<{cpp_class}, {class_parent}'
511  'std::shared_ptr<{cpp_class}>>({module_var}, "{class_name}")'
512  '{wrapped_ctors}'
513  '{wrapped_methods}'
514  '{wrapped_static_methods}'
515  '{wrapped_properties};\n'.format(
516  cpp_class=cpp_class,
517  class_name=stl_class.name,
518  class_parent=str(stl_class.parent_class) +
519  (', ' if stl_class.parent_class else ''),
520  module_var=module_var,
521  wrapped_ctors=self.wrap_ctors(stl_class),
522  wrapped_methods=self.wrap_methods(stl_class.methods,
523  cpp_class),
524  wrapped_static_methods=self.wrap_methods(
525  stl_class.static_methods, cpp_class),
526  wrapped_properties=self.wrap_properties(
527  stl_class.properties, cpp_class),
528  ))
529 
530  def wrap_functions(self,
531  functions,
532  namespace,
533  prefix='\n' + ' ' * 8,
534  suffix=''):
535  """
536  Wrap all the global functions.
537  """
538  res = ""
539  for function in functions:
540 
541  function_name = function.name
542 
543  # Add underscore to disambiguate if the function name matches a python keyword
544  python_keywords = self.python_keywords + ['print']
545  if function_name in python_keywords:
546  function_name = function_name + "_"
547 
548  cpp_method = function.to_cpp()
549 
550  is_static = isinstance(function, parser.StaticMethod)
551  return_void = function.return_type.is_void()
552  args_names = function.args.names()
553  py_args_names = self._py_args_names(function.args)
554  args_signature = self._method_args_signature(function.args)
555 
556  caller = namespace + "::"
557  function_call = ('{opt_return} {caller}{function_name}'
558  '({args_names});'.format(
559  opt_return='return'
560  if not return_void else '',
561  caller=caller,
562  function_name=cpp_method,
563  args_names=', '.join(args_names),
564  ))
565 
566  ret = ('{prefix}.{cdef}("{function_name}",'
567  '[]({args_signature}){{'
568  '{function_call}'
569  '}}'
570  '{py_args_names}){suffix}'.format(
571  prefix=prefix,
572  cdef="def_static" if is_static else "def",
573  function_name=function_name,
574  args_signature=args_signature,
575  function_call=function_call,
576  py_args_names=py_args_names,
577  suffix=suffix))
578 
579  res += ret
580 
581  return res
582 
583  def _partial_match(self, namespaces1, namespaces2):
584  for i in range(min(len(namespaces1), len(namespaces2))):
585  if namespaces1[i] != namespaces2[i]:
586  return False
587  return True
588 
589  def _gen_module_var(self, namespaces):
590  """Get the Pybind11 module name from the namespaces."""
591  # We skip the first value in namespaces since it is empty
592  sub_module_namespaces = namespaces[len(self.top_module_namespaces):]
593  return "m_{}".format('_'.join(sub_module_namespaces))
594 
595  def _add_namespaces(self, name, namespaces):
596  if namespaces:
597  # Ignore the first empty global namespace.
598  idx = 1 if not namespaces[0] else 0
599  return '::'.join(namespaces[idx:] + [name])
600  else:
601  return name
602 
603  def wrap_namespace(self, namespace):
604  """Wrap the complete `namespace`."""
605  wrapped = ""
606  includes = ""
607 
608  namespaces = namespace.full_namespaces()
609  if not self._partial_match(namespaces, self.top_module_namespaces):
610  return "", ""
611 
612  if len(namespaces) < len(self.top_module_namespaces):
613  for element in namespace.content:
614  if isinstance(element, parser.Include):
615  include = "{}\n".format(element)
616  # replace the angle brackets with quotes
617  include = include.replace('<', '"').replace('>', '"')
618  includes += include
619  if isinstance(element, parser.Namespace):
620  (
621  wrapped_namespace,
622  includes_namespace,
623  ) = self.wrap_namespace( # noqa
624  element)
625  wrapped += wrapped_namespace
626  includes += includes_namespace
627  else:
628  module_var = self._gen_module_var(namespaces)
629 
630  if len(namespaces) > len(self.top_module_namespaces):
631  wrapped += (
632  ' ' * 4 + 'pybind11::module {module_var} = '
633  '{parent_module_var}.def_submodule("{namespace}", "'
634  '{namespace} submodule");\n'.format(
635  module_var=module_var,
636  namespace=namespace.name,
637  parent_module_var=self._gen_module_var(
638  namespaces[:-1]),
639  ))
640 
641  # Wrap an include statement, namespace, class or enum
642  for element in namespace.content:
643  if isinstance(element, parser.Include):
644  include = "{}\n".format(element)
645  # replace the angle brackets with quotes
646  include = include.replace('<', '"').replace('>', '"')
647  includes += include
648  elif isinstance(element, parser.Namespace):
649  wrapped_namespace, includes_namespace = self.wrap_namespace(
650  element)
651  wrapped += wrapped_namespace
652  includes += includes_namespace
653 
654  elif isinstance(element, instantiator.InstantiatedClass):
655  wrapped += self.wrap_instantiated_class(element)
656  wrapped += self.wrap_enums(element.enums, element)
657 
658  elif isinstance(element, instantiator.InstantiatedDeclaration):
659  wrapped += self.wrap_instantiated_declaration(element)
660 
661  elif isinstance(element, parser.Variable):
662  variable_namespace = self._add_namespaces('', namespaces)
663  wrapped += self.wrap_variable(namespace=variable_namespace,
664  module_var=module_var,
665  variable=element,
666  prefix='\n' + ' ' * 4)
667 
668  elif isinstance(element, parser.Enum):
669  wrapped += self.wrap_enum(element)
670 
671  # Global functions.
672  all_funcs = [
673  func for func in namespace.content
674  if isinstance(func, (parser.GlobalFunction,
675  instantiator.InstantiatedGlobalFunction))
676  ]
677  wrapped += self.wrap_functions(
678  all_funcs,
679  self._add_namespaces('', namespaces)[:-2],
680  prefix='\n' + ' ' * 4 + module_var,
681  suffix=';',
682  )
683 
684  return wrapped, includes
685 
686  def wrap_file(self, content, module_name=None, submodules=None):
687  """
688  Wrap the code in the interface file.
689 
690  Args:
691  content: The contents of the interface file.
692  module_name: The name of the module.
693  submodules: List of other interface file names that should be linked to.
694  """
695  # Parse the contents of the interface file
696  module = parser.Module.parseString(content)
697  # Instantiate all templates
698  module = instantiator.instantiate_namespace(module)
699 
700  wrapped_namespace, includes = self.wrap_namespace(module)
701 
702  if self.use_boost_serialization:
703  includes += "#include <boost/serialization/export.hpp>"
704 
705  # Export classes for serialization.
706  boost_class_export = ""
707  for cpp_class in self._serializing_classes:
708  new_name = cpp_class
709  # The boost's macro doesn't like commas, so we have to typedef.
710  if ',' in cpp_class:
711  new_name = re.sub("[,:<> ]", "", cpp_class)
712  boost_class_export += "typedef {cpp_class} {new_name};\n".format( # noqa
713  cpp_class=cpp_class, new_name=new_name)
714 
715  boost_class_export += "BOOST_CLASS_EXPORT({new_name})\n".format(
716  new_name=new_name, )
717  else:
718  boost_class_export = ""
719 
720  # Reset the serializing classes list
721  self._serializing_classes = []
722 
723  submodules_init = []
724 
725  if submodules is not None:
726  module_def = "PYBIND11_MODULE({0}, m_)".format(module_name)
727 
728  for idx, submodule in enumerate(submodules):
729  submodules[idx] = "void {0}(py::module_ &);".format(submodule)
730  submodules_init.append("{0}(m_);".format(submodule))
731 
732  else:
733  module_def = "void {0}(py::module_ &m_)".format(module_name)
734  submodules = []
735 
736  return self.module_template.format(
737  module_def=module_def,
738  module_name=module_name,
739  includes=includes,
740  wrapped_namespace=wrapped_namespace,
741  boost_class_export=boost_class_export,
742  submodules="\n".join(submodules),
743  submodules_init="\n".join(submodules_init),
744  )
745 
746  def wrap_submodule(self, source):
747  """
748  Wrap a list of submodule files, i.e. a set of interface files which are
749  in support of a larger wrapping project.
750 
751  E.g. This is used in GTSAM where we have a main gtsam.i, but various smaller .i files
752  which are the submodules.
753  The benefit of this scheme is that it reduces compute and memory usage during compilation.
754 
755  Args:
756  source: Interface file which forms the submodule.
757  """
758  filename = Path(source).name
759  module_name = Path(source).stem
760 
761  # Read in the complete interface (.i) file
762  with open(source, "r", encoding="UTF-8") as f:
763  content = f.read()
764  # Wrap the read-in content
765  cc_content = self.wrap_file(content, module_name=module_name)
766 
767  # Generate the C++ code which Pybind11 will use.
768  with open(filename.replace(".i", ".cpp"), "w", encoding="UTF-8") as f:
769  f.write(cc_content)
770 
771  def wrap(self, sources, main_module_name):
772  """
773  Wrap all the main interface file.
774 
775  Args:
776  sources: List of all interface files.
777  The first file should be the main module.
778  main_module_name: The name for the main module.
779  """
780  main_module = sources[0]
781 
782  # Get all the submodule names.
783  submodules = []
784  for source in sources[1:]:
785  module_name = Path(source).stem
786  submodules.append(module_name)
787 
788  with open(main_module, "r", encoding="UTF-8") as f:
789  content = f.read()
790  cc_content = self.wrap_file(content,
791  module_name=self.module_name,
792  submodules=submodules)
793 
794  # Generate the C++ code which Pybind11 will use.
795  with open(main_module_name, "w", encoding="UTF-8") as f:
796  f.write(cc_content)
gtwrap.xml_parser.xml_parser
Definition: xml_parser.py:1
gtwrap.interface_parser.function.to_cpp
str to_cpp(self)
Definition: interface_parser/function.py:56
gtwrap.pybind_wrapper.PybindWrapper.wrap_dunder_methods
def wrap_dunder_methods(self, methods, cpp_class, prefix='\n'+' ' *8, suffix='')
Definition: pybind_wrapper.py:295
gtwrap.pybind_wrapper.PybindWrapper
Definition: pybind_wrapper.py:24
format
std::string format(const std::string &str, const std::vector< std::string > &find, const std::vector< std::string > &replace)
Definition: openglsupport.cpp:226
gtwrap.pybind_wrapper.PybindWrapper.__init__
def __init__(self, module_name, top_module_namespaces='', use_boost_serialization=False, ignore_classes=(), module_template="", xml_source="")
Definition: pybind_wrapper.py:29
gtwrap.pybind_wrapper.PybindWrapper.method_indent
method_indent
Definition: pybind_wrapper.py:49
gtwrap.pybind_wrapper.PybindWrapper._serializing_classes
_serializing_classes
Definition: pybind_wrapper.py:34
gtwrap.pybind_wrapper.PybindWrapper._add_namespaces
def _add_namespaces(self, name, namespaces)
Definition: pybind_wrapper.py:595
gtwrap.pybind_wrapper.PybindWrapper.wrap_submodule
def wrap_submodule(self, source)
Definition: pybind_wrapper.py:746
gtwrap.pybind_wrapper.PybindWrapper.wrap_instantiated_declaration
def wrap_instantiated_declaration(self, instantiator.InstantiatedDeclaration instantiated_decl)
Definition: pybind_wrapper.py:488
gtwrap.pybind_wrapper.PybindWrapper._partial_match
def _partial_match(self, namespaces1, namespaces2)
Definition: pybind_wrapper.py:583
gtwrap.pybind_wrapper.PybindWrapper.wrap_properties
def wrap_properties(self, properties, cpp_class, prefix='\n'+' ' *8)
Definition: pybind_wrapper.py:363
gtwrap.pybind_wrapper.PybindWrapper.ignore_classes
ignore_classes
Definition: pybind_wrapper.py:33
gtsam::range
Double_ range(const Point2_ &p, const Point2_ &q)
Definition: slam/expressions.h:30
gtwrap.pybind_wrapper.PybindWrapper.wrap_operators
def wrap_operators(self, operators,cpp_class, prefix='\n'+' ' *8)
Definition: pybind_wrapper.py:377
gtwrap.pybind_wrapper.PybindWrapper._wrap_print
def _wrap_print(self, str ret, parser.Method method, str cpp_class, List[str] args_names, str args_signature_with_names, str py_args_names, str prefix, str suffix)
Definition: pybind_wrapper.py:121
gtwrap.xml_parser.xml_parser.XMLDocParser
Definition: xml_parser.py:7
gtwrap.pybind_wrapper.PybindWrapper._wrap_serialization
def _wrap_serialization(self, cpp_class)
Definition: pybind_wrapper.py:101
gtwrap.pybind_wrapper.PybindWrapper._method_args_signature
def _method_args_signature(self, args)
Definition: pybind_wrapper.py:79
isinstance
bool isinstance(handle obj)
Definition: pytypes.h:842
gtwrap.pybind_wrapper.PybindWrapper.top_module_namespaces
top_module_namespaces
Definition: pybind_wrapper.py:31
gtwrap.pybind_wrapper.PybindWrapper.wrap_enum
def wrap_enum(self, enum, class_name='', module=None, prefix=' ' *4)
Definition: pybind_wrapper.py:395
str
Definition: pytypes.h:1560
gtwrap.pybind_wrapper.PybindWrapper.module_name
module_name
Definition: pybind_wrapper.py:30
gtwrap.pybind_wrapper.PybindWrapper.python_keywords
python_keywords
Definition: pybind_wrapper.py:36
gtwrap.pybind_wrapper.PybindWrapper.module_template
module_template
Definition: pybind_wrapper.py:35
gtwrap.pybind_wrapper.PybindWrapper._wrap_method
def _wrap_method(self, method, cpp_class, prefix, suffix, method_suffix="")
Definition: pybind_wrapper.py:207
gtwrap.pybind_wrapper.PybindWrapper.xml_source
xml_source
Definition: pybind_wrapper.py:43
gtwrap.pybind_wrapper.PybindWrapper.dunder_methods
dunder_methods
Definition: pybind_wrapper.py:46
gtwrap.template_instantiator
Definition: wrap/gtwrap/template_instantiator/__init__.py:1
gtwrap.pybind_wrapper.PybindWrapper.wrap_instantiated_class
def wrap_instantiated_class(self, instantiator.InstantiatedClass instantiated_class)
Definition: pybind_wrapper.py:431
gtwrap.pybind_wrapper.PybindWrapper._gen_module_var
def _gen_module_var(self, namespaces)
Definition: pybind_wrapper.py:589
gtwrap.pybind_wrapper.PybindWrapper.wrap_methods
def wrap_methods(self, methods, cpp_class, prefix='\n'+' ' *8, suffix='')
Definition: pybind_wrapper.py:309
gtwrap.pybind_wrapper.PybindWrapper.wrap
def wrap(self, sources, main_module_name)
Definition: pybind_wrapper.py:771
gtwrap.pybind_wrapper.PybindWrapper.wrap_namespace
def wrap_namespace(self, namespace)
Definition: pybind_wrapper.py:603
gtwrap.pybind_wrapper.PybindWrapper.wrap_enums
def wrap_enums(self, enums, instantiated_class, prefix=' ' *4)
Definition: pybind_wrapper.py:420
min
#define min(a, b)
Definition: datatypes.h:19
gtwrap.pybind_wrapper.PybindWrapper.wrap_file
def wrap_file(self, content, module_name=None, submodules=None)
Definition: pybind_wrapper.py:686
gtwrap.pybind_wrapper.PybindWrapper.xml_parser
xml_parser
Definition: pybind_wrapper.py:44
gtwrap.pybind_wrapper.PybindWrapper.use_boost_serialization
use_boost_serialization
Definition: pybind_wrapper.py:32
gtwrap.interface_parser
Definition: wrap/gtwrap/interface_parser/__init__.py:1
gtwrap.pybind_wrapper.PybindWrapper._ipython_special_methods
_ipython_special_methods
Definition: pybind_wrapper.py:52
len
size_t len(handle h)
Get the length of a Python object.
Definition: pytypes.h:2448
gtwrap.pybind_wrapper.PybindWrapper.wrap_stl_class
def wrap_stl_class(self, stl_class)
Definition: pybind_wrapper.py:503
gtwrap.pybind_wrapper.PybindWrapper.wrap_ctors
def wrap_ctors(self, my_class)
Definition: pybind_wrapper.py:90
gtwrap.pybind_wrapper.PybindWrapper._wrap_dunder
def _wrap_dunder(self, method, cpp_class, prefix, suffix, method_suffix="")
Definition: pybind_wrapper.py:162
gtwrap.pybind_wrapper.PybindWrapper.wrap_variable
def wrap_variable(self, namespace, module_var, variable, prefix='\n'+' ' *8)
Definition: pybind_wrapper.py:342
gtwrap.pybind_wrapper.PybindWrapper._py_args_names
def _py_args_names(self, args)
Definition: pybind_wrapper.py:62
gtwrap.pybind_wrapper.PybindWrapper.wrap_functions
def wrap_functions(self, functions, namespace, prefix='\n'+' ' *8, suffix='')
Definition: pybind_wrapper.py:530
repr
str repr(handle h)
Definition: pytypes.h:2469


gtsam
Author(s):
autogenerated on Fri Mar 28 2025 03:03:12