Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion mlbstatsapi/mlb_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@ def __init__(
timeout: TimeoutType = DEFAULT_TIMEOUT,
session: requests.Session | None = None,
*,
strict_http: bool = False,
strict_http: bool = True,
):
# One session is shared by the v1 and v1.1 adapters. The library closes
# only sessions it creates; caller-injected sessions remain caller-owned.
# The versioned User-Agent and retry adapters are applied only to
# library-created Sessions.
# strict_http defaults to True in version 1.0; pass False for the
# historical empty-result compatibility path on final non-404 4xx.
self._owns_session = session is None
if session is None:
self._session = requests.Session()
Expand Down
4 changes: 3 additions & 1 deletion mlbstatsapi/mlb_dataadapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,10 @@ def __init__(
timeout: TimeoutType = DEFAULT_TIMEOUT,
session: requests.Session | None = None,
*,
strict_http: bool = False,
strict_http: bool = True,
):
# strict_http defaults to True in version 1.0; pass False for the
# historical empty-result compatibility path on final non-404 4xx.
self.url = f'https://{hostname}/api/{ver}/'
self._logger = logger or logging.getLogger(__name__)
self._timeout = timeout
Expand Down
33 changes: 18 additions & 15 deletions tests/http_contract_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
from urllib3.util.retry import Retry


# Final non-404 client errors that currently return an empty MlbResult by
# default rather than raising MlbHttpError. Later strict-mode work should
# reuse this matrix when asserting the opposite behavior.
# Final non-404 client errors that raise MlbHttpError under the version 1.0
# strict default, and return an empty MlbResult with a compatibility warning
# when strict_http=False.
COMPATIBILITY_CLIENT_ERRORS = (
400,
401,
Expand Down Expand Up @@ -93,11 +93,8 @@ def assert_library_retry_policy(retry: Retry) -> None:

API_VERSIONS = ("v1", "v1.1")

# Pending version 1.0 default strict HTTP behavior (#284).
XFAIL_PENDING_STRICT_DEFAULT = pytest.mark.xfail(
strict=True,
reason="Pending #284: strict HTTP behavior becomes the 1.0 default",
)
# 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(
Expand All @@ -119,13 +116,19 @@ def standalone_adapter_for_version(
session,
api_version: str,
*,
strict_http: bool = False,
strict_http=_UNSET,
) -> "MlbDataAdapter":
"""Build a versioned MlbDataAdapter sharing a mocked session."""
"""Build a versioned MlbDataAdapter sharing a mocked session.

Omitting ``strict_http`` leaves the constructor argument unset so tests
exercise the real production default rather than an explicit False.
"""
from mlbstatsapi import MlbDataAdapter

return MlbDataAdapter(
session=session,
ver=api_version,
strict_http=strict_http,
)
kwargs = {
"session": session,
"ver": api_version,
}
if strict_http is not _UNSET:
kwargs["strict_http"] = strict_http
return MlbDataAdapter(**kwargs)
31 changes: 18 additions & 13 deletions tests/test_http_contract.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Offline HTTP contract tests for version 1.0 strict defaults and compatibility mode.
Documents the version 1.0 HTTP behavior in deterministic tests. Unimplemented
1.0 default wiring is marked xfail pending issue #284.
Documents the version 1.0 HTTP behavior in deterministic tests.
"""

from __future__ import annotations
Expand Down Expand Up @@ -30,7 +29,6 @@
HTTP_REASON_BY_STATUS,
NOT_FOUND_STATUS,
SERVER_ERRORS,
XFAIL_PENDING_STRICT_DEFAULT,
adapter_for_api_version,
assert_library_retry_policy,
standalone_adapter_for_version,
Expand Down Expand Up @@ -114,10 +112,9 @@ def test_status_matrices_document_compatibility_baseline():
assert set(SERVER_ERRORS).isdisjoint(COMPATIBILITY_CLIENT_ERRORS)


# --- Version 1.0 default strict wiring (pending implementation #284) ---
# --- Version 1.0 default strict wiring ---


@XFAIL_PENDING_STRICT_DEFAULT
def test_mlb_default_matches_explicit_strict_mode_wiring():
"""Mlb() must default to strict mode on the client and both adapters."""
mlb = Mlb()
Expand All @@ -129,7 +126,6 @@ def test_mlb_default_matches_explicit_strict_mode_wiring():
mlb.close()


@XFAIL_PENDING_STRICT_DEFAULT
def test_mlb_data_adapter_default_is_strict():
"""MlbDataAdapter() must default to strict HTTP in version 1.0."""
adapter = MlbDataAdapter()
Expand Down Expand Up @@ -236,12 +232,11 @@ def test_compatibility_client_errors_do_not_raise_mlb_http_error(
assert result.data == {}


# --- Version 1.0 default: final non-404 4xx raises (pending #284) ---
# --- Version 1.0 default: final non-404 4xx raises ---


@pytest.mark.parametrize("api_version", API_VERSIONS)
@pytest.mark.parametrize("status_code", COMPATIBILITY_CLIENT_ERRORS)
@XFAIL_PENDING_STRICT_DEFAULT
def test_default_adapter_raises_on_final_non_404_client_error(
api_version,
status_code,
Expand Down Expand Up @@ -275,7 +270,6 @@ def test_default_adapter_raises_on_final_non_404_client_error(


@pytest.mark.parametrize("status_code", COMPATIBILITY_CLIENT_ERRORS)
@XFAIL_PENDING_STRICT_DEFAULT
def test_default_mlb_raises_on_final_non_404_client_error(status_code):
"""Default Mlb() raises MlbHttpError for final non-404 4xx."""
reason = HTTP_REASON_BY_STATUS[status_code]
Expand Down Expand Up @@ -665,7 +659,7 @@ def test_mlb_constructors_remain_compatible():
try:
assert isinstance(mlb_default._session, requests.Session)
assert mlb_default._timeout == DEFAULT_TIMEOUT
assert mlb_default._strict_http is False
assert mlb_default._strict_http is True
finally:
mlb_default.close()

Expand Down Expand Up @@ -697,7 +691,7 @@ def test_mlb_positional_timeout_remains_third_argument():
assert session.calls[0]["timeout"] == 10
assert session.calls[1]["timeout"] == 10
assert mlb._session is session
assert mlb._strict_http is False
assert mlb._strict_http is True


def test_mlb_strict_http_is_keyword_only():
Expand All @@ -709,7 +703,7 @@ def test_mlb_strict_http_is_keyword_only():
mlb = Mlb("statsapi.mlb.com", logger, 10, session)
assert mlb._session is session
assert mlb._timeout == 10
assert mlb._strict_http is False
assert mlb._strict_http is True

mlb_strict = Mlb(
"statsapi.mlb.com",
Expand All @@ -724,6 +718,17 @@ def test_mlb_strict_http_is_keyword_only():
assert mlb_strict._mlb_adapter_v1._strict_http is True
assert mlb_strict._mlb_adapter_v1_1._strict_http is True

mlb_compat = Mlb(
"statsapi.mlb.com",
logger,
10,
session,
strict_http=False,
)
assert mlb_compat._strict_http is False
assert mlb_compat._mlb_adapter_v1._strict_http is False
assert mlb_compat._mlb_adapter_v1_1._strict_http is False

with pytest.raises(TypeError):
Mlb("statsapi.mlb.com", logger, 10, session, True)

Expand All @@ -738,7 +743,7 @@ def test_adapter_positional_construction_remains_compatible():
assert adapter._logger is logger
assert adapter._timeout == (5.0, 60.0)
assert adapter._session is session
assert adapter._strict_http is False
assert adapter._strict_http is True

adapter.get(endpoint="game")
assert session.calls[0]["timeout"] == (5.0, 60.0)
Expand Down
25 changes: 16 additions & 9 deletions tests/test_mlb_retries.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@
NON_RETRYABLE_CLIENT_ERRORS,
RETRYABLE_STATUS_CODES,
SERVER_ERRORS,
XFAIL_PENDING_STRICT_DEFAULT,
assert_library_retry_policy,
)

_UNSET = object()


def test_create_retry_policy_is_publicly_importable():
"""create_retry_policy is available through the package public API."""
Expand Down Expand Up @@ -196,9 +197,12 @@ def no_retry_sleep(monkeypatch):
def _adapter_against_local_server(
port: int,
*,
strict_http: bool = False,
strict_http=_UNSET,
) -> MlbDataAdapter:
adapter = MlbDataAdapter(strict_http=strict_http)
kwargs = {}
if strict_http is not _UNSET:
kwargs["strict_http"] = strict_http
adapter = MlbDataAdapter(**kwargs)
adapter.url = f"http://127.0.0.1:{port}/api/v1/"
return adapter

Expand Down Expand Up @@ -250,18 +254,23 @@ def test_non_retryable_client_errors_are_not_retried(
scripted_http_server,
no_retry_sleep,
):
"""Ordinary client errors are returned immediately without retries."""
"""Ordinary client errors complete after one attempt without retries."""
configure, port = scripted_http_server
configure([status_code, 200])
adapter = _adapter_against_local_server(port)

try:
result = adapter.get(endpoint="sports")
if status_code == 404:
result = adapter.get(endpoint="sports")
assert result.status_code == status_code
assert result.data == {}
else:
with pytest.raises(MlbHttpError) as exc_info:
adapter.get(endpoint="sports")
assert exc_info.value.status_code == status_code
finally:
adapter.close()

assert result.status_code == status_code
assert result.data == {}
assert _ScriptedHandler.request_count == 1


Expand Down Expand Up @@ -306,7 +315,6 @@ def test_final_429_returns_empty_mlb_result_in_compatibility_mode(
assert _ScriptedHandler.request_count == 4


@XFAIL_PENDING_STRICT_DEFAULT
def test_final_429_raises_mlb_http_error_after_retry_exhaustion_default_adapter(
scripted_http_server,
no_retry_sleep,
Expand All @@ -327,7 +335,6 @@ def test_final_429_raises_mlb_http_error_after_retry_exhaustion_default_adapter(
assert exc_info.value.method == "GET"


@XFAIL_PENDING_STRICT_DEFAULT
def test_final_429_raises_via_default_mlb_client_after_retry_exhaustion(
scripted_http_server,
no_retry_sleep,
Expand Down