This is an automated email from the ASF dual-hosted git repository.

xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 06f088d93 chore(deps): bump the pyo3 group in /bindings/python with 2 
updates (#7120)
06f088d93 is described below

commit 06f088d93e38e1150a3f1558f327ec0252446302
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
AuthorDate: Mon Jan 19 15:41:36 2026 +0800

    chore(deps): bump the pyo3 group in /bindings/python with 2 updates (#7120)
    
    * chore(deps): bump the pyo3 group in /bindings/python with 2 updates
    
    Updates the requirements on [pyo3](https://github.com/pyo3/pyo3) and 
[pyo3-async-runtimes](https://github.com/PyO3/pyo3-async-runtimes) to permit 
the latest version.
    
    Updates `pyo3` to 0.26.0
    - [Release notes](https://github.com/pyo3/pyo3/releases)
    - [Changelog](https://github.com/PyO3/pyo3/blob/main/CHANGELOG.md)
    - [Commits](https://github.com/pyo3/pyo3/compare/v0.26.0...v0.26.0)
    
    Updates `pyo3-async-runtimes` to 0.27.0
    - [Release notes](https://github.com/PyO3/pyo3-async-runtimes/releases)
    - 
[Changelog](https://github.com/PyO3/pyo3-async-runtimes/blob/main/CHANGELOG.md)
    - 
[Commits](https://github.com/PyO3/pyo3-async-runtimes/compare/v0.26.0...v0.27.0)
    
    ---
    updated-dependencies:
    - dependency-name: pyo3
      dependency-version: 0.26.0
      dependency-type: direct:production
      dependency-group: pyo3
    - dependency-name: pyo3-async-runtimes
      dependency-version: 0.27.0
      dependency-type: direct:production
      dependency-group: pyo3
    ...
    
    Signed-off-by: dependabot[bot] <[email protected]>
    
    * Fix build
    
    ---------
    
    Signed-off-by: dependabot[bot] <[email protected]>
    Co-authored-by: dependabot[bot] 
<49699333+dependabot[bot]@users.noreply.github.com>
    Co-authored-by: Xuanwo <[email protected]>
---
 bindings/python/Cargo.toml     |   5 +-
 bindings/python/src/options.rs | 125 +++++++++++++++++++++++++++++++++++++++--
 2 files changed, 122 insertions(+), 8 deletions(-)

diff --git a/bindings/python/Cargo.toml b/bindings/python/Cargo.toml
index b4fe5553b..cabe0b503 100644
--- a/bindings/python/Cargo.toml
+++ b/bindings/python/Cargo.toml
@@ -198,7 +198,6 @@ name = "_opendal"
 
 [dependencies]
 bytes = "1.5.0"
-dict_derive = "0.6.0"
 futures = "0.3.28"
 jiff = { version = "0.2.15" }
 mea = "0.6"
@@ -207,8 +206,8 @@ opendal = { version = ">=0", path = "../../core", features 
= [
   "blocking",
   "layers-mime-guess",
 ] }
-pyo3 = { version = "0.26.0", features = ["generate-import-lib", "jiff-02"] }
-pyo3-async-runtimes = { version = "0.26.0", features = ["tokio-runtime"] }
+pyo3 = { version = "0.27.2", features = ["generate-import-lib", "jiff-02"] }
+pyo3-async-runtimes = { version = "0.27.0", features = ["tokio-runtime"] }
 pyo3-stub-gen = { version = "0.17" }
 tokio = "1"
 
diff --git a/bindings/python/src/options.rs b/bindings/python/src/options.rs
index fa5f74061..f623be266 100644
--- a/bindings/python/src/options.rs
+++ b/bindings/python/src/options.rs
@@ -15,13 +15,22 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use dict_derive::FromPyObject;
 use opendal::{self as ocore, raw::BytesRange};
+use pyo3::Borrowed;
+use pyo3::FromPyObject;
+use pyo3::PyAny;
+use pyo3::PyErr;
+use pyo3::PyResult;
+use pyo3::conversion::FromPyObjectOwned;
+use pyo3::exceptions::PyTypeError;
 use pyo3::pyclass;
+use pyo3::types::PyAnyMethods;
+use pyo3::types::PyDict;
+use pyo3::types::PyDictMethods;
 use std::collections::HashMap;
 
 #[pyclass(module = "opendal")]
-#[derive(FromPyObject, Default)]
+#[derive(Default)]
 pub struct ReadOptions {
     pub version: Option<String>,
     pub concurrent: Option<usize>,
@@ -39,6 +48,55 @@ pub struct ReadOptions {
     pub content_disposition: Option<String>,
 }
 
+fn map_exception(name: &str, err: PyErr) -> PyErr {
+    PyErr::new::<PyTypeError, _>(format!("Unable to convert key: {name}. 
Error: {err}"))
+}
+
+fn extract_optional<'py, T>(dict: &pyo3::Bound<'py, PyDict>, name: &str) -> 
PyResult<Option<T>>
+where
+    T: FromPyObjectOwned<'py>,
+{
+    match dict.get_item(name)? {
+        Some(v) => v
+            .extract::<T>()
+            .map(Some)
+            .map_err(|err| map_exception(name, err.into())),
+        None => Ok(None),
+    }
+}
+
+fn downcast_kwargs<'a, 'py>(obj: Borrowed<'a, 'py, PyAny>) -> 
PyResult<pyo3::Bound<'py, PyDict>> {
+    let obj: &pyo3::Bound<'_, PyAny> = &obj;
+    obj.cast::<PyDict>()
+        .cloned()
+        .map_err(|_| PyErr::new::<PyTypeError, _>("Invalid type to convert, 
expected dict"))
+}
+
+impl<'a, 'py> FromPyObject<'a, 'py> for ReadOptions {
+    type Error = PyErr;
+
+    fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
+        let dict = downcast_kwargs(obj)?;
+
+        Ok(Self {
+            version: extract_optional(&dict, "version")?,
+            concurrent: extract_optional(&dict, "concurrent")?,
+            chunk: extract_optional(&dict, "chunk")?,
+            gap: extract_optional(&dict, "gap")?,
+            offset: extract_optional(&dict, "offset")?,
+            prefetch: extract_optional(&dict, "prefetch")?,
+            size: extract_optional(&dict, "size")?,
+            if_match: extract_optional(&dict, "if_match")?,
+            if_none_match: extract_optional(&dict, "if_none_match")?,
+            if_modified_since: extract_optional(&dict, "if_modified_since")?,
+            if_unmodified_since: extract_optional(&dict, 
"if_unmodified_since")?,
+            content_type: extract_optional(&dict, "content_type")?,
+            cache_control: extract_optional(&dict, "cache_control")?,
+            content_disposition: extract_optional(&dict, 
"content_disposition")?,
+        })
+    }
+}
+
 impl ReadOptions {
     pub fn make_range(&self) -> BytesRange {
         let offset = self.offset.unwrap_or_default() as u64;
@@ -49,7 +107,7 @@ impl ReadOptions {
 }
 
 #[pyclass(module = "opendal")]
-#[derive(FromPyObject, Default)]
+#[derive(Default)]
 pub struct WriteOptions {
     pub append: Option<bool>,
     pub chunk: Option<usize>,
@@ -64,6 +122,28 @@ pub struct WriteOptions {
     pub user_metadata: Option<HashMap<String, String>>,
 }
 
+impl<'a, 'py> FromPyObject<'a, 'py> for WriteOptions {
+    type Error = PyErr;
+
+    fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
+        let dict = downcast_kwargs(obj)?;
+
+        Ok(Self {
+            append: extract_optional(&dict, "append")?,
+            chunk: extract_optional(&dict, "chunk")?,
+            concurrent: extract_optional(&dict, "concurrent")?,
+            cache_control: extract_optional(&dict, "cache_control")?,
+            content_type: extract_optional(&dict, "content_type")?,
+            content_disposition: extract_optional(&dict, 
"content_disposition")?,
+            content_encoding: extract_optional(&dict, "content_encoding")?,
+            if_match: extract_optional(&dict, "if_match")?,
+            if_none_match: extract_optional(&dict, "if_none_match")?,
+            if_not_exists: extract_optional(&dict, "if_not_exists")?,
+            user_metadata: extract_optional(&dict, "user_metadata")?,
+        })
+    }
+}
+
 impl From<ReadOptions> for ocore::options::ReadOptions {
     fn from(opts: ReadOptions) -> Self {
         Self {
@@ -118,7 +198,7 @@ impl From<WriteOptions> for ocore::options::WriteOptions {
 }
 
 #[pyclass(module = "opendal")]
-#[derive(FromPyObject, Default, Debug)]
+#[derive(Default, Debug)]
 pub struct ListOptions {
     pub limit: Option<usize>,
     pub start_after: Option<String>,
@@ -127,6 +207,22 @@ pub struct ListOptions {
     pub deleted: Option<bool>,
 }
 
+impl<'a, 'py> FromPyObject<'a, 'py> for ListOptions {
+    type Error = PyErr;
+
+    fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
+        let dict = downcast_kwargs(obj)?;
+
+        Ok(Self {
+            limit: extract_optional(&dict, "limit")?,
+            start_after: extract_optional(&dict, "start_after")?,
+            recursive: extract_optional(&dict, "recursive")?,
+            versions: extract_optional(&dict, "versions")?,
+            deleted: extract_optional(&dict, "deleted")?,
+        })
+    }
+}
+
 impl From<ListOptions> for ocore::options::ListOptions {
     fn from(opts: ListOptions) -> Self {
         Self {
@@ -140,7 +236,7 @@ impl From<ListOptions> for ocore::options::ListOptions {
 }
 
 #[pyclass(module = "opendal")]
-#[derive(FromPyObject, Default, Debug)]
+#[derive(Default, Debug)]
 pub struct StatOptions {
     pub version: Option<String>,
     pub if_match: Option<String>,
@@ -152,6 +248,25 @@ pub struct StatOptions {
     pub content_disposition: Option<String>,
 }
 
+impl<'a, 'py> FromPyObject<'a, 'py> for StatOptions {
+    type Error = PyErr;
+
+    fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
+        let dict = downcast_kwargs(obj)?;
+
+        Ok(Self {
+            version: extract_optional(&dict, "version")?,
+            if_match: extract_optional(&dict, "if_match")?,
+            if_none_match: extract_optional(&dict, "if_none_match")?,
+            if_modified_since: extract_optional(&dict, "if_modified_since")?,
+            if_unmodified_since: extract_optional(&dict, 
"if_unmodified_since")?,
+            content_type: extract_optional(&dict, "content_type")?,
+            cache_control: extract_optional(&dict, "cache_control")?,
+            content_disposition: extract_optional(&dict, 
"content_disposition")?,
+        })
+    }
+}
+
 impl From<StatOptions> for ocore::options::StatOptions {
     fn from(opts: StatOptions) -> Self {
         Self {

Reply via email to