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

manjusaka pushed a commit to branch manjusaka/support-rename-for-python
in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git

commit 4ff8106d97ec1337ece1396ce866689150adc10c
Author: Manjusaka <[email protected]>
AuthorDate: Thu Nov 2 19:58:11 2023 +0800

    feat(binding/python): Support rename API for Python binding
    
    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)

Reply via email to