diff --git a/bindings/python/python/opendal/__init__.pyi b/bindings/python/python/opendal/__init__.pyi index 7713e2a3c797..2a28f523f3eb 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 000000000000..a14f25c26ff0 --- /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 000000000000..45bff4060e56 --- /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 a9c41fc007b9..076c756964c6 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::()?; m.add_class::()?; m.add_class::()?; - m.add("Error", py.get_type::())?; // 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::())?; + exception_module.add("Unexpected", py.get_type::())?; + exception_module.add("Unsupported", py.get_type::())?; + exception_module.add("ConfigInvalid", py.get_type::())?; + exception_module.add("NotFound", py.get_type::())?; + exception_module.add("PermissionDenied", py.get_type::())?; + exception_module.add("IsADirectory", py.get_type::())?; + exception_module.add("NotADirectory", py.get_type::())?; + exception_module.add("AlreadyExists", py.get_type::())?; + exception_module.add("IsSameFile", py.get_type::())?; + exception_module.add("ConditionNotMatch", py.get_type::())?; + exception_module.add("ContentTruncated", py.get_type::())?; + exception_module.add("ContentIncomplete", py.get_type::())?; + exception_module.add("InvalidInput", py.get_type::())?; + 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 5bc95a2ccffa..f6e8c3f864b3 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 24cbca9da767..df3a41aaa349 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 b5fea80d1734..e2965a65c47c 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 07e94f85e45a..78f1c962f9a2 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 77ed1fc32256..181e9d184475 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 da2e9c14f854..ed0ea6493104 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_exceptions.py b/bindings/python/tests/test_exceptions.py new file mode 100644 index 000000000000..0d872e5ef097 --- /dev/null +++ b/bindings/python/tests/test_exceptions.py @@ -0,0 +1,27 @@ +# 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 inspect + +from opendal import exceptions +from opendal.exceptions import Error + + +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 62a424104d40..4e88f2a9a4ae 100644 --- a/bindings/python/tests/test_read.py +++ b/bindings/python/tests/test_read.py @@ -20,6 +20,7 @@ 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 3db7bb6f16bf..f55ec92fcc75 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 9d06d34cd330..0ce3d32b8968 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 02def280570f..ca6adca01a8d 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 987f0ce3c54f..e98c6e959be9 100644 --- a/bindings/python/tests/test_write.py +++ b/bindings/python/tests/test_write.py @@ -20,6 +20,7 @@ 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 de9041e611b2..cbb43b85a5f7 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