This is an automated email from the ASF dual-hosted git repository.
manjusaka pushed a commit to branch manjusaka/polish-exception
in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git
The following commit(s) were added to refs/heads/manjusaka/polish-exception by
this push:
new d0fa9a22e update code
d0fa9a22e is described below
commit d0fa9a22ed2642be3ca7c2f57ce25bf53b904d34
Author: Manjusaka <[email protected]>
AuthorDate: Mon Nov 6 22:35:12 2023 +0800
update code
Signed-off-by: Manjusaka <[email protected]>
---
bindings/python/python/opendal/exceptions.pyi | 30 ++++++++++----------
bindings/python/src/errors.rs | 9 ++++--
bindings/python/src/lib.rs | 40 ++++++++++-----------------
bindings/python/src/operator.rs | 10 ++++---
bindings/python/tests/test_async_copy.py | 10 +++----
bindings/python/tests/test_async_delete.py | 4 +--
bindings/python/tests/test_async_rename.py | 16 +++++------
bindings/python/tests/test_read.py | 6 ++--
bindings/python/tests/test_sync_copy.py | 10 +++----
bindings/python/tests/test_sync_delete.py | 4 +--
bindings/python/tests/test_sync_rename.py | 16 +++++------
bindings/python/tests/test_write.py | 6 ++--
bindings/python/upgrade.md | 19 +++++++++++++
13 files changed, 97 insertions(+), 83 deletions(-)
diff --git a/bindings/python/python/opendal/exceptions.pyi
b/bindings/python/python/opendal/exceptions.pyi
index 9ca922335..af37d7b6d 100644
--- a/bindings/python/python/opendal/exceptions.pyi
+++ b/bindings/python/python/opendal/exceptions.pyi
@@ -15,72 +15,72 @@
# specific language governing permissions and limitations
# under the License.
-class Error(Exception):
- """OpenDAL unrelated errors"""
+class Unknown(Exception):
+ """Unknown error"""
pass
-class UnexpectedError(Exception):
+class Unexpected(Exception):
"""Unexpected errors"""
pass
-class UnsupportedError(Exception):
+class Unsupported(Exception):
"""Unsupported operation"""
pass
-class ConfigInvalidError(Exception):
+class ConfigInvalid(Exception):
"""Config is invalid"""
pass
-class NotFoundError(Exception):
+class NotFound(Exception):
"""Not found"""
pass
-class PermissionDeniedError(Exception):
+class PermissionDenied(Exception):
"""Permission denied"""
pass
-class IsADirectoryError(Exception):
+class IsADirectory(Exception):
"""Is a directory"""
pass
-class NotADirectoryError(Exception):
+class NotADirectory(Exception):
"""Not a directory"""
pass
-class AlreadyExistsError(Exception):
+class AlreadyExists(Exception):
"""Already exists"""
pass
-class IsSameFileError(Exception):
+class IsSameFile(Exception):
"""Is same file"""
pass
-class ConditionNotMatchError(Exception):
+class ConditionNotMatch(Exception):
"""Condition not match"""
pass
-class ContentTruncatedError(Exception):
+class ContentTruncated(Exception):
"""Content truncated"""
pass
-class ContentIncompleteError(Exception):
+class ContentIncomplete(Exception):
"""Content incomplete"""
pass
-class InvalidInputError(Exception):
+class InvalidInput(Exception):
"""Invalid input"""
pass
diff --git a/bindings/python/src/errors.rs b/bindings/python/src/errors.rs
index 5d7f98e49..af9a10e80 100644
--- a/bindings/python/src/errors.rs
+++ b/bindings/python/src/errors.rs
@@ -63,7 +63,12 @@ create_exception!(
"Content incomplete"
);
create_exception!(opendal, InvalidInputError, PyException, "Invalid input");
-create_exception!(opendal, Error, PyException, "OpenDAL unrelated errors");
+create_exception!(
+ opendal,
+ Unknown,
+ PyException,
+ "OpenDAL Python binding unknown error"
+);
pub fn format_pyerr(err: ocore::Error) -> PyErr {
use ocore::ErrorKind::*;
@@ -81,6 +86,6 @@ pub fn format_pyerr(err: ocore::Error) -> PyErr {
ContentTruncated => ContentTruncatedError::new_err(err.to_string()),
ContentIncomplete => ContentIncompleteError::new_err(err.to_string()),
InvalidInput => InvalidInputError::new_err(err.to_string()),
- _ => Error::new_err(err.to_string()),
+ _ => Unknown::new_err(err.to_string()),
}
}
diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs
index bada6d204..f52e86188 100644
--- a/bindings/python/src/lib.rs
+++ b/bindings/python/src/lib.rs
@@ -95,32 +95,20 @@ fn _opendal(py: Python, m: &PyModule) -> PyResult<()> {
.set_item("opendal.layers", layers_module)?;
let exception_module = PyModule::new(py, "exceptions")?;
- exception_module.add("Error", py.get_type::<Error>())?;
- exception_module.add("UnexpectedError", py.get_type::<UnexpectedError>())?;
- exception_module.add("UnsupportedError",
py.get_type::<UnsupportedError>())?;
- exception_module.add("ConfigInvalidError",
py.get_type::<ConfigInvalidError>())?;
- exception_module.add("NotFoundError", py.get_type::<NotFoundError>())?;
- exception_module.add(
- "PermissionDeniedError",
- py.get_type::<PermissionDeniedError>(),
- )?;
- exception_module.add("IsADirectoryError",
py.get_type::<IsADirectoryError>())?;
- exception_module.add("NotADirectoryError",
py.get_type::<NotADirectoryError>())?;
- exception_module.add("AlreadyExistsError",
py.get_type::<AlreadyExistsError>())?;
- exception_module.add("IsSameFileError", py.get_type::<IsSameFileError>())?;
- exception_module.add(
- "ConditionNotMatchError",
- py.get_type::<ConditionNotMatchError>(),
- )?;
- exception_module.add(
- "ContentTruncatedError",
- py.get_type::<ContentTruncatedError>(),
- )?;
- exception_module.add(
- "ContentIncompleteError",
- py.get_type::<ContentIncompleteError>(),
- )?;
- exception_module.add("InvalidInputError",
py.get_type::<InvalidInputError>())?;
+ exception_module.add("Unknown", py.get_type::<Unknown>())?;
+ 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")?
diff --git a/bindings/python/src/operator.rs b/bindings/python/src/operator.rs
index 5bc95a2cc..ac2326323 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| Unknown::new_err(err.to_string()))?;
if headers.insert(k, v).is_some() {
- return Err(Error::new_err("duplicate header"));
+ return Err(Unknown::new_err("duplicate header"));
}
}
Ok(headers)
diff --git a/bindings/python/tests/test_async_copy.py
b/bindings/python/tests/test_async_copy.py
index 551709e1b..424e3427c 100644
--- a/bindings/python/tests/test_async_copy.py
+++ b/bindings/python/tests/test_async_copy.py
@@ -20,7 +20,7 @@ from random import randint
from uuid import uuid4
import pytest
-from opendal.exceptions import IsADirectoryError, IsSameFileError,
NotFoundError
+from opendal.exceptions import IsADirectory, IsSameFile, NotFound
@pytest.mark.asyncio
@@ -43,7 +43,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(NotFoundError) as e_info:
+ with pytest.raises(NotFound) as e_info:
await async_operator.copy(source_path, target_path)
@@ -53,7 +53,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(IsADirectoryError) as e_info:
+ with pytest.raises(IsADirectory) as e_info:
await async_operator.copy(source_path, target_path)
@@ -65,7 +65,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(IsADirectoryError) as e_info:
+ with pytest.raises(IsADirectory) as e_info:
await async_operator.copy(source_path, target_path)
await async_operator.delete(source_path)
await async_operator.delete(target_path)
@@ -77,7 +77,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(IsSameFileError) as e_info:
+ with pytest.raises(IsSameFile) as e_info:
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 ab1051b58..ff3d41d0c 100644
--- a/bindings/python/tests/test_async_delete.py
+++ b/bindings/python/tests/test_async_delete.py
@@ -20,7 +20,7 @@ from random import randint
from uuid import uuid4
import pytest
-from opendal.exceptions import NotFoundError
+from opendal.exceptions import NotFound
@pytest.mark.asyncio
@@ -44,6 +44,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(NotFoundError) as e_info:
+ with pytest.raises(NotFound) as e_info:
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 33737600d..8c718fc52 100644
--- a/bindings/python/tests/test_async_rename.py
+++ b/bindings/python/tests/test_async_rename.py
@@ -20,7 +20,7 @@ from random import randint
from uuid import uuid4
import pytest
-from opendal.exceptions import IsADirectoryError, IsSameFileError,
NotFoundError
+from opendal.exceptions import IsADirectory, IsSameFile, NotFound
@pytest.mark.asyncio
@@ -31,7 +31,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(NotFoundError) as e_info:
+ with pytest.raises(NotFound) as e_info:
await async_operator.read(source_path)
assert await async_operator.read(target_path) == content
await async_operator.delete(target_path)
@@ -43,7 +43,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(NotFoundError) as e_info:
+ with pytest.raises(NotFound) as e_info:
await async_operator.rename(source_path, target_path)
@@ -53,7 +53,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(IsADirectoryError) as e_info:
+ with pytest.raises(IsADirectory) as e_info:
await async_operator.rename(source_path, target_path)
@@ -64,7 +64,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(IsADirectoryError) as e_info:
+ with pytest.raises(IsADirectory) as e_info:
await async_operator.rename(source_path, target_path)
await async_operator.delete(source_path)
@@ -75,7 +75,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(IsSameFileError) as e_info:
+ with pytest.raises(IsSameFile) as e_info:
await async_operator.rename(source_path, source_path)
await async_operator.delete(source_path)
@@ -88,7 +88,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(NotFoundError) as e_info:
+ with pytest.raises(NotFound) as e_info:
await async_operator.read(source_path)
assert await async_operator.read(target_path) == content
await async_operator.delete(target_path)
@@ -106,7 +106,7 @@ 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(NotFoundError) as e_info:
+ with pytest.raises(NotFound) as e_info:
await async_operator.read(source_path)
assert await async_operator.read(target_path) == source_content
await async_operator.delete(target_path)
diff --git a/bindings/python/tests/test_read.py
b/bindings/python/tests/test_read.py
index 9e6b40460..82eb1e9fd 100644
--- a/bindings/python/tests/test_read.py
+++ b/bindings/python/tests/test_read.py
@@ -20,7 +20,7 @@ from random import randint
from uuid import uuid4
import pytest
-from opendal.exceptions import IsADirectoryError, IsSameFileError,
NotFoundError
+from opendal.exceptions import IsADirectory, IsSameFile, NotFound
@pytest.mark.need_capability("read", "write", "delete")
@@ -135,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(NotFoundError):
+ 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(NotFoundError):
+ 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 de2ad35c9..0c4f2bb54 100644
--- a/bindings/python/tests/test_sync_copy.py
+++ b/bindings/python/tests/test_sync_copy.py
@@ -20,7 +20,7 @@ from random import randint
from uuid import uuid4
import pytest
-from opendal.exceptions import IsADirectoryError, IsSameFileError,
NotFoundError
+from opendal.exceptions import IsADirectory, IsSameFile, NotFound
@pytest.mark.need_capability("read", "write", "copy")
@@ -41,7 +41,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(NotFoundError) as e_info:
+ with pytest.raises(NotFound) as e_info:
operator.copy(source_path, target_path)
@@ -50,7 +50,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(IsADirectoryError) as e_info:
+ with pytest.raises(IsADirectory) as e_info:
operator.copy(source_path, target_path)
@@ -61,7 +61,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(IsADirectoryError) as e_info:
+ with pytest.raises(IsADirectory) as e_info:
operator.copy(source_path, target_path)
operator.delete(source_path)
operator.delete(target_path)
@@ -72,7 +72,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(IsSameFileError) as e_info:
+ with pytest.raises(IsSameFile) as e_info:
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 d969e5587..7deb5c5f2 100644
--- a/bindings/python/tests/test_sync_delete.py
+++ b/bindings/python/tests/test_sync_delete.py
@@ -20,7 +20,7 @@ from random import randint
from uuid import uuid4
import pytest
-from opendal.exceptions import NotFoundError
+from opendal.exceptions import NotFound
@pytest.mark.need_capability("read", "write", "delete", "list", "blocking")
@@ -43,6 +43,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(NotFoundError) as e_info:
+ with pytest.raises(NotFound) as e_info:
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 12e0c73e4..3caced059 100644
--- a/bindings/python/tests/test_sync_rename.py
+++ b/bindings/python/tests/test_sync_rename.py
@@ -20,7 +20,7 @@ from random import randint
from uuid import uuid4
import pytest
-from opendal.exceptions import IsADirectoryError, IsSameFileError,
NotFoundError
+from opendal.exceptions import IsADirectory, IsSameFile, NotFound
@pytest.mark.need_capability("read", "write", "rename")
@@ -30,7 +30,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(NotFoundError) as e_info:
+ with pytest.raises(NotFound) as e_info:
operator.read(source_path)
assert operator.read(target_path) == content
operator.delete(target_path)
@@ -41,7 +41,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(NotFoundError) as e_info:
+ with pytest.raises(NotFound) as e_info:
operator.rename(source_path, target_path)
@@ -50,7 +50,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(IsADirectoryError) as e_info:
+ with pytest.raises(IsADirectory) as e_info:
operator.rename(source_path, target_path)
@@ -60,7 +60,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(IsADirectoryError) as e_info:
+ with pytest.raises(IsADirectory) as e_info:
operator.rename(source_path, target_path)
operator.delete(source_path)
@@ -70,7 +70,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(IsSameFileError) as e_info:
+ with pytest.raises(IsSameFile) as e_info:
operator.rename(source_path, source_path)
operator.delete(source_path)
@@ -82,7 +82,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(NotFoundError) as e_info:
+ with pytest.raises(NotFound) as e_info:
operator.read(source_path)
assert operator.read(target_path) == content
operator.delete(target_path)
@@ -99,7 +99,7 @@ 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(NotFoundError) as e_info:
+ with pytest.raises(NotFound) as e_info:
operator.read(source_path)
assert operator.read(target_path) == source_content
operator.delete(target_path)
diff --git a/bindings/python/tests/test_write.py
b/bindings/python/tests/test_write.py
index 8f7d1a0ed..e98c6e959 100644
--- a/bindings/python/tests/test_write.py
+++ b/bindings/python/tests/test_write.py
@@ -20,7 +20,7 @@ from random import randint
from uuid import uuid4
import pytest
-from opendal.exceptions import NotFoundError
+from opendal.exceptions import NotFound
@pytest.mark.need_capability("write", "delete", "stat")
@@ -85,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(NotFoundError):
+ with pytest.raises(NotFound):
operator.stat(filename)
@@ -98,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(NotFoundError):
+ with pytest.raises(NotFound):
await operator.stat(filename)
diff --git a/bindings/python/upgrade.md b/bindings/python/upgrade.md
index de9041e61..58f9b3c80 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 removed, use `opendal.exceptions.Unknown` instead.
+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.