Skip to content

Commit d7e138f

Browse files
Move to nanobind (duckdb#522)
2 parents fc547d8 + 91bdd3a commit d7e138f

231 files changed

Lines changed: 5126 additions & 3891 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/code_quality.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,4 @@ jobs:
4242

4343
- name: pre-commit (--all-files)
4444
run: |
45-
uvx pre-commit run --show-diff-on-failure --color=always --all-files
45+
uvx --python 3.12 pre-commit run --show-diff-on-failure --color=always --all-files

.github/workflows/packaging_wheels.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
strategy:
3131
fail-fast: false
3232
matrix:
33-
python: [ cp314 ]
33+
python: [ cp311, cp314 ]
3434
platform:
3535
- { os: windows-2022, arch: amd64, cibw_system: win }
3636
- { os: windows-11-arm, arch: ARM64, cibw_system: win }
@@ -127,7 +127,7 @@ jobs:
127127
strategy:
128128
fail-fast: false
129129
matrix:
130-
python: [ cp310, cp311, cp312, cp313 ]
130+
python: [ cp311, cp312, cp313 ]
131131
platform:
132132
- { os: windows-2025, arch: amd64, cibw_system: win }
133133
- { os: windows-11-arm, arch: ARM64, cibw_system: win }
@@ -143,7 +143,6 @@ jobs:
143143
- { minimal: true, python: cp312 }
144144
- { minimal: true, python: cp313 }
145145
- { minimal: true, platform: { arch: universal2 } }
146-
- { python: cp310, platform: { os: windows-11-arm, arch: ARM64 } }
147146
runs-on: ${{ matrix.platform.os }}
148147
env:
149148
CCACHE_DIR: ${{ github.workspace }}/.ccache

CLAUDE.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ This is the **production** duckdb-python client — the `duckdb` package on PyPI
1010
- **Package name**: `duckdb`
1111
- **Bindings**: pybind11
1212
- **Build backend**: `duckdb_packaging.build_backend` (custom wrapper around scikit-build-core)
13-
- **Supported Python**: 3.10, 3.11, 3.12, 3.13, 3.14
13+
- **Supported Python**: 3.11, 3.12, 3.13, 3.14
1414
- **Free-threaded Python**: not supported in this client. A separate prototype client based on DuckDB's C API targets free-threading, Stable ABI, and multi-interpreter support.
1515

1616
## IMPORTANT: build before running anything
@@ -115,7 +115,7 @@ uv sync --no-build-isolation -v --reinstall -p 3.11
115115
uv sync --no-build-isolation -v --reinstall -p 3.14
116116
```
117117

118-
Supported: `3.10`, `3.11`, `3.12`, `3.13`, `3.14`. Do **not** use free-threaded variants (`3.13t`, `3.14t`) — the production client does not support them.
118+
Supported: `3.11`, `3.12`, `3.13`, `3.14`. Do **not** use free-threaded variants (`3.13t`, `3.14t`) — the production client does not support them.
119119

120120
### Build configuration reference
121121

@@ -188,8 +188,11 @@ uv run ruff format src/ tests/
188188
# Type checking (mypy — strict mode, see [tool.mypy] in pyproject.toml)
189189
uv run mypy
190190

191-
# Pre-commit hooks (configured in .pre-commit-config.yaml)
192-
uvx pre-commit run --all-files
191+
# Pre-commit hooks (configured in .pre-commit-config.yaml). Install pinned to 3.12
192+
# (cmakelang crashes on 3.14; keeps hooks off the build interpreter):
193+
uv tool install --python 3.12 pre-commit
194+
pre-commit install # git hook, runs on commit
195+
pre-commit run --all-files # run across the tree
193196
```
194197

195198
## Debugging

CMakeLists.txt

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cmake_minimum_required(VERSION 3.29)
22

3-
project(duckdb_py LANGUAGES CXX)
3+
project(duckdb_python LANGUAGES CXX)
44

55
# Always use C++17
66
set(CMAKE_CXX_STANDARD 17)
@@ -35,8 +35,26 @@ endif()
3535
# ────────────────────────────────────────────
3636
# Dependencies
3737
# ────────────────────────────────────────────
38-
# PyBind11
39-
find_package(pybind11 REQUIRED CONFIG)
38+
# nanobind (requires Python to be located first; pybind11 used to do this
39+
# internally)
40+
find_package(
41+
Python
42+
COMPONENTS Interpreter Development.Module NumPy
43+
REQUIRED)
44+
# Nanobind ships its CMake config inside site-packages/nanobind/cmake, so
45+
# find_package() can't discover it unless we set it. (scikit-build-core does
46+
# this as well)
47+
if(NOT nanobind_ROOT)
48+
execute_process(
49+
COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
50+
OUTPUT_STRIP_TRAILING_WHITESPACE
51+
OUTPUT_VARIABLE nanobind_ROOT)
52+
endif()
53+
find_package(nanobind CONFIG REQUIRED)
54+
# Build nanobind's core support library up front so the object libraries below
55+
# (which include nanobind headers via the umbrella) compile against its include
56+
# dirs + Python headers + flags.
57+
nanobind_build_library(nanobind-static)
4058

4159
# DuckDB
4260
include(cmake/duckdb_loader.cmake)
@@ -49,26 +67,42 @@ duckdb_add_library(duckdb_target)
4967

5068
# Bundle in INTERFACE library
5169
add_library(_duckdb_dependencies INTERFACE)
52-
target_link_libraries(_duckdb_dependencies INTERFACE pybind11::pybind11
70+
target_link_libraries(_duckdb_dependencies INTERFACE nanobind-static
5371
duckdb_target)
5472
# Also add include directory
5573
target_include_directories(
5674
_duckdb_dependencies
57-
INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/duckdb_py/include>
58-
)
75+
INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/include>)
5976

6077
# We link duckdb_static. Without this define, duckdb.h marks C API symbols
6178
# __declspec(dllimport) on Windows, producing unresolvable __imp_* references at
6279
# link time. No-op on non-Windows.
6380
target_compile_definitions(_duckdb_dependencies INTERFACE DUCKDB_STATIC_BUILD)
6481

82+
# Optional AddressSanitizer instrumentation of the Python binding objects ONLY.
83+
# Every binding object library consumes _duckdb_dependencies (for headers) and
84+
# _duckdb links it, so adding the flag here instruments the bindings and links
85+
# the ASAN runtime, while the engine target (duckdb_target, which does NOT
86+
# consume this) stays uninstrumented and keeps hitting the sccache cache. ASAN's
87+
# allocator is process-global, so heap errors involving the instrumented binding
88+
# code are still caught. OFF by default; enable with -DDUCKDB_PY_ASAN=ON.
89+
option(DUCKDB_PY_ASAN
90+
"Instrument the Python binding objects with AddressSanitizer" OFF)
91+
if(DUCKDB_PY_ASAN)
92+
target_compile_options(
93+
_duckdb_dependencies INTERFACE -fsanitize=address -fno-omit-frame-pointer
94+
-g)
95+
target_link_options(_duckdb_dependencies INTERFACE -fsanitize=address)
96+
endif()
97+
6598
# ────────────────────────────────────────────
6699
# Descend into the real DuckDB‑Python sources
67100
# ────────────────────────────────────────────
68-
add_subdirectory(src/duckdb_py)
101+
add_subdirectory(src)
69102

70-
pybind11_add_module(
103+
nanobind_add_module(
71104
_duckdb
105+
NB_STATIC
72106
$<TARGET_OBJECTS:python_src>
73107
$<TARGET_OBJECTS:python_arrow>
74108
$<TARGET_OBJECTS:python_common>
@@ -77,7 +111,6 @@ pybind11_add_module(
77111
$<TARGET_OBJECTS:python_native>
78112
$<TARGET_OBJECTS:python_numpy>
79113
$<TARGET_OBJECTS:python_pandas>
80-
$<TARGET_OBJECTS:python_pybind11>
81114
$<TARGET_OBJECTS:python_connection>
82115
$<TARGET_OBJECTS:python_expression>
83116
$<TARGET_OBJECTS:python_relation>

CONTRIBUTING.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@
44

55
See the [instructions on duckdb.org](https://duckdb.org/docs/stable/dev/building/python).
66

7+
### Pre-commit hooks
8+
9+
Formatting and linting run through [pre-commit](https://pre-commit.com). Install it pinned to Python 3.12 (the `cmake-format` hook's `cmakelang` dependency crashes on 3.14) so the hooks stay independent of your build interpreter, which may be 3.13 or 3.14t:
10+
11+
```bash
12+
uv tool install --python 3.12 pre-commit
13+
pre-commit install # git hook, runs on `git commit`
14+
pre-commit run --all-files # run across the tree
15+
```
16+
17+
The same checks run in CI.
18+
719
## General Guidelines
820

921
### **Did you find a bug?**

duckdb/experimental/spark/_typing.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@
1919
from collections.abc import Callable, Iterable, Sized
2020
from typing import Literal, TypeVar
2121

22-
from numpy import float32, float64, int32, int64, ndarray
2322
from typing_extensions import Protocol, Self
2423

24+
from numpy import float32, float64, int32, int64, ndarray
25+
2526
F = TypeVar("F", bound=Callable)
2627
T_co = TypeVar("T_co", covariant=True)
2728

external/duckdb

Submodule duckdb updated 65 files

pyproject.toml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ dynamic = ["version"]
1010
description = "DuckDB in-process database"
1111
readme = "README.md"
1212
keywords = ["DuckDB", "Database", "SQL", "OLAP"]
13-
requires-python = ">=3.10.0"
13+
requires-python = ">=3.11.0"
1414
classifiers = [
1515
"Development Status :: 5 - Production/Stable",
1616
"License :: OSI Approved :: MIT License",
@@ -25,7 +25,6 @@ classifiers = [
2525
"Programming Language :: Python",
2626
"Programming Language :: Python :: 3",
2727
"Programming Language :: Python :: 3 :: Only",
28-
"Programming Language :: Python :: 3.10",
2928
"Programming Language :: Python :: 3.11",
3029
"Programming Language :: Python :: 3.12",
3130
"Programming Language :: Python :: 3.13",
@@ -63,8 +62,12 @@ build-backend = "duckdb_packaging.build_backend"
6362
backend-path = ["./"]
6463
requires = [
6564
"scikit-build-core>=0.11.4",
66-
"pybind11[global]>=2.6.0",
65+
"nanobind>=2.0",
6766
"setuptools_scm>=8.0",
67+
# numpy C API headers (PyArray_Empty in the result path). Building against numpy 2.x yields a
68+
# binary compatible with numpy >=1.19 AND 2.x at runtime (numpy 2.0 backward-compat), so the
69+
# unpinned runtime numpy range is preserved. Build-time only; the runtime numpy dep is unchanged.
70+
"numpy>=2.0",
6871
]
6972

7073
[tool.scikit-build]
@@ -246,6 +249,7 @@ test = [ # dependencies used for running tests
246249
"pytest-reraise",
247250
"pytest-timeout",
248251
"pytest-timestamper",
252+
"pytest-xdist", # parallel test execution (-n auto); without this `uv sync --reinstall` prunes a manual install
249253
"coverage",
250254
"gcovr; sys_platform != 'win32' or platform_machine != 'ARM64'",
251255
"gcsfs; sys_platform != 'win32' or platform_machine != 'ARM64'",
@@ -294,7 +298,7 @@ pypi = [ # dependencies used by the pypi cleanup script
294298
build = [
295299
"cmake>=3.29.0",
296300
"ninja>=1.10",
297-
"pybind11[global]>=2.6.0",
301+
"nanobind>=2.0",
298302
"scikit_build_core>=0.11.4",
299303
"setuptools_scm>=8.0",
300304
]
@@ -474,6 +478,11 @@ before-build = ["yum install -y ccache"]
474478

475479
[tool.cibuildwheel.macos]
476480
before-build = ["brew install ccache"]
481+
# nanobind uses C++17 aligned new/delete (std::align_val_t), which the runtime only provides on macOS 10.13+.
482+
# cp311's framework defaults to a 10.9 deployment target (used for the x86_64 slice of x86_64/universal2
483+
# wheels), so nanobind fails to compile there; cp312+ frameworks already target 10.13+. Pin 10.14 so every CPython
484+
# version builds (arm64 slices are 11.0 regardless).
485+
environment = { MACOSX_DEPLOYMENT_TARGET = "10.14" }
477486

478487
[tool.cibuildwheel.windows]
479488
before-build = ["choco install ccache"]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# this is used for clang-tidy checks
22
add_subdirectory(pyrelation)
33
add_subdirectory(pyexpression)
4-
add_subdirectory(pybind11)
54
add_subdirectory(numpy)
65
add_subdirectory(native)
76
add_subdirectory(jupyter)
@@ -25,6 +24,7 @@ add_library(
2524
pyrelation.cpp
2625
pyresult.cpp
2726
pystatement.cpp
27+
pyutil.cpp
2828
python_dependency.cpp
2929
python_import_cache.cpp
3030
python_replacement_scan.cpp

0 commit comments

Comments
 (0)