Using a TOML configuration file

A TOML configuration file can optionally be provided to clair-c2py to customize the generated Python bindings. It has the same name as the module, with a .toml extension. If this configuration file is absent, clair uses default options.

Generating a template TOML file

The command

clair-c2py --gen-default-config my_module.cpp

generates a my_module.toml file with default configurations. You can then edit this file to customize the behavior, and provide it with your sources. A basic documentation of each option is provided as comments in the generated file.

TOML Configuration Options

Option

Description

package_name

Name of the package (optional). Module name will be package_name.module_filename.

documentation

The documentation string of the module.

namespaces

Space-separated list of namespaces (e.g. "A A::B"). If non-empty, only elements declared in exactly these namespaces are wrapped.

match_names

Regex pattern to match element names. Only elements whose fully qualified name matches are wrapped.

reject_names

Regex pattern to reject element names. Elements whose fully qualified name matches are excluded.

match_files

Regex pattern to filter by source file. Only elements declared in matching files are wrapped.

wrap_no_arg_methods_as_properties

If true, methods with no arguments are wrapped as read-only properties. Default: false.

exclude_system_headers

If true (default), elements declared in system headers (-isystem) are ignored.

Warning

Beware of regular expressions (Regex), they are not simple strings.
For example, N::* does not match N::f, while N::.* does !
Also note that the qualified name starts with ::. So to match the start of a name, use ^::N::f.

Example

We consider a simple module my_module.cpp which wraps some functions and classes from two files and two namespaces.

// foo.hpp
#include <iostream>
namespace foo {
  inline void f1() { std::cout << "foo::f1" << std::endl; }
  inline void f2() { std::cout << "foo::f2" << std::endl; }
  inline void g1() { std::cout << "foo::g1" << std::endl; }
  inline void g2() { std::cout << "foo::g2" << std::endl; }
  class A {
    int x_;

    public:
    int x() const { return x_; }
    void set_x(int x) { x_ = x; }
  };
} // namespace foo
// bar.hpp
#include <iostream>
namespace bar {
  inline void h1() { std::cout << "bar::h1" << std::endl; }
  inline void h2() { std::cout << "bar::h2" << std::endl; }
} // namespace bar
// my_module.cpp

#include <c2py/c2py.hpp>
#include "foo.hpp"
#include "bar.hpp"

The initial TOML file looks as follows:

# Name of the package [optional].
# Name of the module will be package_name.module_filename
package_name = "my_package"

# The documentation string of the module. 
documentation = "This is an awesome module generated by c2py/clair."

# 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 bar"

# Match/reject elements according to a regex (LLVM regex)
# If match_names/match_names are both set, the algorithm is 
#  for X of fully qualified name qname
#    if match_names and match_names does not match Qname : skip X
#    if reject_names and reject_names matches Qname : skip X
#    else keep X.
#
match_names = ""
reject_names = ""

# Only keep elements declared in files matching the pattern
match_files = "" # String must be a regex

# If true, transform methods with no arguments into a read only property
wrap_no_arg_methods_as_properties = false

# [Advanced] If yes (default), all elements (class/enum/functions)
# declared in headers included with -isystem are ignored for better performance.
exclude_system_headers = true

After generating the Python bindings and compiling, we obtain:

>>> 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.

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:
# 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:

>>> 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:
# 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:

>>> 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:

# 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:

>>> 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:

# Only keep elements declared in files matching the pattern
match_files = ".*foo.*" # String must be a regex

We obtain:

>>> import my_module
>>> dir(my_module)
['A', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'f1', 'f2', 'g1', 'g2']

Wrap no argument methods as properties

If wrap_no_arg_methods_as_properties is set to true, all methods of wrapped classes without argument are 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:
>>> import my_module
>>> a = my_module.A()
>>> a.x()
0
>>> a.set_x(5)
>>> a.x()
5
>>> a.x
<built-in method x of my_package.my_module.A object at 0x101248a10>

To wrap it as a property instead, we modify the TOML file as follows:

# 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:

>>> import my_module
>>> a = my_module.A()
>>> a.x
0
>>> a.set_x(5)
>>> a.x
5