diff --git a/.github/workflows/release_java.yml b/.github/workflows/release_java.yml index cf9f6ec2cbb3..9d21368501f6 100644 --- a/.github/workflows/release_java.yml +++ b/.github/workflows/release_java.yml @@ -169,7 +169,6 @@ jobs: run: | ./mvnw org.sonatype.plugins:nexus-staging-maven-plugin:deploy-staged \ -DaltStagingDirectory=$LOCAL_STAGING_DIR \ - -DskipStagingRepositoryClose=true \ -DserverId=apache.releases.https \ -DnexusUrl=https://repository.apache.org env: diff --git a/bindings/python/python/opendal/exceptions.pyi b/bindings/python/python/opendal/exceptions.pyi index 8a1e21e57940..806ebfba0f77 100644 --- a/bindings/python/python/opendal/exceptions.pyi +++ b/bindings/python/python/opendal/exceptions.pyi @@ -47,6 +47,12 @@ class NotFound(builtins.Exception): class PermissionDenied(builtins.Exception): r"""Permission denied.""" +class RateLimited(builtins.Exception): + r"""Rate limited.""" + +class RangeNotSatisfied(builtins.Exception): + r"""Range not satisfied.""" + class Unexpected(builtins.Exception): r"""Unexpected errors.""" diff --git a/bindings/python/src/errors.rs b/bindings/python/src/errors.rs index 4601b59b7c76..2bf65fe89458 100644 --- a/bindings/python/src/errors.rs +++ b/bindings/python/src/errors.rs @@ -77,6 +77,13 @@ create_exception!( PyException, "Condition not match" ); +create_exception!(opendal.exceptions, RateLimited, PyException, "Rate limited"); +create_exception!( + opendal.exceptions, + RangeNotSatisfied, + PyException, + "Range not satisfied" +); fn format_pyerr_impl(err: &ocore::Error) -> PyErr { let e = format!("{err:?}"); @@ -91,6 +98,8 @@ fn format_pyerr_impl(err: &ocore::Error) -> PyErr { ocore::ErrorKind::AlreadyExists => AlreadyExists::new_err(e), ocore::ErrorKind::IsSameFile => IsSameFile::new_err(e), ocore::ErrorKind::ConditionNotMatch => ConditionNotMatch::new_err(e), + ocore::ErrorKind::RateLimited => RateLimited::new_err(e), + ocore::ErrorKind::RangeNotSatisfied => RangeNotSatisfied::new_err(e), _ => Unexpected::new_err(e), } } diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index 13c6eccaa309..bf413eb7501f 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -97,7 +97,9 @@ fn _opendal(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { NotADirectory, AlreadyExists, IsSameFile, - ConditionNotMatch + ConditionNotMatch, + RateLimited, + RangeNotSatisfied, ] )?; Ok(()) diff --git a/bindings/python/tests/test_exceptions.py b/bindings/python/tests/test_exceptions.py index f07cbeabf64b..82bcb2147d60 100644 --- a/bindings/python/tests/test_exceptions.py +++ b/bindings/python/tests/test_exceptions.py @@ -18,6 +18,8 @@ import builtins import inspect +import pytest + from opendal import exceptions @@ -25,3 +27,49 @@ def test_exceptions(): for _name, obj in inspect.getmembers(exceptions): if inspect.isclass(obj): assert issubclass(obj, builtins.Exception) + + +def test_all_expected_exceptions_present(): + """Verify every expected exception class is present in opendal.exceptions.""" + expected = [ + "Error", + "Unexpected", + "Unsupported", + "ConfigInvalid", + "NotFound", + "PermissionDenied", + "IsADirectory", + "NotADirectory", + "AlreadyExists", + "IsSameFile", + "ConditionNotMatch", + "RateLimited", + "RangeNotSatisfied", + ] + for name in expected: + assert hasattr(exceptions, name), f"exceptions.{name} is missing" + cls = getattr(exceptions, name) + assert inspect.isclass(cls), f"exceptions.{name} is not a class" + assert issubclass(cls, builtins.Exception), ( + f"exceptions.{name} does not inherit from Exception" + ) + + +def test_rate_limited_is_catchable(): + """RateLimited can be raised and caught like a standard exception.""" + msg = "too many requests" + with pytest.raises(exceptions.RateLimited): + raise exceptions.RateLimited(msg) + + +def test_range_not_satisfied_is_catchable(): + """RangeNotSatisfied can be raised and caught like a standard exception.""" + msg = "requested range not satisfiable" + with pytest.raises(exceptions.RangeNotSatisfied): + raise exceptions.RangeNotSatisfied(msg) + + +def test_exceptions_are_distinct(): + """RateLimited and RangeNotSatisfied must not accidentally catch each other.""" + assert not issubclass(exceptions.RateLimited, exceptions.RangeNotSatisfied) + assert not issubclass(exceptions.RangeNotSatisfied, exceptions.RateLimited)