diff --git a/CHANGELOG.md b/CHANGELOG.md index 06b2d01..08da0c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The authentication success and failure displayed in the browser were revamped to be more user-friendly. `requests_auth.testing` was modified to accommodate this change: - `tab.assert_success` `expected_message` parameter was removed. - `tab.assert_failure` `expected_message` parameter should not be prefixed with `Unable to properly perform authentication: ` anymore and `\n` in the message should be replaced with `
`. +- Exceptions issued by `requests_auth` are now inheriting from `requests_auth.RequestsAuthException`, itself inheriting from `requests.RequestException`, instead of `Exception`. ### Fixed - Type information is now provided following [PEP 561](https://www.python.org/dev/peps/pep-0561/). diff --git a/requests_auth/__init__.py b/requests_auth/__init__.py index 8ba1522..c19a25b 100644 --- a/requests_auth/__init__.py +++ b/requests_auth/__init__.py @@ -40,6 +40,7 @@ InvalidToken, TokenExpiryNotProvided, InvalidGrantRequest, + RequestsAuthException, ) from requests_auth.version import __version__ @@ -67,6 +68,7 @@ "SupportMultiAuth", "JsonTokenFileCache", "TokenMemoryCache", + "RequestsAuthException", "GrantNotProvided", "TimeoutOccurred", "AuthenticationFailed", diff --git a/requests_auth/_errors.py b/requests_auth/_errors.py index b8091b6..7cac1b3 100644 --- a/requests_auth/_errors.py +++ b/requests_auth/_errors.py @@ -1,42 +1,45 @@ from json import JSONDecodeError from typing import Union -from requests import Response +from requests import Response, RequestException -class AuthenticationFailed(Exception): +class RequestsAuthException(RequestException): ... + + +class AuthenticationFailed(RequestsAuthException): """User was not authenticated.""" def __init__(self): - Exception.__init__(self, "User was not authenticated.") + RequestsAuthException.__init__(self, "User was not authenticated.") -class TimeoutOccurred(Exception): +class TimeoutOccurred(RequestsAuthException): """No response within timeout interval.""" def __init__(self, timeout: float): - Exception.__init__( + RequestsAuthException.__init__( self, f"User authentication was not received within {timeout} seconds." ) -class InvalidToken(Exception): +class InvalidToken(RequestsAuthException): """Token is invalid.""" def __init__(self, token_name: str): - Exception.__init__(self, f"{token_name} is invalid.") + RequestsAuthException.__init__(self, f"{token_name} is invalid.") -class GrantNotProvided(Exception): +class GrantNotProvided(RequestsAuthException): """Grant was not provided.""" def __init__(self, grant_name: str, dictionary_without_grant: dict): - Exception.__init__( + RequestsAuthException.__init__( self, f"{grant_name} not provided within {dictionary_without_grant}." ) -class InvalidGrantRequest(Exception): +class InvalidGrantRequest(RequestsAuthException): """ If the request failed client authentication or is invalid, the authorization server returns an error response as described in https://tools.ietf.org/html/rfc6749#section-5.2 """ @@ -64,7 +67,7 @@ class InvalidGrantRequest(Exception): } def __init__(self, response: Union[Response, dict]): - Exception.__init__(self, InvalidGrantRequest.to_message(response)) + RequestsAuthException.__init__(self, InvalidGrantRequest.to_message(response)) @staticmethod def to_message(response: Union[Response, dict]) -> str: @@ -114,17 +117,19 @@ def _pop(key: str) -> str: return message -class StateNotProvided(Exception): +class StateNotProvided(RequestsAuthException): """State was not provided.""" def __init__(self, dictionary_without_state: dict): - Exception.__init__( + RequestsAuthException.__init__( self, f"state not provided within {dictionary_without_state}." ) -class TokenExpiryNotProvided(Exception): +class TokenExpiryNotProvided(RequestsAuthException): """Token expiry was not provided.""" def __init__(self, token_body: dict): - Exception.__init__(self, f"Expiry (exp) is not provided in {token_body}.") + RequestsAuthException.__init__( + self, f"Expiry (exp) is not provided in {token_body}." + ) diff --git a/tests/features/token_cache/test_json_token_file_cache.py b/tests/features/token_cache/test_json_token_file_cache.py index 6913cd7..b01b1e9 100644 --- a/tests/features/token_cache/test_json_token_file_cache.py +++ b/tests/features/token_cache/test_json_token_file_cache.py @@ -4,6 +4,7 @@ import pytest import jwt +import requests import requests_auth import requests_auth._oauth2.tokens @@ -78,6 +79,8 @@ def failing_dump(*args): with pytest.raises(requests_auth.AuthenticationFailed) as exception_info: same_cache.get_token("key1") assert str(exception_info.value) == "User was not authenticated." + assert isinstance(exception_info.value, requests_auth.RequestsAuthException) + assert isinstance(exception_info.value, requests.RequestException) assert caplog.messages == [ "Cannot save tokens.", diff --git a/tests/oauth2/authorization_code/test_oauth2_authorization_code.py b/tests/oauth2/authorization_code/test_oauth2_authorization_code.py index 60012d6..368c9b6 100644 --- a/tests/oauth2/authorization_code/test_oauth2_authorization_code.py +++ b/tests/oauth2/authorization_code/test_oauth2_authorization_code.py @@ -151,9 +151,12 @@ def test_oauth2_authorization_code_flow_uses_custom_failure( displayed_html="FAILURE: {display_time}\n{information}", ) - with pytest.raises(requests_auth.InvalidGrantRequest): + with pytest.raises(requests_auth.InvalidGrantRequest) as exception_info: requests.get("http://authorized_only", auth=auth) + assert isinstance(exception_info.value, requests_auth.RequestsAuthException) + assert isinstance(exception_info.value, requests.RequestException) + tab.assert_failure( "invalid_request: The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed." ) @@ -478,6 +481,8 @@ def test_empty_token_is_invalid( str(exception_info.value) == "access_token not provided within {'access_token': '', 'token_type': 'example', 'expires_in': 3600, 'refresh_token': 'tGzv3JOkF0XG5Qx2TlKWIA', 'example_parameter': 'example_value'}." ) + assert isinstance(exception_info.value, requests_auth.RequestsAuthException) + assert isinstance(exception_info.value, requests.RequestException) tab.assert_success() diff --git a/tests/oauth2/implicit/test_oauth2_implicit.py b/tests/oauth2/implicit/test_oauth2_implicit.py index abc23b3..d924ddf 100644 --- a/tests/oauth2/implicit/test_oauth2_implicit.py +++ b/tests/oauth2/implicit/test_oauth2_implicit.py @@ -308,6 +308,8 @@ def open(self, url, new): str(exception_info.value) == "User authentication was not received within 0.1 seconds." ) + assert isinstance(exception_info.value, requests_auth.RequestsAuthException) + assert isinstance(exception_info.value, requests.RequestException) def test_browser_error(token_cache, responses: RequestsMock, monkeypatch): @@ -371,6 +373,8 @@ def test_empty_token_is_invalid(token_cache, browser_mock: BrowserMock): auth=requests_auth.OAuth2Implicit("http://provide_token"), ) assert str(exception_info.value) == " is invalid." + assert isinstance(exception_info.value, requests_auth.RequestsAuthException) + assert isinstance(exception_info.value, requests.RequestException) tab.assert_success() @@ -386,6 +390,8 @@ def test_token_without_expiry_is_invalid(token_cache, browser_mock: BrowserMock) auth=requests_auth.OAuth2Implicit("http://provide_token"), ) assert str(exception_info.value) == "Expiry (exp) is not provided in None." + assert isinstance(exception_info.value, requests_auth.RequestsAuthException) + assert isinstance(exception_info.value, requests.RequestException) tab.assert_success() @@ -612,6 +618,8 @@ def test_oauth2_implicit_flow_post_failure_if_state_is_not_provided( str(exception_info.value) == f"state not provided within {{'access_token': ['{token}']}}." ) + assert isinstance(exception_info.value, requests_auth.RequestsAuthException) + assert isinstance(exception_info.value, requests.RequestException) tab.assert_failure(f"state not provided within {{'access_token': ['{token}']}}.")