Skip to content

urban233/python-c-extension-template

Repository files navigation

Python C++ Extension Template (using nanobind)

A modern, production-ready template repository for building high-performance Python C Extensions with C++23 and nanobind.

This repository is designed to help developers quickly bootstrap, compile, test, and package Python extensions written in C++ without the boilerplate. It uses modern tooling such as scikit-build-core and nanobind, offering significant binary size and compilation speed improvements over legacy tools like pybind11.


🌟 Overview & Key Features

  • Modern C++23 Base: Targets standard C++23 for performance and clean modern coding practices.
  • nanobind Integration: Leverages efficient binding mechanics with zero-copy NumPy array exchanges.
  • Cheminformatics Reference Example: Includes a simple implementation of an XYZ Chemical File Parser showing OOP class mappings, STL container bindings, memory reference policies, and Python-side utility layers.
  • Task Runner: Automates routine tasks (formatting, linting, building, and testing) via a zero-dependency Python-based helper runner (pymake).
  • Cross-Platform Compilation: Supports Windows, macOS, and Linux out of the box with scikit-build-core and conda environments.

📂 Project Structure

python-c-extension-template/
├── src/                     # C++ Source Code & Bindings
│   ├── Atom.hpp             # Definition of the Atom C++ structure
│   ├── Molecule.hpp         # Declaration of the Molecule C++ parser class
│   ├── Molecule.cpp         # Parsing and calculation implementations
│   ├── ext.cpp              # Module binding definition (nanobind mapping)
│   └── CMakeLists.txt       # C++ build settings (CMake targets)
├── python_c_ext/            # Python Package Boundary
│   ├── _ext.pyi             # Autogenerated stub files for IDE type autocompletion
│   ├── __init__.py          # Main package exposure
│   └── xyz_utils.py         # Python-side helper library (NumPy coordinate/distance calculations)
├── tests/                   # Test Suite
│   └── test_xyz_parser.py   # pytest suite validating C++ bindings and Python integration
├── pyproject.toml           # PEP 517/518 build system config (scikit-build-core)
├── pymakefile.py            # Automated tasks runner script
├── pymake.bat / .sh         # Platform-specific entrypoint scripts for automated tasks
├── environment.yml          # Development environment specification
└── README.md                # Project documentation

🧪 Quick-Start Chemistry Example

The template contains a simple chemical file parser example under the ext namespace:

  1. C++ Layer (Atom, Molecule): Implements structured files parsing (standard XYZ coordinate files), geometric center (center of mass) computation, and case-sensitive elemental filtering in native C++.
  2. Binding Layer (ext.cpp): Maps C++ types to Python classes. Demonstrates:
    • STL automatic mappings (std::string and std::vector) via <nanobind/stl/...> headers.
    • Internal reference lifetime management (nb::rv_policy::reference_internal) on atom lists.
    • Python property mapping (def_rw, def_prop_ro) representing C++ fields and getter methods.
  3. Python Utility Layer (xyz_utils.py): Demonstrates seamless integration with Python scientific libraries, transforming the C++ coordinates list into a 2D NumPy array with zero-copy conversion, and performing rapid pairwise distance matrix calculations.

🛠️ Prerequisites

  • Miniforge3 (or another Conda distribution) installed on your system.

⚙️ Environment Setup

To develop or run the extension locally, you must create a .env_path file in the project root directory. This file should contain exactly one line: the absolute path to the root directory of the conda environment you wish to use.

.env_path Examples

Windows:

C:\Users\username\miniconda3\envs\python_c_ext_dev

macOS:

/Users/username/miniconda3/envs/python_c_ext_dev

Linux:

/home/username/miniconda3/envs/python_c_ext_dev

🤖 Development & Automation

This project uses a standalone Python task runner called pymake to automate development tasks. It provides a make-like experience using only the Python standard library.

Running Tasks

Use the platform-specific wrapper scripts to run tasks:

  • Windows: .\pymake.bat <task> [args]
  • Linux/macOS: ./pymake.sh <task> [args]

To see all available tasks and their options, run:

.\pymake.bat --help

Available Tasks

Task Description
format Format the Python codebase with Ruff.
lint Run static analysis (linting) on Python code with Ruff.
format_cpp Format C++ code with clang-format.
lint_cpp Run style linting on C++ code (check-only mode).
test Execute the test suite with pytest.
build_whl Build the wheel file directly using the Conda environment (cross-platform).
package Package the project using conda-pack.

Example: Run the test suite:

.\pymake.bat test verbose=true

⚙️ Build Variable Reference

If you are compiling the CMake project manually or using an IDE (such as CLion or VS Code), configure the following CMake variables pointing to your conda environment paths:

  • Python_ROOT_DIR: Root directory of the conda environment.
  • CMAKE_PREFIX_PATH: Paths to environment library directories (for dependency resolution).
  • NUMPY_INCLUDE_DIR: Include directory path for NumPy headers.

IDE Configuration Example (Windows)

-DPython_ROOT_DIR="C:\Users\YourUsername\Miniforge3\envs\python_c_ext_dev" 
-DCMAKE_PREFIX_PATH="C:\Users\YourUsername\Miniforge3\envs\python_c_ext_dev;C:\Users\YourUsername\Miniforge3\envs\python_c_ext_dev\Library;C:\Users\YourUsername\Miniforge3\envs\python_c_ext_dev\Lib\site-packages" 
-DNUMPY_INCLUDE_DIR="C:\Users\YourUsername\Miniforge3\envs\python_c_ext_dev\Lib\site-packages\numpy\_core\include"

About

A modern, production-ready template repository for building high-performance Python C Extensions with C++23 and nanobind.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Contributors