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.
- mlb_statsapi_pydantic-0.0.1/.claude/CLAUDE.md +11 -0
- mlb_statsapi_pydantic-0.0.1/.claude/hooks/run_pytest.sh +8 -0
- mlb_statsapi_pydantic-0.0.1/.claude/mlb-stats-memory.md +135 -0
- mlb_statsapi_pydantic-0.0.1/.claude/refactor_tasks.md +188 -0
- mlb_statsapi_pydantic-0.0.1/.claude/settings.json +16 -0
- mlb_statsapi_pydantic-0.0.1/.claude/tasks.md +233 -0
- mlb_statsapi_pydantic-0.0.1/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
- mlb_statsapi_pydantic-0.0.1/.github/ISSUE_TEMPLATE/bug_report.yml +33 -0
- mlb_statsapi_pydantic-0.0.1/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- mlb_statsapi_pydantic-0.0.1/.github/ISSUE_TEMPLATE/feature_request.yml +17 -0
- mlb_statsapi_pydantic-0.0.1/.github/dependabot.yml +19 -0
- mlb_statsapi_pydantic-0.0.1/.github/pull_request_template.md +13 -0
- mlb_statsapi_pydantic-0.0.1/.github/workflows/ci.yml +60 -0
- mlb_statsapi_pydantic-0.0.1/.github/workflows/publish.yml +41 -0
- mlb_statsapi_pydantic-0.0.1/.gitignore +32 -0
- mlb_statsapi_pydantic-0.0.1/.pre-commit-config.yaml +17 -0
- mlb_statsapi_pydantic-0.0.1/CHANGELOG.md +29 -0
- mlb_statsapi_pydantic-0.0.1/LICENSE +28 -0
- mlb_statsapi_pydantic-0.0.1/PKG-INFO +137 -0
- mlb_statsapi_pydantic-0.0.1/README.md +102 -0
- mlb_statsapi_pydantic-0.0.1/examples/get_schedule.py +113 -0
- mlb_statsapi_pydantic-0.0.1/pyproject.toml +92 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/__init__.py +20 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/client/__init__.py +1 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/client/_base.py +68 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/client/async_client.py +171 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/client/sync_client.py +175 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/endpoints/__init__.py +1 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/endpoints/registry.py +608 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/enums/__init__.py +2 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/enums/game_types.py +21 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/enums/team_ids.py +39 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/exceptions.py +18 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/__init__.py +196 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/_base.py +193 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/attendance.py +72 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/awards.py +69 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/divisions.py +47 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/draft.py +139 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/enums.py +709 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/game.py +260 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/hydrations.py +65 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/jobs.py +35 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/leagues.py +48 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/livefeed.py +602 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/meta.py +15 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/people.py +104 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/schedule.py +129 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/seasons.py +58 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/sports.py +39 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/standings.py +115 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/stats.py +107 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/teams.py +70 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/transactions.py +42 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/models/venues.py +74 -0
- mlb_statsapi_pydantic-0.0.1/src/mlb_statsapi/py.typed +0 -0
- mlb_statsapi_pydantic-0.0.1/tests/__init__.py +0 -0
- mlb_statsapi_pydantic-0.0.1/tests/conftest.py +39 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/attendance.json +65 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/awards.json +6866 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/boxscore.json +1 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/divisions.json +1 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/draft.json +54463 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/jobs.json +1083 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/leagues.json +1 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/linescore.json +1 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/livefeed.json +1 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/meta_game_types.json +50 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/people_660271.json +1 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/schedule.json +1 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/schedule_hydrated_team.json +151 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/seasons.json +1 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/sports.json +1 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/standings.json +1 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/stats_leaders.json +1 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/teams.json +1 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/transactions.json +4653 -0
- mlb_statsapi_pydantic-0.0.1/tests/fixtures/venue_15.json +1 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_client/__init__.py +0 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_client/test_async.py +79 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_client/test_endpoint_registry.py +138 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_client/test_sync.py +226 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_integration/__init__.py +0 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_integration/test_e2e.py +167 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_integration/test_live_api.py +175 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/__init__.py +0 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_attendance.py +32 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_awards.py +33 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_base.py +310 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_divisions.py +44 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_draft.py +34 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_enums.py +77 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_game.py +219 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_jobs.py +25 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_leagues.py +43 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_livefeed.py +482 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_meta.py +23 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_people.py +91 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_schedule.py +152 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_seasons.py +52 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_sports.py +40 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_standings.py +73 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_stats.py +38 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_teams.py +84 -0
- mlb_statsapi_pydantic-0.0.1/tests/test_models/test_transactions.py +34 -0
- 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,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,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.
|