Search code examples
c++17pybind11std-filesystem

Python bindings using pybind11 with std::filesystem as function argument giving TypeError


I have a class Foo() and class Foo() has a function with the following declaration:

bool Foo::copyFile(const std::filesystem::path& src, const std::filesystem::path& dest)

The requirement is that the class Foo should have Python bindings. I am using pybind11 to create the Python bindings.

I have written the following to create the Python bindings:

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "Foo.h"

namespace py = pybind11;

PYBIND11_MODULE(TestModule, m) {
     py::class_ <Foo>(m, "Foo")
        .def(py::init())
        .def("copyFile",&Foo::copyFile);
};

This compiles OK and I am able to create the Python bindings pyd file. When I use the Python bindings for class Foo using:

from TestModule import Foo

f = Foo()
ret = f.copyFile("C:\Users\csaikia\Downloads\testfile_src", "C:\Users\csaikia\Downloads\testfile_dest")

it gives a TypeError. I suspect that it has something to do with pybind11 support for std::filesystem in c++17 since I did not see this happening with other functions of the class that has std::string or std::vector.

The error I get is:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: copyFile(): incompatible function arguments. The following argument types are supported:
    1. (self: TestModule.Foo, arg0: std::filesystem::path, arg1: std::filesystem::path) -> bool

Invoked with: <TestModule.Foo object at 0x0000000002A33ED8>,  'C:\\Users\\csaikia\\Downloads\\testfile_src', 'C:\\Users\\csaikia\\Downloads\\testfile_dest'

Did you forget to `#include <pybind11/stl.h>`? Or <pybind11/complex.h>,
<pybind11/functional.h>, <pybind11/chrono.h>, etc. Some automatic
conversions are optional and require extra headers to be included
when compiling your pybind11 module.

I am new to pybind11. Can someone please help how I can resolve this?


Solution

  • From my conversation with the pybind11 devs:

    "Pybind has no idea how to convert a py::str to std::filesystem::path. There's no available caster nor has the std::filesystem::path class been bound.

    The easiest way would be to not bind Foo::copyFile directly. Instead bind a lambda that accepts const Foo& and const std::string& as parameters and then you can pass std::string to copyFile where std::filesystem::path is expected, letting C++ implicit conversion take place.

    You can maybe also do py::class_<std::filesystem::path> and make a binding for the std::string convertor and then use py::implicitly_convertible to let all that C++ implicit construction happen on python side, but... meh, too much work."

    It works like a charm!