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