mlb-statsapi-pydantic 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. mlb_statsapi_pydantic-0.0.1/.claude/CLAUDE.md +11 -0
  2. mlb_statsapi_pydantic-0.0.1/.claude/hooks/run_pytest.sh +8 -0
  3. mlb_statsapi_pydantic-0.0.1/.claude/mlb-stats-memory.md +135 -0
  4. mlb_statsapi_pydantic-0.0.1/.claude/refactor_tasks.md +188 -0
  5. mlb_statsapi_pydantic-0.0.1/.claude/settings.json +16 -0
  6. mlb_statsapi_pydantic-0.0.1/.claude/tasks.md +233 -0
  7. mlb_statsapi_pydantic-0.0.1/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
  8. mlb_statsapi_pydantic-0.0.1/.github/ISSUE_TEMPLATE/bug_report.yml +33 -0
  9. mlb_statsapi_pydantic-0.0.1/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  10. mlb_statsapi_pydantic-0.0.1/.github/ISSUE_TEMPLATE/feature_request.yml +17 -0
  11. mlb_statsapi_pydantic-0.0.1/.github/dependabot.yml +19 -0
  12. mlb_statsapi_pydantic-0.0.1/.github/pull_request_template.md +13 -0
  13. mlb_statsapi_pydantic-0.0.1/.github/workflows/ci.yml +60 -0
  14. mlb_statsapi_pydantic-0.0.1/.github/workflows/publish.yml +41 -0
  15. mlb_statsapi_pydantic-0.0.1/.gitignore +32 -0
  16. mlb_statsapi_pydantic-0.0.1/.pre-commit-config.yaml +17 -0
  17. mlb_statsapi_pydantic-0.0.1/CHANGELOG.md +29 -0
  18. mlb_statsapi_pydantic-0.0.1/LICENSE +28 -0
  19. mlb_statsapi_pydantic-0.0.1/PKG-INFO +137 -0
  20. mlb_statsapi_pydantic-0.0.1/README.md +102 -0
  21. mlb_statsapi_pydantic-0.0.1/examples/get_schedule.py +113 -0
  22. mlb_statsapi_pydantic-0.0.1/pyproject.toml +92 -0
  23. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/__init__.py +20 -0
  24. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/client/__init__.py +1 -0
  25. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/client/_base.py +68 -0
  26. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/client/async_client.py +171 -0
  27. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/client/sync_client.py +175 -0
  28. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/endpoints/__init__.py +1 -0
  29. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/endpoints/registry.py +608 -0
  30. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/enums/__init__.py +2 -0
  31. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/enums/game_types.py +21 -0
  32. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/enums/team_ids.py +39 -0
  33. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/exceptions.py +18 -0
  34. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/__init__.py +196 -0
  35. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/_base.py +193 -0
  36. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/attendance.py +72 -0
  37. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/awards.py +69 -0
  38. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/divisions.py +47 -0
  39. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/draft.py +139 -0
  40. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/enums.py +709 -0
  41. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/game.py +260 -0
  42. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/hydrations.py +65 -0
  43. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/jobs.py +35 -0
  44. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/leagues.py +48 -0
  45. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/livefeed.py +602 -0
  46. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/meta.py +15 -0
  47. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/people.py +104 -0
  48. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/schedule.py +129 -0
  49. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/seasons.py +58 -0
  50. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/sports.py +39 -0
  51. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/standings.py +115 -0
  52. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/stats.py +107 -0
  53. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/teams.py +70 -0
  54. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/transactions.py +42 -0
  55. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/venues.py +74 -0
  56. mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/py.typed +0 -0
  57. mlb_statsapi_pydantic-0.0.1/tests/__init__.py +0 -0
  58. mlb_statsapi_pydantic-0.0.1/tests/conftest.py +39 -0
  59. mlb_statsapi_pydantic-0.0.1/tests/fixtures/attendance.json +65 -0
  60. mlb_statsapi_pydantic-0.0.1/tests/fixtures/awards.json +6866 -0
  61. mlb_statsapi_pydantic-0.0.1/tests/fixtures/boxscore.json +1 -0
  62. mlb_statsapi_pydantic-0.0.1/tests/fixtures/divisions.json +1 -0
  63. mlb_statsapi_pydantic-0.0.1/tests/fixtures/draft.json +54463 -0
  64. mlb_statsapi_pydantic-0.0.1/tests/fixtures/jobs.json +1083 -0
  65. mlb_statsapi_pydantic-0.0.1/tests/fixtures/leagues.json +1 -0
  66. mlb_statsapi_pydantic-0.0.1/tests/fixtures/linescore.json +1 -0
  67. mlb_statsapi_pydantic-0.0.1/tests/fixtures/livefeed.json +1 -0
  68. mlb_statsapi_pydantic-0.0.1/tests/fixtures/meta_game_types.json +50 -0
  69. mlb_statsapi_pydantic-0.0.1/tests/fixtures/people_660271.json +1 -0
  70. mlb_statsapi_pydantic-0.0.1/tests/fixtures/schedule.json +1 -0
  71. mlb_statsapi_pydantic-0.0.1/tests/fixtures/schedule_hydrated_team.json +151 -0
  72. mlb_statsapi_pydantic-0.0.1/tests/fixtures/seasons.json +1 -0
  73. mlb_statsapi_pydantic-0.0.1/tests/fixtures/sports.json +1 -0
  74. mlb_statsapi_pydantic-0.0.1/tests/fixtures/standings.json +1 -0
  75. mlb_statsapi_pydantic-0.0.1/tests/fixtures/stats_leaders.json +1 -0
  76. mlb_statsapi_pydantic-0.0.1/tests/fixtures/teams.json +1 -0
  77. mlb_statsapi_pydantic-0.0.1/tests/fixtures/transactions.json +4653 -0
  78. mlb_statsapi_pydantic-0.0.1/tests/fixtures/venue_15.json +1 -0
  79. mlb_statsapi_pydantic-0.0.1/tests/test_client/__init__.py +0 -0
  80. mlb_statsapi_pydantic-0.0.1/tests/test_client/test_async.py +79 -0
  81. mlb_statsapi_pydantic-0.0.1/tests/test_client/test_endpoint_registry.py +138 -0
  82. mlb_statsapi_pydantic-0.0.1/tests/test_client/test_sync.py +226 -0
  83. mlb_statsapi_pydantic-0.0.1/tests/test_integration/__init__.py +0 -0
  84. mlb_statsapi_pydantic-0.0.1/tests/test_integration/test_e2e.py +167 -0
  85. mlb_statsapi_pydantic-0.0.1/tests/test_integration/test_live_api.py +175 -0
  86. mlb_statsapi_pydantic-0.0.1/tests/test_models/__init__.py +0 -0
  87. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_attendance.py +32 -0
  88. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_awards.py +33 -0
  89. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_base.py +310 -0
  90. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_divisions.py +44 -0
  91. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_draft.py +34 -0
  92. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_enums.py +77 -0
  93. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_game.py +219 -0
  94. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_jobs.py +25 -0
  95. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_leagues.py +43 -0
  96. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_livefeed.py +482 -0
  97. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_meta.py +23 -0
  98. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_people.py +91 -0
  99. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_schedule.py +152 -0
  100. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_seasons.py +52 -0
  101. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_sports.py +40 -0
  102. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_standings.py +73 -0
  103. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_stats.py +38 -0
  104. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_teams.py +84 -0
  105. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_transactions.py +34 -0
  106. mlb_statsapi_pydantic-0.0.1/tests/test_models/test_venues.py +35 -0
@@ -0,0 +1,11 @@
1
+ # Overview
2
+
3
+ This project is a Pydantic rewrite of [MLB-StatsAPI](https://github.com/toddrob99/MLB-StatsAPI) to take
4
+ advantage of pydantics type system and rich libraries.
5
+
6
+ Always use Pydantic best practices, preferring structured models over raw dictionaries.
7
+
8
+ Document models in `docs/` and update docs when making model changes. When
9
+ taking on a task, complete all steps and ensure verification methods pass
10
+ before marking the task complete. A completed task should be marked as such in
11
+ `mlb-statsapi-pydantic/.claude/tasks.md`
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+
3
+ source .venv/bin/activate && python3 -m pytest tests/ -x -q >&2
4
+ if [ $? -eq 1 ]; then
5
+ exit 2 # Exit code 2 tells Claude to treat as feedback/error
6
+ else
7
+ exit 0 # Exit code 0 means success, continue
8
+ fi
@@ -0,0 +1,135 @@
1
+ # MLB Stats API — Research Reference
2
+
3
+ ## API Overview
4
+
5
+ - **Base URL:** `https://statsapi.mlb.com/api/`
6
+ - **Versions:** Most endpoints use `v1`; live game feed uses `v1.1`
7
+ - **Auth:** None required for public data (some analytics/statcast endpoints need auth)
8
+ - **Response format:** JSON; all responses include a `copyright` field
9
+
10
+ ---
11
+
12
+ ## Response Patterns
13
+
14
+ ### Common Root Structure
15
+ Every response wraps data in a named array: `{"copyright": "...", "teams": [...]}`, `{"people": [...]}`, `{"dates": [...]}`, etc.
16
+
17
+ ### Reference Object Pattern — `{id, name, link}`
18
+ Used everywhere for cross-references (teams, venues, leagues, divisions, people). Variations:
19
+ - Minimal: `{id, link}` (e.g. `springVenue`)
20
+ - Standard: `{id, name, link}`
21
+ - Extended: `{id, name, link, abbreviation, ...}` (e.g. `springLeague`)
22
+
23
+ ### Code/Description Pattern — `{code, description}`
24
+ Used for: `batSide`, `pitchHand`, `gameType`, position types.
25
+
26
+ ### Enum-like Fields
27
+ | Field | Values |
28
+ |-------|--------|
29
+ | `abstractGameState` | Preview, Live, Final |
30
+ | `codedGameState` | P, S, I, F, and others |
31
+ | `gameType` | R (regular), S (spring), F (wild card), D (division), L (league), W (world series), C, P, A, E, I |
32
+ | `batSide/pitchHand code` | L, R, S (switch) |
33
+ | `dayNight` | day, night |
34
+ | `positionType` | Pitcher, Catcher, Infielder, Outfielder, Hitter, Runner, etc. |
35
+
36
+ ---
37
+
38
+ ## Key Endpoint Response Structures
39
+
40
+ ### Sports (`/api/v1/sports`)
41
+ Flat array. Fields: `id`, `code`, `link`, `name`, `abbreviation`, `sortOrder`, `activeStatus` (boolean).
42
+
43
+ ### Teams (`/api/v1/teams?sportId=1`)
44
+ Rich objects. Notable fields:
45
+ - Multiple name variants: `name`, `teamName`, `shortName`, `franchiseName`, `clubName`
46
+ - Nested refs: `venue`, `springVenue`, `league`, `division`, `springLeague`, `sport`
47
+ - `season` (int), `firstYearOfPlay` (string), `active` (boolean), `allStarStatus` (string)
48
+
49
+ ### Schedule (`/api/v1/schedule?sportId=1&date=MM/DD/YYYY`)
50
+ Nested hierarchy: root → `dates[]` → `games[]` → `teams.{away,home}`
51
+ - Root-level aggregates: `totalItems`, `totalEvents`, `totalGames`, `totalGamesInProgress`
52
+ - Same aggregates repeated per date
53
+ - Game fields: `gamePk`, `gameGuid`, `link`, `gameType`, `season`, `gameDate` (ISO 8601), `officialDate` (YYYY-MM-DD)
54
+ - `status` object: `abstractGameState`, `codedGameState`, `detailedState`, `statusCode`, `startTimeTBD`
55
+ - `venue` ref, `dayNight` field
56
+
57
+ ### People (`/api/v1/people/{id}`)
58
+ Single person wrapped in `people[]` array. Notable fields:
59
+ - `id`, `fullName`, `firstName`, `lastName`, `primaryNumber`
60
+ - `birthDate` (YYYY-MM-DD), `currentAge`, `birthCity`, `birthCountry`
61
+ - `height` (string, e.g. `6' 2"`), `weight` (int)
62
+ - `primaryPosition` — has `code`, `name`, `type`, `abbreviation`
63
+ - `batSide`, `pitchHand` — `{code, description}`
64
+ - `strikeZoneTop`, `strikeZoneBottom` (floats)
65
+ - `mlbDebutDate` (optional), `nickName` (optional)
66
+ - Name variants: `useName`, `boxscoreName`, `nickName`
67
+
68
+ ### Standings
69
+ Division-grouped. Structure: `records[]` → `teamRecords[]`
70
+ - Team record fields: `wins`, `losses`, `winningPercentage`, `gamesBack`, `wildCardGamesBack`, `divisionRank`, `wildCardRank`, `eliminationNumber`
71
+ - `streak` object, `leagueRecord`, `records` (splits)
72
+
73
+ ### Boxscore
74
+ `teams.{away,home}` each with:
75
+ - `teamStats.{batting,pitching,fielding}` — aggregate stats
76
+ - `players` dict keyed by `"ID{playerId}"` — individual player stats
77
+ - `battingOrder` array, `info` array, `note` strings
78
+
79
+ ### Linescore
80
+ `innings[]` array with `home`/`away` each having `runs`, `hits`, `errors` per inning.
81
+ Top-level `teams.{home,away}` with game totals.
82
+
83
+ ### Live Feed (`/api/v1.1/game/{gamePk}/feed/live`)
84
+ Largest response. Top-level: `gameData`, `liveData`
85
+ - `gameData`: datetime, status, teams, players, venue, weather, probablePitchers
86
+ - `liveData.plays`: allPlays, currentPlay, scoringPlays
87
+ - `liveData.linescore`: same as linescore endpoint
88
+ - `liveData.boxscore`: same as boxscore endpoint
89
+
90
+ ---
91
+
92
+ ## Reference Library (toddrob99/MLB-StatsAPI)
93
+
94
+ ### 27 Public Functions
95
+ **Schedule/Game:** `schedule`, `boxscore`, `boxscore_data`, `linescore`, `last_game`, `next_game`, `game_scoring_plays`, `game_scoring_play_data`, `game_highlights`, `game_highlight_data`, `game_pace`, `game_pace_data`
96
+
97
+ **Player:** `player_stats`, `player_stat_data`, `lookup_player`, `latest_season`
98
+
99
+ **Team:** `lookup_team`, `roster`, `team_leaders`, `team_leader_data`
100
+
101
+ **League:** `league_leaders`, `league_leader_data`, `standings`, `standings_data`
102
+
103
+ **Utility:** `meta`, `notes`, `get`
104
+
105
+ ### Endpoint System
106
+ 60+ endpoints defined in `endpoints.py` as a dict. Each has:
107
+ - `url` — template with `{ver}` and `{param}` placeholders
108
+ - `path_params` — dict with type, default, required, leading/trailing slash
109
+ - `query_params` — list of allowed query param names
110
+ - `required_params` — list of lists (OR combinations)
111
+ - `note` — developer guidance (optional)
112
+
113
+ ### Key Details
114
+ - Only dependency: `requests`
115
+ - No type hints or Pydantic models
116
+ - Returns formatted strings (display) or raw dicts (data variants)
117
+ - Core `get()` function handles all HTTP, param validation, URL construction
118
+ - Uses `datetime.now().year` as default season
119
+ - Logger name: `"statsapi"`
120
+
121
+ ---
122
+
123
+ ## Meta Endpoint Types
124
+ Valid types for `/api/v1/meta?type=X`:
125
+ awards, baseballStats, eventTypes, freeGameTypes, gameStatus, gameTypes, hitTrajectories, jobTypes, languages, leagueLeaderTypes, logicalEvents, metrics, pitchCodes, pitchTypes, platforms, positions, reviewReasons, rosterTypes, runnerDetailTypes, scheduleTypes, scheduleEventTypes, situationCodes, sky, standingsTypes, statGroups, statTypes, violationTypes, windDirection
126
+
127
+ ---
128
+
129
+ ## Known Quirks
130
+ - Some fields appear/disappear depending on game state or context
131
+ - `springVenue` has only `{id, link}` — no `name`
132
+ - Boxscore `players` dict uses string keys like `"ID660271"` instead of integer keys
133
+ - Date formats vary: `gameDate` is ISO 8601, `officialDate` is YYYY-MM-DD, schedule query uses MM/DD/YYYY
134
+ - `height` is a display string (`6' 2"`) not a numeric value
135
+ - `firstYearOfPlay` on teams is a string, not int
@@ -0,0 +1,188 @@
1
+ # Model Refactoring Tasks
2
+
3
+ > Branch: `refactor-models`
4
+ > Based on comprehensive code review of all 20 model files.
5
+
6
+ ---
7
+
8
+ ## Task 1: Consolidate shared models into `_base.py`
9
+ **Status:** completed
10
+ **Blocked by:** —
11
+
12
+ Deduplicate models repeated across files:
13
+
14
+ - **PersonRef** (6 duplicates): `PersonRef` (livefeed), `BoxscorePersonRef` (game), `TransactionPerson` (transactions), `JobPerson` (jobs), `LeaderPersonRef` (stats), `DraftPerson` (draft). Create single `PersonRef` in `_base.py` with `id: PersonId`, `full_name: str | None`, `link: str | None`.
15
+ - **PositionRef** (3 duplicates): `PrimaryPosition` (people), `PositionRef` (livefeed), `BoxscorePosition` (game). Create single `PositionRef` in `_base.py` with `code: str`, `name: str | None`, `type: str | None`, `abbreviation: str | None`.
16
+ - **WinLossRecord** (4 duplicates): `LeagueRecord` (schedule), `StandingsLeagueRecord` (standings), `GameDataTeamRecord` (livefeed), plus `SplitRecord` variant. Create single `WinLossRecord` in `_base.py`.
17
+ - **GameStatus** (2 duplicates): `schedule.GameStatus`, `livefeed.GameDataStatus`. Consolidate into one in `_base.py` or `schedule.py`.
18
+
19
+ Update all imports across models and tests.
20
+
21
+ ---
22
+
23
+ ## Task 2: Define custom ID types (NewType) for entity IDs
24
+ **Status:** not started
25
+ **Blocked by:** —
26
+
27
+ Create `NewType` definitions in `_base.py` instead of bare `int`:
28
+
29
+ - `PersonId = NewType("PersonId", int)` — `PersonRef.id`, `Person.id`, `BoxscoreTeam.batters/pitchers/bench/bullpen/batting_order` items
30
+ - `TeamId = NewType("TeamId", int)` — `Team.id`, `GameDataTeam.id`, `BoxscorePlayer.parent_team_id`
31
+ - `GamePk = NewType("GamePk", int)` — `LiveFeedResponse.game_pk`, `ScheduleGame.game_pk`, `AttendanceGame.game_pk`
32
+
33
+ Keep `IdNameLink.id` as plain `int` since it's used generically across entity types.
34
+
35
+ **Verified data patterns:**
36
+ - `BoxscoreTeam.batters/pitchers/bench/bullpen/batting_order` contain **player IDs** (e.g., `608324`) that map to `players["ID608324"]`, NOT array indices.
37
+ - `Plays.scoring_plays`, `Play.pitch_index/action_index/runner_index`, `InningPlays.top/bottom` are **array indices** into sibling lists.
38
+
39
+ ---
40
+
41
+ ## Task 3: Wire up enums to model fields
42
+ **Status:** not started
43
+ **Blocked by:** Task 1
44
+
45
+ All four enums in `enums.py` are defined but never referenced by any model field:
46
+
47
+ - `GameType` → `ScheduleGame.game_type`, attendance `game_type.id`
48
+ - `AbstractGameState` → `GameStatus.abstract_game_state`
49
+ - `HandCode` → bat_side/pitch_hand `code` fields
50
+ - `PositionType` → `PositionRef.type`
51
+
52
+ Since the API may return unexpected values, consider using the enum but keeping `extra="allow"` or using a validator that falls back to `str`.
53
+
54
+ ---
55
+
56
+ ## Task 4: Add index-to-object helper properties
57
+ **Status:** not started
58
+ **Blocked by:** Task 1, Task 2
59
+
60
+ **Array index helpers** (indices into sibling arrays on the same object):
61
+ - `Plays.scoring_plays: list[int]` → `@property scoring_play_objects -> list[Play]` (indices into `all_plays`)
62
+ - `Play.pitch_index: list[int]` → `@property pitches -> list[PlayEvent]` (indices into `play_events`)
63
+ - `Play.action_index: list[int]` → `@property actions -> list[PlayEvent]` (indices into `play_events`)
64
+ - `Play.runner_index: list[int]` → `@property indexed_runners -> list[Runner]` (indices into `runners`)
65
+ - `InningPlays.top/bottom: list[int]` — indices into parent `Plays.all_plays`; document clearly, consider if helper is feasible
66
+
67
+ **Player ID lookup helpers** (PersonId → player object via `players` dict):
68
+ - `BoxscoreTeam.batters/pitchers/bench/bullpen/batting_order: list[PersonId]` → `@property batter_players`, `pitcher_players`, `bench_players`, `bullpen_players`, `batting_order_players` that resolve against `players[f"ID{pid}"]`
69
+
70
+ ---
71
+
72
+ ## Task 5: Fix stringly-typed date/datetime fields
73
+ **Status:** not started
74
+ **Blocked by:** —
75
+
76
+ Convert `str` date fields to proper types (following `seasons.py` and `people.py` pattern):
77
+
78
+ **→ `datetime.date`:**
79
+ - `ScheduleDate.date` (`"2024-07-01"`)
80
+ - `Transaction.date`, `effective_date`, `resolution_date` (`"2024-03-01"`)
81
+ - `DraftPerson.birth_date`
82
+
83
+ **→ `datetime.datetime`:**
84
+ - `ScheduleGame.game_date` (`"2024-07-01T19:07:00Z"`)
85
+ - `TeamStanding.last_updated`, `StandingsRecord.last_updated` (`"2025-12-27T17:29:50Z"`)
86
+ - `PlayEvent.start_time`, `end_time`
87
+ - `PlayAbout.start_time`, `end_time`
88
+ - `Play.play_end_time`
89
+ - `AttendanceRecord.attendance_high_date`, `attendance_low_date`
90
+
91
+ ---
92
+
93
+ ## Task 6: Standardize season field type across models
94
+ **Status:** not started
95
+ **Blocked by:** —
96
+
97
+ Inconsistent `season` typing across models:
98
+ - `int | None` in `Team`
99
+ - `str` in `Division`, `TeamStanding`, `LeaderEntry`, `Season.season_id`
100
+ - `str | None` in `Venue`, `ScheduleGame`
101
+
102
+ Standardize on `int` (more useful for comparisons). Add Pydantic validators to coerce `str → int` where the API returns strings.
103
+
104
+ ---
105
+
106
+ ## Task 7: Replace weak `MlbBaseModel`/`dict` catch-all types with proper models
107
+ **Status:** not started
108
+ **Blocked by:** Task 1
109
+
110
+ 17 fields use `MlbBaseModel` or `dict` as catch-alls. Replace with typed models:
111
+
112
+ **game.py:**
113
+ - `BoxscorePlayer.stats/season_stats` → `PlayerStats` model with batting/pitching/fielding
114
+ - `BoxscoreTeamStats.batting/pitching/fielding` → typed stat models (`BattingStats`, `PitchingStats`, `FieldingStats`)
115
+ - `BoxscoreTeam.info/note` → `BoxscoreInfo` model with `title` + `field_list: list[BoxscoreInfoField]`
116
+ - `BoxscorePlayer.game_status/status` → typed models
117
+
118
+ **livefeed.py:**
119
+ - `GameData.game` → `GameInfo` model
120
+ - `GameData.datetime` → `GameDateTime` model
121
+ - `GameData.game_info` → `GameInfoDetails` model (attendance, firstPitch, gameDurationMinutes)
122
+ - `GameData.review` → `ReviewInfo` model
123
+ - `GameData.flags` → `GameFlags` model (noHitter, perfectGame)
124
+ - `LiveData.linescore` → reuse `LinescoreResponse` (minus copyright) or create `Linescore` base
125
+ - `LiveData.boxscore` → reuse `BoxscoreResponse` (minus copyright) or create `Boxscore` base
126
+ - `Matchup.batter_hot_cold_zones/pitcher_hot_cold_zones` → `HotColdZone` model
127
+
128
+ **draft.py:**
129
+ - `DraftPick.draft_type` → `CodeDescription` from `_base.py`
130
+ - `DraftPerson.primary_position` → shared `PositionRef`
131
+
132
+ **attendance.py:**
133
+ - `AttendanceRecord.game_type` → `GameTypeRef` from stats.py or shared model
134
+
135
+ **stats.py:**
136
+ - `StatSplit.stat` → typed stat dicts or keep `dict` with better type annotation
137
+
138
+ ---
139
+
140
+ ## Task 8: Add computed properties for derived data
141
+ **Status:** not started
142
+ **Blocked by:** Task 1, Task 5
143
+
144
+ - `Person.height_inches -> int | None` — parse `"6' 2\""` → `74`
145
+ - `WinLossRecord.win_pct -> float | None` — parse `".512"` → `0.512`
146
+ - `TeamStanding.games_back_float -> float | None` — parse `"-"` → `0.0`, `"5.0"` → `5.0`
147
+ - `DraftPick.pick_value_amount -> float | None` — parse `"9200000.00"` → `9200000.0`
148
+ - `DraftPick.signing_bonus_amount -> float | None` — same pattern
149
+
150
+ ---
151
+
152
+ ## Task 9: Add commonly-accessed missing fields
153
+ **Status:** not started
154
+ **Blocked by:** Task 1, Task 2
155
+
156
+ Fields currently hidden in `extra` that users would commonly access:
157
+
158
+ **standings.py `TeamStanding`:**
159
+ - `wins: int`, `losses: int`, `winning_percentage: str`
160
+ - `runs_scored: int`, `runs_allowed: int`, `run_differential: int`
161
+ - `division_champ: bool`, `division_leader: bool`, `clinched: bool`
162
+ - `elimination_number: str | None`, `magic_number: str | None`
163
+ - `wild_card_elimination_number: str | None`
164
+
165
+ **livefeed.py `GameData`:**
166
+ - `players: dict[str, PersonRef] | None` — full player dict keyed by `"ID{playerId}"`
167
+
168
+ **schedule.py `ScheduleGame`:**
169
+ - `series_description: str | None`, `games_in_series: int | None`, `series_game_number: int | None`
170
+
171
+ **stats.py `LeaderEntry`:**
172
+ - `num_teams: int | None`
173
+
174
+ Update tests to cover new fields where fixture data exists.
175
+
176
+ ---
177
+
178
+ ## Task 10: Update all tests and verify everything passes
179
+ **Status:** not started
180
+ **Blocked by:** Tasks 1–9
181
+
182
+ - Update all test imports to use consolidated models
183
+ - Add tests for new helper properties (index-to-object, computed properties)
184
+ - Add tests for new ID types
185
+ - Run full test suite: `pytest tests/test_models/ tests/test_client/`
186
+ - Run integration tests: `pytest tests/test_integration/ -m integration`
187
+ - Run `mypy src/mlb_statsapi` — strict, 0 errors
188
+ - Run `ruff check src/` — clean
@@ -0,0 +1,16 @@
1
+ {
2
+ "hooks": {
3
+ "PreToolUse": [
4
+ {
5
+ "matcher": "Bash",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "if": "Bash(git *)",
10
+ "command": "cd \"$CLAUDE_PROJECT_DIR\" && .claude/hooks/run_pytest.sh"
11
+ }
12
+ ]
13
+ }
14
+ ]
15
+ }
16
+ }
@@ -0,0 +1,233 @@
1
+ # mlb-statsapi-pydantic — Implementation Tasks
2
+
3
+ > Pydantic v2 typed client for the MLB Stats API.
4
+ > Reference: [toddrob99/MLB-StatsAPI](https://github.com/toddrob99/MLB-StatsAPI)
5
+ > Approach: TDD — write tests first, then implement until green.
6
+
7
+ ---
8
+
9
+ ## Design Decisions
10
+
11
+ | Decision | Choice | Why |
12
+ |----------|--------|-----|
13
+ | `extra="allow"` on all models | Yes | MLB API adds fields without notice; strict breaks clients |
14
+ | `alias_generator=to_camel` | Yes | Auto snake_case ↔ camelCase without per-field aliases |
15
+ | httpx (not requests) | Yes | Native async support, modern API |
16
+ | `src/` layout | Yes | Packaging best practice, avoids import shadowing |
17
+ | Tiered endpoint coverage | Yes | Ship Tier 1 fast, expand incrementally |
18
+ | `IdNameLink.name` optional | Yes | Some API refs omit `name` (e.g. `springVenue`) |
19
+
20
+ ## Package Structure
21
+
22
+ ```
23
+ src/mlb_statsapi/
24
+ ├── __init__.py # Public re-exports
25
+ ├── exceptions.py # MlbApiError, MlbValidationError
26
+ ├── client/
27
+ │ ├── __init__.py
28
+ │ ├── _base.py # Shared: URL building, param validation, response parsing
29
+ │ ├── sync_client.py # MlbClient (httpx sync)
30
+ │ └── async_client.py # AsyncMlbClient (httpx async)
31
+ ├── endpoints/
32
+ │ ├── __init__.py
33
+ │ └── registry.py # EndpointDef dataclass + 60+ endpoint definitions
34
+ └── models/
35
+ ├── __init__.py # Re-exports
36
+ ├── _base.py # MlbBaseModel, BaseResponse, IdNameLink, CodeDescription
37
+ ├── enums.py # GameType, GameState, HandCode, PositionType, etc.
38
+ ├── sports.py # Sport, SportsResponse
39
+ ├── divisions.py # Division, DivisionsResponse
40
+ ├── leagues.py # League, LeaguesResponse
41
+ ├── venues.py # Venue, VenuesResponse
42
+ ├── seasons.py # Season, SeasonsResponse
43
+ ├── teams.py # Team, TeamsResponse, TeamRecord, Roster
44
+ ├── people.py # Person, PeopleResponse, PrimaryPosition
45
+ ├── schedule.py # ScheduleResponse, ScheduleDate, ScheduleGame, GameStatus
46
+ ├── standings.py # StandingsResponse, StandingsRecord, TeamStanding
47
+ ├── game.py # BoxscoreResponse, LinescoreResponse, LiveFeedResponse
48
+ ├── stats.py # StatGroup, StatSplit, LeaderCategory, StatsResponse
49
+ ├── transactions.py
50
+ ├── draft.py
51
+ ├── awards.py
52
+ ├── attendance.py
53
+ ├── jobs.py
54
+ └── meta.py
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Tasks
60
+
61
+ ### Task 1: Project Setup & TDD Foundation
62
+ **Status:** not started
63
+ **Goal:** Repo, tooling, deps, directory skeleton, and first test suite so every future task starts with red tests.
64
+
65
+ **Steps:**
66
+ 1. `git init`, create GitHub repo via `gh repo create`
67
+ 2. Create `pyproject.toml` — hatchling build system
68
+ - deps: `pydantic>=2.0`, `httpx>=0.25`
69
+ - dev deps: `pytest`, `pytest-asyncio`, `respx`, `ruff`, `mypy`
70
+ - `requires-python = ">=3.10"`
71
+ 3. `.gitignore` (Python template), `LICENSE` (MIT)
72
+ 4. Create full directory skeleton (all `__init__.py` files, empty modules)
73
+ 5. Create Python venv, install deps in editable mode (`pip install -e ".[dev]"`)
74
+ 6. Write `src/mlb_statsapi/exceptions.py` — `MlbApiError`, `MlbValidationError`
75
+ 7. Write initial test stubs in `tests/`:
76
+ - `conftest.py` with fixture loading helper
77
+ - `tests/fixtures/` — fetch & save JSON from live API: sports, teams (sportId=1), schedule, people/660271, standings
78
+ - `tests/test_models/test_base.py` — tests for `MlbBaseModel`, `BaseResponse`, `IdNameLink`, `CodeDescription`
79
+ - `tests/test_models/test_enums.py` — tests for all enum types
80
+ 8. Run `pytest` — confirm tests exist and fail (red)
81
+ 9. Initial commit + push
82
+
83
+ **Verification:** `pytest` runs, discovers tests, all fail (no implementations yet). `ruff check` clean. Venv works.
84
+
85
+ ---
86
+
87
+ ### Task 2: Base Models & Enums
88
+ **Status:** not started
89
+ **Depends on:** Task 1
90
+ **Goal:** Implement foundation models until Task 1 tests go green.
91
+
92
+ **Files to create:**
93
+ - `src/mlb_statsapi/models/_base.py`
94
+ - `MlbBaseModel(BaseModel)` — `ConfigDict(populate_by_name=True, extra="allow", alias_generator=to_camel)`
95
+ - `BaseResponse(MlbBaseModel)` — `copyright: str`
96
+ - `IdNameLink(MlbBaseModel)` — `id: int, name: str | None = None, link: str`
97
+ - `CodeDescription(MlbBaseModel)` — `code: str, description: str`
98
+ - `src/mlb_statsapi/models/enums.py`
99
+ - `GameType`, `AbstractGameState`, `HandCode`, `PositionType` as `StrEnum`
100
+ - `src/mlb_statsapi/models/__init__.py` — re-exports
101
+
102
+ **Verification:** `pytest tests/test_models/test_base.py tests/test_models/test_enums.py` all green.
103
+
104
+ ---
105
+
106
+ ### Task 3: Simple Domain Models (sports, divisions, leagues, venues, seasons)
107
+ **Status:** completed
108
+ **Depends on:** Task 2
109
+ **Goal:** TDD the simplest response models — flat arrays with minimal nesting.
110
+
111
+ **For each module:** write tests first using saved JSON fixtures, then implement.
112
+ - `sports.py` — `Sport`, `SportsResponse`
113
+ - `divisions.py` — `Division`, `DivisionsResponse`
114
+ - `leagues.py` — `League`, `LeaguesResponse`
115
+ - `venues.py` — `Venue`, `VenuesResponse`
116
+ - `seasons.py` — `Season`, `SeasonsResponse`
117
+
118
+ **Verification:** `pytest tests/test_models/` all green. Models validate against real API fixtures.
119
+
120
+ ---
121
+
122
+ ### Task 4: Teams & People Models
123
+ **Status:** completed
124
+ **Depends on:** Task 2
125
+ **Goal:** TDD the team and player models with nested references.
126
+
127
+ - `teams.py` — `Team`, `TeamsResponse`, `TeamRecord`, `Roster`, `RosterEntry`
128
+ - Nested: `IdNameLink` for venue, league, division, spring_league
129
+ - `people.py` — `Person`, `PeopleResponse`, `PrimaryPosition`, `BatPitchSide`
130
+ - Nested: `CodeDescription` for bat_side, pitch_hand
131
+ - Date fields as `datetime.date`
132
+
133
+ **Verification:** `pytest tests/test_models/test_teams.py tests/test_models/test_people.py` green.
134
+
135
+ ---
136
+
137
+ ### Task 5: Schedule & Standings Models
138
+ **Status:** completed
139
+ **Depends on:** Task 2
140
+ **Goal:** TDD the most complex response hierarchies.
141
+
142
+ - `schedule.py` — `ScheduleResponse`, `ScheduleDate`, `ScheduleGame`, `GameStatus`, `ScheduleTeam`
143
+ - Hierarchy: dates → games → teams.{away,home}
144
+ - Multiple status representations (abstract, coded, detailed)
145
+ - `standings.py` — `StandingsResponse`, `StandingsRecord`, `TeamStanding`, `Streak`
146
+ - Division-grouped team records
147
+
148
+ **Verification:** `pytest tests/test_models/test_schedule.py tests/test_models/test_standings.py` green.
149
+
150
+ ---
151
+
152
+ ### Task 6: Game Models (Boxscore, Linescore, Live Feed)
153
+ **Status:** completed
154
+ **Depends on:** Task 4 (reuses Team/Person models)
155
+ **Goal:** TDD the deepest, most complex models.
156
+
157
+ - `game.py` — `BoxscoreResponse`, `LinescoreResponse`, `LiveFeedResponse`
158
+ - Boxscore: `teams.{away,home}` with batting/pitching/fielding stats, players dict keyed by `"ID{playerId}"`
159
+ - Linescore: innings array with home/away runs/hits/errors
160
+ - LiveFeed: `game_data`, `live_data.plays`, `live_data.linescore`, `live_data.boxscore`
161
+ - `stats.py` — `StatGroup`, `StatSplit`, `LeaderCategory`, `StatsResponse`
162
+
163
+ **Verification:** `pytest tests/test_models/test_game.py tests/test_models/test_stats.py` green.
164
+
165
+ ---
166
+
167
+ ### Task 7: Endpoint Registry
168
+ **Status:** completed
169
+ **Depends on:** Tasks 2–6
170
+ **Goal:** Define all 60+ MLB API endpoints in a typed registry.
171
+
172
+ - `endpoints/registry.py`
173
+ - `EndpointDef` frozen dataclass: `url_template`, `path_params`, `query_params`, `required_params`, `default_version`, `response_model`
174
+ - Registry `dict[str, EndpointDef]` covering all endpoints
175
+ - Tier 1 (~15): full model — schedule, teams, people, standings, game, boxscore, linescore, stats_leaders, roster, seasons, sports
176
+ - Tier 2 (~15): model, no convenience method — divisions, leagues, venues, transactions, draft, awards
177
+ - Tier 3 (~30): `BaseResponse` — remaining endpoints accessible via `client.get()`
178
+ - Tests: endpoint URL construction, required param validation
179
+
180
+ **Verification:** `pytest tests/test_client/test_endpoint_registry.py` green.
181
+
182
+ ---
183
+
184
+ ### Task 8: Client Implementation (Sync + Async)
185
+ **Status:** completed
186
+ **Depends on:** Task 7
187
+ **Goal:** Build the httpx-based client with typed convenience methods.
188
+
189
+ - `client/_base.py` — `_ClientMixin`
190
+ - `_build_request(endpoint, **params) -> (url, query_params)`
191
+ - `_parse_response(endpoint, data) -> BaseResponse`
192
+ - Required param validation
193
+ - `client/sync_client.py` — `MlbClient`
194
+ - Convenience methods: `schedule()`, `teams()`, `person()`, `standings()`, `boxscore()`, `linescore()`, `roster()`, `player_stats()`, `league_leaders()`, `get()`
195
+ - `client/async_client.py` — `AsyncMlbClient` (same interface, `async def`)
196
+ - Tests: mock httpx with `respx`, test URL building, param validation, error handling
197
+
198
+ **Verification:** `pytest tests/test_client/` all green.
199
+
200
+ ---
201
+
202
+ ### Task 9: Remaining Models & Public API Polish
203
+ **Status:** completed
204
+ **Depends on:** Task 8
205
+ **Goal:** Fill in Tier 2 models and finalize the package surface.
206
+
207
+ - Implement remaining model files: `transactions.py`, `draft.py`, `awards.py`, `attendance.py`, `jobs.py`, `meta.py`
208
+ - `src/mlb_statsapi/__init__.py` — export `MlbClient`, `AsyncMlbClient`, all response models
209
+ - `src/mlb_statsapi/py.typed` — PEP 561 marker
210
+ - `models/__init__.py` — complete re-exports
211
+
212
+ **Verification:** `mypy src/mlb_statsapi/` passes. `ruff check src/` clean.
213
+
214
+ ---
215
+
216
+ ### Task 10: Integration Tests & Smoke Test
217
+ **Status:** not started
218
+ **Depends on:** Task 9
219
+ **Goal:** Validate against the live MLB API.
220
+
221
+ - `tests/test_integration/test_live_api.py` — marked `@pytest.mark.integration`
222
+ - Hit live API for key endpoints, validate parsing
223
+ - Sports, teams, schedule, person, standings, boxscore
224
+ - Manual smoke test:
225
+ ```python
226
+ from mlb_statsapi import MlbClient
227
+ client = MlbClient()
228
+ schedule = client.schedule(date="03/27/2026")
229
+ print(schedule.dates[0].games[0].teams.home.team.name)
230
+ ```
231
+ - `pyproject.toml` — add `[tool.pytest.ini_options]` markers config
232
+
233
+ **Verification:** `pytest tests/ -m "not integration"` all green. `pytest tests/ -m integration` passes against live API.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: "[BUG] "
5
+ labels: bug
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Describe the bug**
11
+ A clear and concise description of what the bug is.
12
+
13
+ **To Reproduce**
14
+ Steps to reproduce the behavior:
15
+ 1. Go to '...'
16
+ 2. Click on '....'
17
+ 3. Scroll down to '....'
18
+ 4. See error
19
+
20
+ **Expected behavior**
21
+ A clear and concise description of what you expected to happen.
22
+
23
+ **Screenshots**
24
+ If applicable, add screenshots to help explain your problem.
25
+
26
+ **Additional context**
27
+ Add any other context about the problem here.
@@ -0,0 +1,33 @@
1
+ name: Bug Report
2
+ description: Report a bug or unexpected behavior
3
+ labels: [bug]
4
+ body:
5
+ - type: textarea
6
+ id: description
7
+ attributes:
8
+ label: Description
9
+ description: What happened? What did you expect?
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: reproduce
14
+ attributes:
15
+ label: Steps to reproduce
16
+ description: Minimal code or steps to reproduce the issue.
17
+ render: python
18
+ validations:
19
+ required: true
20
+ - type: input
21
+ id: version
22
+ attributes:
23
+ label: Package version
24
+ placeholder: "0.1.0"
25
+ validations:
26
+ required: true
27
+ - type: input
28
+ id: python-version
29
+ attributes:
30
+ label: Python version
31
+ placeholder: "3.12"
32
+ validations:
33
+ required: true
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an idea for this project
4
+ title: "[FEAT] "
5
+ labels: enhancement
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ ## Is your feature request related to a problem? Please describe.
11
+ A clear description of the problem, including what prevents you from achieving your goal.
12
+
13
+ ## Describe the solution you'd like
14
+ A clear description of the desired functionality and potential implementation details.
15
+
16
+ ## Describe alternatives you've considered
17
+ Any alternative solutions or workarounds you've considered.
18
+
19
+ ## Additional context
20
+ Add any other context, examples, or screenshots here.