1515// specific language governing permissions and limitations
1616// under the License.
1717
18- use dict_derive:: FromPyObject ;
1918use opendal:: { self as ocore, raw:: BytesRange } ;
19+ use pyo3:: Borrowed ;
20+ use pyo3:: FromPyObject ;
21+ use pyo3:: PyAny ;
22+ use pyo3:: PyErr ;
23+ use pyo3:: PyResult ;
24+ use pyo3:: conversion:: FromPyObjectOwned ;
25+ use pyo3:: exceptions:: PyTypeError ;
2026use pyo3:: pyclass;
27+ use pyo3:: types:: PyAnyMethods ;
28+ use pyo3:: types:: PyDict ;
29+ use pyo3:: types:: PyDictMethods ;
2130use std:: collections:: HashMap ;
2231
2332#[ pyclass( module = "opendal" ) ]
24- #[ derive( FromPyObject , Default ) ]
33+ #[ derive( Default ) ]
2534pub struct ReadOptions {
2635 pub version : Option < String > ,
2736 pub concurrent : Option < usize > ,
@@ -39,6 +48,55 @@ pub struct ReadOptions {
3948 pub content_disposition : Option < String > ,
4049}
4150
51+ fn map_exception ( name : & str , err : PyErr ) -> PyErr {
52+ PyErr :: new :: < PyTypeError , _ > ( format ! ( "Unable to convert key: {name}. Error: {err}" ) )
53+ }
54+
55+ fn extract_optional < ' py , T > ( dict : & pyo3:: Bound < ' py , PyDict > , name : & str ) -> PyResult < Option < T > >
56+ where
57+ T : FromPyObjectOwned < ' py > ,
58+ {
59+ match dict. get_item ( name) ? {
60+ Some ( v) => v
61+ . extract :: < T > ( )
62+ . map ( Some )
63+ . map_err ( |err| map_exception ( name, err. into ( ) ) ) ,
64+ None => Ok ( None ) ,
65+ }
66+ }
67+
68+ fn downcast_kwargs < ' a , ' py > ( obj : Borrowed < ' a , ' py , PyAny > ) -> PyResult < pyo3:: Bound < ' py , PyDict > > {
69+ let obj: & pyo3:: Bound < ' _ , PyAny > = & obj;
70+ obj. cast :: < PyDict > ( )
71+ . cloned ( )
72+ . map_err ( |_| PyErr :: new :: < PyTypeError , _ > ( "Invalid type to convert, expected dict" ) )
73+ }
74+
75+ impl < ' a , ' py > FromPyObject < ' a , ' py > for ReadOptions {
76+ type Error = PyErr ;
77+
78+ fn extract ( obj : Borrowed < ' a , ' py , PyAny > ) -> Result < Self , Self :: Error > {
79+ let dict = downcast_kwargs ( obj) ?;
80+
81+ Ok ( Self {
82+ version : extract_optional ( & dict, "version" ) ?,
83+ concurrent : extract_optional ( & dict, "concurrent" ) ?,
84+ chunk : extract_optional ( & dict, "chunk" ) ?,
85+ gap : extract_optional ( & dict, "gap" ) ?,
86+ offset : extract_optional ( & dict, "offset" ) ?,
87+ prefetch : extract_optional ( & dict, "prefetch" ) ?,
88+ size : extract_optional ( & dict, "size" ) ?,
89+ if_match : extract_optional ( & dict, "if_match" ) ?,
90+ if_none_match : extract_optional ( & dict, "if_none_match" ) ?,
91+ if_modified_since : extract_optional ( & dict, "if_modified_since" ) ?,
92+ if_unmodified_since : extract_optional ( & dict, "if_unmodified_since" ) ?,
93+ content_type : extract_optional ( & dict, "content_type" ) ?,
94+ cache_control : extract_optional ( & dict, "cache_control" ) ?,
95+ content_disposition : extract_optional ( & dict, "content_disposition" ) ?,
96+ } )
97+ }
98+ }
99+
42100impl ReadOptions {
43101 pub fn make_range ( & self ) -> BytesRange {
44102 let offset = self . offset . unwrap_or_default ( ) as u64 ;
@@ -49,7 +107,7 @@ impl ReadOptions {
49107}
50108
51109#[ pyclass( module = "opendal" ) ]
52- #[ derive( FromPyObject , Default ) ]
110+ #[ derive( Default ) ]
53111pub struct WriteOptions {
54112 pub append : Option < bool > ,
55113 pub chunk : Option < usize > ,
@@ -64,6 +122,28 @@ pub struct WriteOptions {
64122 pub user_metadata : Option < HashMap < String , String > > ,
65123}
66124
125+ impl < ' a , ' py > FromPyObject < ' a , ' py > for WriteOptions {
126+ type Error = PyErr ;
127+
128+ fn extract ( obj : Borrowed < ' a , ' py , PyAny > ) -> Result < Self , Self :: Error > {
129+ let dict = downcast_kwargs ( obj) ?;
130+
131+ Ok ( Self {
132+ append : extract_optional ( & dict, "append" ) ?,
133+ chunk : extract_optional ( & dict, "chunk" ) ?,
134+ concurrent : extract_optional ( & dict, "concurrent" ) ?,
135+ cache_control : extract_optional ( & dict, "cache_control" ) ?,
136+ content_type : extract_optional ( & dict, "content_type" ) ?,
137+ content_disposition : extract_optional ( & dict, "content_disposition" ) ?,
138+ content_encoding : extract_optional ( & dict, "content_encoding" ) ?,
139+ if_match : extract_optional ( & dict, "if_match" ) ?,
140+ if_none_match : extract_optional ( & dict, "if_none_match" ) ?,
141+ if_not_exists : extract_optional ( & dict, "if_not_exists" ) ?,
142+ user_metadata : extract_optional ( & dict, "user_metadata" ) ?,
143+ } )
144+ }
145+ }
146+
67147impl From < ReadOptions > for ocore:: options:: ReadOptions {
68148 fn from ( opts : ReadOptions ) -> Self {
69149 Self {
@@ -118,7 +198,7 @@ impl From<WriteOptions> for ocore::options::WriteOptions {
118198}
119199
120200#[ pyclass( module = "opendal" ) ]
121- #[ derive( FromPyObject , Default , Debug ) ]
201+ #[ derive( Default , Debug ) ]
122202pub struct ListOptions {
123203 pub limit : Option < usize > ,
124204 pub start_after : Option < String > ,
@@ -127,6 +207,22 @@ pub struct ListOptions {
127207 pub deleted : Option < bool > ,
128208}
129209
210+ impl < ' a , ' py > FromPyObject < ' a , ' py > for ListOptions {
211+ type Error = PyErr ;
212+
213+ fn extract ( obj : Borrowed < ' a , ' py , PyAny > ) -> Result < Self , Self :: Error > {
214+ let dict = downcast_kwargs ( obj) ?;
215+
216+ Ok ( Self {
217+ limit : extract_optional ( & dict, "limit" ) ?,
218+ start_after : extract_optional ( & dict, "start_after" ) ?,
219+ recursive : extract_optional ( & dict, "recursive" ) ?,
220+ versions : extract_optional ( & dict, "versions" ) ?,
221+ deleted : extract_optional ( & dict, "deleted" ) ?,
222+ } )
223+ }
224+ }
225+
130226impl From < ListOptions > for ocore:: options:: ListOptions {
131227 fn from ( opts : ListOptions ) -> Self {
132228 Self {
@@ -140,7 +236,7 @@ impl From<ListOptions> for ocore::options::ListOptions {
140236}
141237
142238#[ pyclass( module = "opendal" ) ]
143- #[ derive( FromPyObject , Default , Debug ) ]
239+ #[ derive( Default , Debug ) ]
144240pub struct StatOptions {
145241 pub version : Option < String > ,
146242 pub if_match : Option < String > ,
@@ -152,6 +248,25 @@ pub struct StatOptions {
152248 pub content_disposition : Option < String > ,
153249}
154250
251+ impl < ' a , ' py > FromPyObject < ' a , ' py > for StatOptions {
252+ type Error = PyErr ;
253+
254+ fn extract ( obj : Borrowed < ' a , ' py , PyAny > ) -> Result < Self , Self :: Error > {
255+ let dict = downcast_kwargs ( obj) ?;
256+
257+ Ok ( Self {
258+ version : extract_optional ( & dict, "version" ) ?,
259+ if_match : extract_optional ( & dict, "if_match" ) ?,
260+ if_none_match : extract_optional ( & dict, "if_none_match" ) ?,
261+ if_modified_since : extract_optional ( & dict, "if_modified_since" ) ?,
262+ if_unmodified_since : extract_optional ( & dict, "if_unmodified_since" ) ?,
263+ content_type : extract_optional ( & dict, "content_type" ) ?,
264+ cache_control : extract_optional ( & dict, "cache_control" ) ?,
265+ content_disposition : extract_optional ( & dict, "content_disposition" ) ?,
266+ } )
267+ }
268+ }
269+
155270impl From < StatOptions > for ocore:: options:: StatOptions {
156271 fn from ( opts : StatOptions ) -> Self {
157272 Self {
0 commit comments