Self-registering nanobind helpers (import as easybind)
Project description
easybind
Simple self-registering helpers for distributed nanobind bindings.
Install
pip install easybind
import easybind
from easybind import sample # optional demo module
PyPI project: easybind (same name as the import).
Version: Set by Git tags at build time (v0.1.0, …) via setuptools-scm; see RELEASING.md. At runtime, easybind._version.__version__ is written when the wheel is built (or use importlib.metadata.version("easybind")).
Local dev / clangd
An editable install configures CMake under ./build/ and generates build/compile_commands.json, which clangd picks up via .clangd — same database as the Python build, no second configure step:
uv pip install -e . # or: pip install -e .
If you need compile_commands.json without pip, run scripts/clangd-update.sh (plain cmake -S . -B build …).
Python layer
The Python package is implemented as native extensions. It exposes:
easybind(core helpers and macros)easybind.module(module tree API)easybind.sample(demo bindings)
Build-time SDK
Installed wheels ship CMake helpers under easybind/cmake/:
easybind_pip.cmake—easybind_pip_setup()finds Python,nanobind(pip),libeasybind, and include roots for#include <easybind/...>, then pulls ineasybind_dependencies.cmake. Helpers:easybind_pip_link_magic_enum(target),easybind_pip_set_rpath_next_to_easybind(target easybind_pkg_dir).easybind_dependencies.cmake— pins nanobind, magic_enum, reflect-cpp (same tags as this repo). Useeasybind_fetch_third_party_deps()to pull all three, oreasybind_fetch_nanobind()/easybind_fetch_magic_enum()/easybind_fetch_reflect_cpp()when you only need a subset (e.g. pip already provides nanobind, so call onlyeasybind_fetch_magic_enum()).
Typical consumer bootstrap:
find_package(Python REQUIRED COMPONENTS Interpreter Development.Module)
execute_process(COMMAND "${Python_EXECUTABLE}" -c
"import pathlib, easybind; print(pathlib.Path(easybind.__file__).resolve().parent / 'cmake' / 'easybind_pip.cmake')"
OUTPUT_VARIABLE _eb_pip OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY)
include("${_eb_pip}")
easybind_pip_setup()
easybind_fetch_magic_enum() # if you include easybind headers that need magic_enum
When developing inside this repository, easybind_add_extension(...) is defined in the top-level CMakeLists.txt (not shipped in the wheel).
Bumping {distribution}~=… pins (downstream projects)
Use easybind.devtools or the easybind-pin-pyproject CLI to rewrite every compatible-release line {name}~=X.Y.Z in a pyproject.toml for any PyPI distribution name name (not only easybind). Examples:
- cppdantic pinning easybind in several tables.
- A future project pinning cppdantic the same way: pass
--distribution cppdantic.
CLI (defaults: distribution=easybind, version = highest v* tag on GitHub — repo URL taken from that distribution’s PyPI metadata; run from the tree that contains pyproject.toml):
easybind-pin-pyproject --dry-run
easybind-pin-pyproject
easybind-pin-pyproject --from-pypi # PyPI “latest published” instead of GitHub tags
easybind-pin-pyproject --from-github # same as no flag; optional OWNER/REPO override
easybind-pin-pyproject --from-github ORG/REPO
easybind-pin-pyproject --installed
easybind-pin-pyproject --version 0.2.3
easybind-pin-pyproject --distribution cppdantic
easybind-pin-pyproject --pyproject /path/to/pyproject.toml
Use GITHUB_TOKEN for private GitHub repos or higher API rate limits.
GitHub tags API: responses can be a few seconds behind right after you push a new v* tag. If easybind-pin-pyproject --dry-run still shows the previous release, wait briefly and run again (or pin with --version until the API catches up).
Other devtools CLIs (install easybind first — pip install -e . from this repo, or PyPI):
easybind-release-tag --dry-run # tag message uses [project].name from pyproject.toml
easybind-wait-pypi # poll PyPI until pins resolve (downstream CI)
Without installing, run the same modules with PYTHONPATH pointing at this repo’s src (e.g. PYTHONPATH=src python -m easybind.devtools.release_tag).
Library:
from easybind.devtools import (
bump_compatible_pins_in_file,
fetch_pypi_version,
github_owner_repo_from_pypi_distribution,
latest_release_version_from_github,
)
# Match CLI default: latest v* tag on GitHub (same as easybind-pin-pyproject with no version flags)
or_ = github_owner_repo_from_pypi_distribution("easybind")
ver = latest_release_version_from_github(or_)
bump_compatible_pins_in_file("pyproject.toml", "easybind", ver)
# Or: latest published on PyPI (CLI: --from-pypi)
ver = fetch_pypi_version("easybind")
bump_compatible_pins_in_file("pyproject.toml", "easybind", ver)
Shorthands bump_easybind_compatible_pins / bump_easybind_compatible_pins_in_file remain for distribution=\"easybind\" only.
CI (downstream): easybind-wait-pypi wraps wait_pypi_for_compatible_pin in easybind.devtools — poll PyPI until the pinned version exists.
Core idea
- Each namespace/module defines a
ModuleNodeand a bind callback. - The module entry point calls
apply_initto run the callback and recurse. - Submodules are created on demand and registered in
sys.modules. - Shared-object modules are marked so recursion stops at their boundary.
- A minimal sample module lives at
easybind.sample.
Developer note: layout rules
__init__.cppmarks the Python boundary (NB_MODULE) for a package/module.node.cpp/.hppis the pure C++ module-tree core.ns_module.hppdefines theEASYBIND_NS_MODULE*macros.- Directory layout mirrors namespaces and Python modules.
Smallest possible example
1) Define a C++ type (normal code)
#pragma once
#include <string>
struct PeerInfo {
std::string peer_id;
int transport = 0;
};
2) Bind it in a separate file (module node)
#include <easybind/bind.hpp>
struct PeerInfo; // forward declare or include the header
EASYBIND_NS_MODULE(my_pkg, m, false, {
nanobind::class_<PeerInfo>(m, "PeerInfo")
.def(nanobind::init<>())
.def_rw("peer_id", &PeerInfo::peer_id)
.def_rw("transport", &PeerInfo::transport);
});
3) Module entry point (shared-object boundary)
Use this only for the package that has its own .so and NB_MODULE entry point.
Do not pair it with EASYBIND_NS_MODULE for the same my_pkg name.
If you need to add bindings from another file, use EASYBIND_NS_MODULE_EXTEND
to extend the same module node instead.
#include <easybind/bind.hpp>
EASYBIND_NS_MODULE_SHARED_OBJECT(my_pkg, my_pkg, m, true, {
m.doc() = "my_pkg module";
});
4) Extend from another file
#include <easybind/bind.hpp>
EASYBIND_NS_MODULE_EXTEND(my_pkg, m, {
m.def("ping", [] { return "pong"; });
});
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file easybind-0.2.15.tar.gz.
File metadata
- Download URL: easybind-0.2.15.tar.gz
- Upload date:
- Size: 35.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98f2910b0527ece878ce60410b34efb3b824be359afc7034bd4ba8d59f22cfa7
|
|
| MD5 |
9067cf1eec4185bdef0ce967b42ed802
|
|
| BLAKE2b-256 |
4bf362edaf3b5990b9614ce9cb77187bb265d5c9aadebf159f5498ae2a09fa17
|
File details
Details for the file easybind-0.2.15-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: easybind-0.2.15-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 305.3 kB
- Tags: CPython 3.14, manylinux: glibc 2.27+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b0f9f6db1e852fde7a1c745b6bef43911dd733c40588fd4a43e2fd7b74ad8265
|
|
| MD5 |
844a8bd2550c96207b113ef37531e29c
|
|
| BLAKE2b-256 |
e535faeb73a624a592d91ab34ad40a70b1ceb5ac9a643490c4c2d5e69923c381
|
File details
Details for the file easybind-0.2.15-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: easybind-0.2.15-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 305.2 kB
- Tags: CPython 3.13, manylinux: glibc 2.27+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7f95181ca29092b60695f9653888a49a1f8a35893ddc182c80b383860468c6bd
|
|
| MD5 |
8828037225ef16bb55df9546e7556678
|
|
| BLAKE2b-256 |
12494c9a75c5def02fb3339c806480a410aeb831461e4de58888b5d98e18d5d8
|
File details
Details for the file easybind-0.2.15-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: easybind-0.2.15-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 305.2 kB
- Tags: CPython 3.12, manylinux: glibc 2.27+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
711e2da7bce9fe3bc3d14880aaedade14537993c31d66d4e641594e2845d9dfd
|
|
| MD5 |
03631b9e871541e55e7d8a32b6febca4
|
|
| BLAKE2b-256 |
b174c52ee7e1992dce7962f95b3207ed1ef054544c8af35bfc53f87e76b12702
|
File details
Details for the file easybind-0.2.15-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: easybind-0.2.15-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 305.6 kB
- Tags: CPython 3.11, manylinux: glibc 2.27+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3aca06c3617c977615e218b9a49895abfa27511cd321518d2921e73b6a121c1d
|
|
| MD5 |
5bf5a084a5f93d845eca3b3128af5e53
|
|
| BLAKE2b-256 |
77a886f8145dfaac7ff07ab316006da297f7f9c1a0ad92f0138fd4c8d9464d19
|