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/incubator-opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 044cf6437 feat(binding/python): allow setting append/buffer/more in 
write() call (#3256)
044cf6437 is described below

commit 044cf6437277e215b7d14ff18364758e46854575
Author: Wang Guan <[email protected]>
AuthorDate: Thu Oct 12 13:29:21 2023 +0900

    feat(binding/python): allow setting append/buffer/more in write() call 
(#3256)
    
    * binding/python: add new parameters to stub methods
    
    * add parameters to implementations
    
    * run cargo fmt
    
    * make clippy happy
    
    * run black on py stubs
    
    * Operator: read and apply kwargs in PyDict
    
    * refactor kwargs recognition to struct WriteOptions
    
    * touch test to see if WriteOptions worked
    
    * Revert "touch test to see if WriteOptions worked"
    
    This reverts commit bb381278ed668147e6693c2f168e3efe187bbbae.
    
    * remove WriteOptions and reuse od::OpWrite
    
    * rewrite build_opwrite()
---
 bindings/python/python/opendal/__init__.pyi | 28 ++++++++++--
 bindings/python/src/asyncio.rs              | 26 ++++++++++-
 bindings/python/src/lib.rs                  | 69 ++++++++++++++++++++++++++++-
 3 files changed, 115 insertions(+), 8 deletions(-)

diff --git a/bindings/python/python/opendal/__init__.pyi 
b/bindings/python/python/opendal/__init__.pyi
index 2b3501500..b25d66e4c 100644
--- a/bindings/python/python/opendal/__init__.pyi
+++ b/bindings/python/python/opendal/__init__.pyi
@@ -23,7 +23,16 @@ class Operator:
     def __init__(self, scheme: str, **kwargs): ...
     def read(self, path: str) -> bytes: ...
     def open_reader(self, path: str) -> Reader: ...
-    def write(self, path: str, bs: bytes): ...
+    def write(
+        self,
+        path: str,
+        bs: bytes,
+        append: bool = None,
+        buffer: int = None,
+        content_type: str = None,
+        content_disposition: str = None,
+        cache_control: str = None,
+    ): ...
     def stat(self, path: str) -> Metadata: ...
     def create_dir(self, path: str): ...
     def delete(self, path: str): ...
@@ -34,7 +43,16 @@ class AsyncOperator:
     def __init__(self, scheme: str, **kwargs): ...
     async def read(self, path: str) -> bytes: ...
     def open_reader(self, path: str) -> AsyncReader: ...
-    async def write(self, path: str, bs: bytes): ...
+    async def write(
+        self,
+        path: str,
+        bs: bytes,
+        append: bool = None,
+        buffer: int = None,
+        content_type: str = None,
+        content_disposition: str = None,
+        cache_control: str = None,
+    ): ...
     async def stat(self, path: str) -> Metadata: ...
     async def create_dir(self, path: str): ...
     async def delete(self, path: str): ...
@@ -42,7 +60,9 @@ class AsyncOperator:
     async def scan(self, path: str) -> AsyncIterable[Entry]: ...
     async def presign_stat(self, path: str, expire_second: int) -> 
PresignedRequest: ...
     async def presign_read(self, path: str, expire_second: int) -> 
PresignedRequest: ...
-    async def presign_write(self, path: str, expire_second: int) -> 
PresignedRequest: ...
+    async def presign_write(
+        self, path: str, expire_second: int
+    ) -> PresignedRequest: ...
 
 class Reader:
     def read(self, size: Optional[int] = None) -> bytes: ...
@@ -86,4 +106,4 @@ class PresignedRequest:
     @property
     def method(self) -> str: ...
     @property
-    def headers(self) -> dict[str, str]: ...
\ No newline at end of file
+    def headers(self) -> dict[str, str]: ...
diff --git a/bindings/python/src/asyncio.rs b/bindings/python/src/asyncio.rs
index 91ea655f3..4b71b948c 100644
--- a/bindings/python/src/asyncio.rs
+++ b/bindings/python/src/asyncio.rs
@@ -36,6 +36,7 @@ use tokio::io::AsyncSeekExt;
 use tokio::sync::Mutex;
 
 use crate::build_operator;
+use crate::build_opwrite;
 use crate::format_pyerr;
 use crate::layers;
 use crate::Entry;
@@ -87,11 +88,32 @@ impl AsyncOperator {
     }
 
     /// Write bytes into given path.
-    pub fn write<'p>(&'p self, py: Python<'p>, path: String, bs: &PyBytes) -> 
PyResult<&'p PyAny> {
+    #[pyo3(signature = (path, bs, **kwargs))]
+    pub fn write<'p>(
+        &'p self,
+        py: Python<'p>,
+        path: String,
+        bs: &PyBytes,
+        kwargs: Option<&PyDict>,
+    ) -> PyResult<&'p PyAny> {
+        let opwrite = build_opwrite(kwargs)?;
         let this = self.0.clone();
         let bs = bs.as_bytes().to_vec();
         future_into_py(py, async move {
-            this.write(&path, bs).await.map_err(format_pyerr)
+            let mut write = this.write_with(&path, 
bs).append(opwrite.append());
+            if let Some(buffer) = opwrite.buffer() {
+                write = write.buffer(buffer);
+            }
+            if let Some(content_type) = opwrite.content_type() {
+                write = write.content_type(content_type);
+            }
+            if let Some(content_disposition) = opwrite.content_disposition() {
+                write = write.content_disposition(content_disposition);
+            }
+            if let Some(cache_control) = opwrite.cache_control() {
+                write = write.cache_control(cache_control);
+            }
+            write.await.map_err(format_pyerr)
         })
     }
 
diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs
index 2d90c6028..0ad5b0e09 100644
--- a/bindings/python/src/lib.rs
+++ b/bindings/python/src/lib.rs
@@ -120,8 +120,24 @@ impl Operator {
     }
 
     /// Write bytes into given path.
-    pub fn write(&self, path: &str, bs: Vec<u8>) -> PyResult<()> {
-        self.0.write(path, bs).map_err(format_pyerr)
+    #[pyo3(signature = (path, bs, **kwargs))]
+    pub fn write(&self, path: &str, bs: Vec<u8>, kwargs: Option<&PyDict>) -> 
PyResult<()> {
+        let opwrite = build_opwrite(kwargs)?;
+        let mut write = self.0.write_with(path, bs).append(opwrite.append());
+        if let Some(buffer) = opwrite.buffer() {
+            write = write.buffer(buffer);
+        }
+        if let Some(content_type) = opwrite.content_type() {
+            write = write.content_type(content_type);
+        }
+        if let Some(content_disposition) = opwrite.content_disposition() {
+            write = write.content_disposition(content_disposition);
+        }
+        if let Some(cache_control) = opwrite.cache_control() {
+            write = write.cache_control(cache_control);
+        }
+
+        write.call().map_err(format_pyerr)
     }
 
     /// Get current path's metadata **without cache** directly.
@@ -421,6 +437,55 @@ fn format_pyerr(err: od::Error) -> PyErr {
     }
 }
 
+/// recognize OpWrite-equivalent options passed as python dict
+pub(crate) fn build_opwrite(kwargs: Option<&PyDict>) -> 
PyResult<od::raw::OpWrite> {
+    use od::raw::OpWrite;
+    let mut op = OpWrite::new();
+
+    let dict = if let Some(kwargs) = kwargs {
+        kwargs
+    } else {
+        return Ok(op);
+    };
+
+    if let Some(append) = dict.get_item("append") {
+        let v = append
+            .extract::<bool>()
+            .map_err(|err| PyValueError::new_err(format!("append must be bool, 
got {}", err)))?;
+        op = op.with_append(v);
+    }
+
+    if let Some(buffer) = dict.get_item("buffer") {
+        let v = buffer
+            .extract::<usize>()
+            .map_err(|err| PyValueError::new_err(format!("buffer must be 
usize, got {}", err)))?;
+        op = op.with_buffer(v);
+    }
+
+    if let Some(content_type) = dict.get_item("content_type") {
+        let v = content_type.extract::<String>().map_err(|err| {
+            PyValueError::new_err(format!("content_type must be str, got {}", 
err))
+        })?;
+        op = op.with_content_type(v.as_str());
+    }
+
+    if let Some(content_disposition) = dict.get_item("content_disposition") {
+        let v = content_disposition.extract::<String>().map_err(|err| {
+            PyValueError::new_err(format!("content_disposition must be str, 
got {}", err))
+        })?;
+        op = op.with_content_disposition(v.as_str());
+    }
+
+    if let Some(cache_control) = dict.get_item("cache_control") {
+        let v = cache_control.extract::<String>().map_err(|err| {
+            PyValueError::new_err(format!("cache_control must be str, got {}", 
err))
+        })?;
+        op = op.with_cache_control(v.as_str());
+    }
+
+    Ok(op)
+}
+
 /// OpenDAL Python binding
 ///
 /// ## Installation

Reply via email to