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 87a7ac153 refactor(binding/python): Add multiple custom exception for
each of error code in Rust Core (#3492)
87a7ac153 is described below
commit 87a7ac15386b75d7135bdce425144ed32abce2e7
Author: Nadeshiko Manju <[email protected]>
AuthorDate: Tue Nov 7 14:37:32 2023 +0800
refactor(binding/python): Add multiple custom exception for each of error
code in Rust Core (#3492)
* refactor(binding/python): Add multiple custom exception for each of error
code in Rust Core
Signed-off-by: Manjusaka <[email protected]>
* update code
Signed-off-by: Manjusaka <[email protected]>
* Update code
Signed-off-by: Manjusaka <[email protected]>
* Update code
Signed-off-by: Manjusaka <[email protected]>
* Update code
Signed-off-by: Manjusaka <[email protected]>
* Revert "Update code"
This reverts commit 1d7d5a797c9c4236df78ba780b117451001dd02d.
* Update code
Signed-off-by: Manjusaka <[email protected]>
---------
Signed-off-by: Manjusaka <[email protected]>
---
bindings/python/python/opendal/__init__.pyi | 2 -
bindings/python/python/opendal/exceptions.pyi | 86 ++++++++++++++++++++++
bindings/python/src/errors.rs | 61 +++++++++++++++
bindings/python/src/lib.rs | 22 +++++-
bindings/python/src/operator.rs | 10 ++-
bindings/python/src/utils.rs | 21 ------
bindings/python/tests/test_async_copy.py | 10 +--
bindings/python/tests/test_async_delete.py | 4 +-
bindings/python/tests/test_async_rename.py | 18 ++---
bindings/python/tests/test_capability.py | 2 +-
.../{test_capability.py => test_exceptions.py} | 18 ++---
bindings/python/tests/test_read.py | 5 +-
bindings/python/tests/test_sync_copy.py | 10 +--
bindings/python/tests/test_sync_delete.py | 4 +-
bindings/python/tests/test_sync_rename.py | 18 ++---
bindings/python/tests/test_write.py | 5 +-
bindings/python/upgrade.md | 19 +++++
17 files changed, 239 insertions(+), 76 deletions(-)
diff --git a/bindings/python/python/opendal/__init__.pyi
b/bindings/python/python/opendal/__init__.pyi
index 7713e2a3c..2a28f523f 100644
--- a/bindings/python/python/opendal/__init__.pyi
+++ b/bindings/python/python/opendal/__init__.pyi
@@ -19,8 +19,6 @@ from typing import AsyncIterable, Iterable, Optional
from opendal.layers import Layer
-class Error(Exception): ...
-
class Operator:
def __init__(self, scheme: str, **kwargs): ...
def layer(self, layer: Layer): ...
diff --git a/bindings/python/python/opendal/exceptions.pyi
b/bindings/python/python/opendal/exceptions.pyi
new file mode 100644
index 000000000..a14f25c26
--- /dev/null
+++ b/bindings/python/python/opendal/exceptions.pyi
@@ -0,0 +1,86 @@
+# 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.
+
+class Error(Exception):
+ """Base class for exceptions in this module."""
+
+ pass
+
+class Unexpected(Error):
+ """Unexpected errors"""
+
+ pass
+
+class Unsupported(Error):
+ """Unsupported operation"""
+
+ pass
+
+class ConfigInvalid(Error):
+ """Config is invalid"""
+
+ pass
+
+class NotFound(Error):
+ """Not found"""
+
+ pass
+
+class PermissionDenied(Error):
+ """Permission denied"""
+
+ pass
+
+class IsADirectory(Error):
+ """Is a directory"""
+
+ pass
+
+class NotADirectory(Error):
+ """Not a directory"""
+
+ pass
+
+class AlreadyExists(Error):
+ """Already exists"""
+
+ pass
+
+class IsSameFile(Error):
+ """Is same file"""
+
+ pass
+
+class ConditionNotMatch(Error):
+ """Condition not match"""
+
+ pass
+
+class ContentTruncated(Error):
+ """Content truncated"""
+
+ pass
+
+class ContentIncomplete(Error):
+ """Content incomplete"""
+
+ pass
+
+class InvalidInput(Error):
+ """Invalid input"""
+
+ pass
diff --git a/bindings/python/src/errors.rs b/bindings/python/src/errors.rs
new file mode 100644
index 000000000..45bff4060
--- /dev/null
+++ b/bindings/python/src/errors.rs
@@ -0,0 +1,61 @@
+// 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.
+
+use pyo3::create_exception;
+use pyo3::exceptions::PyException;
+
+use crate::*;
+
+create_exception!(opendal, Error, PyException, "OpenDAL Base Exception");
+create_exception!(opendal, UnexpectedError, Error, "Unexpected errors");
+create_exception!(opendal, UnsupportedError, Error, "Unsupported operation");
+create_exception!(opendal, ConfigInvalidError, Error, "Config is invalid");
+create_exception!(opendal, NotFoundError, Error, "Not found");
+create_exception!(opendal, PermissionDeniedError, Error, "Permission denied");
+create_exception!(opendal, IsADirectoryError, Error, "Is a directory");
+create_exception!(opendal, NotADirectoryError, Error, "Not a directory");
+create_exception!(opendal, AlreadyExistsError, Error, "Already exists");
+create_exception!(opendal, IsSameFileError, Error, "Is same file");
+create_exception!(
+ opendal,
+ ConditionNotMatchError,
+ Error,
+ "Condition not match"
+);
+create_exception!(opendal, ContentTruncatedError, Error, "Content truncated");
+create_exception!(opendal, ContentIncompleteError, Error, "Content
incomplete");
+create_exception!(opendal, InvalidInputError, Error, "Invalid input");
+
+pub fn format_pyerr(err: ocore::Error) -> PyErr {
+ use ocore::ErrorKind::*;
+ match err.kind() {
+ Unexpected => UnexpectedError::new_err(err.to_string()),
+ Unsupported => UnsupportedError::new_err(err.to_string()),
+ ConfigInvalid => ConfigInvalidError::new_err(err.to_string()),
+ NotFound => NotFoundError::new_err(err.to_string()),
+ PermissionDenied => PermissionDeniedError::new_err(err.to_string()),
+ IsADirectory => IsADirectoryError::new_err(err.to_string()),
+ NotADirectory => NotADirectoryError::new_err(err.to_string()),
+ AlreadyExists => AlreadyExistsError::new_err(err.to_string()),
+ IsSameFile => IsSameFileError::new_err(err.to_string()),
+ ConditionNotMatch => ConditionNotMatchError::new_err(err.to_string()),
+ ContentTruncated => ContentTruncatedError::new_err(err.to_string()),
+ ContentIncomplete => ContentIncompleteError::new_err(err.to_string()),
+ InvalidInput => InvalidInputError::new_err(err.to_string()),
+ _ => UnexpectedError::new_err(err.to_string()),
+ }
+}
diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs
index a9c41fc00..076c75696 100644
--- a/bindings/python/src/lib.rs
+++ b/bindings/python/src/lib.rs
@@ -37,6 +37,8 @@ mod file;
pub use file::*;
mod utils;
pub use utils::*;
+mod errors;
+pub use errors::*;
/// OpenDAL Python binding
///
@@ -82,7 +84,6 @@ fn _opendal(py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Metadata>()?;
m.add_class::<PresignedRequest>()?;
m.add_class::<Capability>()?;
- m.add("Error", py.get_type::<Error>())?;
// Layer module
let layers_module = PyModule::new(py, "layers")?;
@@ -93,5 +94,24 @@ fn _opendal(py: Python, m: &PyModule) -> PyResult<()> {
.getattr("modules")?
.set_item("opendal.layers", layers_module)?;
+ let exception_module = PyModule::new(py, "exceptions")?;
+ exception_module.add("Error", py.get_type::<Error>())?;
+ exception_module.add("Unexpected", py.get_type::<UnexpectedError>())?;
+ exception_module.add("Unsupported", py.get_type::<UnsupportedError>())?;
+ exception_module.add("ConfigInvalid",
py.get_type::<ConfigInvalidError>())?;
+ exception_module.add("NotFound", py.get_type::<NotFoundError>())?;
+ exception_module.add("PermissionDenied",
py.get_type::<PermissionDeniedError>())?;
+ exception_module.add("IsADirectory", py.get_type::<IsADirectoryError>())?;
+ exception_module.add("NotADirectory",
py.get_type::<NotADirectoryError>())?;
+ exception_module.add("AlreadyExists",
py.get_type::<AlreadyExistsError>())?;
+ exception_module.add("IsSameFile", py.get_type::<IsSameFileError>())?;
+ exception_module.add("ConditionNotMatch",
py.get_type::<ConditionNotMatchError>())?;
+ exception_module.add("ContentTruncated",
py.get_type::<ContentTruncatedError>())?;
+ exception_module.add("ContentIncomplete",
py.get_type::<ContentIncompleteError>())?;
+ exception_module.add("InvalidInput", py.get_type::<InvalidInputError>())?;
+ m.add_submodule(exception_module)?;
+ py.import("sys")?
+ .getattr("modules")?
+ .set_item("opendal.exceptions", exception_module)?;
Ok(())
}
diff --git a/bindings/python/src/operator.rs b/bindings/python/src/operator.rs
index 5bc95a2cc..f6e8c3f86 100644
--- a/bindings/python/src/operator.rs
+++ b/bindings/python/src/operator.rs
@@ -86,7 +86,7 @@ impl Operator {
let w = this.writer(&path).map_err(format_pyerr)?;
Ok(File::new_writer(w))
} else {
- Err(Error::new_err(format!(
+ Err(UnsupportedError::new_err(format!(
"OpenDAL doesn't support mode: {mode}"
)))
}
@@ -245,7 +245,7 @@ impl AsyncOperator {
let w = this.writer(&path).await.map_err(format_pyerr)?;
Ok(AsyncFile::new_writer(w))
} else {
- Err(Error::new_err(format!(
+ Err(UnsupportedError::new_err(format!(
"OpenDAL doesn't support mode: {mode}"
)))
}
@@ -547,9 +547,11 @@ impl PresignedRequest {
let mut headers = HashMap::new();
for (k, v) in self.0.header().iter() {
let k = k.as_str();
- let v = v.to_str().map_err(|err| Error::new_err(err.to_string()))?;
+ let v = v
+ .to_str()
+ .map_err(|err| UnexpectedError::new_err(err.to_string()))?;
if headers.insert(k, v).is_some() {
- return Err(Error::new_err("duplicate header"));
+ return Err(UnexpectedError::new_err("duplicate header"));
}
}
Ok(headers)
diff --git a/bindings/python/src/utils.rs b/bindings/python/src/utils.rs
index 24cbca9da..df3a41aaa 100644
--- a/bindings/python/src/utils.rs
+++ b/bindings/python/src/utils.rs
@@ -17,20 +17,10 @@
use std::os::raw::c_int;
-use pyo3::create_exception;
-use pyo3::exceptions::PyException;
-use pyo3::exceptions::PyFileExistsError;
-use pyo3::exceptions::PyFileNotFoundError;
-use pyo3::exceptions::PyNotImplementedError;
-use pyo3::exceptions::PyPermissionError;
use pyo3::ffi;
use pyo3::prelude::*;
use pyo3::AsPyPointer;
-use crate::*;
-
-create_exception!(opendal, Error, PyException, "OpenDAL related errors");
-
/// A bytes-like object that implements buffer protocol.
#[pyclass(module = "opendal")]
pub struct Buffer {
@@ -83,14 +73,3 @@ impl Buffer {
Ok(())
}
}
-
-pub fn format_pyerr(err: ocore::Error) -> PyErr {
- use ocore::ErrorKind::*;
- match err.kind() {
- NotFound => PyFileNotFoundError::new_err(err.to_string()),
- AlreadyExists => PyFileExistsError::new_err(err.to_string()),
- PermissionDenied => PyPermissionError::new_err(err.to_string()),
- Unsupported => PyNotImplementedError::new_err(err.to_string()),
- _ => Error::new_err(err.to_string()),
- }
-}
diff --git a/bindings/python/tests/test_async_copy.py
b/bindings/python/tests/test_async_copy.py
index b5fea80d1..e2965a65c 100644
--- a/bindings/python/tests/test_async_copy.py
+++ b/bindings/python/tests/test_async_copy.py
@@ -16,10 +16,10 @@
# under the License.
import os
-from random import randint
from uuid import uuid4
import pytest
+from opendal.exceptions import IsADirectory, IsSameFile, NotFound
@pytest.mark.asyncio
@@ -42,7 +42,7 @@ async def test_async_copy(service_name, operator,
async_operator):
async def test_async_copy_non_exist(service_name, operator, async_operator):
source_path = f"random_file_{str(uuid4())}"
target_path = f"random_file_{str(uuid4())}"
- with pytest.raises(Exception) as e_info:
+ with pytest.raises(NotFound) :
await async_operator.copy(source_path, target_path)
@@ -52,7 +52,7 @@ async def test_async_copy_source_directory(service_name,
operator, async_operato
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:
+ with pytest.raises(IsADirectory) :
await async_operator.copy(source_path, target_path)
@@ -64,7 +64,7 @@ async def test_async_copy_target_directory(service_name,
operator, async_operato
await async_operator.write(source_path, content)
target_path = f"random_file_{str(uuid4())}/"
await async_operator.create_dir(target_path)
- with pytest.raises(Exception) as e_info:
+ with pytest.raises(IsADirectory) :
await async_operator.copy(source_path, target_path)
await async_operator.delete(source_path)
await async_operator.delete(target_path)
@@ -76,7 +76,7 @@ async def test_async_copy_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:
+ with pytest.raises(IsSameFile) :
await async_operator.copy(source_path, source_path)
await async_operator.delete(source_path)
diff --git a/bindings/python/tests/test_async_delete.py
b/bindings/python/tests/test_async_delete.py
index 07e94f85e..78f1c962f 100644
--- a/bindings/python/tests/test_async_delete.py
+++ b/bindings/python/tests/test_async_delete.py
@@ -16,10 +16,10 @@
# under the License.
import os
-from random import randint
from uuid import uuid4
import pytest
+from opendal.exceptions import NotFound
@pytest.mark.asyncio
@@ -43,6 +43,6 @@ async def test_async_remove_all(service_name, operator,
async_operator):
await async_operator.remove_all(f"{parent}/x/")
for path in excepted:
if not path.endswith("/"):
- with pytest.raises(FileNotFoundError) as e_info:
+ with pytest.raises(NotFound) :
await async_operator.read(f"{parent}/{path}")
await async_operator.remove_all(f"{parent}/")
diff --git a/bindings/python/tests/test_async_rename.py
b/bindings/python/tests/test_async_rename.py
index 77ed1fc32..181e9d184 100644
--- a/bindings/python/tests/test_async_rename.py
+++ b/bindings/python/tests/test_async_rename.py
@@ -16,10 +16,10 @@
# under the License.
import os
-from random import randint
from uuid import uuid4
import pytest
+from opendal.exceptions import IsADirectory, IsSameFile, NotFound
@pytest.mark.asyncio
@@ -30,7 +30,7 @@ async def test_async_rename_file(service_name, operator,
async_operator):
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:
+ with pytest.raises(NotFound) :
await async_operator.read(source_path)
assert await async_operator.read(target_path) == content
await async_operator.delete(target_path)
@@ -42,7 +42,7 @@ async def test_async_rename_file(service_name, operator,
async_operator):
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:
+ with pytest.raises(NotFound) :
await async_operator.rename(source_path, target_path)
@@ -52,7 +52,7 @@ 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:
+ with pytest.raises(IsADirectory) :
await async_operator.rename(source_path, target_path)
@@ -63,7 +63,7 @@ async def test_async_rename_file_to_directory(service_name,
operator, async_oper
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:
+ with pytest.raises(IsADirectory) :
await async_operator.rename(source_path, target_path)
await async_operator.delete(source_path)
@@ -74,7 +74,7 @@ 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:
+ with pytest.raises(IsSameFile) :
await async_operator.rename(source_path, source_path)
await async_operator.delete(source_path)
@@ -87,7 +87,7 @@ async def test_async_rename_nested(service_name, operator,
async_operator):
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:
+ with pytest.raises(NotFound) :
await async_operator.read(source_path)
assert await async_operator.read(target_path) == content
await async_operator.delete(target_path)
@@ -105,8 +105,8 @@ async def test_async_rename_overwrite(service_name,
operator, async_operator):
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)
+ with pytest.raises(NotFound) :
+ await async_operator.read(source_path)
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_capability.py
b/bindings/python/tests/test_capability.py
index da2e9c14f..ed0ea6493 100644
--- a/bindings/python/tests/test_capability.py
+++ b/bindings/python/tests/test_capability.py
@@ -27,5 +27,5 @@ def test_capability(service_name, operator):
def test_capability_exception(service_name, operator):
cap = operator.capability()
assert cap is not None
- with pytest.raises(AttributeError) as e_info:
+ with pytest.raises(AttributeError) :
cap.read_demo
diff --git a/bindings/python/tests/test_capability.py
b/bindings/python/tests/test_exceptions.py
similarity index 70%
copy from bindings/python/tests/test_capability.py
copy to bindings/python/tests/test_exceptions.py
index da2e9c14f..0d872e5ef 100644
--- a/bindings/python/tests/test_capability.py
+++ b/bindings/python/tests/test_exceptions.py
@@ -15,17 +15,13 @@
# specific language governing permissions and limitations
# under the License.
-import pytest
+import inspect
+from opendal import exceptions
+from opendal.exceptions import Error
-def test_capability(service_name, operator):
- cap = operator.capability()
- assert cap is not None
- assert cap.read is not None
-
-def test_capability_exception(service_name, operator):
- cap = operator.capability()
- assert cap is not None
- with pytest.raises(AttributeError) as e_info:
- cap.read_demo
+def test_exceptions():
+ for name, obj in inspect.getmembers(exceptions):
+ if inspect.isclass(obj):
+ assert issubclass(obj, Error)
diff --git a/bindings/python/tests/test_read.py
b/bindings/python/tests/test_read.py
index 62a424104..4e88f2a9a 100644
--- a/bindings/python/tests/test_read.py
+++ b/bindings/python/tests/test_read.py
@@ -20,6 +20,7 @@ from random import randint
from uuid import uuid4
import pytest
+from opendal.exceptions import NotFound
@pytest.mark.need_capability("read", "write", "delete")
@@ -134,12 +135,12 @@ async def test_async_read_stat(service_name, operator,
async_operator):
@pytest.mark.need_capability("read")
def test_sync_read_not_exists(service_name, operator, async_operator):
- with pytest.raises(FileNotFoundError):
+ with pytest.raises(NotFound):
operator.read(str(uuid4()))
@pytest.mark.asyncio
@pytest.mark.need_capability("read")
async def test_async_read_not_exists(service_name, operator, async_operator):
- with pytest.raises(FileNotFoundError):
+ with pytest.raises(NotFound):
await async_operator.read(str(uuid4()))
diff --git a/bindings/python/tests/test_sync_copy.py
b/bindings/python/tests/test_sync_copy.py
index 3db7bb6f1..f55ec92fc 100644
--- a/bindings/python/tests/test_sync_copy.py
+++ b/bindings/python/tests/test_sync_copy.py
@@ -16,10 +16,10 @@
# under the License.
import os
-from random import randint
from uuid import uuid4
import pytest
+from opendal.exceptions import IsADirectory, IsSameFile, NotFound
@pytest.mark.need_capability("read", "write", "copy")
@@ -40,7 +40,7 @@ def test_sync_copy(service_name, operator, async_operator):
def test_sync_copy_non_exist(service_name, operator, async_operator):
source_path = f"random_file_{str(uuid4())}"
target_path = f"random_file_{str(uuid4())}"
- with pytest.raises(Exception) as e_info:
+ with pytest.raises(NotFound) :
operator.copy(source_path, target_path)
@@ -49,7 +49,7 @@ def test_sync_copy_source_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:
+ with pytest.raises(IsADirectory) :
operator.copy(source_path, target_path)
@@ -60,7 +60,7 @@ def test_sync_copy_target_directory(service_name, operator,
async_operator):
operator.write(source_path, content)
target_path = f"random_file_{str(uuid4())}/"
operator.create_dir(target_path)
- with pytest.raises(Exception) as e_info:
+ with pytest.raises(IsADirectory) :
operator.copy(source_path, target_path)
operator.delete(source_path)
operator.delete(target_path)
@@ -71,7 +71,7 @@ def test_sync_copy_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:
+ with pytest.raises(IsSameFile) :
operator.copy(source_path, source_path)
operator.delete(source_path)
diff --git a/bindings/python/tests/test_sync_delete.py
b/bindings/python/tests/test_sync_delete.py
index 9d06d34cd..0ce3d32b8 100644
--- a/bindings/python/tests/test_sync_delete.py
+++ b/bindings/python/tests/test_sync_delete.py
@@ -16,10 +16,10 @@
# under the License.
import os
-from random import randint
from uuid import uuid4
import pytest
+from opendal.exceptions import NotFound
@pytest.mark.need_capability("read", "write", "delete", "list", "blocking")
@@ -42,6 +42,6 @@ def test_sync_remove_all(service_name, operator,
async_operator):
operator.remove_all(f"{parent}/x/")
for path in excepted:
if not path.endswith("/"):
- with pytest.raises(FileNotFoundError) as e_info:
+ with pytest.raises(NotFound) :
operator.read(f"{parent}/{path}")
operator.remove_all(f"{parent}/")
diff --git a/bindings/python/tests/test_sync_rename.py
b/bindings/python/tests/test_sync_rename.py
index 02def2805..ca6adca01 100644
--- a/bindings/python/tests/test_sync_rename.py
+++ b/bindings/python/tests/test_sync_rename.py
@@ -16,10 +16,10 @@
# under the License.
import os
-from random import randint
from uuid import uuid4
import pytest
+from opendal.exceptions import IsADirectory, IsSameFile, NotFound
@pytest.mark.need_capability("read", "write", "rename")
@@ -29,7 +29,7 @@ def test_sync_rename_file(service_name, operator,
async_operator):
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:
+ with pytest.raises(NotFound) :
operator.read(source_path)
assert operator.read(target_path) == content
operator.delete(target_path)
@@ -40,7 +40,7 @@ def test_sync_rename_file(service_name, operator,
async_operator):
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:
+ with pytest.raises(NotFound) :
operator.rename(source_path, target_path)
@@ -49,7 +49,7 @@ 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:
+ with pytest.raises(IsADirectory) :
operator.rename(source_path, target_path)
@@ -59,7 +59,7 @@ def test_sync_rename_file_to_directory(service_name,
operator, async_operator):
content = os.urandom(1024)
operator.write(source_path, content)
target_path = f"random_file_{str(uuid4())}/"
- with pytest.raises(Exception) as e_info:
+ with pytest.raises(IsADirectory) :
operator.rename(source_path, target_path)
operator.delete(source_path)
@@ -69,7 +69,7 @@ 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:
+ with pytest.raises(IsSameFile) :
operator.rename(source_path, source_path)
operator.delete(source_path)
@@ -81,7 +81,7 @@ def test_sync_rename_nested(service_name, operator,
async_operator):
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:
+ with pytest.raises(NotFound) :
operator.read(source_path)
assert operator.read(target_path) == content
operator.delete(target_path)
@@ -98,8 +98,8 @@ def test_sync_rename_overwrite(service_name, operator,
async_operator):
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)
+ with pytest.raises(NotFound) :
+ operator.read(source_path)
assert operator.read(target_path) == source_content
operator.delete(target_path)
operator.delete(source_path)
diff --git a/bindings/python/tests/test_write.py
b/bindings/python/tests/test_write.py
index 987f0ce3c..e98c6e959 100644
--- a/bindings/python/tests/test_write.py
+++ b/bindings/python/tests/test_write.py
@@ -20,6 +20,7 @@ from random import randint
from uuid import uuid4
import pytest
+from opendal.exceptions import NotFound
@pytest.mark.need_capability("write", "delete", "stat")
@@ -84,7 +85,7 @@ def test_sync_delete(service_name, operator, async_operator):
size = len(content)
operator.write(filename, content)
operator.delete(filename)
- with pytest.raises(FileNotFoundError):
+ with pytest.raises(NotFound):
operator.stat(filename)
@@ -97,5 +98,5 @@ async def test_async_delete(service_name, operator,
async_operator):
size = len(content)
await async_operator.write(filename, content)
await async_operator.delete(filename)
- with pytest.raises(FileNotFoundError):
+ with pytest.raises(NotFound):
await operator.stat(filename)
diff --git a/bindings/python/upgrade.md b/bindings/python/upgrade.md
index de9041e61..cbb43b85a 100644
--- a/bindings/python/upgrade.md
+++ b/bindings/python/upgrade.md
@@ -27,3 +27,22 @@ Open a file for reading in async way:
async with await op.open(filename, "rb") as r:
content = await r.read()
```
+
+## Breaking change for Errors
+
+We remove the old error classes and provide a couple of Exception based class
for the error handling.
+
+1. `opendal.Error` is based class for all the exceptions now.
+2. `opendal.exceptions.Unexpected` is added.
+3. `opendal.exceptions.Unsupported` is added.
+4. `opendal.exceptions.ConfigInvalid` is added.
+5. `opendal.exceptions.NotFound` is added.
+6. `opendal.exceptions.PermissionDenied` is added.
+7. `opendal.exceptions.IsADirectory` is added.
+8. `opendal.exceptions.NotADirectory` is added.
+9. `opendal.exceptions.AlreadyExists` is added.
+10. `opendal.exceptions.IsSameFile` is added.
+11. `opendal.exceptions.ConditionNotMatch` is added.
+12. `opendal.exceptions.ContentTruncated` is added.
+13. `opendal.exceptions.ContentIncomplete` is added.
+14. `opendal.exceptions.InvalidInput` is added.
\ No newline at end of file