Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
gaoqiong
pybind11
Commits
5bcaaa04
Unverified
Commit
5bcaaa04
authored
Jul 02, 2021
by
Antony Lee
Committed by
GitHub
Jul 02, 2021
Browse files
Add a std::filesystem::path <-> os.PathLike caster. (#2730)
parent
f067deb5
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
149 additions
and
0 deletions
+149
-0
docs/advanced/cast/overview.rst
docs/advanced/cast/overview.rst
+6
-0
include/pybind11/stl.h
include/pybind11/stl.h
+81
-0
tests/CMakeLists.txt
tests/CMakeLists.txt
+37
-0
tests/test_stl.cpp
tests/test_stl.cpp
+6
-0
tests/test_stl.py
tests/test_stl.py
+19
-0
No files found.
docs/advanced/cast/overview.rst
View file @
5bcaaa04
...
@@ -151,6 +151,8 @@ as arguments and return values, refer to the section on binding :ref:`classes`.
...
@@ -151,6 +151,8 @@ as arguments and return values, refer to the section on binding :ref:`classes`.
+------------------------------------+---------------------------+-------------------------------+
+------------------------------------+---------------------------+-------------------------------+
| ``std::variant<...>`` | Type-safe union (C++17) | :file:`pybind11/stl.h` |
| ``std::variant<...>`` | Type-safe union (C++17) | :file:`pybind11/stl.h` |
+------------------------------------+---------------------------+-------------------------------+
+------------------------------------+---------------------------+-------------------------------+
| ``std::filesystem::path<T>`` | STL path (C++17) [#]_ | :file:`pybind11/stl.h` |
+------------------------------------+---------------------------+-------------------------------+
| ``std::function<...>`` | STL polymorphic function | :file:`pybind11/functional.h` |
| ``std::function<...>`` | STL polymorphic function | :file:`pybind11/functional.h` |
+------------------------------------+---------------------------+-------------------------------+
+------------------------------------+---------------------------+-------------------------------+
| ``std::chrono::duration<...>`` | STL time duration | :file:`pybind11/chrono.h` |
| ``std::chrono::duration<...>`` | STL time duration | :file:`pybind11/chrono.h` |
...
@@ -163,3 +165,7 @@ as arguments and return values, refer to the section on binding :ref:`classes`.
...
@@ -163,3 +165,7 @@ as arguments and return values, refer to the section on binding :ref:`classes`.
+------------------------------------+---------------------------+-------------------------------+
+------------------------------------+---------------------------+-------------------------------+
| ``Eigen::SparseMatrix<...>`` | Eigen: sparse matrix | :file:`pybind11/eigen.h` |
| ``Eigen::SparseMatrix<...>`` | Eigen: sparse matrix | :file:`pybind11/eigen.h` |
+------------------------------------+---------------------------+-------------------------------+
+------------------------------------+---------------------------+-------------------------------+
.. [#] ``std::filesystem::path`` is converted to ``pathlib.Path`` and
``os.PathLike`` is converted to ``std::filesystem::path``, but this requires
Python 3.6 (for ``__fspath__`` support).
include/pybind11/stl.h
View file @
5bcaaa04
...
@@ -41,11 +41,21 @@
...
@@ -41,11 +41,21 @@
# include <variant>
# include <variant>
# define PYBIND11_HAS_VARIANT 1
# define PYBIND11_HAS_VARIANT 1
# endif
# endif
// std::filesystem::path
# if defined(PYBIND11_CPP17) && __has_include(<filesystem>) && \
PY_VERSION_HEX >= 0x03060000
# include <filesystem>
# define PYBIND11_HAS_FILESYSTEM 1
# endif
#elif defined(_MSC_VER) && defined(PYBIND11_CPP17)
#elif defined(_MSC_VER) && defined(PYBIND11_CPP17)
# include <optional>
# include <optional>
# include <variant>
# include <variant>
# define PYBIND11_HAS_OPTIONAL 1
# define PYBIND11_HAS_OPTIONAL 1
# define PYBIND11_HAS_VARIANT 1
# define PYBIND11_HAS_VARIANT 1
# if PY_VERSION_HEX >= 0x03060000
# include <filesystem>
# define PYBIND11_HAS_FILESYSTEM 1
# endif
#endif
#endif
PYBIND11_NAMESPACE_BEGIN
(
PYBIND11_NAMESPACE
)
PYBIND11_NAMESPACE_BEGIN
(
PYBIND11_NAMESPACE
)
...
@@ -377,6 +387,77 @@ template <typename... Ts>
...
@@ -377,6 +387,77 @@ template <typename... Ts>
struct
type_caster
<
std
::
variant
<
Ts
...
>>
:
variant_caster
<
std
::
variant
<
Ts
...
>>
{
};
struct
type_caster
<
std
::
variant
<
Ts
...
>>
:
variant_caster
<
std
::
variant
<
Ts
...
>>
{
};
#endif
#endif
#if defined(PYBIND11_HAS_FILESYSTEM)
template
<
typename
T
>
struct
path_caster
{
private:
static
PyObject
*
unicode_from_fs_native
(
const
std
::
string
&
w
)
{
#if !defined(PYPY_VERSION)
return
PyUnicode_DecodeFSDefaultAndSize
(
w
.
c_str
(),
ssize_t
(
w
.
size
()));
#else
// PyPy mistakenly declares the first parameter as non-const.
return
PyUnicode_DecodeFSDefaultAndSize
(
const_cast
<
char
*>
(
w
.
c_str
()),
ssize_t
(
w
.
size
()));
#endif
}
static
PyObject
*
unicode_from_fs_native
(
const
std
::
wstring
&
w
)
{
return
PyUnicode_FromWideChar
(
w
.
c_str
(),
ssize_t
(
w
.
size
()));
}
public:
static
handle
cast
(
const
T
&
path
,
return_value_policy
,
handle
)
{
if
(
auto
py_str
=
unicode_from_fs_native
(
path
.
native
()))
{
return
module
::
import
(
"pathlib"
).
attr
(
"Path"
)(
reinterpret_steal
<
object
>
(
py_str
))
.
release
();
}
return
nullptr
;
}
bool
load
(
handle
handle
,
bool
)
{
// PyUnicode_FSConverter and PyUnicode_FSDecoder normally take care of
// calling PyOS_FSPath themselves, but that's broken on PyPy (PyPy
// issue #3168) so we do it ourselves instead.
PyObject
*
buf
=
PyOS_FSPath
(
handle
.
ptr
());
if
(
!
buf
)
{
PyErr_Clear
();
return
false
;
}
PyObject
*
native
=
nullptr
;
if
constexpr
(
std
::
is_same_v
<
typename
T
::
value_type
,
char
>
)
{
if
(
PyUnicode_FSConverter
(
buf
,
&
native
))
{
if
(
auto
c_str
=
PyBytes_AsString
(
native
))
{
// AsString returns a pointer to the internal buffer, which
// must not be free'd.
value
=
c_str
;
}
}
}
else
if
constexpr
(
std
::
is_same_v
<
typename
T
::
value_type
,
wchar_t
>
)
{
if
(
PyUnicode_FSDecoder
(
buf
,
&
native
))
{
if
(
auto
c_str
=
PyUnicode_AsWideCharString
(
native
,
nullptr
))
{
// AsWideCharString returns a new string that must be free'd.
value
=
c_str
;
// Copies the string.
PyMem_Free
(
c_str
);
}
}
}
Py_XDECREF
(
native
);
Py_DECREF
(
buf
);
if
(
PyErr_Occurred
())
{
PyErr_Clear
();
return
false
;
}
else
{
return
true
;
}
}
PYBIND11_TYPE_CASTER
(
T
,
_
(
"os.PathLike"
));
};
template
<
>
struct
type_caster
<
std
::
filesystem
::
path
>
:
public
path_caster
<
std
::
filesystem
::
path
>
{};
#endif
PYBIND11_NAMESPACE_END
(
detail
)
PYBIND11_NAMESPACE_END
(
detail
)
inline
std
::
ostream
&
operator
<<
(
std
::
ostream
&
os
,
const
handle
&
obj
)
{
inline
std
::
ostream
&
operator
<<
(
std
::
ostream
&
os
,
const
handle
&
obj
)
{
...
...
tests/CMakeLists.txt
View file @
5bcaaa04
...
@@ -247,6 +247,41 @@ if(Boost_FOUND)
...
@@ -247,6 +247,41 @@ if(Boost_FOUND)
endif
()
endif
()
endif
()
endif
()
# Check if we need to add -lstdc++fs or -lc++fs or nothing
if
(
MSVC
)
set
(
STD_FS_NO_LIB_NEEDED TRUE
)
else
()
file
(
WRITE
${
CMAKE_CURRENT_BINARY_DIR
}
/main.cpp
"#include <filesystem>
\n
int main(int argc, char ** argv) {
\n
std::filesystem::path p(argv[0]);
\n
return p.string().length();
\n
}"
)
try_compile
(
STD_FS_NO_LIB_NEEDED
${
CMAKE_CURRENT_BINARY_DIR
}
SOURCES
${
CMAKE_CURRENT_BINARY_DIR
}
/main.cpp
COMPILE_DEFINITIONS -std=c++17
)
try_compile
(
STD_FS_NEEDS_STDCXXFS
${
CMAKE_CURRENT_BINARY_DIR
}
SOURCES
${
CMAKE_CURRENT_BINARY_DIR
}
/main.cpp
COMPILE_DEFINITIONS -std=c++17
LINK_LIBRARIES stdc++fs
)
try_compile
(
STD_FS_NEEDS_CXXFS
${
CMAKE_CURRENT_BINARY_DIR
}
SOURCES
${
CMAKE_CURRENT_BINARY_DIR
}
/main.cpp
COMPILE_DEFINITIONS -std=c++17
LINK_LIBRARIES c++fs
)
endif
()
if
(
${
STD_FS_NEEDS_STDCXXFS
}
)
set
(
STD_FS_LIB stdc++fs
)
elseif
(
${
STD_FS_NEEDS_CXXFS
}
)
set
(
STD_FS_LIB c++fs
)
elseif
(
${
STD_FS_NO_LIB_NEEDED
}
)
set
(
STD_FS_LIB
""
)
else
()
message
(
WARNING
"Unknown compiler - not passing -lstdc++fs"
)
set
(
STD_FS_LIB
""
)
endif
()
# Compile with compiler warnings turned on
# Compile with compiler warnings turned on
function
(
pybind11_enable_warnings target_name
)
function
(
pybind11_enable_warnings target_name
)
if
(
MSVC
)
if
(
MSVC
)
...
@@ -357,6 +392,8 @@ foreach(target ${test_targets})
...
@@ -357,6 +392,8 @@ foreach(target ${test_targets})
target_compile_definitions
(
${
target
}
PRIVATE -DPYBIND11_TEST_BOOST
)
target_compile_definitions
(
${
target
}
PRIVATE -DPYBIND11_TEST_BOOST
)
endif
()
endif
()
target_link_libraries
(
${
target
}
PRIVATE
${
STD_FS_LIB
}
)
# Always write the output file directly into the 'tests' directory (even on MSVC)
# Always write the output file directly into the 'tests' directory (even on MSVC)
if
(
NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY
)
if
(
NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY
)
set_target_properties
(
${
target
}
PROPERTIES LIBRARY_OUTPUT_DIRECTORY
set_target_properties
(
${
target
}
PROPERTIES LIBRARY_OUTPUT_DIRECTORY
...
...
tests/test_stl.cpp
View file @
5bcaaa04
...
@@ -238,6 +238,12 @@ TEST_SUBMODULE(stl, m) {
...
@@ -238,6 +238,12 @@ TEST_SUBMODULE(stl, m) {
.
def
(
"member_initialized"
,
&
opt_exp_holder
::
member_initialized
);
.
def
(
"member_initialized"
,
&
opt_exp_holder
::
member_initialized
);
#endif
#endif
#ifdef PYBIND11_HAS_FILESYSTEM
// test_fs_path
m
.
attr
(
"has_filesystem"
)
=
true
;
m
.
def
(
"parent_path"
,
[](
const
std
::
filesystem
::
path
&
p
)
{
return
p
.
parent_path
();
});
#endif
#ifdef PYBIND11_HAS_VARIANT
#ifdef PYBIND11_HAS_VARIANT
static_assert
(
std
::
is_same
<
py
::
detail
::
variant_caster_visitor
::
result_type
,
py
::
handle
>::
value
,
static_assert
(
std
::
is_same
<
py
::
detail
::
variant_caster_visitor
::
result_type
,
py
::
handle
>::
value
,
"visitor::result_type is required by boost::variant in C++11 mode"
);
"visitor::result_type is required by boost::variant in C++11 mode"
);
...
...
tests/test_stl.py
View file @
5bcaaa04
...
@@ -162,6 +162,25 @@ def test_exp_optional():
...
@@ -162,6 +162,25 @@ def test_exp_optional():
assert
holder
.
member_initialized
()
assert
holder
.
member_initialized
()
@
pytest
.
mark
.
skipif
(
not
hasattr
(
m
,
"has_filesystem"
),
reason
=
"no <filesystem>"
)
def
test_fs_path
():
from
pathlib
import
Path
class
PseudoStrPath
:
def
__fspath__
(
self
):
return
"foo/bar"
class
PseudoBytesPath
:
def
__fspath__
(
self
):
return
b
"foo/bar"
assert
m
.
parent_path
(
Path
(
"foo/bar"
))
==
Path
(
"foo"
)
assert
m
.
parent_path
(
"foo/bar"
)
==
Path
(
"foo"
)
assert
m
.
parent_path
(
b
"foo/bar"
)
==
Path
(
"foo"
)
assert
m
.
parent_path
(
PseudoStrPath
())
==
Path
(
"foo"
)
assert
m
.
parent_path
(
PseudoBytesPath
())
==
Path
(
"foo"
)
@
pytest
.
mark
.
skipif
(
not
hasattr
(
m
,
"load_variant"
),
reason
=
"no <variant>"
)
@
pytest
.
mark
.
skipif
(
not
hasattr
(
m
,
"load_variant"
),
reason
=
"no <variant>"
)
def
test_variant
(
doc
):
def
test_variant
(
doc
):
assert
m
.
load_variant
(
1
)
==
"int"
assert
m
.
load_variant
(
1
)
==
"int"
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment