@@ -7,8 +7,6 @@ use std::borrow::Cow;
77use std:: ffi:: OsString ;
88use std:: path:: { Path , PathBuf } ;
99
10- // See osstr.rs for why there's no FromPyObject impl for &Path
11-
1210impl FromPyObject < ' _ , ' _ > for PathBuf {
1311 type Error = PyErr ;
1412
@@ -66,6 +64,19 @@ impl<'py> IntoPyObject<'py> for &Cow<'_, Path> {
6664 }
6765}
6866
67+ impl < ' a > FromPyObject < ' a , ' _ > for Cow < ' a , Path > {
68+ type Error = PyErr ;
69+
70+ fn extract ( obj : Borrowed < ' a , ' _ , PyAny > ) -> Result < Self , Self :: Error > {
71+ #[ cfg( any( Py_3_10 , not( Py_LIMITED_API ) ) ) ]
72+ if let Ok ( s) = obj. extract :: < & str > ( ) {
73+ return Ok ( Cow :: Borrowed ( s. as_ref ( ) ) ) ;
74+ }
75+
76+ obj. extract :: < PathBuf > ( ) . map ( Cow :: Owned )
77+ }
78+ }
79+
6980impl < ' py > IntoPyObject < ' py > for PathBuf {
7081 type Target = PyAny ;
7182 type Output = Bound < ' py , Self :: Target > ;
@@ -90,18 +101,22 @@ impl<'py> IntoPyObject<'py> for &PathBuf {
90101
91102#[ cfg( test) ]
92103mod tests {
93- use crate :: types:: { PyAnyMethods , PyString } ;
94- use crate :: { IntoPyObject , IntoPyObjectExt , Python } ;
95- use std:: borrow:: Cow ;
104+ use super :: * ;
105+ use crate :: {
106+ types:: { PyAnyMethods , PyString } ,
107+ IntoPyObjectExt ,
108+ } ;
109+ use std:: ffi:: OsStr ;
96110 use std:: fmt:: Debug ;
97- use std:: path:: { Path , PathBuf } ;
111+ #[ cfg( unix) ]
112+ use std:: os:: unix:: ffi:: OsStringExt ;
113+ #[ cfg( windows) ]
114+ use std:: os:: windows:: ffi:: OsStringExt ;
98115
99116 #[ test]
100117 #[ cfg( not( windows) ) ]
101118 fn test_non_utf8_conversion ( ) {
102119 Python :: attach ( |py| {
103- use crate :: types:: PyAnyMethods ;
104- use std:: ffi:: OsStr ;
105120 #[ cfg( not( target_os = "wasi" ) ) ]
106121 use std:: os:: unix:: ffi:: OsStrExt ;
107122 #[ cfg( target_os = "wasi" ) ]
@@ -147,4 +162,40 @@ mod tests {
147162 assert_eq ! ( roundtrip, Path :: new( path) ) ;
148163 } ) ;
149164 }
165+
166+ #[ test]
167+ fn test_extract_cow ( ) {
168+ Python :: attach ( |py| {
169+ fn test_extract < ' py , T > ( py : Python < ' py > , path : & T , is_borrowed : bool )
170+ where
171+ for < ' a > & ' a T : IntoPyObject < ' py , Output = Bound < ' py , PyString > > ,
172+ for < ' a > <& ' a T as IntoPyObject < ' py > >:: Error : Debug ,
173+ T : AsRef < Path > + ?Sized ,
174+ {
175+ let pystring = path. into_pyobject ( py) . unwrap ( ) ;
176+ let cow: Cow < ' _ , Path > = pystring. extract ( ) . unwrap ( ) ;
177+ assert_eq ! ( cow, path. as_ref( ) ) ;
178+ assert_eq ! ( is_borrowed, matches!( cow, Cow :: Borrowed ( _) ) ) ;
179+ }
180+
181+ // On Python 3.10+ or when not using the limited API, we can borrow strings from python
182+ let can_borrow_str = cfg ! ( any( Py_3_10 , not( Py_LIMITED_API ) ) ) ;
183+ // This can be borrowed because it is valid UTF-8
184+ test_extract :: < str > ( py, "Hello\0 \n 🐍" , can_borrow_str) ;
185+ test_extract :: < str > ( py, "Hello, world!" , can_borrow_str) ;
186+
187+ #[ cfg( windows) ]
188+ let os_str = {
189+ // 'A', unpaired surrogate, 'B'
190+ OsString :: from_wide ( & [ 'A' as u16 , 0xD800 , 'B' as u16 ] )
191+ } ;
192+
193+ #[ cfg( unix) ]
194+ let os_str = { OsString :: from_vec ( vec ! [ 250 , 251 , 252 , 253 , 254 , 255 , 0 , 255 ] ) } ;
195+
196+ // This cannot be borrowed because it is not valid UTF-8
197+ #[ cfg( any( unix, windows) ) ]
198+ test_extract :: < OsStr > ( py, & os_str, false ) ;
199+ } ) ;
200+ }
150201}
0 commit comments