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


gtsam
Author(s):
autogenerated on Tue Jun 25 2024 03:02:24