Skip to content

fix(python): Address new pybind11 float/int auto-conversion behavior#5058

Open
lgritz wants to merge 4 commits intoAcademySoftwareFoundation:mainfrom
lgritz:lg-pybind11
Open

fix(python): Address new pybind11 float/int auto-conversion behavior#5058
lgritz wants to merge 4 commits intoAcademySoftwareFoundation:mainfrom
lgritz:lg-pybind11

Conversation

@lgritz
Copy link
Collaborator

@lgritz lgritz commented Feb 22, 2026

It's not in any pybind11 tagged release yet, but in its master, a change has been introduced that causes it to allow functions with float paramters to automatically match int arguments. This is fatal for us, as we have set up several cases of methods that overload on int vs float, and with this change, the float one gets called when an int is passed. Pybind11 docs say that the .noconvert() modifier on the argument will do the trick, but I'm finding that it doesn't help for overloaded function names.

The solution is to find these cases, change our declaration to a generic py::object, and then sort out what type was passed with py::isinstance. It seems to mainly affect attribute() style calls.

In the process, minor changes to the pybind11 auto-builder allow it to take a git commit hash directly. Also, I took the opportunity to remove a few reference outputs that are no longer needed because they were python2-specific.

Meanwhile, just in case anybody had been paying attention, the problem in pybind11 master that had caused us to temporarily stop CI testing against master seems to have been resolved. So re-enable the bleeding edge test to use pybind11 master (which will fail if not for this PR here), and bump the "latest version" tests to the latest pybind11 3.0.2.

I diagnosed the problem myself, wrote the solution for one class and verified that it solved the issues with that class. Then I used Claude Code to make the analogous set of changes to the other classes that needed them and I reviewed that it was correct.

It's not in any pybind11 tagged release yet, but in its master, a
change has been introduced that causes it to allow functions with
float paramters to automatically match int arguments. This is fatal
for us, as we have set up several cases of methods that overload on
int vs float, and with this change, the float one gets called when an
int is passed. Pybind11 docs say that the `.noconvert()` modifier on
the argument will do the trick, but I'm finding that it doesn't help
for overloaded function names.

The solution is to find these cases, change our declaration to a
generic py::object, and then sort out what type was passed with
py::isinstance. It seems to mainly affect `attribute()` style calls.

In the process, minor changes to the pybind11 auto-builder allow it to
take a git commit hash directly. Also, I took the opportunity to
remove a few reference outputs that are no longer needed because they
were python2-specific.

Meanwhile, just in case anybody had been paying attention, the problem
in pybind11 master that had caused us to temporarily stop CI testing
against master seems to have been resolved. So re-enable the bleeding
edge test to use pybind11 master (which will fail if not for this PR
here), and bump the "latest version" tests t the new pybind11 3.0.2.

Signed-off-by: Larry Gritz <lg@larrygritz.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the project’s Python bindings to be resilient to a new pybind11 behavior that implicitly matches int arguments to float parameters, which breaks int vs float overloads (notably attribute()-style APIs). It also refreshes CI pybind11 version coverage and cleans up legacy Python2-related reference outputs.

Changes:

  • Replace several attribute(name, int/float/string) pybind11 overload sets with a single attribute(name, object) binding that dispatches via py::isinstance to preserve int/float distinction.
  • Extend the local pybind11 CMake dependency builder to accept an explicit git commit hash.
  • Update CI to test pybind11 v3.0.2 and re-enable testing against pybind11 master; remove python3-specific reference output files and normalize some ref output formatting.

Reviewed changes

Copilot reviewed 14 out of 16 changed files in this pull request and generated no comments.

Show a summary per file
File Description
testsuite/python-imagespec/ref/out.txt Minor ref output normalization (removes stray blank line).
testsuite/python-imagespec/ref/out-python3.txt Removes python3-only reference output.
testsuite/python-imageinput/ref/out.txt Updates reference output formatting (removes L suffixes, aligns formatting).
testsuite/python-imageinput/ref/out-python3.txt Removes python3-only reference output.
testsuite/python-imagebuf/ref/out.txt Updates array pretty-print formatting in reference output.
testsuite/python-imagebuf/ref/out-python3.txt Removes python3-only reference output.
testsuite/python-imagebuf/ref/out-alt.txt Same reference-output formatting updates as out.txt.
testsuite/python-imagebuf/ref/out-alt-python3.txt Removes python3-only reference output.
src/python/py_texturesys.cpp Routes TextureSystem.attribute(name, value) through object-based dispatch to avoid int→float overload capture.
src/python/py_paramvalue.cpp Uses the shared object-based dispatch for ParamValueList.attribute(name, value).
src/python/py_oiio.h Introduces attribute_onearg() helper that dispatches by Python runtime type.
src/python/py_oiio.cpp Updates global OpenImageIO.attribute(name, value) to use the same dispatch helper.
src/python/py_imagespec.cpp Updates ImageSpec.attribute(name, value) to use runtime dispatch helper.
src/python/py_imagecache.cpp Updates ImageCache.attribute(name, value) to use runtime dispatch helper.
src/cmake/build_pybind11.cmake Passes GIT_COMMIT through to the dependency build macro.
.github/workflows/ci.yml Bumps “latest releases” pybind11 to v3.0.2 and sets “bleeding edge” pybind11 to master.
Comments suppressed due to low confidence (3)

src/python/py_oiio.h:526

  • attribute_onearg no longer accepts Python bytes values (only str), whereas delegate_setitem in the same header explicitly supports py::bytes. Previously the attribute(..., const std::string&) overloads would typically accept both str and bytes via pybind11’s string caster, so this is an API compatibility regression. Consider handling py::bytes here (and converting to std::string) so attribute(name, b"...") continues to work consistently with __setitem__.
// Dispatch a single-value attribute() call based on the type of the
// Python object (int, float, or string).
template<typename T>
inline void
attribute_onearg(T& myobj, string_view name, const py::object& obj)
{
    if (py::isinstance<py::float_>(obj))
        myobj.attribute(name, float(obj.template cast<py::float_>()));
    else if (py::isinstance<py::int_>(obj))
        myobj.attribute(name, int(obj.template cast<py::int_>()));
    else if (py::isinstance<py::str>(obj))
        myobj.attribute(name, std::string(obj.template cast<py::str>()));
    else
        throw std::invalid_argument("Bad type for attribute");
}

src/python/py_oiio.h:526

  • The thrown exception message "Bad type for attribute" is quite generic and doesn’t indicate which Python types are accepted. Since this will surface as a Python exception for callers, consider including the allowed types (e.g., int/float/str[/bytes]) and/or the actual received type to make debugging easier.
        myobj.attribute(name, std::string(obj.template cast<py::str>()));
    else
        throw std::invalid_argument("Bad type for attribute");
}

src/python/py_paramvalue.cpp:87

  • ParamValueList_attribute_onearg is a non-static free function in this translation unit. Since it’s only used as a binding helper within this file, consider making it static (or placing it in an anonymous namespace) to avoid unnecessarily exporting a symbol and to match the existing pattern of other file-local helpers (e.g., ParamValue_from_pyobject).
void
ParamValueList_attribute_onearg(ParamValueList& self, const std::string& name,
                                const py::object& obj)
{
    attribute_onearg(self, name, obj);
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@lgritz
Copy link
Collaborator Author

lgritz commented Mar 1, 2026

Any comments on this? It's been up for a week.

lgritz added 3 commits March 1, 2026 17:47
Signed-off-by: Larry Gritz <lg@larrygritz.com>
…tside this file

Signed-off-by: Larry Gritz <lg@larrygritz.com>
Signed-off-by: Larry Gritz <lg@larrygritz.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants