Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Manually add custom bindings

Litgen normally generates bindings automatically from C++ headers, but sometimes you may want to extend the API with extra methods or functions that are not present in the C++ code. LitgenOptions.custom_bindings lets you do this without modifying your C++ headers.

You can attach:

  • extra methods/properties to a C++ class,

  • free functions to a C++ namespace (shown as a Python submodule),

  • free functions to the main module.

Each injection consists of:

  • stub code (Python declarations added to the generated .pyi file),

  • pydef code (C++ binding code inserted into the generated binding .cpp file, using pybind11 or nanobind syntax).

Placeholders are available inside pydef_code:

PlaceholderRoleExpansion (example)
LG_CLASSThe current bound C++ class objectpyNsRootNs_ClassFoo (of type nb::class_ or py::class_)
LG_SUBMODULEThe current submodule (for a namespace)pyNsRootNs (a nb::module_ or py::module_)
LG_CPP_CLASS_NAMEThe C++ name of the current class, with its template argumentsRootNs::Foo, or ImVector<int> for a template specialization
LG_MODULEThe main Python module objectm (the top-level module)

These placeholders are automatically replaced by litgen during C++ binding code generation. For a template class, the custom code is emitted once per specialization: use LG_CPP_CLASS_NAME in your lambdas to name the concrete type (e.g. [](LG_CPP_CLASS_NAME& self) { ... }). You can safely use them inside your pydef_code snippets without declaring them yourself.

Note about pydef_code syntax:

  • You can use either pybind11 or nanobind syntax in pydef_code, depending on which backend you are using.

  • Since the pydef_code is inserted into a function, limit yourself to statements that are valid inside a function: no function/class definitions, no #include. Use lambdas for small helpers.

  • When writing lambdas, fully qualify C++ types if the class/namespace isn’t open (e.g. const RootNs::Foo& self).

  • Argument helpers differ by backend (py::arg vs nb::arg). Use the one matching your active backend.

Example C++ code

This page demonstrates how to add custom bindings to the following C++ code.

We will be adding custom bindings to the class RootNs::Foo, the namespace RootNs, and to the main module.

Custom bindings for a class

options.custom_bindings.add_custom_bindings_to_class(qualified_class, stub_code, pydef_code), lets us extend the generated Python bindings with extra methods, properties, or static methods.

Args:

  • qualified_class: Fully qualified C++ class name (e.g. "RootNs::Foo").

  • stub_code: Python stub declarations to be inserted into the generated stub (“.pyi”) file. These should be written in normal Python syntax with type annotations.

  • pydef_code: Custom binding code in C++ (pybind11/nanobind syntax). You can use the placeholder LG_CLASS to refer to the bound py::class_ / nb::class_ object.

Custom bindings for C++ namespace / Python submodule

options.custom_bindings.add_custom_bindings_to_submodule(qualified_namespace, stub_code, pydef_code), lets us extend the generated Python bindings with extra functions.

Args:

  • qualified_namespace: Fully qualified C++ namespace name (e.g. “RootNs”).

  • stub_code: Python stub declarations to be inserted into the generated stub (“.pyi”) file. These should be written in normal Python syntax with type annotations. Functions here should be decorated with @staticmethod. Explanation for this: in stubs, namespaces are represented as proxy classes. Thus, functions must be declared as @staticmethod to indicate they are module-level, not instance methods.

  • pydef_code: Custom binding code in C++ (pybind11/nanobind syntax). You can use the placeholder LG_SUBMODULE to refer to the bound submodule object.

Namespace stubs appear as a proxy class.
When declaring functions in a C++ namespace (Python submodule) in the stub, mark them with @staticmethod. These are module-level functions, not instance methods.

Custom bindings for the main module

options.custom_bindings.add_custom_bindings_to_main_module(stub_code, pydef_code), lets us extend the generated Python bindings with extra functions in the main module.

Args:

  • stub_code: Python stub declarations to be inserted into the generated stub (“.pyi)” file. These should be written in normal Python syntax with type annotations.

  • pydef_code: Custom binding code in C++ (pybind11/nanobind syntax). You can use the placeholder LG_MODULE to refer to the bound module object.

Generated code (with custom bindings)

We may now call litgen.generate_code to generate the bindings, which will include our custom additions. The generated code is shown below.

Loading...

Note about ordering:

If you call add_custom_code_to_* multiple times for the same target (class/namespace/module), snippets are emitted in the order they were added.