Skip to content

feat(async): complete AsyncMlb endpoint coverage (stats, persons, schedule, gamepace) - #327

Merged
Mattsface merged 2 commits into
feature/305-async-lookupsfrom
feature/305-async-stats
Aug 24, 2026
Merged

feat(async): complete AsyncMlb endpoint coverage (stats, persons, schedule, gamepace)#327
Mattsface merged 2 commits into
feature/305-async-lookupsfrom
feature/305-async-stats

Conversation

@Mattsface

Copy link
Copy Markdown
Member

Closes #305.

Ports the last seven Mlb methods to AsyncMlb. Both clients now expose 41
public methods; the only remaining difference is that close() is spelled
aclose().

What's here

Two commits.

7d2f0ec — the stats group. get_stats, get_player_stats,
get_team_stats, get_players_stats_for_game.

113a8c0 — the last three. get_persons, get_scheduled_games_by_date,
get_gamepace.

Scope was deliberately held to strict parity. No new types, constants, or
validation — the usability problems these endpoints have are real but are
tracked separately in #326 rather than mixed into a port.

The bug worth reviewing carefully

Mlb.get_gamepace builds its request like this:

self._mlb_adapter_v1.get(endpoint=f'gamePace?season={season}', ep_params=params)

The season rides in the endpoint string; sportId and everything else arrive
via ep_params. That works on the sync side because Requests merges an
endpoint's existing query with the params it's given. HTTPX replaces it:

requests: GET /api/v1/gamePace?season=2021&sportId=1
httpx:    GET /api/v1/gamePace?sportId=1

Copying the sync idiom into AsyncMlb would therefore have dropped the season
silently — no exception, just game pace data for the wrong scope. AsyncMlb
passes the season as an ordinary param instead, producing an identical request.

Two follow-on notes:

  • get_awards is the only other method that embeds a query in its endpoint
    string (awards/{id}/recipients?). That query is empty, so merge-vs-replace
    makes no difference there and it was never affected.
  • Mlb is left alone. Its idiom is fragile but correct under Requests, and
    changing it is out of scope for a parity port.

The test helper had the same blind spot

tests/test_async_mlb.py::assert_matches_sync derives each expected request
from Mlb itself, which is what makes these ports trustworthy. But it compared
url.path against the raw endpoint string, so gamePace?season=2021 was never
parsed as a query and the drift above would have passed review.

It now splits an endpoint's embedded query and folds it into the expected
params — the same thing Requests does — so the expectation is the merged query
either client has to end up sending, regardless of how it built the URL. That
also subsumes the get_awards trailing-? special case it had been carrying.

Other changes

Three new shared parsers, following the pattern the rest of the async port
uses: _parsers/stats.py::parse_split_stats,
_parsers/schedules.py::parse_scheduled_games, and _parsers/gamepace.py.
Each replaces inline logic that existed only in mlb_api.py; the stats one
alone collapses four copy-pasted blocks into one.

A sync bugfix. Mlb.get_players_stats_for_game accepted **params and
never passed ep_params, so every caller-supplied keyword was silently
discarded. Both clients now forward them. This changes sync behavior, but in
the direction its own signature already advertised — agreed on before
implementing.

Two preserved quirks, both documented rather than corrected:

  • get_scheduled_games_by_date is annotated list[ScheduleGames] but returns
    None when nothing selects a date. Asserted explicitly in the parity suite.
  • The stats methods return {} for a 404, for a body with no stats, and for
    an unrecognized stat type or group. That last case is the subject of Make the stats endpoints easier to use #326.

Docstring corrections on Mlb.get_players_stats_for_game, which described
person_id as "the team id", game_id as "list of stat types", and whose
example called get_player_stats_for_game — not a method.

Testing

poetry run pytest tests/ --ignore=tests/external_tests
  • Before: 974 passed
  • After: 1057 passed (+83)
poetry run pytest tests/external_tests/

148 passed, 1 skipped against the live API — this is what covers the sync
refactors, since create_split_data and the schedule/gamepace parsers now run
through new code paths on real payloads.

Also verified directly against the live API that get_gamepace, get_persons,
and get_scheduled_games_by_date return objects equal to their sync
counterparts, rather than trusting the canned fixtures alone.

New coverage: 8 stats-parser tests, 7 gamepace-parser tests, 4
scheduled-games-parser tests, 24 async client tests, and 33 parity tests.

Risk

Low for the async additions — new methods, nothing existing changes shape.

Moderate for the two sync-side changes: the get_players_stats_for_game param
fix is a real behavior change, and the four stats methods plus
get_scheduled_games_by_date/get_gamepace now route through extracted
parsers. The external suite is the check that matters there and it's green.

🤖 Generated with Claude Code

Mattsface and others added 2 commits August 23, 2026 15:11
Ports the last four stats methods to AsyncMlb at strict parity with Mlb:
get_stats, get_player_stats, get_team_stats, and get_players_stats_for_game.

All four sync methods ended in the same copy-pasted tail -- short-circuit on
400-499, then create_split_data(data['stats']) if present and truthy, else {}.
That block existed four times in mlb_api.py. It moves to a shared
_parsers/stats.py::parse_split_stats(), following the pattern the rest of the
async port already uses, and both clients now call the one copy.

Also fixes a real bug on the sync side while collapsing those copies:
get_players_stats_for_game accepted **params and never passed ep_params to the
adapter, so every caller-supplied keyword was silently discarded before the
request was built. Both clients now forward them, covered by a named
regression test in the parity suite.

No new types, constants, or validation: an unrecognized stat type or group
still yields {} rather than raising, matching sync exactly. docs/public-api.md
notes that sharp edge alongside the newly supported methods.

Docstring corrections on Mlb.get_players_stats_for_game: it described game_id
as "list of stat types", person_id as "the team id", and its example called
get_player_stats_for_game, which is not a method.

Tests: 1016 passed (up from 974). tests/external_tests/stats/ 30 passed
against the live API, confirming the sync refactor did not move behavior.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ports get_persons, get_scheduled_games_by_date, and get_gamepace. AsyncMlb now
exposes every endpoint method Mlb does; the only remaining public difference is
that close() is spelled aclose().

Two more shared parsers, following the established pattern:
_parsers/schedules.py gains parse_scheduled_games(), and _parsers/gamepace.py
is new. Both replace inline loops/conditionals in mlb_api.py, so the two
clients share one copy.

Fixes an httpx/requests divergence that would have silently broken get_gamepace
on the async side. Mlb builds that request as endpoint="gamePace?season=2021"
with ep_params={"sportId": 1} and relies on Requests merging the endpoint's
query with the params. HTTPX does not merge -- passing params replaces a query
already on the URL -- so copying the sync idiom drops the season entirely and
silently returns whatever the unfiltered endpoint gives back. AsyncMlb passes
the season as an ordinary param instead, which produces a byte-identical
request. Verified against the live API: async and sync return equal GamePace
objects for season 2021.

tests/test_async_mlb.py's assert_matches_sync() had the same blind spot -- it
compared url.path against the raw endpoint string and would not have caught
this. It now splits an endpoint's embedded query and folds it into the expected
params, which is what Requests does, so the expectation is the merged query
either client must end up sending. This also subsumes the get_awards trailing-?
special case it previously carried.

get_scheduled_games_by_date preserves Mlb's quirk of returning None rather than
the [] its annotation promises when no date selector was given, asserted
explicitly in the parity suite rather than left implicit.

Tests: 1057 passed (up from 1016). tests/external_tests/ 148 passed, 1 skipped
against the live API.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Mattsface
Mattsface merged commit cafedb8 into feature/305-async-lookups Aug 24, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant