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 27b2a16f1 feat(binding/python): Support rename API for Python binding
(#3467)
27b2a16f1 is described below
commit 27b2a16f104a825cfaf4a33fb5b5e3da7d335123
Author: Nadeshiko Manju <[email protected]>
AuthorDate: Thu Nov 2 20:29:46 2023 +0800
feat(binding/python): Support rename API for Python binding (#3467)
Signed-off-by: Manjusaka <[email protected]>
---
bindings/python/python/opendal/__init__.pyi | 2 +
bindings/python/src/asyncio.rs | 13 ++++
bindings/python/src/lib.rs | 4 +
bindings/python/tests/conftest.py | 8 +-
bindings/python/tests/test_async_rename.py | 112 ++++++++++++++++++++++++++++
bindings/python/tests/test_sync_rename.py | 105 ++++++++++++++++++++++++++
6 files changed, 242 insertions(+), 2 deletions(-)
diff --git a/bindings/python/python/opendal/__init__.pyi
b/bindings/python/python/opendal/__init__.pyi
index 4d7f8a7ee..dcb7faf8d 100644
--- a/bindings/python/python/opendal/__init__.pyi
+++ b/bindings/python/python/opendal/__init__.pyi
@@ -41,6 +41,7 @@ class Operator:
def scan(self, path: str) -> Iterable[Entry]: ...
def capability(self) -> Capability: ...
def copy(self, source: str, target: str): ...
+ def rename(self, source: str, target: str): ...
class AsyncOperator:
def __init__(self, scheme: str, **kwargs): ...
@@ -69,6 +70,7 @@ class AsyncOperator:
) -> PresignedRequest: ...
def capability(self) -> Capability: ...
async def copy(self, source: str, target: str): ...
+ async def rename(self, source: str, target: str): ...
class Reader:
def read(self, size: Optional[int] = None) -> memoryview: ...
diff --git a/bindings/python/src/asyncio.rs b/bindings/python/src/asyncio.rs
index 12c9eefee..027a73f98 100644
--- a/bindings/python/src/asyncio.rs
+++ b/bindings/python/src/asyncio.rs
@@ -158,6 +158,19 @@ impl AsyncOperator {
})
}
+ /// Rename filename
+ pub fn rename<'p>(
+ &'p self,
+ py: Python<'p>,
+ source: String,
+ target: String,
+ ) -> PyResult<&'p PyAny> {
+ let this = self.0.clone();
+ future_into_py(py, async move {
+ this.rename(&source, &target).await.map_err(format_pyerr)
+ })
+ }
+
/// Create a dir at given path.
///
/// # Notes
diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs
index dee9db8f4..65dc8fdb6 100644
--- a/bindings/python/src/lib.rs
+++ b/bindings/python/src/lib.rs
@@ -181,6 +181,10 @@ impl Operator {
self.0.copy(source, target).map_err(format_pyerr)
}
+ /// Rename filename.
+ pub fn rename(&self, source: &str, target: &str) -> PyResult<()> {
+ self.0.rename(source, target).map_err(format_pyerr)
+ }
/// Create a dir at given path.
///
/// # Notes
diff --git a/bindings/python/tests/conftest.py
b/bindings/python/tests/conftest.py
index bc831de69..970bb3ba5 100644
--- a/bindings/python/tests/conftest.py
+++ b/bindings/python/tests/conftest.py
@@ -56,12 +56,16 @@ def setup_config(service_name):
@pytest.fixture()
def operator(service_name, setup_config):
- return opendal.Operator(service_name,
**setup_config).layer(opendal.layers.RetryLayer())
+ return opendal.Operator(service_name, **setup_config).layer(
+ opendal.layers.RetryLayer()
+ )
@pytest.fixture()
def async_operator(service_name, setup_config):
- return opendal.AsyncOperator(service_name,
**setup_config).layer(opendal.layers.RetryLayer())
+ return opendal.AsyncOperator(service_name, **setup_config).layer(
+ opendal.layers.RetryLayer()
+ )
@pytest.fixture(autouse=True)
diff --git a/bindings/python/tests/test_async_rename.py
b/bindings/python/tests/test_async_rename.py
new file mode 100644
index 000000000..77ed1fc32
--- /dev/null
+++ b/bindings/python/tests/test_async_rename.py
@@ -0,0 +1,112 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import os
+from random import randint
+from uuid import uuid4
+
+import pytest
+
+
[email protected]
[email protected]_capability("read", "write", "rename")
+async def test_async_rename_file(service_name, operator, async_operator):
+ source_path = f"random_file_{str(uuid4())}"
+ content = os.urandom(1024)
+ await async_operator.write(source_path, content)
+ target_path = f"random_file_{str(uuid4())}"
+ await async_operator.rename(source_path, target_path)
+ with pytest.raises(FileNotFoundError) as e_info:
+ await async_operator.read(source_path)
+ assert await async_operator.read(target_path) == content
+ await async_operator.delete(target_path)
+ await async_operator.delete(source_path)
+
+
[email protected]
[email protected]_capability("read", "write", "rename")
+async def test_async_rename_non_exists_file(service_name, operator,
async_operator):
+ source_path = f"random_file_{str(uuid4())}"
+ target_path = f"random_file_{str(uuid4())}"
+ with pytest.raises(FileNotFoundError) as e_info:
+ await async_operator.rename(source_path, target_path)
+
+
[email protected]
[email protected]_capability("read", "write", "rename")
+async def test_async_rename_directory(service_name, operator, async_operator):
+ source_path = f"random_file_{str(uuid4())}/"
+ await async_operator.create_dir(source_path)
+ target_path = f"random_file_{str(uuid4())}"
+ with pytest.raises(Exception) as e_info:
+ await async_operator.rename(source_path, target_path)
+
+
[email protected]
[email protected]_capability("read", "write", "rename")
+async def test_async_rename_file_to_directory(service_name, operator,
async_operator):
+ source_path = f"random_file_{str(uuid4())}"
+ content = os.urandom(1024)
+ await async_operator.write(source_path, content)
+ target_path = f"random_file_{str(uuid4())}/"
+ with pytest.raises(Exception) as e_info:
+ await async_operator.rename(source_path, target_path)
+ await async_operator.delete(source_path)
+
+
[email protected]
[email protected]_capability("read", "write", "rename")
+async def test_async_rename_self(service_name, operator, async_operator):
+ source_path = f"random_file_{str(uuid4())}"
+ content = os.urandom(1024)
+ await async_operator.write(source_path, content)
+ with pytest.raises(Exception) as e_info:
+ await async_operator.rename(source_path, source_path)
+ await async_operator.delete(source_path)
+
+
[email protected]
[email protected]_capability("read", "write", "rename")
+async def test_async_rename_nested(service_name, operator, async_operator):
+ source_path = f"random_file_{str(uuid4())}"
+ content = os.urandom(1024)
+ await async_operator.write(source_path, content)
+ target_path = f"random_file_{str(uuid4())}/{str(uuid4())}/{str(uuid4())}"
+ await async_operator.rename(source_path, target_path)
+ with pytest.raises(FileNotFoundError) as e_info:
+ await async_operator.read(source_path)
+ assert await async_operator.read(target_path) == content
+ await async_operator.delete(target_path)
+ await async_operator.delete(source_path)
+
+
[email protected]
[email protected]_capability("read", "write", "rename")
+async def test_async_rename_overwrite(service_name, operator, async_operator):
+ source_path = f"random_file_{str(uuid4())}"
+ target_path = f"random_file_{str(uuid4())}"
+ source_content = os.urandom(1024)
+ target_content = os.urandom(1024)
+ assert source_content != target_content
+ await async_operator.write(source_path, source_content)
+ await async_operator.write(target_path, target_content)
+ await async_operator.rename(source_path, target_path)
+ with pytest.raises(Exception) as e_info:
+ await async_operator.read(source_content)
+ assert await async_operator.read(target_path) == source_content
+ await async_operator.delete(target_path)
+ await async_operator.delete(source_path)
diff --git a/bindings/python/tests/test_sync_rename.py
b/bindings/python/tests/test_sync_rename.py
new file mode 100644
index 000000000..02def2805
--- /dev/null
+++ b/bindings/python/tests/test_sync_rename.py
@@ -0,0 +1,105 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import os
+from random import randint
+from uuid import uuid4
+
+import pytest
+
+
[email protected]_capability("read", "write", "rename")
+def test_sync_rename_file(service_name, operator, async_operator):
+ source_path = f"random_file_{str(uuid4())}"
+ content = os.urandom(1024)
+ operator.write(source_path, content)
+ target_path = f"random_file_{str(uuid4())}"
+ operator.rename(source_path, target_path)
+ with pytest.raises(FileNotFoundError) as e_info:
+ operator.read(source_path)
+ assert operator.read(target_path) == content
+ operator.delete(target_path)
+ operator.delete(source_path)
+
+
[email protected]_capability("read", "write", "rename")
+def test_sync_rename_non_exists_file(service_name, operator, async_operator):
+ source_path = f"random_file_{str(uuid4())}"
+ target_path = f"random_file_{str(uuid4())}"
+ with pytest.raises(FileNotFoundError) as e_info:
+ operator.rename(source_path, target_path)
+
+
[email protected]_capability("read", "write", "rename")
+def test_sync_rename_directory(service_name, operator, async_operator):
+ source_path = f"random_file_{str(uuid4())}/"
+ operator.create_dir(source_path)
+ target_path = f"random_file_{str(uuid4())}"
+ with pytest.raises(Exception) as e_info:
+ operator.rename(source_path, target_path)
+
+
[email protected]_capability("read", "write", "rename")
+def test_sync_rename_file_to_directory(service_name, operator, async_operator):
+ source_path = f"random_file_{str(uuid4())}"
+ content = os.urandom(1024)
+ operator.write(source_path, content)
+ target_path = f"random_file_{str(uuid4())}/"
+ with pytest.raises(Exception) as e_info:
+ operator.rename(source_path, target_path)
+ operator.delete(source_path)
+
+
[email protected]_capability("read", "write", "rename")
+def test_sync_rename_self(service_name, operator, async_operator):
+ source_path = f"random_file_{str(uuid4())}"
+ content = os.urandom(1024)
+ operator.write(source_path, content)
+ with pytest.raises(Exception) as e_info:
+ operator.rename(source_path, source_path)
+ operator.delete(source_path)
+
+
[email protected]_capability("read", "write", "rename")
+def test_sync_rename_nested(service_name, operator, async_operator):
+ source_path = f"random_file_{str(uuid4())}"
+ content = os.urandom(1024)
+ operator.write(source_path, content)
+ target_path = f"random_file_{str(uuid4())}/{str(uuid4())}/{str(uuid4())}"
+ operator.rename(source_path, target_path)
+ with pytest.raises(FileNotFoundError) as e_info:
+ operator.read(source_path)
+ assert operator.read(target_path) == content
+ operator.delete(target_path)
+ operator.delete(source_path)
+
+
[email protected]_capability("read", "write", "rename")
+def test_sync_rename_overwrite(service_name, operator, async_operator):
+ source_path = f"random_file_{str(uuid4())}"
+ target_path = f"random_file_{str(uuid4())}"
+ source_content = os.urandom(1024)
+ target_content = os.urandom(1024)
+ assert source_content != target_content
+ operator.write(source_path, source_content)
+ operator.write(target_path, target_content)
+ operator.rename(source_path, target_path)
+ with pytest.raises(Exception) as e_info:
+ operator.read(source_content)
+ assert operator.read(target_path) == source_content
+ operator.delete(target_path)
+ operator.delete(source_path)