diff --git a/litellm/litellm_core_utils/asyncify.py b/litellm/litellm_core_utils/asyncify.py index bd21974029e..695312ee234 100644 --- a/litellm/litellm_core_utils/asyncify.py +++ b/litellm/litellm_core_utils/asyncify.py @@ -70,7 +70,7 @@ def asyncify( def is_event_loop_running() -> bool: try: - _ = asyncio.get_running_loop() + asyncio.get_running_loop() except RuntimeError: return False return True diff --git a/litellm/llms/chatgpt/authenticator.py b/litellm/llms/chatgpt/authenticator.py index 1b5850d8d42..244083e92e9 100644 --- a/litellm/llms/chatgpt/authenticator.py +++ b/litellm/llms/chatgpt/authenticator.py @@ -26,6 +26,7 @@ from .common_utils import ( ) TOKEN_EXPIRY_SKEW_SECONDS: Final = 60 +TOKEN_REFRESH_TIMEOUT_SECONDS: Final = 30 DEVICE_CODE_TIMEOUT_SECONDS: Final = 15 * 60 DEVICE_CODE_COOLDOWN_SECONDS: Final = 5 * 60 DEVICE_CODE_POLL_SLEEP_SECONDS: Final = 5 @@ -322,6 +323,7 @@ class Authenticator: "refresh_token": refresh_token, "scope": "openid profile email", }, + timeout=TOKEN_REFRESH_TIMEOUT_SECONDS, ) resp.raise_for_status() data: Final = _JSON_OBJECT_ADAPTER.validate_python(resp.json()) diff --git a/tests/test_litellm/llms/chatgpt/test_chatgpt_authenticator.py b/tests/test_litellm/llms/chatgpt/test_chatgpt_authenticator.py index be54d33d18d..982d4807caf 100644 --- a/tests/test_litellm/llms/chatgpt/test_chatgpt_authenticator.py +++ b/tests/test_litellm/llms/chatgpt/test_chatgpt_authenticator.py @@ -1,11 +1,14 @@ import base64 import json import time -from unittest.mock import mock_open, patch +from unittest.mock import MagicMock, mock_open, patch import pytest -from litellm.llms.chatgpt.authenticator import Authenticator +from litellm.llms.chatgpt.authenticator import ( + TOKEN_REFRESH_TIMEOUT_SECONDS, + Authenticator, +) from litellm.llms.chatgpt.common_utils import GetAccessTokenError @@ -55,6 +58,23 @@ class TestChatGPTAuthenticator: token = authenticator.get_access_token() assert token == "token-new" + def test_refresh_tokens_uses_bounded_timeout(self, authenticator): + client = MagicMock() + response = MagicMock() + response.json.return_value = { + "access_token": "token-new", + "id_token": "id-123", + } + client.post.return_value = response + + with patch( # test-quality-ok: requested seam for asserting timeout propagation + "litellm.llms.chatgpt.authenticator._get_httpx_client", return_value=client + ): + refreshed = authenticator._refresh_tokens("refresh-123") + + assert refreshed["access_token"] == "token-new" + assert client.post.call_args.kwargs["timeout"] == TOKEN_REFRESH_TIMEOUT_SECONDS + @pytest.mark.asyncio async def test_get_access_token_refuses_device_code_login_in_event_loop(self, authenticator): with ( @@ -97,9 +117,7 @@ class TestChatGPTAuthenticator: assert token == "tok" def test_get_account_id_from_id_token(self, authenticator): - id_token = _make_jwt( - {"https://api.openai.com/auth": {"chatgpt_account_id": "acct-123"}} - ) + id_token = _make_jwt({"https://api.openai.com/auth": {"chatgpt_account_id": "acct-123"}}) auth_data = json.dumps({"id_token": id_token}) with (