diff --git a/bindings/python/python/opendal/__init__.pyi b/bindings/python/python/opendal/__init__.pyi index dcb7faf8d7e..2c33057e3eb 100644 --- a/bindings/python/python/opendal/__init__.pyi +++ b/bindings/python/python/opendal/__init__.pyi @@ -42,6 +42,7 @@ class Operator: def capability(self) -> Capability: ... def copy(self, source: str, target: str): ... def rename(self, source: str, target: str): ... + def remove_all(self, path: str): ... class AsyncOperator: def __init__(self, scheme: str, **kwargs): ... @@ -71,6 +72,7 @@ class AsyncOperator: def capability(self) -> Capability: ... async def copy(self, source: str, target: str): ... async def rename(self, source: str, target: str): ... + async def remove_all(self, path: 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 027a73f98be..2ef6253d62f 100644 --- a/bindings/python/src/asyncio.rs +++ b/bindings/python/src/asyncio.rs @@ -171,6 +171,14 @@ impl AsyncOperator { }) } + /// Remove all file + pub fn remove_all<'p>(&'p self, py: Python<'p>, path: String) -> PyResult<&'p PyAny> { + let this = self.0.clone(); + future_into_py(py, async move { + this.remove_all(&path).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 65dc8fdb6aa..cacbadc9ce6 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -185,6 +185,12 @@ impl Operator { pub fn rename(&self, source: &str, target: &str) -> PyResult<()> { self.0.rename(source, target).map_err(format_pyerr) } + + /// Remove all file + pub fn remove_all(&self, path: &str) -> PyResult<()> { + self.0.remove_all(path).map_err(format_pyerr) + } + /// Create a dir at given path. /// /// # Notes diff --git a/bindings/python/tests/test_async_delete.py b/bindings/python/tests/test_async_delete.py new file mode 100644 index 00000000000..9b230f0f876 --- /dev/null +++ b/bindings/python/tests/test_async_delete.py @@ -0,0 +1,48 @@ +# 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 + + +@pytest.mark.asyncio +@pytest.mark.need_capability("read", "write", "delete", "list") +async def test_async_remove_all(service_name, operator, async_operator): + parent = f"random_dir_{str(uuid4())}" + excepted = [ + "x/", + "x/y", + "x/x/", + "x/x/y", + "x/x/x/", + "x/x/x/y", + "x/x/x/x/", + ] + for path in excepted: + if path.endswith("/"): + await async_operator.create_dir(f"{parent}/{path}") + else: + await async_operator.write(f"{parent}/{path}", os.urandom(1024)) + await async_operator.remove_all(f"{parent}/x/") + for path in excepted: + if not path.endswith("/"): + with pytest.raises(FileNotFoundError) as e_info: + await async_operator.read(f"{parent}/{path}") + await async_operator.remove_all(f"{parent}/") \ No newline at end of file diff --git a/bindings/python/tests/test_sync_delete.py b/bindings/python/tests/test_sync_delete.py new file mode 100644 index 00000000000..9d06d34cd33 --- /dev/null +++ b/bindings/python/tests/test_sync_delete.py @@ -0,0 +1,47 @@ +# 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 + + +@pytest.mark.need_capability("read", "write", "delete", "list", "blocking") +def test_sync_remove_all(service_name, operator, async_operator): + parent = f"random_dir_{str(uuid4())}" + excepted = [ + "x/", + "x/y", + "x/x/", + "x/x/y", + "x/x/x/", + "x/x/x/y", + "x/x/x/x/", + ] + for path in excepted: + if path.endswith("/"): + operator.create_dir(f"{parent}/{path}") + else: + operator.write(f"{parent}/{path}", os.urandom(1024)) + operator.remove_all(f"{parent}/x/") + for path in excepted: + if not path.endswith("/"): + with pytest.raises(FileNotFoundError) as e_info: + operator.read(f"{parent}/{path}") + operator.remove_all(f"{parent}/")