Skip to main content

Self-registering nanobind helpers (import as pymergetic.easybind)

Project description

easybind

Simple self-registering helpers for distributed nanobind bindings.

Install

pip install pymergetic-easybind
import pymergetic.easybind
from pymergetic.easybind import sample  # optional demo module

PyPI project: pymergetic-easybind (install name). Import: pymergetic.easybind.

Version: Set by Git tags at build time (v0.1.0, …) via setuptools-scm; see RELEASING.md. At runtime, pymergetic.easybind._version.__version__ is written when the wheel is built (or use importlib.metadata.version("pymergetic-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:

  • pymergetic.easybind (core helpers and macros)
  • pymergetic.easybind.module (module tree API)
  • pymergetic.easybind.sample (demo bindings)

Build-time SDK

Installed wheels ship CMake helpers under pymergetic/easybind/cmake/:

  • easybind_pip.cmakeeasybind_pip_setup() finds Python, nanobind (pip), libeasybind, and include roots for #include <pymergetic/easybind/...>, then pulls in easybind_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). Use easybind_fetch_third_party_deps() to pull all three, or easybind_fetch_nanobind() / easybind_fetch_magic_enum() / easybind_fetch_reflect_cpp() when you only need a subset (e.g. pip already provides nanobind, so call only easybind_fetch_magic_enum()).

Typical consumer bootstrap:

find_package(Python REQUIRED COMPONENTS Interpreter Development.Module)
execute_process(COMMAND "${Python_EXECUTABLE}" -c
  "import pathlib, pymergetic.easybind; print(pathlib.Path(pymergetic.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 pymergetic.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 pymergetic-easybind). Examples:

  • cppdantic pinning pymergetic-easybind in several tables.
  • A future project pinning cppdantic the same way: pass --distribution cppdantic.

CLI (defaults: distribution=pymergetic-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 pymergetic-easybind first — uv 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 pymergetic.easybind.devtools.release_tag).

Library:

from pymergetic.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("pymergetic-easybind")
ver = latest_release_version_from_github(or_)
bump_compatible_pins_in_file("pyproject.toml", "pymergetic-easybind", ver)

# Or: latest published on PyPI (CLI: --from-pypi)
ver = fetch_pypi_version("pymergetic-easybind")
bump_compatible_pins_in_file("pyproject.toml", "pymergetic-easybind", ver)

Shorthands bump_easybind_compatible_pins / bump_easybind_compatible_pins_in_file remain for distribution=\"pymergetic-easybind\" only.

CI (downstream): easybind-wait-pypi wraps wait_pypi_for_compatible_pin in pymergetic.easybind.devtools — poll PyPI until the pinned version exists.

Core idea

  • Each namespace/module defines a ModuleNode and a bind callback.
  • The module entry point calls apply_init to 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 pymergetic.easybind.sample.

Developer note: layout rules

  • __init__.cpp marks the Python boundary (NB_MODULE) for a package/module.
  • node.cpp/.hpp is the pure C++ module-tree core.
  • ns_module.hpp defines the EASYBIND_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 <pymergetic/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 <pymergetic/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 <pymergetic/easybind/bind.hpp>

EASYBIND_NS_MODULE_EXTEND(my_pkg, m, {
    m.def("ping", [] { return "pong"; });
});

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pymergetic_easybind-0.2.16.tar.gz (63.5 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pymergetic_easybind-0.2.16-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (308.0 kB view details)

Uploaded CPython 3.14manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

pymergetic_easybind-0.2.16-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (307.9 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

pymergetic_easybind-0.2.16-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (307.9 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

pymergetic_easybind-0.2.16-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (308.3 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

File details

Details for the file pymergetic_easybind-0.2.16.tar.gz.

File metadata

  • Download URL: pymergetic_easybind-0.2.16.tar.gz
  • Upload date:
  • Size: 63.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pymergetic_easybind-0.2.16.tar.gz
Algorithm Hash digest
SHA256 bc922a234af88fe092effd2185a0508c0ed7b1e28f34a6c7d15217e2bfb5e878
MD5 27f415715e4d60f4d4a843fbed159649
BLAKE2b-256 b731be8bb161bd048cecd8d30cec665436afcf141966b5af415a4db0c53ae969

See more details on using hashes here.

File details

Details for the file pymergetic_easybind-0.2.16-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pymergetic_easybind-0.2.16-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 cf128d88de227abf1013cf2506e47516b81ffcae9383ff849a871a6d35fd7a57
MD5 db9b93e802ad71f49301663a806a795c
BLAKE2b-256 e2a8e706b24fb63edfdd8c91c2f328900a582f94dae34f8a2b7cd612af798373

See more details on using hashes here.

File details

Details for the file pymergetic_easybind-0.2.16-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pymergetic_easybind-0.2.16-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 58a09acc940b1e3f07f571a21cfecea646b30d8cb982eeb6177c381ee5595b46
MD5 e7d47ed0297536a75e3f0b0ab8ab8ad3
BLAKE2b-256 20758f1d908d1c26d4255bc26ddce4e84a83066e003d212dade5e431001e4f20

See more details on using hashes here.

File details

Details for the file pymergetic_easybind-0.2.16-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pymergetic_easybind-0.2.16-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 e1d68a3b223f3bd75bef95bfb9272799016a75bb8f6d6de87cd54c18a5416a34
MD5 78f7e88690fc970157b338abc9e80147
BLAKE2b-256 b62c3a281372eb4453aea40af16b8d9e7f504e0bb8d5ff0efb9c516c7d1a8146

See more details on using hashes here.

File details

Details for the file pymergetic_easybind-0.2.16-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pymergetic_easybind-0.2.16-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 546219e4bf03d5ea09835bc37fd1d2afa6470ca098395cad10ff5b0efdf4607b
MD5 120ba6a98ae1ff6180836ed135edeae0
BLAKE2b-256 1bc569734da29fd6917efe465e9790498dde7e87397e14f335a5da7138ba5c70

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page