diff --git a/.tool-versions b/.tool-versions index 860f5bbb..8b869bd7 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -python 3.8.10 \ No newline at end of file +python 3.8.10 diff --git a/jwt/exceptions.py b/jwt/exceptions.py index 735f6f5d..d3cbf31f 100644 --- a/jwt/exceptions.py +++ b/jwt/exceptions.py @@ -69,5 +69,6 @@ class PyJWKClientError(PyJWTError): class PyJWKClientConnectionError(PyJWKClientError): pass + class PyJWKAsyncDisabledError(PyJWKClientError): - pass \ No newline at end of file + pass diff --git a/jwt/jwks_client.py b/jwt/jwks_client.py index 738a04d9..2a7d6043 100644 --- a/jwt/jwks_client.py +++ b/jwt/jwks_client.py @@ -7,18 +7,16 @@ from .api_jwk import PyJWK, PyJWKSet from .api_jwt import decode_complete as decode_token -from .exceptions import PyJWKClientConnectionError, PyJWKClientError, PyJWKClientError, PyJWKAsyncDisabledError +from .exceptions import ( + PyJWKAsyncDisabledError, + PyJWKClientConnectionError, + PyJWKClientError, +) from .jwk_set_cache import JWKSetCache - - try: from async_lru import alru_cache - from httpx import ( - AsyncClient, - HTTPError, - Timeout - ) + from httpx import AsyncClient, HTTPError, Timeout has_async = True except ModuleNotFoundError: @@ -80,7 +78,7 @@ def fetch_data(self) -> Any: finally: if self.jwk_set_cache is not None: self.jwk_set_cache.put(jwk_set) - + async def fetch_data_async(self) -> Any: if not has_async: raise PyJWKAsyncDisabledError() @@ -102,7 +100,6 @@ async def fetch_data_async(self) -> Any: if self.jwk_set_cache is not None: self.jwk_set_cache.put(jwk_set) - def get_jwk_set(self, refresh: bool = False) -> PyJWKSet: data = None if self.jwk_set_cache is not None and not refresh: @@ -115,7 +112,7 @@ def get_jwk_set(self, refresh: bool = False) -> PyJWKSet: raise PyJWKClientError("The JWKS endpoint did not return a JSON object") return PyJWKSet.from_dict(data) - + async def get_jwk_set_async(self, refresh: bool = False) -> PyJWKSet: if not has_async: raise PyJWKAsyncDisabledError() @@ -143,7 +140,7 @@ def get_signing_keys(self, refresh: bool = False) -> List[PyJWK]: raise PyJWKClientError("The JWKS endpoint did not contain any signing keys") return signing_keys - + async def get_signing_keys_async(self, refresh: bool = False) -> List[PyJWK]: if not has_async: raise PyJWKAsyncDisabledError() @@ -197,7 +194,7 @@ def get_signing_key_from_jwt(self, token: str) -> PyJWK: unverified = decode_token(token, options={"verify_signature": False}) header = unverified["header"] return self.get_signing_key(header.get("kid")) - + async def get_signing_key_from_jwt_async(self, token: str) -> PyJWK: if not has_async: raise PyJWKAsyncDisabledError() diff --git a/tests/test_jwks_client.py b/tests/test_jwks_client.py index 2feccdb7..c12bf6b8 100644 --- a/tests/test_jwks_client.py +++ b/tests/test_jwks_client.py @@ -55,9 +55,12 @@ def mocked_success_response(data): urlopen_mock.return_value = response yield urlopen_mock + @contextlib.contextmanager async def mocked_success_response_async(data): - with mock.patch.object(jwt.jwks_client, "AsyncClient", mock.AsyncMock()) as async_client_mock: + with mock.patch.object( + jwt.jwks_client, "AsyncClient", mock.AsyncMock() + ) as async_client_mock: response = mock.AsyncMock() response.__aenter__ = mock.AsyncMock(return_value=response) response.__aexit__ = mock.AsyncMock() @@ -229,7 +232,6 @@ def test_get_signing_key_from_jwt(self): "gty": "client-credentials", } - @pytest.mark.asyncio async def test_get_signing_key_from_jwt_async(self): token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRdyJ9.eyJpc3MiOiJodHRwczovL2Rldi04N2V2eDlydS5hdXRoMC5jb20vIiwic3ViIjoiYVc0Q2NhNzl4UmVMV1V6MGFFMkg2a0QwTzNjWEJWdENAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vZXhwZW5zZXMtYXBpIiwiaWF0IjoxNTcyMDA2OTU0LCJleHAiOjE1NzIwMDY5NjQsImF6cCI6ImFXNENjYTc5eFJlTFdVejBhRTJINmtEME8zY1hCVnRDIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.PUxE7xn52aTCohGiWoSdMBZGiYAHwE5FYie0Y1qUT68IHSTXwXVd6hn02HTah6epvHHVKA2FqcFZ4GGv5VTHEvYpeggiiZMgbxFrmTEY0csL6VNkX1eaJGcuehwQCRBKRLL3zKmA5IKGy5GeUnIbpPHLHDxr-GXvgFzsdsyWlVQvPX2xjeaQ217r2PtxDeqjlf66UYl6oY6AqNS8DH3iryCvIfCcybRZkc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA"