From 5c27e4ca0cc3bd567fe83d26755611d61bbe444c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 21:30:21 +0000 Subject: [PATCH] feat: preserve explicit HTTP compatibility mode Co-authored-by: Matthew Spah --- mlbstatsapi/mlb_dataadapter.py | 42 +++++++++++++++++++++++++++------- tests/http_contract_support.py | 6 ----- tests/test_http_warnings.py | 5 ++-- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/mlbstatsapi/mlb_dataadapter.py b/mlbstatsapi/mlb_dataadapter.py index 1395cbb..8082896 100644 --- a/mlbstatsapi/mlb_dataadapter.py +++ b/mlbstatsapi/mlb_dataadapter.py @@ -8,6 +8,7 @@ MlbTransportError, ) from .warnings import MlbHttpCompatibilityWarning +import inspect import logging import warnings @@ -28,9 +29,33 @@ # Bounded excerpt for error response bodies attached to MlbHttpError. HTTP_ERROR_BODY_EXCERPT_LIMIT = 500 -# Frames from warnings.warn() out to whoever called MlbDataAdapter.get(), so the -# warning points at application code rather than the helper below. -COMPATIBILITY_WARNING_STACKLEVEL = 3 + +def _is_mlbstatsapi_module(module_name: str) -> bool: + """Return True when *module_name* belongs to this package.""" + return module_name == "mlbstatsapi" or module_name.startswith("mlbstatsapi.") + + +def _compatibility_warning_stacklevel() -> int: + """Return a warnings.warn stacklevel for the first non-package caller. + + A fixed stack level cannot serve both direct MlbDataAdapter.get() calls and + public Mlb endpoint methods that wrap the adapter. Walk frames from the + caller of this helper outward and stop at the first module outside the + mlbstatsapi package namespace. + """ + frame = inspect.currentframe() + stacklevel = 1 + try: + frame = frame.f_back + while frame is not None: + module_name = frame.f_globals.get("__name__", "") + if not _is_mlbstatsapi_module(module_name): + return stacklevel + stacklevel += 1 + frame = frame.f_back + finally: + del frame + return 1 def _warn_http_compatibility( @@ -45,13 +70,14 @@ def _warn_http_compatibility( """ warnings.warn( ( - f"HTTP {status_code} for {url} was handled through compatibility mode " - "and returned the historical empty result. Pass strict_http=True to " - "raise MlbHttpError. This compatibility behavior may change in " - "version 1.0." + f"HTTP {status_code} for {url} was suppressed because " + "strict_http=False explicitly selected compatibility mode, so the " + "historical empty result was returned. Strict HTTP behavior is the " + "default in version 1.0. Remove strict_http=False or pass " + "strict_http=True to raise MlbHttpError." ), MlbHttpCompatibilityWarning, - stacklevel=COMPATIBILITY_WARNING_STACKLEVEL, + stacklevel=_compatibility_warning_stacklevel(), ) diff --git a/tests/http_contract_support.py b/tests/http_contract_support.py index 27822ae..63c3475 100644 --- a/tests/http_contract_support.py +++ b/tests/http_contract_support.py @@ -96,12 +96,6 @@ def assert_library_retry_policy(retry: Retry) -> None: # Sentinel so helpers can omit strict_http and exercise the real constructor default. _UNSET = object() -# Pending compatibility warning caller location via public Mlb endpoints (#285). -XFAIL_PENDING_WARNING_CALL_SITE = pytest.mark.xfail( - strict=True, - reason="Pending #285: compatibility warning must point to the public caller", -) - def adapter_for_api_version(mlb, api_version: str): """Return the internal MlbDataAdapter for v1 or v1.1.""" diff --git a/tests/test_http_warnings.py b/tests/test_http_warnings.py index 92343d6..b7231d1 100644 --- a/tests/test_http_warnings.py +++ b/tests/test_http_warnings.py @@ -31,7 +31,6 @@ HTTP_REASON_BY_STATUS, NOT_FOUND_STATUS, SERVER_ERRORS, - XFAIL_PENDING_WARNING_CALL_SITE, adapter_for_api_version, standalone_adapter_for_version, ) @@ -171,10 +170,11 @@ def test_compatibility_warning_message_contains_migration_guidance(status_code): message = str(warning_info[0].message) assert str(status_code) in message assert SPORTS_URL in message + assert "strict_http=False" in message assert "compatibility mode" in message + assert "default in version 1.0" in message assert "strict_http=True" in message assert "MlbHttpError" in message - assert "version 1.0" in message def test_compatibility_warning_excludes_response_body(): @@ -490,7 +490,6 @@ def test_compatibility_warning_points_to_direct_adapter_caller_line(): assert warning.lineno == expected_lineno -@XFAIL_PENDING_WARNING_CALL_SITE def test_compatibility_warning_points_to_public_mlb_endpoint_caller_line(): """Public Mlb endpoint warnings must reference the application caller line.""" import inspect