cfb-data 0.4.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 (132) hide show
  1. cfb_data-0.4.1/LICENSE +21 -0
  2. cfb_data-0.4.1/PKG-INFO +414 -0
  3. cfb_data-0.4.1/README.md +377 -0
  4. cfb_data-0.4.1/cfb_data/cfb_data/__init__.py +234 -0
  5. cfb_data-0.4.1/cfb_data/cfb_data/_dataframes.py +279 -0
  6. cfb_data-0.4.1/cfb_data/cfb_data/_executor.py +129 -0
  7. cfb_data-0.4.1/cfb_data/cfb_data/_parquet.py +197 -0
  8. cfb_data-0.4.1/cfb_data/cfb_data/_request_rules.py +41 -0
  9. cfb_data-0.4.1/cfb_data/cfb_data/_requests.py +36 -0
  10. cfb_data-0.4.1/cfb_data/cfb_data/_tabular.py +676 -0
  11. cfb_data-0.4.1/cfb_data/cfb_data/_transport.py +452 -0
  12. cfb_data-0.4.1/cfb_data/cfb_data/adjusted_metrics/__init__.py +29 -0
  13. cfb_data-0.4.1/cfb_data/cfb_data/adjusted_metrics/models/__init__.py +1 -0
  14. cfb_data-0.4.1/cfb_data/cfb_data/adjusted_metrics/models/pydantic/__init__.py +29 -0
  15. cfb_data-0.4.1/cfb_data/cfb_data/adjusted_metrics/models/pydantic/requests.py +45 -0
  16. cfb_data-0.4.1/cfb_data/cfb_data/adjusted_metrics/models/pydantic/responses.py +88 -0
  17. cfb_data-0.4.1/cfb_data/cfb_data/adjusted_metrics/resource.py +220 -0
  18. cfb_data-0.4.1/cfb_data/cfb_data/base/__init__.py +6 -0
  19. cfb_data-0.4.1/cfb_data/cfb_data/base/types.py +112 -0
  20. cfb_data-0.4.1/cfb_data/cfb_data/betting/__init__.py +15 -0
  21. cfb_data-0.4.1/cfb_data/cfb_data/betting/models/__init__.py +1 -0
  22. cfb_data-0.4.1/cfb_data/cfb_data/betting/models/pydantic/__init__.py +6 -0
  23. cfb_data-0.4.1/cfb_data/cfb_data/betting/models/pydantic/requests.py +35 -0
  24. cfb_data-0.4.1/cfb_data/cfb_data/betting/models/pydantic/responses.py +62 -0
  25. cfb_data-0.4.1/cfb_data/cfb_data/betting/resource.py +91 -0
  26. cfb_data-0.4.1/cfb_data/cfb_data/client.py +305 -0
  27. cfb_data-0.4.1/cfb_data/cfb_data/coaches/__init__.py +57 -0
  28. cfb_data-0.4.1/cfb_data/cfb_data/coaches/models/__init__.py +1 -0
  29. cfb_data-0.4.1/cfb_data/cfb_data/coaches/models/pydantic/__init__.py +57 -0
  30. cfb_data-0.4.1/cfb_data/cfb_data/coaches/models/pydantic/requests.py +75 -0
  31. cfb_data-0.4.1/cfb_data/cfb_data/coaches/models/pydantic/responses.py +261 -0
  32. cfb_data-0.4.1/cfb_data/cfb_data/coaches/resource.py +216 -0
  33. cfb_data-0.4.1/cfb_data/cfb_data/conferences/__init__.py +23 -0
  34. cfb_data-0.4.1/cfb_data/cfb_data/conferences/models/__init__.py +1 -0
  35. cfb_data-0.4.1/cfb_data/cfb_data/conferences/models/pydantic/__init__.py +23 -0
  36. cfb_data-0.4.1/cfb_data/cfb_data/conferences/models/pydantic/requests.py +69 -0
  37. cfb_data-0.4.1/cfb_data/cfb_data/conferences/models/pydantic/responses.py +64 -0
  38. cfb_data-0.4.1/cfb_data/cfb_data/conferences/resource.py +175 -0
  39. cfb_data-0.4.1/cfb_data/cfb_data/draft/__init__.py +19 -0
  40. cfb_data-0.4.1/cfb_data/cfb_data/draft/models/__init__.py +1 -0
  41. cfb_data-0.4.1/cfb_data/cfb_data/draft/models/pydantic/__init__.py +12 -0
  42. cfb_data-0.4.1/cfb_data/cfb_data/draft/models/pydantic/requests.py +18 -0
  43. cfb_data-0.4.1/cfb_data/cfb_data/draft/models/pydantic/responses.py +65 -0
  44. cfb_data-0.4.1/cfb_data/cfb_data/draft/resource.py +132 -0
  45. cfb_data-0.4.1/cfb_data/cfb_data/drives/__init__.py +13 -0
  46. cfb_data-0.4.1/cfb_data/cfb_data/drives/models/__init__.py +1 -0
  47. cfb_data-0.4.1/cfb_data/cfb_data/drives/models/pydantic/__init__.py +17 -0
  48. cfb_data-0.4.1/cfb_data/cfb_data/drives/models/pydantic/requests.py +42 -0
  49. cfb_data-0.4.1/cfb_data/cfb_data/drives/models/pydantic/responses.py +46 -0
  50. cfb_data-0.4.1/cfb_data/cfb_data/drives/resource.py +94 -0
  51. cfb_data-0.4.1/cfb_data/cfb_data/enums.py +93 -0
  52. cfb_data-0.4.1/cfb_data/cfb_data/errors.py +234 -0
  53. cfb_data-0.4.1/cfb_data/cfb_data/games/__init__.py +42 -0
  54. cfb_data-0.4.1/cfb_data/cfb_data/games/models/__init__.py +1 -0
  55. cfb_data-0.4.1/cfb_data/cfb_data/games/models/pydantic/__init__.py +106 -0
  56. cfb_data-0.4.1/cfb_data/cfb_data/games/models/pydantic/requests.py +266 -0
  57. cfb_data-0.4.1/cfb_data/cfb_data/games/models/pydantic/responses.py +495 -0
  58. cfb_data-0.4.1/cfb_data/cfb_data/games/resource.py +486 -0
  59. cfb_data-0.4.1/cfb_data/cfb_data/info/__init__.py +25 -0
  60. cfb_data-0.4.1/cfb_data/cfb_data/info/models/__init__.py +1 -0
  61. cfb_data-0.4.1/cfb_data/cfb_data/info/models/pydantic/__init__.py +23 -0
  62. cfb_data-0.4.1/cfb_data/cfb_data/info/models/pydantic/requests.py +18 -0
  63. cfb_data-0.4.1/cfb_data/cfb_data/info/models/pydantic/responses.py +103 -0
  64. cfb_data-0.4.1/cfb_data/cfb_data/info/resource.py +88 -0
  65. cfb_data-0.4.1/cfb_data/cfb_data/metrics/__init__.py +53 -0
  66. cfb_data-0.4.1/cfb_data/cfb_data/metrics/models/__init__.py +1 -0
  67. cfb_data-0.4.1/cfb_data/cfb_data/metrics/models/pydantic/__init__.py +49 -0
  68. cfb_data-0.4.1/cfb_data/cfb_data/metrics/models/pydantic/requests.py +121 -0
  69. cfb_data-0.4.1/cfb_data/cfb_data/metrics/models/pydantic/responses.py +182 -0
  70. cfb_data-0.4.1/cfb_data/cfb_data/metrics/resource.py +371 -0
  71. cfb_data-0.4.1/cfb_data/cfb_data/players/__init__.py +44 -0
  72. cfb_data-0.4.1/cfb_data/cfb_data/players/models/__init__.py +1 -0
  73. cfb_data-0.4.1/cfb_data/cfb_data/players/models/pydantic/__init__.py +41 -0
  74. cfb_data-0.4.1/cfb_data/cfb_data/players/models/pydantic/requests.py +70 -0
  75. cfb_data-0.4.1/cfb_data/cfb_data/players/models/pydantic/responses.py +171 -0
  76. cfb_data-0.4.1/cfb_data/cfb_data/players/resource.py +258 -0
  77. cfb_data-0.4.1/cfb_data/cfb_data/playoffs/__init__.py +41 -0
  78. cfb_data-0.4.1/cfb_data/cfb_data/playoffs/models/__init__.py +1 -0
  79. cfb_data-0.4.1/cfb_data/cfb_data/playoffs/models/pydantic/__init__.py +37 -0
  80. cfb_data-0.4.1/cfb_data/cfb_data/playoffs/models/pydantic/requests.py +30 -0
  81. cfb_data-0.4.1/cfb_data/cfb_data/playoffs/models/pydantic/responses.py +173 -0
  82. cfb_data-0.4.1/cfb_data/cfb_data/playoffs/resource.py +149 -0
  83. cfb_data-0.4.1/cfb_data/cfb_data/plays/__init__.py +43 -0
  84. cfb_data-0.4.1/cfb_data/cfb_data/plays/models/__init__.py +1 -0
  85. cfb_data-0.4.1/cfb_data/cfb_data/plays/models/pydantic/__init__.py +35 -0
  86. cfb_data-0.4.1/cfb_data/cfb_data/plays/models/pydantic/requests.py +85 -0
  87. cfb_data-0.4.1/cfb_data/cfb_data/plays/models/pydantic/responses.py +231 -0
  88. cfb_data-0.4.1/cfb_data/cfb_data/plays/resource.py +249 -0
  89. cfb_data-0.4.1/cfb_data/cfb_data/py.typed +0 -0
  90. cfb_data-0.4.1/cfb_data/cfb_data/rankings/__init__.py +16 -0
  91. cfb_data-0.4.1/cfb_data/cfb_data/rankings/models/__init__.py +1 -0
  92. cfb_data-0.4.1/cfb_data/cfb_data/rankings/models/pydantic/__init__.py +6 -0
  93. cfb_data-0.4.1/cfb_data/cfb_data/rankings/models/pydantic/requests.py +36 -0
  94. cfb_data-0.4.1/cfb_data/cfb_data/rankings/models/pydantic/responses.py +42 -0
  95. cfb_data-0.4.1/cfb_data/cfb_data/rankings/resource.py +89 -0
  96. cfb_data-0.4.1/cfb_data/cfb_data/ratings/__init__.py +59 -0
  97. cfb_data-0.4.1/cfb_data/cfb_data/ratings/models/__init__.py +1 -0
  98. cfb_data-0.4.1/cfb_data/cfb_data/ratings/models/pydantic/__init__.py +55 -0
  99. cfb_data-0.4.1/cfb_data/cfb_data/ratings/models/pydantic/requests.py +87 -0
  100. cfb_data-0.4.1/cfb_data/cfb_data/ratings/models/pydantic/responses.py +215 -0
  101. cfb_data-0.4.1/cfb_data/cfb_data/ratings/resource.py +342 -0
  102. cfb_data-0.4.1/cfb_data/cfb_data/recruiting/__init__.py +26 -0
  103. cfb_data-0.4.1/cfb_data/cfb_data/recruiting/models/__init__.py +1 -0
  104. cfb_data-0.4.1/cfb_data/cfb_data/recruiting/models/pydantic/__init__.py +23 -0
  105. cfb_data-0.4.1/cfb_data/cfb_data/recruiting/models/pydantic/requests.py +69 -0
  106. cfb_data-0.4.1/cfb_data/cfb_data/recruiting/models/pydantic/responses.py +70 -0
  107. cfb_data-0.4.1/cfb_data/cfb_data/recruiting/resource.py +180 -0
  108. cfb_data-0.4.1/cfb_data/cfb_data/retry.py +49 -0
  109. cfb_data-0.4.1/cfb_data/cfb_data/stats/__init__.py +69 -0
  110. cfb_data-0.4.1/cfb_data/cfb_data/stats/models/__init__.py +1 -0
  111. cfb_data-0.4.1/cfb_data/cfb_data/stats/models/pydantic/__init__.py +65 -0
  112. cfb_data-0.4.1/cfb_data/cfb_data/stats/models/pydantic/requests.py +167 -0
  113. cfb_data-0.4.1/cfb_data/cfb_data/stats/models/pydantic/responses.py +291 -0
  114. cfb_data-0.4.1/cfb_data/cfb_data/stats/resource.py +400 -0
  115. cfb_data-0.4.1/cfb_data/cfb_data/teams/__init__.py +40 -0
  116. cfb_data-0.4.1/cfb_data/cfb_data/teams/models/__init__.py +1 -0
  117. cfb_data-0.4.1/cfb_data/cfb_data/teams/models/pydantic/__init__.py +26 -0
  118. cfb_data-0.4.1/cfb_data/cfb_data/teams/models/pydantic/requests.py +96 -0
  119. cfb_data-0.4.1/cfb_data/cfb_data/teams/models/pydantic/responses.py +116 -0
  120. cfb_data-0.4.1/cfb_data/cfb_data/teams/resource.py +270 -0
  121. cfb_data-0.4.1/cfb_data/cfb_data/venues/__init__.py +6 -0
  122. cfb_data-0.4.1/cfb_data/cfb_data/venues/models/__init__.py +1 -0
  123. cfb_data-0.4.1/cfb_data/cfb_data/venues/models/pydantic/__init__.py +5 -0
  124. cfb_data-0.4.1/cfb_data/cfb_data/venues/models/pydantic/responses.py +24 -0
  125. cfb_data-0.4.1/cfb_data/cfb_data/venues/resource.py +51 -0
  126. cfb_data-0.4.1/cfb_data/cfb_data.egg-info/PKG-INFO +414 -0
  127. cfb_data-0.4.1/cfb_data/cfb_data.egg-info/SOURCES.txt +130 -0
  128. cfb_data-0.4.1/cfb_data/cfb_data.egg-info/dependency_links.txt +1 -0
  129. cfb_data-0.4.1/cfb_data/cfb_data.egg-info/requires.txt +20 -0
  130. cfb_data-0.4.1/cfb_data/cfb_data.egg-info/top_level.txt +1 -0
  131. cfb_data-0.4.1/pyproject.toml +115 -0
  132. cfb_data-0.4.1/setup.cfg +4 -0
cfb_data-0.4.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Ryan Anderson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,414 @@
1
+ Metadata-Version: 2.4
2
+ Name: cfb-data
3
+ Version: 0.4.1
4
+ Summary: Async validated CollegeFootballData access for pandas and Polars
5
+ Author: Ryan Anderson
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://ryanpaulanderson.github.io/cfb-data/
8
+ Project-URL: Repository, https://github.com/ryanpaulanderson/cfb-data
9
+ Project-URL: Issues, https://github.com/ryanpaulanderson/cfb-data/issues
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Python: >=3.12
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: aiohttp<4,>=3.13
19
+ Requires-Dist: pandas<4,>=3.0
20
+ Requires-Dist: pydantic<3,>=2.12
21
+ Requires-Dist: pyarrow<26,>=25
22
+ Provides-Extra: polars
23
+ Requires-Dist: polars<2,>=1.43.2; extra == "polars"
24
+ Provides-Extra: dev
25
+ Requires-Dist: build<2,>=1.5; extra == "dev"
26
+ Requires-Dist: furo>=2025.12.19; extra == "dev"
27
+ Requires-Dist: mypy<3,>=2.3; extra == "dev"
28
+ Requires-Dist: myst-parser<6,>=5.1; extra == "dev"
29
+ Requires-Dist: pre-commit<5,>=4.5; extra == "dev"
30
+ Requires-Dist: pyarrow-stubs<21,>=20.0.0.20260625; extra == "dev"
31
+ Requires-Dist: pytest<10,>=9; extra == "dev"
32
+ Requires-Dist: pytest-asyncio<2,>=1.3; extra == "dev"
33
+ Requires-Dist: ruff<0.17,>=0.15.22; extra == "dev"
34
+ Requires-Dist: sphinx<10,>=9.1; extra == "dev"
35
+ Requires-Dist: twine<8,>=7; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ # College Football Data Python Toolkit
39
+
40
+ `cfb-data` 0.4.1 is an asynchronous, validated client for the public
41
+ [CollegeFootballData API](https://collegefootballdata.com/) REST endpoint
42
+ groups. It returns eager pandas DataFrames by default and can return the same
43
+ logical tables as Polars DataFrames. Irreducibly nested analytical responses
44
+ and operational account metadata return validated models instead.
45
+
46
+ Read the complete [documentation](https://ryanpaulanderson.github.io/cfb-data/)
47
+ for the getting-started guide, request rules and allowed values, namespace
48
+ contracts, result schemas, and generated Python API reference.
49
+
50
+ The request path is explicit:
51
+
52
+ ```text
53
+ HTTP → Pydantic models → logical schema → canonical Arrow table → DataFrame
54
+ ```
55
+
56
+ Malformed upstream data never reaches a DataFrame. Choosing Polars changes the
57
+ concrete return type and native nested representation, not endpoint names,
58
+ validation, retry behavior, columns, row order, logical values, or the
59
+ canonical storage schema.
60
+
61
+ ## Installation
62
+
63
+ pandas and PyArrow are included in the default installation:
64
+
65
+ ```sh
66
+ python -m pip install cfb-data
67
+ ```
68
+
69
+ Install the optional Polars backend with:
70
+
71
+ ```sh
72
+ python -m pip install "cfb-data[polars]"
73
+ ```
74
+
75
+ Python 3.12 and 3.13 are supported. DataFrames are eager; Polars
76
+ `LazyFrame` results are not part of the 0.4.1 contract.
77
+
78
+ ## Authentication and lifecycle
79
+
80
+ Set a CollegeFootballData API key in the environment:
81
+
82
+ ```sh
83
+ export CFBD_API_KEY="..."
84
+ ```
85
+
86
+ Every client is one-shot and must own its reusable HTTP session through
87
+ `async with`:
88
+
89
+ ```python
90
+ import asyncio
91
+
92
+ from cfb_data import CFBDClient
93
+
94
+
95
+ async def main() -> None:
96
+ async with CFBDClient() as client:
97
+ games = await client.games.list(year=2024, team="Michigan")
98
+ drives = await client.drives.list(year=2024, team="Michigan")
99
+ plays = await client.plays.list(year=2024, week=1, team="Michigan")
100
+ stats = await client.stats.team_season(year=2024, team="Michigan")
101
+ ratings = await client.ratings.elo(year=2024, team="Michigan")
102
+ players = await client.players.search(
103
+ search_term="Edwards", year=2024, team="Michigan"
104
+ )
105
+ teams = await client.teams.fbs(year=2024)
106
+ draft_picks = await client.draft.picks(year=2024, school="Michigan")
107
+ playoff = await client.playoffs.cfp(year=2024)
108
+ adjusted = await client.adjusted_metrics.team_season(
109
+ year=2024, team="Michigan"
110
+ )
111
+ account = await client.info.account()
112
+
113
+ print(games.head())
114
+ print(drives.head())
115
+ print(plays.head())
116
+ print(stats.head())
117
+ print(ratings.head())
118
+ print(players.head())
119
+ print(teams.head())
120
+ print(draft_picks.head())
121
+ print(playoff.champion)
122
+ print(adjusted.head())
123
+ print(account.tier_name)
124
+
125
+
126
+ asyncio.run(main())
127
+ ```
128
+
129
+ An explicit non-empty `api_key` takes precedence over `CFBD_API_KEY`. Passing
130
+ an empty explicit value is a configuration error and does not fall back to the
131
+ environment. Calls before context entry, after exit, during a nested entry, or
132
+ after attempted re-entry raise `CFBDClientStateError`.
133
+
134
+ For Polars, select only the backend:
135
+
136
+ ```python
137
+ from cfb_data import CFBDClient
138
+
139
+ async with CFBDClient(dataframe_backend="polars") as client:
140
+ calendar = await client.games.calendar(year=2024)
141
+ ```
142
+
143
+ Type checkers infer `pandas.DataFrame` for the default client and
144
+ `polars.DataFrame` when the literal backend is `"polars"`.
145
+
146
+ ## Endpoints
147
+
148
+ Each method accepts either one positional request model or explicit snake-case
149
+ keyword filters. The styles are mutually exclusive:
150
+
151
+ ```python
152
+ from cfb_data import CFBDClient, GamesRequest, SeasonType
153
+
154
+ request = GamesRequest(year=2024, season_type=SeasonType.regular)
155
+
156
+ async with CFBDClient() as client:
157
+ by_model = await client.games.list(request)
158
+ by_keywords = await client.games.list(year=2024, season_type="regular")
159
+ ```
160
+
161
+ Unknown filters and invalid combinations fail before HTTP. The public Python
162
+ name is always `game_id`; request serialization maps it to upstream `id` or
163
+ `gameId` as required.
164
+
165
+ | Method | Request model | Result |
166
+ | --- | --- | --- |
167
+ | `client.games.list` | `GamesRequest` | `Game` rows as selected frame |
168
+ | `client.games.records` | `RecordsRequest` | `TeamRecords` rows as selected frame |
169
+ | `client.games.calendar` | `CalendarRequest` | `CalendarWeek` rows as selected frame |
170
+ | `client.games.scoreboard` | `ScoreboardRequest` | `ScoreboardGame` rows as selected frame |
171
+ | `client.games.media` | `GameMediaRequest` | `GameMedia` rows as selected frame |
172
+ | `client.games.weather` | `GameWeatherRequest` | `GameWeather` rows as selected frame |
173
+ | `client.games.player_stats` | `PlayerGameStatsRequest` | `PlayerGameStats` rows as selected frame |
174
+ | `client.games.team_stats` | `TeamGameStatsRequest` | `TeamGameStats` rows as selected frame |
175
+ | `client.games.advanced_box_score` | `AdvancedBoxScoreRequest` | `AdvancedBoxScore` model |
176
+ | `client.drives.list` | `DrivesRequest` | `Drive` rows as selected frame |
177
+ | `client.plays.list` | `PlaysRequest` | `Play` rows as selected frame |
178
+ | `client.plays.types` | None | `PlayType` rows as selected frame |
179
+ | `client.plays.stats` | `PlayStatsRequest` | `PlayStat` rows as selected frame |
180
+ | `client.plays.stat_types` | None | `PlayStatType` rows as selected frame |
181
+ | `client.plays.live` | `LivePlaysRequest` | `LiveGame` model |
182
+ | `client.venues.list` | None | `Venue` rows as selected frame |
183
+ | `client.conferences.list` | `ConferencesRequest` | `Conference` rows as selected frame |
184
+ | `client.conferences.changes` | `ConferenceChangesRequest` | `TeamConferenceChange` rows as selected frame |
185
+ | `client.conferences.affiliations` | `ConferenceAffiliationsRequest` | `TeamConferenceAffiliation` rows as selected frame |
186
+ | `client.teams.list` | `TeamsRequest` | `Team` rows as selected frame |
187
+ | `client.teams.fbs` | `FBSTeamsRequest` | `Team` rows as selected frame |
188
+ | `client.teams.matchup` | `TeamMatchupRequest` | one `Matchup` row as selected frame |
189
+ | `client.teams.ats` | `TeamATSRequest` | `TeamATS` rows as selected frame |
190
+ | `client.teams.roster` | `RosterRequest` | `RosterPlayer` rows as selected frame |
191
+ | `client.teams.talent` | `TalentRequest` | `TeamTalent` rows as selected frame |
192
+ | `client.stats.player_season` | `PlayerSeasonStatsRequest` | `PlayerStat` rows as selected frame |
193
+ | `client.stats.player_season_success` | `PlayerSeasonSuccessRequest` | `PlayerSeasonSuccessRate` rows as selected frame |
194
+ | `client.stats.player_game_success` | `PlayerGameSuccessRequest` | `PlayerGameSuccessRate` rows as selected frame |
195
+ | `client.stats.team_season` | `TeamSeasonStatsRequest` | `TeamStat` rows as selected frame |
196
+ | `client.stats.categories` | None | `StatCategory` rows as selected frame |
197
+ | `client.stats.advanced_season` | `AdvancedSeasonStatsRequest` | `AdvancedSeasonStat` rows as selected frame |
198
+ | `client.stats.advanced_game` | `AdvancedGameStatsRequest` | `AdvancedGameStat` rows as selected frame |
199
+ | `client.stats.game_havoc` | `GameHavocRequest` | `GameHavocStats` rows as selected frame |
200
+ | `client.metrics.predicted_points` | `PredictedPointsRequest` | `PredictedPointsValue` rows as selected frame |
201
+ | `client.metrics.team_season_ppa` | `TeamSeasonPPARequest` | `TeamSeasonPredictedPointsAdded` rows as selected frame |
202
+ | `client.metrics.team_game_ppa` | `TeamGamePPARequest` | `TeamGamePredictedPointsAdded` rows as selected frame |
203
+ | `client.metrics.player_game_ppa` | `PlayerGamePPARequest` | `PlayerGamePredictedPointsAdded` rows as selected frame |
204
+ | `client.metrics.player_season_ppa` | `PlayerSeasonPPARequest` | `PlayerSeasonPredictedPointsAdded` rows as selected frame |
205
+ | `client.metrics.win_probability` | `WinProbabilityRequest` | `PlayWinProbability` rows as selected frame |
206
+ | `client.metrics.pregame_win_probability` | `PregameWinProbabilityRequest` | `PregameWinProbability` rows as selected frame |
207
+ | `client.metrics.field_goal_expected_points` | None | `FieldGoalExpectedPoints` rows as selected frame |
208
+ | `client.ratings.core` | `CoreRatingsRequest` | `TeamCoreRating` rows as selected frame |
209
+ | `client.ratings.sp` | `SPRatingsRequest` | `TeamSP` rows as selected frame |
210
+ | `client.ratings.conference_sp` | `ConferenceSPRatingsRequest` | `ConferenceSP` rows as selected frame |
211
+ | `client.ratings.srs` | `SRSRatingsRequest` | `TeamSRS` rows as selected frame |
212
+ | `client.ratings.expanded_srs` | `ExpandedSRSRatingsRequest` | `ExpandedTeamSRS` rows as selected frame |
213
+ | `client.ratings.elo` | `EloRatingsRequest` | `TeamElo` rows as selected frame |
214
+ | `client.ratings.fpi` | `FPIRatingsRequest` | `TeamFPI` rows as selected frame |
215
+ | `client.players.search` | `PlayerSearchRequest` | `PlayerSearchResult` rows as selected frame |
216
+ | `client.players.usage` | `PlayerUsageRequest` | `PlayerUsage` rows as selected frame |
217
+ | `client.players.season_overview` | `PlayerSeasonOverviewRequest` | one `PlayerSeasonOverview` row as selected frame |
218
+ | `client.players.returning_production` | `ReturningProductionRequest` | `ReturningProduction` rows as selected frame |
219
+ | `client.players.transfer_portal` | `TransferPortalRequest` | `PlayerTransfer` rows as selected frame |
220
+ | `client.rankings.list` | `RankingsRequest` | `PollWeek` rows as selected frame |
221
+ | `client.betting.lines` | `BettingLinesRequest` | `BettingGame` rows as selected frame |
222
+ | `client.recruiting.players` | `RecruitingPlayersRequest` | `Recruit` rows as selected frame |
223
+ | `client.recruiting.teams` | `RecruitingTeamsRequest` | `TeamRecruitingRanking` rows as selected frame |
224
+ | `client.recruiting.groups` | `RecruitingGroupsRequest` | `AggregatedTeamRecruiting` rows as selected frame |
225
+ | `client.coaches.list` | `CoachesRequest` | `Coach` rows as selected frame |
226
+ | `client.coaches.profile` | `CoachProfileRequest` | one `CoachProfile` row as selected frame |
227
+ | `client.coaches.seasons` | `CoachSeasonsRequest` | `DetailedCoachSeason` rows as selected frame |
228
+ | `client.coaches.tenures` | `CoachTenuresRequest` | `CoachTenure` rows as selected frame |
229
+ | `client.draft.teams` | None | `DraftTeam` rows as selected frame |
230
+ | `client.draft.positions` | None | `DraftPosition` rows as selected frame |
231
+ | `client.draft.picks` | `DraftPicksRequest` | `DraftPick` rows as selected frame |
232
+ | `client.playoffs.cfp` | `CfpPlayoffRequest` | `CfpPlayoff` model |
233
+ | `client.playoffs.participants` | `CfpParticipantsRequest` | `PlayoffParticipant` rows as selected frame |
234
+ | `client.playoffs.games` | `CfpGamesRequest` | `PlayoffMatchup` rows as selected frame |
235
+ | `client.adjusted_metrics.team_season` | `AdjustedTeamMetricsRequest` | `AdjustedTeamMetrics` rows as selected frame |
236
+ | `client.adjusted_metrics.player_passing` | `AdjustedPlayerPassingRequest` | `PlayerWeightedEPA` rows as selected frame |
237
+ | `client.adjusted_metrics.player_rushing` | `AdjustedPlayerRushingRequest` | `PlayerWeightedEPA` rows as selected frame |
238
+ | `client.adjusted_metrics.kicker_paar` | `KickerPAARRequest` | `KickerPAAR` rows as selected frame |
239
+ | `client.info.account` | None | `UserInfo` model |
240
+ | `client.info.usage` | `InfoUsageRequest` | `UserUsage` model |
241
+
242
+ Request models and shared `StrEnum` values are exported from `cfb_data` and
243
+ their supported domain namespaces. Enum fields also accept their documented
244
+ string values.
245
+
246
+ Raw JSON and general validated-model return modes are intentionally excluded.
247
+ Advanced box score, live plays, and the complete CFP bracket return models
248
+ because their nested sections do not form one natural table. Info returns
249
+ models because account and usage metadata are operational objects rather than
250
+ analytical tables. The upstream live-plays route requires Patreon Tier 2
251
+ access; all Adjusted Metrics routes require Patreon Tier 1 access.
252
+
253
+ Team matchup, player season overview, and coach profile are one-row frames
254
+ containing nested columns. Rankings preserve polls and ranks, betting preserves
255
+ provider lines, and coach summaries preserve seasons as nested values. pandas
256
+ represents nested values as `object`; Polars represents them as native `Struct`
257
+ and `List[Struct]` values.
258
+
259
+ ## DataFrame contract
260
+
261
+ Response model field order defines exact snake-case column order. API row order
262
+ and row count are preserved. Conversion never flattens, explodes, indexes by
263
+ ID, or drops rows, and an empty response produces a correctly typed empty
264
+ frame.
265
+
266
+ pandas uses:
267
+
268
+ - `int64`, `float64`, and `bool` for required scalars;
269
+ - `Int64`, `Float64`, and `boolean` for nullable scalars;
270
+ - pandas `string` and `datetime64[ns, UTC]` dtypes;
271
+ - `object` columns for nested structs and lists;
272
+ - `object` for the explicitly heterogeneous Stats `stat_value` scalar;
273
+ - a normal `RangeIndex`.
274
+
275
+ Polars uses strict `Int64`, `Float64`, `Boolean`, `String`, UTC `Datetime`,
276
+ `Struct`, `List`, and the explicit heterogeneous `Object` Stats scalar. This
277
+ means nested values have the same logical content while remaining Python
278
+ mappings/lists in pandas and native nested columns in Polars. The
279
+ `client.stats.team_season` `stat_value` column preserves each upstream string,
280
+ integer, or float without coercion and is `object`/`Object` in both backends.
281
+
282
+ All response timestamps must include a timezone. Validation normalizes them to
283
+ UTC before conversion.
284
+
285
+ ## Arrow and Parquet contract
286
+
287
+ Every tabular response is represented as one explicit Arrow table before
288
+ pandas or Polars materialization. The Arrow schema is derived from the same
289
+ Pydantic annotations and retains ordered struct fields, typed list elements,
290
+ nullability, and UTC timestamps even for empty or all-null responses.
291
+
292
+ The internal versioned Parquet codec uses that schema directly. It stores
293
+ standard nested Parquet values plus cfb-data storage version, row-model
294
+ identity, logical-schema digest, and writer-version metadata. The heterogeneous
295
+ Stats scalar is stored losslessly as a tagged struct and decoded back to its
296
+ original string, integer, or float value for DataFrames.
297
+
298
+ Direct pandas and Polars Parquet methods are not the cfb-data persistence
299
+ compatibility contract: pandas object inference and Polars `Object` columns do
300
+ not provide identical empty- and mixed-value behavior. Version 0.2.0 keeps the
301
+ codec internal so future caching can use it without prematurely committing to
302
+ a public save/load API. See
303
+ [`ADR 0003`](docs/architecture/0003-canonical-arrow-parquet.md) for the format,
304
+ validation, and compatibility decisions.
305
+
306
+ ## Retries and errors
307
+
308
+ The default immutable `RetryPolicy` makes at most three total safe GET
309
+ attempts. It retries connection failures, timeouts, truncated payloads, and
310
+ HTTP `408`, `429`, `500`, `502`, `503`, and `504` with capped exponential
311
+ full-jitter backoff. Set `RetryPolicy(max_attempts=1)` to disable retries.
312
+
313
+ Valid numeric and HTTP-date `Retry-After` values are honored up to 90 seconds.
314
+ A longer requested delay fails immediately and remains available as
315
+ `retry_after_seconds` on the HTTP error. Redirects are disabled, TLS
316
+ verification remains enabled, every attempt has a finite timeout, and
317
+ cancellation is preserved.
318
+
319
+ ```python
320
+ from cfb_data import CFBDClient, RetryPolicy
321
+
322
+ policy = RetryPolicy(
323
+ max_attempts=4,
324
+ base_delay_seconds=0.25,
325
+ max_backoff_seconds=4.0,
326
+ max_retry_after_seconds=20.0,
327
+ )
328
+
329
+ async with CFBDClient(retry_policy=policy) as client:
330
+ scoreboard = await client.games.scoreboard()
331
+ ```
332
+
333
+ All library exceptions derive from `CFBDError`. Public subclasses distinguish
334
+ configuration, optional dependencies, client state, request validation,
335
+ timeouts and transport, HTTP/authentication/authorization/rate-limit/server
336
+ responses, response decoding and validation, and DataFrame conversion. Error
337
+ text and debug retry events include only safe endpoint/status/attempt metadata,
338
+ never credentials, query parameters, response payloads, or secrets.
339
+
340
+ ## Migrating from 0.1.x
341
+
342
+ The inherited raw, validation, and pandas client families were removed rather
343
+ than retained as compatibility wrappers.
344
+
345
+ Raw client calls:
346
+
347
+ ```python
348
+ # Before: CFBDGamesAPI(...).make_request("/games", {"year": 2024})
349
+ async with CFBDClient() as client:
350
+ games = await client.games.list(year=2024)
351
+ ```
352
+
353
+ Validated-model client calls:
354
+
355
+ ```python
356
+ # Before: CFBDGamesValidationAPI(...).make_request("/calendar", {"year": 2024})
357
+ async with CFBDClient() as client:
358
+ calendar_frame = await client.games.calendar(year=2024)
359
+ ```
360
+
361
+ pandas client calls:
362
+
363
+ ```python
364
+ # Before: CFBDDrivesPandasAPI(...).make_request("/drives", {"year": 2024})
365
+ async with CFBDClient() as client:
366
+ drives_frame = await client.drives.list(year=2024)
367
+ ```
368
+
369
+ There is no public generic path router or `make_request(path, params)` method.
370
+ Use the typed namespace method and either its request model or keyword filters.
371
+
372
+ ## Datasets and workflows
373
+
374
+ Version 0.4.1 does not expose `client.datasets` or `client.workflows`. The
375
+ accepted architecture reserves two higher layers:
376
+
377
+ - datasets compose validated endpoint results and validated subdatasets
378
+ through joins into one validated tabular row model, converting only the
379
+ final table;
380
+ - workflows orchestrate endpoints, datasets, and broader control flow and may
381
+ return multiple artifacts.
382
+
383
+ See
384
+ [`docs/architecture/0001-validated-models-before-dataframes.md`](docs/architecture/0001-validated-models-before-dataframes.md)
385
+ for the decision and extension boundaries.
386
+
387
+ ## Development
388
+
389
+ ```sh
390
+ git clone https://github.com/ryanpaulanderson/cfb-data.git
391
+ cd cfb-data
392
+ make install
393
+ make hooks
394
+ make format
395
+ make docs
396
+ make check
397
+ ```
398
+
399
+ `make install` creates `.venv` and installs `.[dev,polars]`, giving
400
+ contributors the complete Arrow/Parquet and two-backend test contract.
401
+ `make check` runs Ruff, strict mypy, a warning-free Sphinx build, and pytest
402
+ under the same contract as CI. `make docs` writes the local HTML site to
403
+ `docs/_build/html`. Package metadata and all dependency groups live only in
404
+ `pyproject.toml`.
405
+
406
+ See [`CONTRIBUTING.md`](CONTRIBUTING.md) and
407
+ [`docs/project-status.md`](docs/project-status.md) for repository and release
408
+ status.
409
+
410
+ ## License
411
+
412
+ This project is licensed under the MIT License. See [`LICENSE`](LICENSE).
413
+
414
+ This project is not affiliated with CollegeFootballData.com.