cfb-data 0.4.1__py3-none-any.whl

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 (127) hide show
  1. cfb_data/__init__.py +234 -0
  2. cfb_data/_dataframes.py +279 -0
  3. cfb_data/_executor.py +129 -0
  4. cfb_data/_parquet.py +197 -0
  5. cfb_data/_request_rules.py +41 -0
  6. cfb_data/_requests.py +36 -0
  7. cfb_data/_tabular.py +676 -0
  8. cfb_data/_transport.py +452 -0
  9. cfb_data/adjusted_metrics/__init__.py +29 -0
  10. cfb_data/adjusted_metrics/models/__init__.py +1 -0
  11. cfb_data/adjusted_metrics/models/pydantic/__init__.py +29 -0
  12. cfb_data/adjusted_metrics/models/pydantic/requests.py +45 -0
  13. cfb_data/adjusted_metrics/models/pydantic/responses.py +88 -0
  14. cfb_data/adjusted_metrics/resource.py +220 -0
  15. cfb_data/base/__init__.py +6 -0
  16. cfb_data/base/types.py +112 -0
  17. cfb_data/betting/__init__.py +15 -0
  18. cfb_data/betting/models/__init__.py +1 -0
  19. cfb_data/betting/models/pydantic/__init__.py +6 -0
  20. cfb_data/betting/models/pydantic/requests.py +35 -0
  21. cfb_data/betting/models/pydantic/responses.py +62 -0
  22. cfb_data/betting/resource.py +91 -0
  23. cfb_data/client.py +305 -0
  24. cfb_data/coaches/__init__.py +57 -0
  25. cfb_data/coaches/models/__init__.py +1 -0
  26. cfb_data/coaches/models/pydantic/__init__.py +57 -0
  27. cfb_data/coaches/models/pydantic/requests.py +75 -0
  28. cfb_data/coaches/models/pydantic/responses.py +261 -0
  29. cfb_data/coaches/resource.py +216 -0
  30. cfb_data/conferences/__init__.py +23 -0
  31. cfb_data/conferences/models/__init__.py +1 -0
  32. cfb_data/conferences/models/pydantic/__init__.py +23 -0
  33. cfb_data/conferences/models/pydantic/requests.py +69 -0
  34. cfb_data/conferences/models/pydantic/responses.py +64 -0
  35. cfb_data/conferences/resource.py +175 -0
  36. cfb_data/draft/__init__.py +19 -0
  37. cfb_data/draft/models/__init__.py +1 -0
  38. cfb_data/draft/models/pydantic/__init__.py +12 -0
  39. cfb_data/draft/models/pydantic/requests.py +18 -0
  40. cfb_data/draft/models/pydantic/responses.py +65 -0
  41. cfb_data/draft/resource.py +132 -0
  42. cfb_data/drives/__init__.py +13 -0
  43. cfb_data/drives/models/__init__.py +1 -0
  44. cfb_data/drives/models/pydantic/__init__.py +17 -0
  45. cfb_data/drives/models/pydantic/requests.py +42 -0
  46. cfb_data/drives/models/pydantic/responses.py +46 -0
  47. cfb_data/drives/resource.py +94 -0
  48. cfb_data/enums.py +93 -0
  49. cfb_data/errors.py +234 -0
  50. cfb_data/games/__init__.py +42 -0
  51. cfb_data/games/models/__init__.py +1 -0
  52. cfb_data/games/models/pydantic/__init__.py +106 -0
  53. cfb_data/games/models/pydantic/requests.py +266 -0
  54. cfb_data/games/models/pydantic/responses.py +495 -0
  55. cfb_data/games/resource.py +486 -0
  56. cfb_data/info/__init__.py +25 -0
  57. cfb_data/info/models/__init__.py +1 -0
  58. cfb_data/info/models/pydantic/__init__.py +23 -0
  59. cfb_data/info/models/pydantic/requests.py +18 -0
  60. cfb_data/info/models/pydantic/responses.py +103 -0
  61. cfb_data/info/resource.py +88 -0
  62. cfb_data/metrics/__init__.py +53 -0
  63. cfb_data/metrics/models/__init__.py +1 -0
  64. cfb_data/metrics/models/pydantic/__init__.py +49 -0
  65. cfb_data/metrics/models/pydantic/requests.py +121 -0
  66. cfb_data/metrics/models/pydantic/responses.py +182 -0
  67. cfb_data/metrics/resource.py +371 -0
  68. cfb_data/players/__init__.py +44 -0
  69. cfb_data/players/models/__init__.py +1 -0
  70. cfb_data/players/models/pydantic/__init__.py +41 -0
  71. cfb_data/players/models/pydantic/requests.py +70 -0
  72. cfb_data/players/models/pydantic/responses.py +171 -0
  73. cfb_data/players/resource.py +258 -0
  74. cfb_data/playoffs/__init__.py +41 -0
  75. cfb_data/playoffs/models/__init__.py +1 -0
  76. cfb_data/playoffs/models/pydantic/__init__.py +37 -0
  77. cfb_data/playoffs/models/pydantic/requests.py +30 -0
  78. cfb_data/playoffs/models/pydantic/responses.py +173 -0
  79. cfb_data/playoffs/resource.py +149 -0
  80. cfb_data/plays/__init__.py +43 -0
  81. cfb_data/plays/models/__init__.py +1 -0
  82. cfb_data/plays/models/pydantic/__init__.py +35 -0
  83. cfb_data/plays/models/pydantic/requests.py +85 -0
  84. cfb_data/plays/models/pydantic/responses.py +231 -0
  85. cfb_data/plays/resource.py +249 -0
  86. cfb_data/py.typed +0 -0
  87. cfb_data/rankings/__init__.py +16 -0
  88. cfb_data/rankings/models/__init__.py +1 -0
  89. cfb_data/rankings/models/pydantic/__init__.py +6 -0
  90. cfb_data/rankings/models/pydantic/requests.py +36 -0
  91. cfb_data/rankings/models/pydantic/responses.py +42 -0
  92. cfb_data/rankings/resource.py +89 -0
  93. cfb_data/ratings/__init__.py +59 -0
  94. cfb_data/ratings/models/__init__.py +1 -0
  95. cfb_data/ratings/models/pydantic/__init__.py +55 -0
  96. cfb_data/ratings/models/pydantic/requests.py +87 -0
  97. cfb_data/ratings/models/pydantic/responses.py +215 -0
  98. cfb_data/ratings/resource.py +342 -0
  99. cfb_data/recruiting/__init__.py +26 -0
  100. cfb_data/recruiting/models/__init__.py +1 -0
  101. cfb_data/recruiting/models/pydantic/__init__.py +23 -0
  102. cfb_data/recruiting/models/pydantic/requests.py +69 -0
  103. cfb_data/recruiting/models/pydantic/responses.py +70 -0
  104. cfb_data/recruiting/resource.py +180 -0
  105. cfb_data/retry.py +49 -0
  106. cfb_data/stats/__init__.py +69 -0
  107. cfb_data/stats/models/__init__.py +1 -0
  108. cfb_data/stats/models/pydantic/__init__.py +65 -0
  109. cfb_data/stats/models/pydantic/requests.py +167 -0
  110. cfb_data/stats/models/pydantic/responses.py +291 -0
  111. cfb_data/stats/resource.py +400 -0
  112. cfb_data/teams/__init__.py +40 -0
  113. cfb_data/teams/models/__init__.py +1 -0
  114. cfb_data/teams/models/pydantic/__init__.py +26 -0
  115. cfb_data/teams/models/pydantic/requests.py +96 -0
  116. cfb_data/teams/models/pydantic/responses.py +116 -0
  117. cfb_data/teams/resource.py +270 -0
  118. cfb_data/venues/__init__.py +6 -0
  119. cfb_data/venues/models/__init__.py +1 -0
  120. cfb_data/venues/models/pydantic/__init__.py +5 -0
  121. cfb_data/venues/models/pydantic/responses.py +24 -0
  122. cfb_data/venues/resource.py +51 -0
  123. cfb_data-0.4.1.dist-info/METADATA +414 -0
  124. cfb_data-0.4.1.dist-info/RECORD +127 -0
  125. cfb_data-0.4.1.dist-info/WHEEL +5 -0
  126. cfb_data-0.4.1.dist-info/licenses/LICENSE +21 -0
  127. cfb_data-0.4.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,291 @@
1
+ """Validate responses from implemented CFBD Stats endpoints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
6
+
7
+ from cfb_data.enums import SeasonType
8
+
9
+
10
+ class _ResponseModel(BaseModel):
11
+ """Apply the upstream closed-object contract to Stats responses."""
12
+
13
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
14
+
15
+
16
+ class PlayerStat(_ResponseModel):
17
+ """Represent one player statistic aggregated by season."""
18
+
19
+ season: int = Field(ge=1869)
20
+ player_id: str = Field(alias="playerId")
21
+ player: str
22
+ position: str
23
+ team: str
24
+ conference: str
25
+ category: str
26
+ stat_type: str = Field(alias="statType")
27
+ stat: str
28
+
29
+
30
+ class PlayerSuccessRateSplit(_ResponseModel):
31
+ """Represent passing or rushing success-rate totals."""
32
+
33
+ plays: int = Field(ge=0)
34
+ successes: int = Field(ge=0)
35
+ success_rate: float | None = Field(alias="successRate")
36
+
37
+
38
+ class PlayerSeasonSuccessRate(_ResponseModel):
39
+ """Represent one player's passing and rushing season success rates."""
40
+
41
+ season: int = Field(ge=1869)
42
+ id: str
43
+ name: str
44
+ position: str
45
+ team: str
46
+ conference: str
47
+ passing: PlayerSuccessRateSplit
48
+ rushing: PlayerSuccessRateSplit
49
+
50
+
51
+ class PlayerGameSuccessRate(_ResponseModel):
52
+ """Represent one player's passing and rushing success rates for a game."""
53
+
54
+ season: int = Field(ge=1869)
55
+ season_type: SeasonType = Field(alias="seasonType")
56
+ week: int = Field(ge=0)
57
+ game_id: int = Field(alias="gameId", gt=0)
58
+ id: str
59
+ name: str
60
+ position: str
61
+ team: str
62
+ conference: str
63
+ opponent: str
64
+ passing: PlayerSuccessRateSplit
65
+ rushing: PlayerSuccessRateSplit
66
+
67
+
68
+ class TeamStat(_ResponseModel):
69
+ """Represent one season-level team statistic without scalar coercion."""
70
+
71
+ season: int = Field(ge=1869)
72
+ team: str
73
+ conference: str
74
+ stat_name: str = Field(alias="statName")
75
+ stat_value: str | int | float = Field(alias="statValue")
76
+
77
+ @field_validator("stat_value", mode="before")
78
+ @classmethod
79
+ def reject_non_stat_scalars(cls, value: object) -> object:
80
+ """Accept only upstream string and numeric statistic values."""
81
+ if isinstance(value, bool) or not isinstance(value, str | int | float):
82
+ raise ValueError("statValue must be a string or number")
83
+ return value
84
+
85
+
86
+ class StatCategory(_ResponseModel):
87
+ """Represent one team-stat category for tabular presentation."""
88
+
89
+ category: str
90
+
91
+
92
+ class FieldPosition(_ResponseModel):
93
+ """Represent average starting field position and predicted points."""
94
+
95
+ average_start: float | None = Field(alias="averageStart")
96
+ average_predicted_points: float | None = Field(alias="averagePredictedPoints")
97
+
98
+
99
+ class Havoc(_ResponseModel):
100
+ """Represent aggregate havoc rates by position group."""
101
+
102
+ total: float | None
103
+ front_seven: float | None = Field(alias="frontSeven")
104
+ db: float | None
105
+
106
+
107
+ class SeasonDownStats(_ResponseModel):
108
+ """Represent advanced season metrics for one down classification."""
109
+
110
+ rate: float
111
+ ppa: float
112
+ success_rate: float = Field(alias="successRate")
113
+ explosiveness: float | None
114
+
115
+
116
+ class SeasonPassingDownStats(SeasonDownStats):
117
+ """Represent defensive passing-down season metrics."""
118
+
119
+ total_ppa: float = Field(alias="totalPPA")
120
+
121
+
122
+ class SeasonPlayStats(_ResponseModel):
123
+ """Represent advanced season metrics for one play classification."""
124
+
125
+ rate: float
126
+ ppa: float
127
+ total_ppa: float = Field(alias="totalPPA")
128
+ success_rate: float = Field(alias="successRate")
129
+ explosiveness: float | None
130
+
131
+
132
+ class AdvancedSeasonOffense(_ResponseModel):
133
+ """Represent a team's advanced season offense metrics."""
134
+
135
+ plays: int = Field(ge=0)
136
+ drives: int = Field(ge=0)
137
+ ppa: float
138
+ total_ppa: float = Field(alias="totalPPA")
139
+ success_rate: float = Field(alias="successRate")
140
+ explosiveness: float | None
141
+ power_success: float | None = Field(alias="powerSuccess")
142
+ stuff_rate: float = Field(alias="stuffRate")
143
+ line_yards: float = Field(alias="lineYards")
144
+ line_yards_total: int = Field(alias="lineYardsTotal")
145
+ second_level_yards: float = Field(alias="secondLevelYards")
146
+ second_level_yards_total: int = Field(alias="secondLevelYardsTotal")
147
+ open_field_yards: float = Field(alias="openFieldYards")
148
+ open_field_yards_total: int = Field(alias="openFieldYardsTotal")
149
+ total_opportunities: int = Field(alias="totalOpportunies")
150
+ points_per_opportunity: float = Field(alias="pointsPerOpportunity")
151
+ field_position: FieldPosition = Field(alias="fieldPosition")
152
+ havoc: Havoc
153
+ standard_downs: SeasonDownStats = Field(alias="standardDowns")
154
+ passing_downs: SeasonDownStats = Field(alias="passingDowns")
155
+ rushing_plays: SeasonPlayStats = Field(alias="rushingPlays")
156
+ passing_plays: SeasonPlayStats = Field(alias="passingPlays")
157
+
158
+
159
+ class AdvancedSeasonDefense(AdvancedSeasonOffense):
160
+ """Represent a team's advanced season defense metrics."""
161
+
162
+ passing_downs: SeasonPassingDownStats = Field(alias="passingDowns")
163
+
164
+
165
+ class AdvancedSeasonStat(_ResponseModel):
166
+ """Represent advanced team metrics aggregated by season."""
167
+
168
+ season: int = Field(ge=1869)
169
+ team: str
170
+ conference: str
171
+ offense: AdvancedSeasonOffense
172
+ defense: AdvancedSeasonDefense
173
+
174
+
175
+ class GameDownStats(_ResponseModel):
176
+ """Represent advanced game metrics for one down classification."""
177
+
178
+ ppa: float
179
+ success_rate: float = Field(alias="successRate")
180
+ explosiveness: float | None
181
+
182
+
183
+ class GamePlayStats(_ResponseModel):
184
+ """Represent advanced game metrics for one play classification."""
185
+
186
+ ppa: float
187
+ total_ppa: float = Field(alias="totalPPA")
188
+ success_rate: float = Field(alias="successRate")
189
+ explosiveness: float | None
190
+
191
+
192
+ class _AdvancedGameUnit(_ResponseModel):
193
+ """Represent fields shared by advanced game offense and defense."""
194
+
195
+ plays: int = Field(ge=0)
196
+ drives: int = Field(ge=0)
197
+ ppa: float
198
+ total_ppa: float = Field(alias="totalPPA")
199
+ success_rate: float = Field(alias="successRate")
200
+ explosiveness: float | None
201
+ power_success: float | None = Field(alias="powerSuccess")
202
+ stuff_rate: float = Field(alias="stuffRate")
203
+ line_yards: float = Field(alias="lineYards")
204
+ line_yards_total: int = Field(alias="lineYardsTotal")
205
+ second_level_yards: float = Field(alias="secondLevelYards")
206
+ second_level_yards_total: int = Field(alias="secondLevelYardsTotal")
207
+ open_field_yards: float | None = Field(alias="openFieldYards")
208
+ open_field_yards_total: int | None = Field(alias="openFieldYardsTotal")
209
+ standard_downs: GameDownStats = Field(alias="standardDowns")
210
+ passing_downs: GameDownStats = Field(alias="passingDowns")
211
+ rushing_plays: GamePlayStats = Field(alias="rushingPlays")
212
+ passing_plays: GamePlayStats = Field(alias="passingPlays")
213
+
214
+
215
+ class AdvancedGameOffense(_AdvancedGameUnit):
216
+ """Represent a team's advanced offense metrics for one game."""
217
+
218
+ open_field_yards_total: int = Field(alias="openFieldYardsTotal")
219
+
220
+
221
+ class AdvancedGameDefense(_AdvancedGameUnit):
222
+ """Represent a team's advanced defense metrics for one game."""
223
+
224
+ explosiveness: float
225
+ open_field_yards: float = Field(alias="openFieldYards")
226
+ open_field_yards_total: int | None = Field(alias="openFieldYardsTotal")
227
+
228
+
229
+ class AdvancedGameStat(_ResponseModel):
230
+ """Represent advanced team metrics for one game."""
231
+
232
+ game_id: int = Field(alias="gameId", gt=0)
233
+ season: int = Field(ge=1869)
234
+ season_type: SeasonType = Field(alias="seasonType")
235
+ week: int = Field(ge=0)
236
+ team: str
237
+ opponent: str
238
+ offense: AdvancedGameOffense
239
+ defense: AdvancedGameDefense
240
+
241
+
242
+ class GameHavocUnit(_ResponseModel):
243
+ """Represent havoc event counts and rates for one side of a game."""
244
+
245
+ total_plays: float = Field(alias="totalPlays", ge=0)
246
+ total_havoc_events: float = Field(alias="totalHavocEvents", ge=0)
247
+ front_seven_havoc_events: float = Field(alias="frontSevenHavocEvents", ge=0)
248
+ db_havoc_events: float = Field(alias="dbHavocEvents", ge=0)
249
+ havoc_rate: float = Field(alias="havocRate")
250
+ front_seven_havoc_rate: float = Field(alias="frontSevenHavocRate")
251
+ db_havoc_rate: float = Field(alias="dbHavocRate")
252
+
253
+
254
+ class GameHavocStats(_ResponseModel):
255
+ """Represent team havoc statistics for one game."""
256
+
257
+ game_id: int = Field(alias="gameId", gt=0)
258
+ season: int = Field(ge=1869)
259
+ season_type: SeasonType = Field(alias="seasonType")
260
+ week: int = Field(ge=0)
261
+ team: str
262
+ conference: str | None
263
+ opponent: str
264
+ opponent_conference: str | None = Field(alias="opponentConference")
265
+ offense: GameHavocUnit
266
+ defense: GameHavocUnit
267
+
268
+
269
+ __all__ = [
270
+ "AdvancedGameDefense",
271
+ "AdvancedGameOffense",
272
+ "AdvancedGameStat",
273
+ "AdvancedSeasonDefense",
274
+ "AdvancedSeasonOffense",
275
+ "AdvancedSeasonStat",
276
+ "FieldPosition",
277
+ "GameDownStats",
278
+ "GameHavocStats",
279
+ "GameHavocUnit",
280
+ "GamePlayStats",
281
+ "Havoc",
282
+ "PlayerGameSuccessRate",
283
+ "PlayerSeasonSuccessRate",
284
+ "PlayerStat",
285
+ "PlayerSuccessRateSplit",
286
+ "SeasonDownStats",
287
+ "SeasonPassingDownStats",
288
+ "SeasonPlayStats",
289
+ "StatCategory",
290
+ "TeamStat",
291
+ ]
@@ -0,0 +1,400 @@
1
+ """Expose typed Stats endpoints through the primary client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from typing import Literal, TypeVar, overload
7
+
8
+ from pydantic import BaseModel, ConfigDict, TypeAdapter
9
+
10
+ from cfb_data._dataframes import _DataFrameAdapter
11
+ from cfb_data._executor import _EndpointExecutor
12
+ from cfb_data._requests import _resolve_request
13
+ from cfb_data.enums import Classification, SeasonType
14
+ from cfb_data.stats.models.pydantic.requests import (
15
+ AdvancedGameStatsRequest,
16
+ AdvancedSeasonStatsRequest,
17
+ GameHavocRequest,
18
+ PlayerGameSuccessRequest,
19
+ PlayerSeasonStatsRequest,
20
+ PlayerSeasonSuccessRequest,
21
+ TeamSeasonStatsRequest,
22
+ )
23
+ from cfb_data.stats.models.pydantic.responses import (
24
+ AdvancedGameStat,
25
+ AdvancedSeasonStat,
26
+ GameHavocStats,
27
+ PlayerGameSuccessRate,
28
+ PlayerSeasonSuccessRate,
29
+ PlayerStat,
30
+ StatCategory,
31
+ TeamStat,
32
+ )
33
+
34
+ _RequestT = TypeVar("_RequestT", bound=BaseModel)
35
+ _RowT = TypeVar("_RowT", bound=BaseModel)
36
+ type _ClassificationArgument = Classification | Literal["fbs", "fcs", "ii", "iii"]
37
+ type _SeasonTypeArgument = (
38
+ SeasonType
39
+ | Literal[
40
+ "regular",
41
+ "postseason",
42
+ "both",
43
+ "allstar",
44
+ "spring_regular",
45
+ "spring_postseason",
46
+ ]
47
+ )
48
+
49
+ _PLAYER_STAT_ROWS = TypeAdapter(list[PlayerStat])
50
+ _PLAYER_SEASON_SUCCESS_ROWS = TypeAdapter(list[PlayerSeasonSuccessRate])
51
+ _PLAYER_GAME_SUCCESS_ROWS = TypeAdapter(list[PlayerGameSuccessRate])
52
+ _TEAM_STAT_ROWS = TypeAdapter(list[TeamStat])
53
+ _CATEGORY_VALUES = TypeAdapter(list[str])
54
+ _ADVANCED_SEASON_ROWS = TypeAdapter(list[AdvancedSeasonStat])
55
+ _ADVANCED_GAME_ROWS = TypeAdapter(list[AdvancedGameStat])
56
+ _GAME_HAVOC_ROWS = TypeAdapter(list[GameHavocStats])
57
+
58
+
59
+ class _CategoriesRequest(BaseModel):
60
+ """Represent the empty filter set accepted by ``GET /stats/categories``."""
61
+
62
+ model_config = ConfigDict(extra="forbid")
63
+
64
+
65
+ _CATEGORIES_REQUEST = _CategoriesRequest()
66
+
67
+
68
+ class StatsResource[FrameT]:
69
+ """Provide validated Stats endpoints with backend-specific frame results."""
70
+
71
+ def __init__(
72
+ self,
73
+ executor: _EndpointExecutor,
74
+ dataframe_adapter: _DataFrameAdapter[FrameT],
75
+ ) -> None:
76
+ """Bind the namespace to shared execution and presentation services."""
77
+ self._executor = executor
78
+ self._dataframe_adapter = dataframe_adapter
79
+
80
+ @overload
81
+ async def player_season(self, request: PlayerSeasonStatsRequest, /) -> FrameT: ...
82
+
83
+ @overload
84
+ async def player_season(
85
+ self,
86
+ request: None = None,
87
+ /,
88
+ *,
89
+ year: int,
90
+ conference: str | None = None,
91
+ team: str | None = None,
92
+ start_week: int | None = None,
93
+ end_week: int | None = None,
94
+ season_type: _SeasonTypeArgument | None = None,
95
+ category: str | None = None,
96
+ ) -> FrameT: ...
97
+
98
+ async def player_season(
99
+ self, request: PlayerSeasonStatsRequest | None = None, /, **filters: object
100
+ ) -> FrameT:
101
+ """Return player statistics aggregated by season.
102
+
103
+ :param request: Validated request model, mutually exclusive with filters.
104
+ :param filters: Explicit snake-case endpoint filters.
105
+ :return: Eager frame containing validated ``PlayerStat`` rows.
106
+ :raises TypeError: If request styles are mixed or the model type is wrong.
107
+ :raises CFBDError: If request, transport, response, or conversion fails.
108
+ """
109
+ return await self._fetch_many(
110
+ endpoint="/stats/player/season",
111
+ request_type=PlayerSeasonStatsRequest,
112
+ request=request,
113
+ filters=filters,
114
+ response_adapter=_PLAYER_STAT_ROWS,
115
+ row_model=PlayerStat,
116
+ )
117
+
118
+ @overload
119
+ async def player_season_success(
120
+ self, request: PlayerSeasonSuccessRequest, /
121
+ ) -> FrameT: ...
122
+
123
+ @overload
124
+ async def player_season_success(
125
+ self,
126
+ request: None = None,
127
+ /,
128
+ *,
129
+ year: int | None = None,
130
+ conference: str | None = None,
131
+ team: str | None = None,
132
+ player_id: int | None = None,
133
+ season_type: _SeasonTypeArgument | None = None,
134
+ start_week: int | None = None,
135
+ end_week: int | None = None,
136
+ threshold: int | None = None,
137
+ exclude_garbage_time: bool | None = None,
138
+ ) -> FrameT: ...
139
+
140
+ async def player_season_success(
141
+ self, request: PlayerSeasonSuccessRequest | None = None, /, **filters: object
142
+ ) -> FrameT:
143
+ """Return player passing and rushing success rates by season.
144
+
145
+ :param request: Validated request model, mutually exclusive with filters.
146
+ :param filters: Explicit snake-case endpoint filters.
147
+ :return: Eager frame containing validated season success-rate rows.
148
+ :raises TypeError: If request styles are mixed or the model type is wrong.
149
+ :raises CFBDError: If request, transport, response, or conversion fails.
150
+ """
151
+ return await self._fetch_many(
152
+ endpoint="/stats/player/success",
153
+ request_type=PlayerSeasonSuccessRequest,
154
+ request=request,
155
+ filters=filters,
156
+ response_adapter=_PLAYER_SEASON_SUCCESS_ROWS,
157
+ row_model=PlayerSeasonSuccessRate,
158
+ )
159
+
160
+ @overload
161
+ async def player_game_success(
162
+ self, request: PlayerGameSuccessRequest, /
163
+ ) -> FrameT: ...
164
+
165
+ @overload
166
+ async def player_game_success(
167
+ self,
168
+ request: None = None,
169
+ /,
170
+ *,
171
+ year: int,
172
+ week: int | None = None,
173
+ season_type: _SeasonTypeArgument | None = None,
174
+ conference: str | None = None,
175
+ team: str | None = None,
176
+ player_id: int | None = None,
177
+ threshold: int | None = None,
178
+ exclude_garbage_time: bool | None = None,
179
+ ) -> FrameT: ...
180
+
181
+ async def player_game_success(
182
+ self, request: PlayerGameSuccessRequest | None = None, /, **filters: object
183
+ ) -> FrameT:
184
+ """Return player passing and rushing success rates by game.
185
+
186
+ :param request: Validated request model, mutually exclusive with filters.
187
+ :param filters: Explicit snake-case endpoint filters.
188
+ :return: Eager frame containing validated game success-rate rows.
189
+ :raises TypeError: If request styles are mixed or the model type is wrong.
190
+ :raises CFBDError: If request, transport, response, or conversion fails.
191
+ """
192
+ return await self._fetch_many(
193
+ endpoint="/stats/player/success/game",
194
+ request_type=PlayerGameSuccessRequest,
195
+ request=request,
196
+ filters=filters,
197
+ response_adapter=_PLAYER_GAME_SUCCESS_ROWS,
198
+ row_model=PlayerGameSuccessRate,
199
+ )
200
+
201
+ @overload
202
+ async def team_season(self, request: TeamSeasonStatsRequest, /) -> FrameT: ...
203
+
204
+ @overload
205
+ async def team_season(
206
+ self,
207
+ request: None = None,
208
+ /,
209
+ *,
210
+ year: int | None = None,
211
+ team: str | None = None,
212
+ conference: str | None = None,
213
+ start_week: int | None = None,
214
+ end_week: int | None = None,
215
+ classification: _ClassificationArgument | None = None,
216
+ ) -> FrameT: ...
217
+
218
+ async def team_season(
219
+ self, request: TeamSeasonStatsRequest | None = None, /, **filters: object
220
+ ) -> FrameT:
221
+ """Return team statistics aggregated by season.
222
+
223
+ ``stat_value`` preserves upstream strings and numbers in an object-typed
224
+ column for both supported DataFrame backends.
225
+
226
+ :param request: Validated request model, mutually exclusive with filters.
227
+ :param filters: Explicit snake-case endpoint filters.
228
+ :return: Eager frame containing validated ``TeamStat`` rows.
229
+ :raises TypeError: If request styles are mixed or the model type is wrong.
230
+ :raises CFBDError: If request, transport, response, or conversion fails.
231
+ """
232
+ return await self._fetch_many(
233
+ endpoint="/stats/season",
234
+ request_type=TeamSeasonStatsRequest,
235
+ request=request,
236
+ filters=filters,
237
+ response_adapter=_TEAM_STAT_ROWS,
238
+ row_model=TeamStat,
239
+ )
240
+
241
+ async def categories(self) -> FrameT:
242
+ """Return team-stat categories as a one-column selected frame.
243
+
244
+ :return: Eager frame with one ``category`` column in upstream order.
245
+ :raises CFBDError: If transport, response, or conversion fails.
246
+ """
247
+ values = await self._executor.fetch_values(
248
+ endpoint="/stats/categories",
249
+ request=_CATEGORIES_REQUEST,
250
+ response_adapter=_CATEGORY_VALUES,
251
+ )
252
+ rows = [StatCategory(category=value) for value in values]
253
+ return self._dataframe_adapter.from_models(
254
+ endpoint="/stats/categories",
255
+ row_model=StatCategory,
256
+ models=rows,
257
+ )
258
+
259
+ @overload
260
+ async def advanced_season(
261
+ self, request: AdvancedSeasonStatsRequest, /
262
+ ) -> FrameT: ...
263
+
264
+ @overload
265
+ async def advanced_season(
266
+ self,
267
+ request: None = None,
268
+ /,
269
+ *,
270
+ year: int | None = None,
271
+ team: str | None = None,
272
+ exclude_garbage_time: bool | None = None,
273
+ start_week: int | None = None,
274
+ end_week: int | None = None,
275
+ classification: _ClassificationArgument | None = None,
276
+ ) -> FrameT: ...
277
+
278
+ async def advanced_season(
279
+ self, request: AdvancedSeasonStatsRequest | None = None, /, **filters: object
280
+ ) -> FrameT:
281
+ """Return advanced team statistics aggregated by season.
282
+
283
+ :param request: Validated request model, mutually exclusive with filters.
284
+ :param filters: Explicit snake-case endpoint filters.
285
+ :return: Eager frame containing validated advanced season rows.
286
+ :raises TypeError: If request styles are mixed or the model type is wrong.
287
+ :raises CFBDError: If request, transport, response, or conversion fails.
288
+ """
289
+ return await self._fetch_many(
290
+ endpoint="/stats/season/advanced",
291
+ request_type=AdvancedSeasonStatsRequest,
292
+ request=request,
293
+ filters=filters,
294
+ response_adapter=_ADVANCED_SEASON_ROWS,
295
+ row_model=AdvancedSeasonStat,
296
+ )
297
+
298
+ @overload
299
+ async def advanced_game(self, request: AdvancedGameStatsRequest, /) -> FrameT: ...
300
+
301
+ @overload
302
+ async def advanced_game(
303
+ self,
304
+ request: None = None,
305
+ /,
306
+ *,
307
+ year: int | None = None,
308
+ team: str | None = None,
309
+ week: int | None = None,
310
+ opponent: str | None = None,
311
+ exclude_garbage_time: bool | None = None,
312
+ season_type: _SeasonTypeArgument | None = None,
313
+ ) -> FrameT: ...
314
+
315
+ async def advanced_game(
316
+ self, request: AdvancedGameStatsRequest | None = None, /, **filters: object
317
+ ) -> FrameT:
318
+ """Return advanced team statistics by game.
319
+
320
+ :param request: Validated request model, mutually exclusive with filters.
321
+ :param filters: Explicit snake-case endpoint filters.
322
+ :return: Eager frame containing validated advanced game rows.
323
+ :raises TypeError: If request styles are mixed or the model type is wrong.
324
+ :raises CFBDError: If request, transport, response, or conversion fails.
325
+ """
326
+ return await self._fetch_many(
327
+ endpoint="/stats/game/advanced",
328
+ request_type=AdvancedGameStatsRequest,
329
+ request=request,
330
+ filters=filters,
331
+ response_adapter=_ADVANCED_GAME_ROWS,
332
+ row_model=AdvancedGameStat,
333
+ )
334
+
335
+ @overload
336
+ async def game_havoc(self, request: GameHavocRequest, /) -> FrameT: ...
337
+
338
+ @overload
339
+ async def game_havoc(
340
+ self,
341
+ request: None = None,
342
+ /,
343
+ *,
344
+ year: int | None = None,
345
+ team: str | None = None,
346
+ week: int | None = None,
347
+ opponent: str | None = None,
348
+ season_type: _SeasonTypeArgument | None = None,
349
+ ) -> FrameT: ...
350
+
351
+ async def game_havoc(
352
+ self, request: GameHavocRequest | None = None, /, **filters: object
353
+ ) -> FrameT:
354
+ """Return team havoc statistics by game.
355
+
356
+ :param request: Validated request model, mutually exclusive with filters.
357
+ :param filters: Explicit snake-case endpoint filters.
358
+ :return: Eager frame containing validated game havoc rows.
359
+ :raises TypeError: If request styles are mixed or the model type is wrong.
360
+ :raises CFBDError: If request, transport, response, or conversion fails.
361
+ """
362
+ return await self._fetch_many(
363
+ endpoint="/stats/game/havoc",
364
+ request_type=GameHavocRequest,
365
+ request=request,
366
+ filters=filters,
367
+ response_adapter=_GAME_HAVOC_ROWS,
368
+ row_model=GameHavocStats,
369
+ )
370
+
371
+ async def _fetch_many(
372
+ self,
373
+ *,
374
+ endpoint: str,
375
+ request_type: type[_RequestT],
376
+ request: _RequestT | None,
377
+ filters: Mapping[str, object],
378
+ response_adapter: TypeAdapter[list[_RowT]],
379
+ row_model: type[_RowT],
380
+ ) -> FrameT:
381
+ """Validate, fetch, and convert one Stats list route."""
382
+ validated = _resolve_request(
383
+ endpoint=endpoint,
384
+ request_type=request_type,
385
+ request=request,
386
+ filters=filters,
387
+ )
388
+ rows = await self._executor.fetch_many(
389
+ endpoint=endpoint,
390
+ request=validated,
391
+ response_adapter=response_adapter,
392
+ )
393
+ return self._dataframe_adapter.from_models(
394
+ endpoint=endpoint,
395
+ row_model=row_model,
396
+ models=rows,
397
+ )
398
+
399
+
400
+ __all__ = ["StatsResource"]