From 38385dd0cad1014870023d1d8c371d9a97093d09 Mon Sep 17 00:00:00 2001 From: Lucain Pouget Date: Fri, 3 Oct 2025 11:41:07 +0200 Subject: [PATCH 1/3] Use EventHooks instead of custom Transport --- src/huggingface_hub/__init__.py | 6 ---- src/huggingface_hub/utils/__init__.py | 2 -- src/huggingface_hub/utils/_http.py | 46 ++++++--------------------- tests/test_utils_http.py | 3 -- 4 files changed, 9 insertions(+), 48 deletions(-) diff --git a/src/huggingface_hub/__init__.py b/src/huggingface_hub/__init__.py index a354cd63f0..754cb92e87 100644 --- a/src/huggingface_hub/__init__.py +++ b/src/huggingface_hub/__init__.py @@ -513,8 +513,6 @@ "CorruptedCacheException", "DeleteCacheStrategy", "HFCacheInfo", - "HfHubAsyncTransport", - "HfHubTransport", "cached_assets_path", "close_session", "dump_environment_info", @@ -645,8 +643,6 @@ "HfFileSystemFile", "HfFileSystemResolvedPath", "HfFileSystemStreamFile", - "HfHubAsyncTransport", - "HfHubTransport", "ImageClassificationInput", "ImageClassificationOutputElement", "ImageClassificationOutputTransform", @@ -1515,8 +1511,6 @@ def __dir__(): CorruptedCacheException, # noqa: F401 DeleteCacheStrategy, # noqa: F401 HFCacheInfo, # noqa: F401 - HfHubAsyncTransport, # noqa: F401 - HfHubTransport, # noqa: F401 cached_assets_path, # noqa: F401 close_session, # noqa: F401 dump_environment_info, # noqa: F401 diff --git a/src/huggingface_hub/utils/__init__.py b/src/huggingface_hub/utils/__init__.py index 1b2eccdafc..20830984c3 100644 --- a/src/huggingface_hub/utils/__init__.py +++ b/src/huggingface_hub/utils/__init__.py @@ -53,8 +53,6 @@ from ._http import ( ASYNC_CLIENT_FACTORY_T, CLIENT_FACTORY_T, - HfHubAsyncTransport, - HfHubTransport, close_session, fix_hf_endpoint_in_url, get_async_session, diff --git a/src/huggingface_hub/utils/_http.py b/src/huggingface_hub/utils/_http.py index ff59f4b092..d0d512925a 100644 --- a/src/huggingface_hub/utils/_http.py +++ b/src/huggingface_hub/utils/_http.py @@ -69,49 +69,21 @@ ) -class HfHubTransport(httpx.HTTPTransport): +def hf_request_event_hook(request: httpx.Request) -> None: """ - Transport that will be used to make HTTP requests to the Hugging Face Hub. + Event hook that will be used to make HTTP requests to the Hugging Face Hub. What it does: - Block requests if offline mode is enabled - Add a request ID to the request headers - Log the request if debug mode is enabled """ + if constants.HF_HUB_OFFLINE: + raise OfflineModeIsEnabled( + f"Cannot reach {request.url}: offline mode is enabled. To disable it, please unset the `HF_HUB_OFFLINE` environment variable." + ) - def handle_request(self, request: httpx.Request) -> httpx.Response: - if constants.HF_HUB_OFFLINE: - raise OfflineModeIsEnabled( - f"Cannot reach {request.url}: offline mode is enabled. To disable it, please unset the `HF_HUB_OFFLINE` environment variable." - ) - request_id = _add_request_id(request) - try: - return super().handle_request(request) - except httpx.RequestError as e: - if request_id is not None: - # Taken from https://stackoverflow.com/a/58270258 - e.args = (*e.args, f"(Request ID: {request_id})") - raise - - -class HfHubAsyncTransport(httpx.AsyncHTTPTransport): - async def handle_async_request(self, request: httpx.Request) -> httpx.Response: - if constants.HF_HUB_OFFLINE: - raise OfflineModeIsEnabled( - f"Cannot reach {request.url}: offline mode is enabled. To disable it, please unset the `HF_HUB_OFFLINE` environment variable." - ) - request_id = _add_request_id(request) - try: - return await super().handle_async_request(request) - except httpx.RequestError as e: - if request_id is not None: - # Taken from https://stackoverflow.com/a/58270258 - e.args = (*e.args, f"(Request ID: {request_id})") - raise - - -def _add_request_id(request: httpx.Request) -> Optional[str]: - # Add random request ID => easier for server-side debug + # Add random request ID => easier for server-side debugging if X_AMZN_TRACE_ID not in request.headers: request.headers[X_AMZN_TRACE_ID] = request.headers.get(X_REQUEST_ID) or str(uuid.uuid4()) request_id = request.headers.get(X_AMZN_TRACE_ID) @@ -135,7 +107,7 @@ def default_client_factory() -> httpx.Client: Factory function to create a `httpx.Client` with the default transport. """ return httpx.Client( - transport=HfHubTransport(), + event_hooks={"request": [hf_request_event_hook]}, follow_redirects=True, timeout=httpx.Timeout(constants.DEFAULT_REQUEST_TIMEOUT, write=60.0), ) @@ -146,7 +118,7 @@ def default_async_client_factory() -> httpx.AsyncClient: Factory function to create a `httpx.AsyncClient` with the default transport. """ return httpx.AsyncClient( - transport=HfHubAsyncTransport(), + event_hooks={"request": [hf_request_event_hook]}, follow_redirects=True, timeout=httpx.Timeout(constants.DEFAULT_REQUEST_TIMEOUT, write=60.0), ) diff --git a/tests/test_utils_http.py b/tests/test_utils_http.py index c35628f83a..c4d87c439d 100644 --- a/tests/test_utils_http.py +++ b/tests/test_utils_http.py @@ -14,7 +14,6 @@ from huggingface_hub.constants import ENDPOINT from huggingface_hub.errors import OfflineModeIsEnabled from huggingface_hub.utils._http import ( - HfHubTransport, _adjust_range_header, default_client_factory, fix_hf_endpoint_in_url, @@ -170,8 +169,6 @@ def test_default_configuration(self) -> None: # Check httpx.Client default configuration self.assertTrue(client.follow_redirects) self.assertIsNotNone(client.timeout) - # Check that it's using the HfHubTransport - self.assertIsInstance(client._transport, HfHubTransport) def test_set_configuration(self) -> None: set_client_factory(self._factory) From 77d03a9e6c92193a332c82d50829522f5311b813 Mon Sep 17 00:00:00 2001 From: Lucain Pouget Date: Fri, 3 Oct 2025 11:57:13 +0200 Subject: [PATCH 2/3] add test --- tests/test_utils_http.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_utils_http.py b/tests/test_utils_http.py index c4d87c439d..008cca2758 100644 --- a/tests/test_utils_http.py +++ b/tests/test_utils_http.py @@ -329,3 +329,32 @@ def test_adjust_range_header(): _adjust_range_header("bytes=0-100", 150) with pytest.raises(RuntimeError): _adjust_range_header("bytes=-50", 100) + + +def test_proxy_env_is_used(monkeypatch): + """Regression test for https://github.com/huggingface/transformers/issues/41301. + + Test is hacky and uses httpx internal attributes, but it works. + We just need to test that proxies from env vars are used when creating the client. + """ + monkeypatch.setenv("HTTP_PROXY", "http://proxy.example1.com:8080") + monkeypatch.setenv("HTTPS_PROXY", "http://proxy.example2.com:8181") + + client = get_session() + mounts = client._mounts + url_patterns = list(mounts.keys()) + assert len(url_patterns) == 2 # http and https + + http_url_pattern = next(url for url in url_patterns if url.pattern == "http://") + http_proxy_url = mounts[http_url_pattern]._pool._proxy_url + assert http_proxy_url.scheme == b"http" + assert http_proxy_url.host == b"proxy.example1.com" + assert http_proxy_url.port == 8080 + assert http_proxy_url.target == b"/" + + https_url_pattern = next(url for url in url_patterns if url.pattern == "https://") + https_proxy_url = mounts[https_url_pattern]._pool._proxy_url + assert https_proxy_url.scheme == b"http" + assert https_proxy_url.host == b"proxy.example2.com" + assert https_proxy_url.port == 8181 + assert https_proxy_url.target == b"/" From d688421c49533944366c6af04a9183628b771d47 Mon Sep 17 00:00:00 2001 From: Lucain Date: Tue, 7 Oct 2025 15:32:33 +0200 Subject: [PATCH 3/3] Update tests/test_utils_http.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: célina --- tests/test_utils_http.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_utils_http.py b/tests/test_utils_http.py index 008cca2758..b61e23292d 100644 --- a/tests/test_utils_http.py +++ b/tests/test_utils_http.py @@ -340,6 +340,7 @@ def test_proxy_env_is_used(monkeypatch): monkeypatch.setenv("HTTP_PROXY", "http://proxy.example1.com:8080") monkeypatch.setenv("HTTPS_PROXY", "http://proxy.example2.com:8181") + set_client_factory(default_client_factory) client = get_session() mounts = client._mounts url_patterns = list(mounts.keys())