.. _toml: TOML input file *************** Here we demonstrate how a TOML input file can be used to customize the generated Python bindings. Basic setup ----------- In all examples we will work with the following 3 files: .. literalinclude:: ./foo.hpp :language: cpp .. literalinclude:: ./bar.hpp :language: cpp .. literalinclude:: ./my_module.cpp :language: cpp We declare some simple functions and a class in the namespaces ``foo`` and ``bar`` in the header files ``foo.hpp`` and ``bar.hpp``, respectively. Their definitions are given in ``my_module.cpp`` which is also the file passed to ``clair-c2py`` to generate the Python bindings. Our initial TOML file looks as follows: .. literalinclude:: ./initial.toml :language: toml We can then generate the Python bindings and compile the extension module: .. code-block:: bash clair-c2py my_module.cpp -- -std=c++20 `c2py_flags -i` clang++ my_module.cpp -std=c++20 -shared -o my_module.so `c2py_flags` Running ``python`` and importing ``my_module``, we can see that everything in both namespaces has been wrapped: .. code-block:: console >>> import my_module >>> dir(my_module) ['A', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'f1', 'f2', 'g1', 'g2', 'h1', 'h2'] >>> print(my_module.__doc__) This is an awesome module generated by c2py/clair. For the rest of the examples, we will not repeat the binding generation and compilation commands. We will only show the modifications made to the TOML file and the resulting behavior in Python. Filters ------- By default, ``clair`` will generate bindings for all non-template classes and functions, except those defined in `system` files, i.e. the standard library and more generally anything the compiler includes via `-isystem` options. In order to refine this behaviour, several filters can be used. .. warning:: Be careful that some these strings are Regex. For example, ``N::*`` does not match ``N::f``. Also note that the qualified name starts with ``::``. So to match the start of a name, use ``^::N::f``. Filtering by namespace ====================== We can restrict the wrapping to only functions declared in specific namespaces using the ``namespaces`` option. For example, to only wrap functions in the ``foo`` namespace, we modify the TOML file as follows: .. code-block:: toml # List of namespaces as a space separated string e.g. "A A::B" # If non-empty, only the elements declared in EXACTLY these namespaces are wrapped. # NB e.g. in previous example A::detail will be ignored, whatever match_names. namespaces = "foo" Now only the names in the ``foo`` namespace are available in Python: .. code-block:: console >>> import my_module >>> dir(my_module) ['A', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'f1', 'f2', 'g1', 'g2'] Filtering by name ================= A more fine-grained filtering can be achieved using the ``match_names`` and ``reject_names`` options. For example, to further restrict the wrapping to only functions whose name starts with ``f``, we modify the TOML file as follows: .. code-block:: toml # Match/reject elements according to a regex (LLVM regex) # If match_names/match_names are both set, the algorithm is # for each element X of qualified name Qname: # if match_names and not match_names matches Qname : skip X # if reject_names and reject_names matches Qname : skip X # else keep X. # match_names = "foo::f.*" # reject_names = "" Now only the functions ``f1`` and ``f2`` are available in Python: .. code-block:: console >>> import my_module >>> dir(my_module) ['__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'f1', 'f2'] To only wrap the function ``f1``, we can for example reject ``foo::f2`` explicitly: .. code-block:: toml # Match/reject elements according to a regex (LLVM regex) # If match_names/match_names are both set, the algorithm is # for each element X of qualified name Qname: # if match_names and not match_names matches Qname : skip X # if reject_names and reject_names matches Qname : skip X # else keep X. # match_names = "foo::f.*" reject_names = "foo::f2" This results in only ``f1`` being available in Python: .. code-block:: console >>> import my_module >>> dir(my_module) ['__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'f1'] Filter by file name =================== Finally, we can restrict the wrapping to only elements declared in files matching a given regex using the ``match_files`` option. Let's start again from our initial TOML file without any filtering. To only wrap functions declared in files whose name contains ``foo``, we modify the TOML file as follows: .. code-block:: toml # Only keep elements declared in files matching the pattern match_files = ".*foo.*" # String must be a regex This should give us the same as filtering by namespace for ``foo`` only: .. code-block:: console >>> import my_module >>> dir(my_module) ['A', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'f1', 'f2', 'g1', 'g2'] Wrap no argument methods as properties -------------------------------------- If we set the ``wrap_no_arg_methods_as_properties`` option to ``true``, all methods of wrapped classes with no argument will be wrapped as Python properties. For example, our class ``A`` defined in ``foo.hpp`` has the getter method ``A::x()`` with no argument. By default, this method is wrapped as a normal method in Python: .. code-block:: console >>> import my_module >>> a = my_module.A() >>> a.x() 0 >>> a.set_x(5) >>> a.x() 5 >>> a.x To wrap it as a property instead, we modify the TOML file as follows: .. code-block:: toml # If true, all methods with no argument are wrapped as properties wrap_no_arg_methods_as_properties = true Now the method ``A::x()`` is wrapped as a read-only property in Python: .. code-block:: console >>> import my_module >>> a = my_module.A() >>> a.x 0 >>> a.set_x(5) >>> a.x 5 Exclude system headers ---------------------- The option ``exclude_system_header`` is an advanced feature and the default behavior is usually what you want.