Skip to content

Commit 96fcc54

Browse files
authored
Warn on server warning 'X-HF-Warning' (#3589)
* Warn on server warning 'X-HF-Warning' * add awrn
1 parent d6374cd commit 96fcc54

File tree

3 files changed

+78
-1
lines changed

3 files changed

+78
-1
lines changed

src/huggingface_hub/utils/_cache_assets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def cached_assets_path(
2424
subfolder: str = "default",
2525
*,
2626
assets_dir: Union[str, Path, None] = None,
27-
):
27+
) -> Path:
2828
"""Return a folder path to cache arbitrary files.
2929
3030
`huggingface_hub` provides a canonical folder path to store assets. This is the

src/huggingface_hub/utils/_http.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,12 @@ def hf_raise_for_status(response: httpx.Response, endpoint_name: Optional[str] =
647647
> - [`~utils.HfHubHTTPError`]
648648
> If request failed for a reason not listed above.
649649
"""
650+
try:
651+
_warn_on_warning_headers(response)
652+
except Exception:
653+
# Never raise on warning parsing
654+
logger.debug("Failed to parse warning headers", exc_info=True)
655+
650656
try:
651657
response.raise_for_status()
652658
except httpx.HTTPStatusError as e:
@@ -746,6 +752,33 @@ def hf_raise_for_status(response: httpx.Response, endpoint_name: Optional[str] =
746752
raise _format(HfHubHTTPError, str(e), response) from e
747753

748754

755+
_WARNED_TOPICS = set()
756+
757+
758+
def _warn_on_warning_headers(response: httpx.Response) -> None:
759+
"""
760+
Emit warnings if warning headers are present in the HTTP response.
761+
762+
Expected header format: 'X-HF-Warning: topic; message'
763+
764+
Only the first warning for each topic will be shown. Topic is optional and can be empty. Note that several warning
765+
headers can be present in a single response.
766+
767+
Args:
768+
response (`httpx.Response`):
769+
The HTTP response to check for warning headers.
770+
"""
771+
server_warnings = response.headers.get_list("X-HF-Warning")
772+
for server_warning in server_warnings:
773+
topic, message = server_warning.split(";", 1) if ";" in server_warning else ("", server_warning)
774+
topic = topic.strip()
775+
if topic not in _WARNED_TOPICS:
776+
message = message.strip()
777+
if message:
778+
_WARNED_TOPICS.add(topic)
779+
logger.warning("WARNING: %s", message)
780+
781+
749782
def _format(error_type: type[HfHubHTTPError], custom_message: str, response: httpx.Response) -> HfHubHTTPError:
750783
server_errors = []
751784

tests/test_utils_http.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
from huggingface_hub.constants import ENDPOINT
1515
from huggingface_hub.errors import HfHubHTTPError, OfflineModeIsEnabled
1616
from huggingface_hub.utils._http import (
17+
_WARNED_TOPICS,
1718
RateLimitInfo,
1819
_adjust_range_header,
20+
_warn_on_warning_headers,
1921
default_client_factory,
2022
fix_hf_endpoint_in_url,
2123
get_async_session,
@@ -560,3 +562,45 @@ def test_429_without_ratelimit_headers(self):
560562

561563
assert "429 Too Many Requests" in str(exc_info.value)
562564
assert "api/models" in str(exc_info.value)
565+
566+
567+
class TestWarnOnWarningHeaders:
568+
def test_warn_on_warning_headers(self, caplog):
569+
# Request #1 (multiple warnings)
570+
response = Mock(spec=httpx.Response)
571+
response.headers = httpx.Headers(
572+
[
573+
("X-HF-Warning", "Topic1; This is the first warning message."),
574+
("X-HF-Warning", "Topic2; This is the second warning message."),
575+
("X-HF-Warning", "Topic1; This is a repeated warning message for Topic1."),
576+
("X-HF-Warning", "This is a warning without a topic."),
577+
("X-HF-Warning", "This is another warning without a topic."),
578+
]
579+
)
580+
581+
with caplog.at_level("WARNING"):
582+
_warn_on_warning_headers(response)
583+
584+
assert _WARNED_TOPICS == {"Topic1", "Topic2", ""}
585+
warnings = [record.message for record in caplog.records if record.levelname == "WARNING"]
586+
assert "WARNING: This is the first warning message." in warnings
587+
assert "WARNING: This is the second warning message." in warnings
588+
assert "WARNING: This is a repeated warning message for Topic1." not in warnings
589+
assert "WARNING: This is a warning without a topic." in warnings
590+
assert "WARNING: This is another warning without a topic." not in warnings
591+
# Request #2 (exact same warnings, should not warn again)
592+
caplog.clear()
593+
with caplog.at_level("WARNING"):
594+
_warn_on_warning_headers(response)
595+
warnings = [record.message for record in caplog.records if record.levelname == "WARNING"]
596+
assert len(warnings) == 0 # No new warnings should be added
597+
598+
# Request #3 (single warning with new topic, should warn)
599+
response.headers = httpx.Headers({"X-HF-Warning": "Topic4; Another warning."})
600+
caplog.clear()
601+
with caplog.at_level("WARNING"):
602+
_warn_on_warning_headers(response)
603+
warnings = [record.message for record in caplog.records if record.levelname == "WARNING"]
604+
assert len(warnings) == 1
605+
assert warnings == ["WARNING: Another warning."]
606+
assert "Topic4" in _WARNED_TOPICS

0 commit comments

Comments
 (0)