Skip to content

Commit 2c5e718

Browse files
committed
Merge remote-tracking branch 'origin/master' into interop
2 parents 54c5e11 + 30bbda1 commit 2c5e718

34 files changed

+478
-175
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
fail-fast: false
2222
matrix:
2323
os: ['ubuntu-latest', 'windows-2022', 'macos-13']
24-
python: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14.0-alpha.7', 'pypy3.9-v7.3.16', 'pypy3.10-v7.3.17']
24+
python: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14.0-rc.2', 'pypy3.9-v7.3.16', 'pypy3.10-v7.3.17']
2525

2626
name: "Python ${{ matrix.python }} / ${{ matrix.os }}"
2727
runs-on: ${{ matrix.os }}

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,6 @@ __pycache__
3838
nanobind.egg-info
3939
test_*_ext*.so
4040
test_*_ext*.pyd
41-
test_*.pyi
41+
*.pyi
4242
py\.typed
4343
.mypy_cache

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
<p align="center">
1111
<picture>
12-
<source media="(prefers-color-scheme: dark)" width="800" srcset="https://rgl.s3.eu-central-1.amazonaws.com/media/uploads/wjakob/2023/03/28/nanobind_logo_dark.png">
12+
<source media="(prefers-color-scheme: dark)" width="800" srcset="https://d38rqfq1h7iukm.cloudfront.net/media/uploads/wjakob/2023/03/28/nanobind_logo_dark.png">
1313
<source media="(prefers-color-scheme: light)" width="800" srcset="https://github.com/wjakob/nanobind/raw/master/docs/images/logo.jpg">
1414
<img alt="nanobind logo" width="800" src="https://github.com/wjakob/nanobind/raw/master/docs/images/logo.jpg">
1515
</picture>
@@ -55,5 +55,5 @@ discourse:
5555

5656
The nanobind logo was designed by [AndoTwin Studio](https://andotwinstudio.com)
5757
(high-resolution download:
58-
[light](https://rgl.s3.eu-central-1.amazonaws.com/media/uploads/wjakob/2023/03/27/nanobind_logo.jpg),
59-
[dark](https://rgl.s3.eu-central-1.amazonaws.com/media/uploads/wjakob/2023/03/28/nanobind_logo_dark_1.png)).
58+
[light](https://d38rqfq1h7iukm.cloudfront.net/media/uploads/wjakob/2023/03/27/nanobind_logo.jpg),
59+
[dark](https://d38rqfq1h7iukm.cloudfront.net/media/uploads/wjakob/2023/03/28/nanobind_logo_dark_1.png)).

cmake/nanobind-config.cmake

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ endfunction()
599599
# ---------------------------------------------------------------------------
600600

601601
function (nanobind_add_stub name)
602-
cmake_parse_arguments(PARSE_ARGV 1 ARG "VERBOSE;INCLUDE_PRIVATE;EXCLUDE_DOCSTRINGS;INSTALL_TIME;EXCLUDE_FROM_ALL" "MODULE;OUTPUT;MARKER_FILE;COMPONENT;PATTERN_FILE" "PYTHON_PATH;DEPENDS")
602+
cmake_parse_arguments(PARSE_ARGV 1 ARG "VERBOSE;INCLUDE_PRIVATE;EXCLUDE_DOCSTRINGS;INSTALL_TIME;RECURSIVE;EXCLUDE_FROM_ALL" "MODULE;COMPONENT;PATTERN_FILE;OUTPUT_PATH" "PYTHON_PATH;DEPENDS;MARKER_FILE;OUTPUT")
603603

604604
if (EXISTS ${NB_DIR}/src/stubgen.py)
605605
set(NB_STUBGEN "${NB_DIR}/src/stubgen.py")
@@ -623,17 +623,23 @@ function (nanobind_add_stub name)
623623
list(APPEND NB_STUBGEN_ARGS -D)
624624
endif()
625625

626-
foreach (TMP IN LISTS ARG_PYTHON_PATH)
627-
list(APPEND NB_STUBGEN_ARGS -i "${TMP}")
626+
if (ARG_RECURSIVE)
627+
list(APPEND NB_STUBGEN_ARGS -r)
628+
endif()
629+
630+
foreach (PYTHON_PATH IN LISTS ARG_PYTHON_PATH)
631+
list(APPEND NB_STUBGEN_ARGS -i "${PYTHON_PATH}")
628632
endforeach()
629633

630634
if (ARG_PATTERN_FILE)
631635
list(APPEND NB_STUBGEN_ARGS -p "${ARG_PATTERN_FILE}")
632636
endif()
633637

634638
if (ARG_MARKER_FILE)
635-
list(APPEND NB_STUBGEN_ARGS -M "${ARG_MARKER_FILE}")
636-
list(APPEND NB_STUBGEN_OUTPUTS "${ARG_MARKER_FILE}")
639+
foreach (MARKER_FILE IN LISTS ARG_MARKER_FILE)
640+
list(APPEND NB_STUBGEN_ARGS -M "${MARKER_FILE}")
641+
list(APPEND NB_STUBGEN_OUTPUTS "${MARKER_FILE}")
642+
endforeach()
637643
endif()
638644

639645
if (NOT ARG_MODULE)
@@ -642,13 +648,38 @@ function (nanobind_add_stub name)
642648
list(APPEND NB_STUBGEN_ARGS -m "${ARG_MODULE}")
643649
endif()
644650

645-
if (NOT ARG_OUTPUT)
646-
message(FATAL_ERROR "nanobind_add_stub(): an 'OUTPUT' argument must be specified!")
651+
list(LENGTH ARG_OUTPUT OUTPUT_LEN)
652+
653+
# Some sanity hecks
654+
if (ARG_RECURSIVE)
655+
if (NOT ARG_INSTALL_TIME)
656+
if ((OUTPUT_LEN EQUAL 0) AND NOT ARG_OUTPUT_PATH)
657+
message(FATAL_ERROR "nanobind_add_stub(): either 'OUTPUT' or 'OUTPUT_PATH' must be specified when 'RECURSIVE' is set!")
658+
endif()
659+
endif()
647660
else()
648-
list(APPEND NB_STUBGEN_ARGS -o "${ARG_OUTPUT}")
649-
list(APPEND NB_STUBGEN_OUTPUTS "${ARG_OUTPUT}")
661+
if ((OUTPUT_LEN EQUAL 0) AND NOT ARG_INSTALL_TIME)
662+
message(FATAL_ERROR "nanobind_add_stub(): an 'OUTPUT' argument must be specified.")
663+
endif()
664+
if ((OUTPUT_LEN GREATER 0) AND ARG_OUTPUT_PATH)
665+
message(FATAL_ERROR "nanobind_add_stub(): 'OUTPUT' and 'OUTPUT_PATH' can only be specified together when 'RECURSIVE' is set!")
666+
endif()
667+
if (OUTPUT_LEN GREATER 1)
668+
message(FATAL_ERROR "nanobind_add_stub(): specifying more than one 'OUTPUT' requires that 'RECURSIVE' is set!")
669+
endif()
670+
endif()
671+
672+
if (ARG_OUTPUT_PATH)
673+
list(APPEND NB_STUBGEN_ARGS -O "${ARG_OUTPUT_PATH}")
650674
endif()
651675

676+
foreach (OUTPUT IN LISTS ARG_OUTPUT)
677+
if (NOT ARG_RECURSIVE)
678+
list(APPEND NB_STUBGEN_ARGS -o "${OUTPUT}")
679+
endif()
680+
list(APPEND NB_STUBGEN_OUTPUTS "${OUTPUT}")
681+
endforeach()
682+
652683
file(TO_CMAKE_PATH ${Python_EXECUTABLE} NB_Python_EXECUTABLE)
653684

654685
set(NB_STUBGEN_CMD "${NB_Python_EXECUTABLE}" "${NB_STUBGEN}" ${NB_STUBGEN_ARGS})

docs/api_cmake.rst

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -444,13 +444,46 @@ Nanobind's CMake tooling includes a convenience command to interface with the
444444
generation of all dependencies (see ``DEPENDS``). When this parameter
445445
is specified, stub generation is instead postponed to the
446446
installation phase.
447+
448+
* - ``RECURSIVE``
449+
- If specified, the stub generator automatically traverses the module
450+
hierarchy and generates a stub for each discovered submodule. The
451+
files are either placed right next to the original Python code, or
452+
relative to ``OUTPUT_PATH``.
453+
454+
In this special mode, you may pass multiple arguments ``OUTPUT`` so
455+
that CMake's dependency management can keep track of the generated
456+
files.
457+
447458
* - ``MODULE``
448-
- Specifies the name of the module that should be imported. Mandatory.
459+
- Specifies the name of the module that should be imported. Only
460+
a single module can be specified. Mandatory.
461+
449462
* - ``OUTPUT``
450-
- Specifies the name of the stub file that should be written. The path
463+
- Specifies the name of the stub (``.pyi``) file to be written. The path
451464
is relative to ``CMAKE_CURRENT_BINARY_DIR`` for build-time stub
452465
generation and relative to ``CMAKE_INSTALL_PREFIX`` for install-time
453-
stub generation. Mandatory.
466+
stub generation.
467+
468+
When ``RECURSIVE`` is set, *multiple* paths may be specified. Note
469+
that these are not actually passed to the stub generator and purely
470+
used for dependency management within CMake (e.g., to remove files
471+
when executing the ``clean`` target, or to track dependencies when
472+
stub files are subsequently consumed by other targets).
473+
474+
This parameter is generally mandatory. When ``INSTALL_TIME`` is set,
475+
it can be omitted since dependency tracking is not needed in this
476+
case.
477+
478+
* - ``OUTPUT_PATH``
479+
- Overrides the base directory in which stub files should be written.
480+
This parameter can only be used when ``INSTALL_TIME`` or
481+
``RECURSIVE`` (or both) are set.
482+
483+
The path is relative to ``CMAKE_CURRENT_BINARY_DIR`` for build-time
484+
stub generation and relative to ``CMAKE_INSTALL_PREFIX`` for
485+
install-time stub generation.
486+
454487
* - ``PYTHON_PATH``
455488
- List of search paths that should be considered when importing the
456489
module. The paths are relative to ``CMAKE_CURRENT_BINARY_DIR`` for
@@ -462,32 +495,41 @@ Nanobind's CMake tooling includes a convenience command to interface with the
462495
when :cmake:command:`nanobind_add_stub` is used for build-time stub
463496
generation. Otherwise, generator expressions should not be used.
464497
Optional.
498+
465499
* - ``DEPENDS``
466500
- Any targets listed here will be marked as a dependencies. This should
467501
generally be used to list the target names of one or more prior
468502
:cmake:command:`nanobind_add_module` declarations. Note that this
469503
parameter tracks *build-time* dependencies and does not need to be
470504
specified when stub generation occurs at install time (see
471505
``INSTALL_TIME``). Optional.
506+
472507
* - ``VERBOSE``
473508
- Show status messages generated by ``stubgen``.
509+
474510
* - ``EXCLUDE_DOCSTRINGS``
475511
- Generate a stub containing only typed signatures without docstrings.
512+
476513
* - ``INCLUDE_PRIVATE``
477514
- Also include private members, whose names begin or end with a single
478515
underscore.
516+
479517
* - ``MARKER_FILE``
480518
- Typed extensions normally identify themselves via the presence of an
481519
empty file named ``py.typed`` in each module directory. When this
482520
parameter is specified, :cmake:command:`nanobind_add_stub` will
483521
automatically generate such an empty file as well.
522+
Multiple marker file paths can be optionally passed to this parameter.
523+
484524
* - ``PATTERN_FILE``
485525
- Specify a pattern file used to replace declarations in the stub. The
486526
syntax is described in the section on :ref:`stub generation <stubs>`.
527+
487528
* - ``COMPONENT``
488529
- Specify a component when ``INSTALL_TIME`` stub generation is used.
489530
This is analogous to ``install(..., COMPONENT [name])`` in other
490531
install targets.
532+
491533
* - ``EXCLUDE_FROM_ALL``
492534
- If specified, the file is only installed as part of a
493535
component-specific installation when ``INSTALL_TIME`` stub generation

docs/api_core.rst

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,7 +1085,7 @@ Wrapper classes
10851085
*Note*: The C string will be deleted when the `str` instance is garbage
10861086
collected.
10871087

1088-
.. cpp:function:: template <typename... Args> str format(Args&&... args)
1088+
.. cpp:function:: template <typename... Args> str format(Args&&... args) const
10891089

10901090
C++ analog of the Python routine ``str.format``. Can be called with
10911091
positional and keyword arguments.
@@ -1610,7 +1610,10 @@ Casting
16101610
When the `convert` argument is set to ``true`` (the default), the
16111611
implementation may also attempt *implicit conversions* to perform the cast.
16121612

1613-
The function raises a :cpp:type:`cast_error` when the conversion fails.
1613+
The function raises an exception when the conversion fails.
1614+
If the type caster uses the CPython error API (e.g., ``PyErr_SetString()``
1615+
or ``PyErr_Format()``) to report a failure, then :cpp:class:`python_error`
1616+
is raised. Otherwise, :cpp:type:`cast_error` is raised.
16141617
See :cpp:func:`try_cast()` for an alternative that never raises.
16151618

16161619
.. cpp:function:: template <typename T, typename Derived> bool try_cast(const detail::api<Derived> &value, T &out, bool convert = true) noexcept
@@ -1632,7 +1635,10 @@ Casting
16321635
policy `policy` is used to handle ownership-related questions when a new
16331636
Python object must be created.
16341637

1635-
The function raises a :cpp:type:`cast_error` when the conversion fails.
1638+
The function raises an exception when the conversion fails.
1639+
If the type caster uses the CPython error API (e.g., ``PyErr_SetString()``
1640+
or ``PyErr_Format()``) to report a failure, then :cpp:class:`python_error`
1641+
is raised. Otherwise, :cpp:type:`cast_error` is raised.
16361642

16371643
.. cpp:function:: template <typename T> object cast(T &&value, rv_policy policy, handle parent)
16381644

@@ -1641,7 +1647,10 @@ Casting
16411647
Python object must be created. A valid `parent` object is required when
16421648
specifying a `reference_internal` return value policy.
16431649

1644-
The function raises a :cpp:type:`cast_error` when the conversion fails.
1650+
The function raises an exception when the conversion fails.
1651+
If the type caster uses the CPython error API (e.g., ``PyErr_SetString()``
1652+
or ``PyErr_Format()``) to report a failure, then :cpp:class:`python_error`
1653+
is raised. Otherwise, :cpp:type:`cast_error` is raised.
16451654

16461655
.. cpp:function:: template <typename T> object find(const T &value) noexcept
16471656

@@ -1655,7 +1664,10 @@ Casting
16551664
value policy `policy` is used to handle ownership-related questions when a
16561665
new Python objects must be created.
16571666

1658-
The function raises a :cpp:type:`cast_error` when the conversion fails.
1667+
The function raises an exception when the conversion fails.
1668+
If the type caster uses the CPython error API (e.g., ``PyErr_SetString()``
1669+
or ``PyErr_Format()``) to report a failure, then :cpp:class:`python_error`
1670+
is raised. Otherwise, :cpp:type:`cast_error` is raised.
16591671

16601672
Common binding annotations
16611673
--------------------------

docs/api_extra.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,10 @@ convert into an equivalent representation in one of the following frameworks:
11041104

11051105
.. cpp:class:: cupy
11061106

1107+
.. cpp:class:: memview
1108+
1109+
Builtin Python ``memoryview`` for CPU-resident data.
1110+
11071111
Eigen convenience type aliases
11081112
------------------------------
11091113

docs/changelog.rst

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,95 @@ Version TBD (unreleased)
3131
and later. This feature is likely to be of **great utility** to anyone who
3232
is working on porting large or interconnected extension modules from pybind11
3333
to nanobind. See the extensive :ref:`interoperability documentation <interop>`
34-
for more details.
34+
for more details. (PR `#1140 <https://github.com/wjakob/nanobind/pull/1140>`__).
35+
36+
Version 2.9.2 (Sep 4, 2025)
37+
---------------------------
38+
39+
This is a patch release to fix an issue in the new recursive stub generation feature:
40+
41+
- When creating stubs for a module, the generator must decide whether to store
42+
declarations locally (e.g., in ``foo.pyi``) or in a subdirectory (e.g., in
43+
``foo/__init__.pyi``). The latter is necessary, e.g., when ``foo`` contains
44+
submodules. However, the implemented submodule test was far too conservative
45+
and interpreted any imported module (e.g. ``import os``) as a submodule. The
46+
patch release fixes this.
47+
(commit `a65e1b
48+
<https://github.com/wjakob/nanobind/commit/a65e1b36ec0670e7c8d7a3bacfa5cff425fe92fe>`__).
49+
50+
Version 2.9.1 (Sep 4, 2025)
51+
---------------------------
52+
53+
This is a patch release to fix a regression in the CMake build system:
54+
55+
- nanobind 2.9.0 internally adopted the CMake command ``cmake_path()`` to
56+
normalize paths. This was done for cosmetic reasons, since it improves the
57+
readability of generated commands. However, ``cmake_path()`` is only
58+
available on CMake 3.20+, while nanobind officially supports CMake 3.15+.
59+
Version 2.9.1 removes the full path normalization.
60+
(commit `f703fd
61+
<https://github.com/wjakob/nanobind/commit/f703fd403aed32cd903f5cfdff414bdfd13f6430>`__).
62+
63+
64+
Version 2.9.0 (Sep 4, 2025)
65+
---------------------------
66+
67+
- Nanobind's CMake stub generation command :cmake:command:`nanobind_add_stub`
68+
can now automatically traverse submodule hierarchies and generate :ref:`many
69+
stub files at once <stubgen_recursive_cmake>`. (PR `#1148
70+
<https://github.com/wjakob/nanobind/pull/1148>`__).
71+
72+
- Recursive stub generation now correctly organizes stub files hierarchically (e.g.
73+
``my_ext.pyi`` versus ``my_ext/__init__.pyi``). (commits `ad9d3fe
74+
<https://github.com/wjakob/nanobind/commit/ad9d3fe4a631b25dbef0eca54a4ac5f96f064596>`__,
75+
`620c1c1
76+
<https://github.com/wjakob/nanobind/commit/620c1c13430bed882d76d2c12efadaa4e9f3f37d>`__).
77+
78+
- The stub generator now exposes NumPy array types as ``NDArray[np.float32]``
79+
(or similar) instead of ``Annotated[ArrayLike, dict(...)]`` to simplify
80+
type-checking. (PR `#1149 <https://github.com/wjakob/nanobind/pull/1149>`__,
81+
commit `37dd2c
82+
<https://github.com/wjakob/nanobind/commit/37dd2c6d6a44f9657fb08c46b2d5e5c1623a1048>`__).
83+
84+
- Nanobind (finally!) correctly implements in-place updates to dicts, lists,
85+
etc. Previously, a C++ operation like
86+
87+
.. code-block:: cpp
88+
89+
nb::dict my_dict = ...;
90+
my_dict["key"] += 1;
91+
92+
performed an addition but failed to reassign the updated value to the
93+
original key. (PR `#1119 <https://github.com/wjakob/nanobind/pull/1119>`__).
94+
95+
- When cast operations like :cpp:func:`nb::cast() <cast>` fail to convert a
96+
value, they previously raised a generic ``std::bad_cast`` exception that
97+
loses the context of why the cast failed. When a Python error status is
98+
available, they now preferentially raise :cpp:class:`nb::python_error
99+
<python_error>`. This helps to track down issues with default argument
100+
conversion. (PR `#1137 <https://github.com/wjakob/nanobind/pull/1137>`__).
101+
102+
- Miscellaneous minor fixes and improvements. (PRs
103+
`#1092 <https://github.com/wjakob/nanobind/pull/1092>`__,
104+
`#1107 <https://github.com/wjakob/nanobind/pull/1107>`__,
105+
`#1120 <https://github.com/wjakob/nanobind/pull/1120>`__,
106+
`#1124 <https://github.com/wjakob/nanobind/pull/1124>`__.
107+
`#1128 <https://github.com/wjakob/nanobind/pull/1128>`__,
108+
`#1135 <https://github.com/wjakob/nanobind/pull/1135>`__,
109+
`#1138 <https://github.com/wjakob/nanobind/pull/1138>`__,
110+
`#1142 <https://github.com/wjakob/nanobind/pull/1142>`__,
111+
commits
112+
`d99b3f3 <https://github.com/wjakob/nanobind/commit/d99b3f3580b6c956f04851e8ed91e7eb5f259557>`__,
113+
`0147904 <https://github.com/wjakob/nanobind/commit/0147904cee4baaa597780b22920f8cf0577af4d6>`__).
114+
115+
- Minor documentation tweaks. (PRs
116+
`#1109 <https://github.com/wjakob/nanobind/pull/1109>`__,
117+
`#1108 <https://github.com/wjakob/nanobind/pull/1108>`__,
118+
`#1114 <https://github.com/wjakob/nanobind/pull/1114>`__,
119+
`#1117 <https://github.com/wjakob/nanobind/pull/1117>`__,
120+
`#1134 <https://github.com/wjakob/nanobind/pull/1134>`__,
121+
`#1132 <https://github.com/wjakob/nanobind/pull/1132>`__,
122+
`#1090 <https://github.com/wjakob/nanobind/pull/1090>`__).
35123

36124
Version 2.8.0 (July 16, 2025)
37125
-----------------------------
@@ -46,10 +134,10 @@ Version 2.8.0 (July 16, 2025)
46134
from source code literals. (PR `#1051
47135
<https://github.com/wjakob/nanobind/pull/1051>`__).
48136

49-
- Added :cpp:func:`nb::dict::empty() <dict::empty>`,
137+
- Added the convenience methods :cpp:func:`nb::dict::empty() <dict::empty>`,
50138
:cpp:func:`nb::list::empty() <list::empty>`, :cpp:func:`nb::set::empty()
51-
<set::empty>`, and :cpp:func:`nb::tuple::empty() <tuple::empty>` convenience
52-
methods. (PR `#1052 <https://github.com/wjakob/nanobind/pull/1052>`__)
139+
<set::empty>`, and :cpp:func:`nb::tuple::empty() <tuple::empty>`. (PR `#1052
140+
<https://github.com/wjakob/nanobind/pull/1052>`__)
53141

54142
- Added a :cpp:func:`nb::dict::get() <dict::get>` function to perform
55143
dictionary lookups with a fallback value in case of failures. (commit `d38284
@@ -59,11 +147,10 @@ Version 2.8.0 (July 16, 2025)
59147
when registering modules. However, multi-interpreter extensions remain
60148
unsupported. (PR `#1059 <https://github.com/wjakob/nanobind/pull/1059>`__).
61149

62-
- Added :cpp:class:`nb::frozenset` that wraps the Python ``frozenset`` type.
150+
- Added :cpp:class:`nb::frozenset <frozenset>` that wraps the Python ``frozenset`` type.
63151
(PR `#1068 <https://github.com/wjakob/nanobind/pull/1068>`__)
64152

65-
- Miscellaneous fixes and improvements (
66-
commits
153+
- Miscellaneous fixes and improvements (commits
67154
`d4b245 <https://github.com/wjakob/nanobind/commit/d4b245ad69f729c3d2095be4c1cb5b94810dae26>`__,
68155
`667451 <https://github.com/wjakob/nanobind/commit/667451fb4566dcd7151d64d81e118f9ba194a889>`__,
69156
`62fc99 <https://github.com/wjakob/nanobind/commit/62fc996018d9ea4d51af9c86cf008c2562b4eeab>`__,

0 commit comments

Comments
 (0)