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.
- cfb_data/__init__.py +234 -0
- cfb_data/_dataframes.py +279 -0
- cfb_data/_executor.py +129 -0
- cfb_data/_parquet.py +197 -0
- cfb_data/_request_rules.py +41 -0
- cfb_data/_requests.py +36 -0
- cfb_data/_tabular.py +676 -0
- cfb_data/_transport.py +452 -0
- cfb_data/adjusted_metrics/__init__.py +29 -0
- cfb_data/adjusted_metrics/models/__init__.py +1 -0
- cfb_data/adjusted_metrics/models/pydantic/__init__.py +29 -0
- cfb_data/adjusted_metrics/models/pydantic/requests.py +45 -0
- cfb_data/adjusted_metrics/models/pydantic/responses.py +88 -0
- cfb_data/adjusted_metrics/resource.py +220 -0
- cfb_data/base/__init__.py +6 -0
- cfb_data/base/types.py +112 -0
- cfb_data/betting/__init__.py +15 -0
- cfb_data/betting/models/__init__.py +1 -0
- cfb_data/betting/models/pydantic/__init__.py +6 -0
- cfb_data/betting/models/pydantic/requests.py +35 -0
- cfb_data/betting/models/pydantic/responses.py +62 -0
- cfb_data/betting/resource.py +91 -0
- cfb_data/client.py +305 -0
- cfb_data/coaches/__init__.py +57 -0
- cfb_data/coaches/models/__init__.py +1 -0
- cfb_data/coaches/models/pydantic/__init__.py +57 -0
- cfb_data/coaches/models/pydantic/requests.py +75 -0
- cfb_data/coaches/models/pydantic/responses.py +261 -0
- cfb_data/coaches/resource.py +216 -0
- cfb_data/conferences/__init__.py +23 -0
- cfb_data/conferences/models/__init__.py +1 -0
- cfb_data/conferences/models/pydantic/__init__.py +23 -0
- cfb_data/conferences/models/pydantic/requests.py +69 -0
- cfb_data/conferences/models/pydantic/responses.py +64 -0
- cfb_data/conferences/resource.py +175 -0
- cfb_data/draft/__init__.py +19 -0
- cfb_data/draft/models/__init__.py +1 -0
- cfb_data/draft/models/pydantic/__init__.py +12 -0
- cfb_data/draft/models/pydantic/requests.py +18 -0
- cfb_data/draft/models/pydantic/responses.py +65 -0
- cfb_data/draft/resource.py +132 -0
- cfb_data/drives/__init__.py +13 -0
- cfb_data/drives/models/__init__.py +1 -0
- cfb_data/drives/models/pydantic/__init__.py +17 -0
- cfb_data/drives/models/pydantic/requests.py +42 -0
- cfb_data/drives/models/pydantic/responses.py +46 -0
- cfb_data/drives/resource.py +94 -0
- cfb_data/enums.py +93 -0
- cfb_data/errors.py +234 -0
- cfb_data/games/__init__.py +42 -0
- cfb_data/games/models/__init__.py +1 -0
- cfb_data/games/models/pydantic/__init__.py +106 -0
- cfb_data/games/models/pydantic/requests.py +266 -0
- cfb_data/games/models/pydantic/responses.py +495 -0
- cfb_data/games/resource.py +486 -0
- cfb_data/info/__init__.py +25 -0
- cfb_data/info/models/__init__.py +1 -0
- cfb_data/info/models/pydantic/__init__.py +23 -0
- cfb_data/info/models/pydantic/requests.py +18 -0
- cfb_data/info/models/pydantic/responses.py +103 -0
- cfb_data/info/resource.py +88 -0
- cfb_data/metrics/__init__.py +53 -0
- cfb_data/metrics/models/__init__.py +1 -0
- cfb_data/metrics/models/pydantic/__init__.py +49 -0
- cfb_data/metrics/models/pydantic/requests.py +121 -0
- cfb_data/metrics/models/pydantic/responses.py +182 -0
- cfb_data/metrics/resource.py +371 -0
- cfb_data/players/__init__.py +44 -0
- cfb_data/players/models/__init__.py +1 -0
- cfb_data/players/models/pydantic/__init__.py +41 -0
- cfb_data/players/models/pydantic/requests.py +70 -0
- cfb_data/players/models/pydantic/responses.py +171 -0
- cfb_data/players/resource.py +258 -0
- cfb_data/playoffs/__init__.py +41 -0
- cfb_data/playoffs/models/__init__.py +1 -0
- cfb_data/playoffs/models/pydantic/__init__.py +37 -0
- cfb_data/playoffs/models/pydantic/requests.py +30 -0
- cfb_data/playoffs/models/pydantic/responses.py +173 -0
- cfb_data/playoffs/resource.py +149 -0
- cfb_data/plays/__init__.py +43 -0
- cfb_data/plays/models/__init__.py +1 -0
- cfb_data/plays/models/pydantic/__init__.py +35 -0
- cfb_data/plays/models/pydantic/requests.py +85 -0
- cfb_data/plays/models/pydantic/responses.py +231 -0
- cfb_data/plays/resource.py +249 -0
- cfb_data/py.typed +0 -0
- cfb_data/rankings/__init__.py +16 -0
- cfb_data/rankings/models/__init__.py +1 -0
- cfb_data/rankings/models/pydantic/__init__.py +6 -0
- cfb_data/rankings/models/pydantic/requests.py +36 -0
- cfb_data/rankings/models/pydantic/responses.py +42 -0
- cfb_data/rankings/resource.py +89 -0
- cfb_data/ratings/__init__.py +59 -0
- cfb_data/ratings/models/__init__.py +1 -0
- cfb_data/ratings/models/pydantic/__init__.py +55 -0
- cfb_data/ratings/models/pydantic/requests.py +87 -0
- cfb_data/ratings/models/pydantic/responses.py +215 -0
- cfb_data/ratings/resource.py +342 -0
- cfb_data/recruiting/__init__.py +26 -0
- cfb_data/recruiting/models/__init__.py +1 -0
- cfb_data/recruiting/models/pydantic/__init__.py +23 -0
- cfb_data/recruiting/models/pydantic/requests.py +69 -0
- cfb_data/recruiting/models/pydantic/responses.py +70 -0
- cfb_data/recruiting/resource.py +180 -0
- cfb_data/retry.py +49 -0
- cfb_data/stats/__init__.py +69 -0
- cfb_data/stats/models/__init__.py +1 -0
- cfb_data/stats/models/pydantic/__init__.py +65 -0
- cfb_data/stats/models/pydantic/requests.py +167 -0
- cfb_data/stats/models/pydantic/responses.py +291 -0
- cfb_data/stats/resource.py +400 -0
- cfb_data/teams/__init__.py +40 -0
- cfb_data/teams/models/__init__.py +1 -0
- cfb_data/teams/models/pydantic/__init__.py +26 -0
- cfb_data/teams/models/pydantic/requests.py +96 -0
- cfb_data/teams/models/pydantic/responses.py +116 -0
- cfb_data/teams/resource.py +270 -0
- cfb_data/venues/__init__.py +6 -0
- cfb_data/venues/models/__init__.py +1 -0
- cfb_data/venues/models/pydantic/__init__.py +5 -0
- cfb_data/venues/models/pydantic/responses.py +24 -0
- cfb_data/venues/resource.py +51 -0
- cfb_data-0.4.1.dist-info/METADATA +414 -0
- cfb_data-0.4.1.dist-info/RECORD +127 -0
- cfb_data-0.4.1.dist-info/WHEEL +5 -0
- cfb_data-0.4.1.dist-info/licenses/LICENSE +21 -0
- cfb_data-0.4.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""Expose typed Ratings 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, 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.ratings.models.pydantic.requests import (
|
|
15
|
+
ConferenceSPRatingsRequest,
|
|
16
|
+
CoreRatingsRequest,
|
|
17
|
+
EloRatingsRequest,
|
|
18
|
+
ExpandedSRSRatingsRequest,
|
|
19
|
+
FPIRatingsRequest,
|
|
20
|
+
SPRatingsRequest,
|
|
21
|
+
SRSRatingsRequest,
|
|
22
|
+
)
|
|
23
|
+
from cfb_data.ratings.models.pydantic.responses import (
|
|
24
|
+
ConferenceSP,
|
|
25
|
+
ExpandedTeamSRS,
|
|
26
|
+
TeamCoreRating,
|
|
27
|
+
TeamElo,
|
|
28
|
+
TeamFPI,
|
|
29
|
+
TeamSP,
|
|
30
|
+
TeamSRS,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
_RequestT = TypeVar("_RequestT", bound=BaseModel)
|
|
34
|
+
_RowT = TypeVar("_RowT", bound=BaseModel)
|
|
35
|
+
type _ClassificationArgument = Classification | Literal["fbs", "fcs", "ii", "iii"]
|
|
36
|
+
type _SeasonTypeArgument = (
|
|
37
|
+
SeasonType
|
|
38
|
+
| Literal[
|
|
39
|
+
"regular",
|
|
40
|
+
"postseason",
|
|
41
|
+
"both",
|
|
42
|
+
"allstar",
|
|
43
|
+
"spring_regular",
|
|
44
|
+
"spring_postseason",
|
|
45
|
+
]
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
_CORE_ROWS = TypeAdapter(list[TeamCoreRating])
|
|
49
|
+
_SP_ROWS = TypeAdapter(list[TeamSP])
|
|
50
|
+
_CONFERENCE_SP_ROWS = TypeAdapter(list[ConferenceSP])
|
|
51
|
+
_SRS_ROWS = TypeAdapter(list[TeamSRS])
|
|
52
|
+
_EXPANDED_SRS_ROWS = TypeAdapter(list[ExpandedTeamSRS])
|
|
53
|
+
_ELO_ROWS = TypeAdapter(list[TeamElo])
|
|
54
|
+
_FPI_ROWS = TypeAdapter(list[TeamFPI])
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class RatingsResource[FrameT]:
|
|
58
|
+
"""Provide validated Ratings endpoints with selected frame results."""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
executor: _EndpointExecutor,
|
|
63
|
+
dataframe_adapter: _DataFrameAdapter[FrameT],
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Bind the namespace to shared execution and presentation services."""
|
|
66
|
+
self._executor = executor
|
|
67
|
+
self._dataframe_adapter = dataframe_adapter
|
|
68
|
+
|
|
69
|
+
@overload
|
|
70
|
+
async def core(self, request: CoreRatingsRequest, /) -> FrameT: ...
|
|
71
|
+
|
|
72
|
+
@overload
|
|
73
|
+
async def core(
|
|
74
|
+
self,
|
|
75
|
+
request: None = None,
|
|
76
|
+
/,
|
|
77
|
+
*,
|
|
78
|
+
year: int | None = None,
|
|
79
|
+
team: str | None = None,
|
|
80
|
+
conference: str | None = None,
|
|
81
|
+
) -> FrameT: ...
|
|
82
|
+
|
|
83
|
+
async def core(
|
|
84
|
+
self, request: CoreRatingsRequest | None = None, /, **filters: object
|
|
85
|
+
) -> FrameT:
|
|
86
|
+
"""Return Context and Opponent-Relative Efficiency ratings.
|
|
87
|
+
|
|
88
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
89
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
90
|
+
:return: Eager frame containing validated CORE rows.
|
|
91
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
92
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
93
|
+
"""
|
|
94
|
+
return await self._fetch_many(
|
|
95
|
+
endpoint="/ratings/core",
|
|
96
|
+
request_type=CoreRatingsRequest,
|
|
97
|
+
request=request,
|
|
98
|
+
filters=filters,
|
|
99
|
+
response_adapter=_CORE_ROWS,
|
|
100
|
+
row_model=TeamCoreRating,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
@overload
|
|
104
|
+
async def sp(self, request: SPRatingsRequest, /) -> FrameT: ...
|
|
105
|
+
|
|
106
|
+
@overload
|
|
107
|
+
async def sp(
|
|
108
|
+
self,
|
|
109
|
+
request: None = None,
|
|
110
|
+
/,
|
|
111
|
+
*,
|
|
112
|
+
year: int | None = None,
|
|
113
|
+
team: str | None = None,
|
|
114
|
+
) -> FrameT: ...
|
|
115
|
+
|
|
116
|
+
async def sp(
|
|
117
|
+
self, request: SPRatingsRequest | None = None, /, **filters: object
|
|
118
|
+
) -> FrameT:
|
|
119
|
+
"""Return team SP+ ratings by season.
|
|
120
|
+
|
|
121
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
122
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
123
|
+
:return: Eager frame containing validated team SP+ rows.
|
|
124
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
125
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
126
|
+
"""
|
|
127
|
+
return await self._fetch_many(
|
|
128
|
+
endpoint="/ratings/sp",
|
|
129
|
+
request_type=SPRatingsRequest,
|
|
130
|
+
request=request,
|
|
131
|
+
filters=filters,
|
|
132
|
+
response_adapter=_SP_ROWS,
|
|
133
|
+
row_model=TeamSP,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
@overload
|
|
137
|
+
async def conference_sp(self, request: ConferenceSPRatingsRequest, /) -> FrameT: ...
|
|
138
|
+
|
|
139
|
+
@overload
|
|
140
|
+
async def conference_sp(
|
|
141
|
+
self,
|
|
142
|
+
request: None = None,
|
|
143
|
+
/,
|
|
144
|
+
*,
|
|
145
|
+
year: int | None = None,
|
|
146
|
+
conference: str | None = None,
|
|
147
|
+
classification: _ClassificationArgument | None = None,
|
|
148
|
+
) -> FrameT: ...
|
|
149
|
+
|
|
150
|
+
async def conference_sp(
|
|
151
|
+
self,
|
|
152
|
+
request: ConferenceSPRatingsRequest | None = None,
|
|
153
|
+
/,
|
|
154
|
+
**filters: object,
|
|
155
|
+
) -> FrameT:
|
|
156
|
+
"""Return conference-level SP+ ratings by season.
|
|
157
|
+
|
|
158
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
159
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
160
|
+
:return: Eager frame containing validated conference SP+ rows.
|
|
161
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
162
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
163
|
+
"""
|
|
164
|
+
return await self._fetch_many(
|
|
165
|
+
endpoint="/ratings/sp/conferences",
|
|
166
|
+
request_type=ConferenceSPRatingsRequest,
|
|
167
|
+
request=request,
|
|
168
|
+
filters=filters,
|
|
169
|
+
response_adapter=_CONFERENCE_SP_ROWS,
|
|
170
|
+
row_model=ConferenceSP,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
@overload
|
|
174
|
+
async def srs(self, request: SRSRatingsRequest, /) -> FrameT: ...
|
|
175
|
+
|
|
176
|
+
@overload
|
|
177
|
+
async def srs(
|
|
178
|
+
self,
|
|
179
|
+
request: None = None,
|
|
180
|
+
/,
|
|
181
|
+
*,
|
|
182
|
+
year: int | None = None,
|
|
183
|
+
team: str | None = None,
|
|
184
|
+
conference: str | None = None,
|
|
185
|
+
) -> FrameT: ...
|
|
186
|
+
|
|
187
|
+
async def srs(
|
|
188
|
+
self, request: SRSRatingsRequest | None = None, /, **filters: object
|
|
189
|
+
) -> FrameT:
|
|
190
|
+
"""Return Simple Rating System ratings by team and season.
|
|
191
|
+
|
|
192
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
193
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
194
|
+
:return: Eager frame containing validated SRS rows.
|
|
195
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
196
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
197
|
+
"""
|
|
198
|
+
return await self._fetch_many(
|
|
199
|
+
endpoint="/ratings/srs",
|
|
200
|
+
request_type=SRSRatingsRequest,
|
|
201
|
+
request=request,
|
|
202
|
+
filters=filters,
|
|
203
|
+
response_adapter=_SRS_ROWS,
|
|
204
|
+
row_model=TeamSRS,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
@overload
|
|
208
|
+
async def expanded_srs(self, request: ExpandedSRSRatingsRequest, /) -> FrameT: ...
|
|
209
|
+
|
|
210
|
+
@overload
|
|
211
|
+
async def expanded_srs(
|
|
212
|
+
self,
|
|
213
|
+
request: None = None,
|
|
214
|
+
/,
|
|
215
|
+
*,
|
|
216
|
+
year: int | None = None,
|
|
217
|
+
team: str | None = None,
|
|
218
|
+
conference: str | None = None,
|
|
219
|
+
classification: _ClassificationArgument | None = None,
|
|
220
|
+
) -> FrameT: ...
|
|
221
|
+
|
|
222
|
+
async def expanded_srs(
|
|
223
|
+
self,
|
|
224
|
+
request: ExpandedSRSRatingsRequest | None = None,
|
|
225
|
+
/,
|
|
226
|
+
**filters: object,
|
|
227
|
+
) -> FrameT:
|
|
228
|
+
"""Return expanded SRS ratings including lower classifications.
|
|
229
|
+
|
|
230
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
231
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
232
|
+
:return: Eager frame containing validated expanded SRS rows.
|
|
233
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
234
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
235
|
+
"""
|
|
236
|
+
return await self._fetch_many(
|
|
237
|
+
endpoint="/ratings/srs/expanded",
|
|
238
|
+
request_type=ExpandedSRSRatingsRequest,
|
|
239
|
+
request=request,
|
|
240
|
+
filters=filters,
|
|
241
|
+
response_adapter=_EXPANDED_SRS_ROWS,
|
|
242
|
+
row_model=ExpandedTeamSRS,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
@overload
|
|
246
|
+
async def elo(self, request: EloRatingsRequest, /) -> FrameT: ...
|
|
247
|
+
|
|
248
|
+
@overload
|
|
249
|
+
async def elo(
|
|
250
|
+
self,
|
|
251
|
+
request: None = None,
|
|
252
|
+
/,
|
|
253
|
+
*,
|
|
254
|
+
year: int | None = None,
|
|
255
|
+
week: int | None = None,
|
|
256
|
+
season_type: _SeasonTypeArgument | None = None,
|
|
257
|
+
team: str | None = None,
|
|
258
|
+
conference: str | None = None,
|
|
259
|
+
) -> FrameT: ...
|
|
260
|
+
|
|
261
|
+
async def elo(
|
|
262
|
+
self, request: EloRatingsRequest | None = None, /, **filters: object
|
|
263
|
+
) -> FrameT:
|
|
264
|
+
"""Return historical Elo ratings for the selected period.
|
|
265
|
+
|
|
266
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
267
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
268
|
+
:return: Eager frame containing validated Elo rows.
|
|
269
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
270
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
271
|
+
"""
|
|
272
|
+
return await self._fetch_many(
|
|
273
|
+
endpoint="/ratings/elo",
|
|
274
|
+
request_type=EloRatingsRequest,
|
|
275
|
+
request=request,
|
|
276
|
+
filters=filters,
|
|
277
|
+
response_adapter=_ELO_ROWS,
|
|
278
|
+
row_model=TeamElo,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
@overload
|
|
282
|
+
async def fpi(self, request: FPIRatingsRequest, /) -> FrameT: ...
|
|
283
|
+
|
|
284
|
+
@overload
|
|
285
|
+
async def fpi(
|
|
286
|
+
self,
|
|
287
|
+
request: None = None,
|
|
288
|
+
/,
|
|
289
|
+
*,
|
|
290
|
+
year: int | None = None,
|
|
291
|
+
team: str | None = None,
|
|
292
|
+
conference: str | None = None,
|
|
293
|
+
) -> FrameT: ...
|
|
294
|
+
|
|
295
|
+
async def fpi(
|
|
296
|
+
self, request: FPIRatingsRequest | None = None, /, **filters: object
|
|
297
|
+
) -> FrameT:
|
|
298
|
+
"""Return Football Power Index ratings by team and season.
|
|
299
|
+
|
|
300
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
301
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
302
|
+
:return: Eager frame containing validated FPI rows.
|
|
303
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
304
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
305
|
+
"""
|
|
306
|
+
return await self._fetch_many(
|
|
307
|
+
endpoint="/ratings/fpi",
|
|
308
|
+
request_type=FPIRatingsRequest,
|
|
309
|
+
request=request,
|
|
310
|
+
filters=filters,
|
|
311
|
+
response_adapter=_FPI_ROWS,
|
|
312
|
+
row_model=TeamFPI,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
async def _fetch_many(
|
|
316
|
+
self,
|
|
317
|
+
*,
|
|
318
|
+
endpoint: str,
|
|
319
|
+
request_type: type[_RequestT],
|
|
320
|
+
request: _RequestT | None,
|
|
321
|
+
filters: Mapping[str, object],
|
|
322
|
+
response_adapter: TypeAdapter[list[_RowT]],
|
|
323
|
+
row_model: type[_RowT],
|
|
324
|
+
) -> FrameT:
|
|
325
|
+
"""Resolve, validate, fetch, and tabularize one list endpoint."""
|
|
326
|
+
validated = _resolve_request(
|
|
327
|
+
endpoint=endpoint,
|
|
328
|
+
request_type=request_type,
|
|
329
|
+
request=request,
|
|
330
|
+
filters=filters,
|
|
331
|
+
)
|
|
332
|
+
rows = await self._executor.fetch_many(
|
|
333
|
+
endpoint=endpoint,
|
|
334
|
+
request=validated,
|
|
335
|
+
response_adapter=response_adapter,
|
|
336
|
+
)
|
|
337
|
+
return self._dataframe_adapter.from_models(
|
|
338
|
+
endpoint=endpoint, row_model=row_model, models=rows
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
__all__ = ["RatingsResource"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Export the supported Recruiting namespace and public contracts."""
|
|
2
|
+
|
|
3
|
+
from cfb_data.enums import RecruitClassification
|
|
4
|
+
|
|
5
|
+
from .models.pydantic import (
|
|
6
|
+
AggregatedTeamRecruiting,
|
|
7
|
+
Recruit,
|
|
8
|
+
RecruitHometown,
|
|
9
|
+
RecruitingGroupsRequest,
|
|
10
|
+
RecruitingPlayersRequest,
|
|
11
|
+
RecruitingTeamsRequest,
|
|
12
|
+
TeamRecruitingRanking,
|
|
13
|
+
)
|
|
14
|
+
from .resource import RecruitingResource
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AggregatedTeamRecruiting",
|
|
18
|
+
"Recruit",
|
|
19
|
+
"RecruitClassification",
|
|
20
|
+
"RecruitHometown",
|
|
21
|
+
"RecruitingGroupsRequest",
|
|
22
|
+
"RecruitingPlayersRequest",
|
|
23
|
+
"RecruitingResource",
|
|
24
|
+
"RecruitingTeamsRequest",
|
|
25
|
+
"TeamRecruitingRanking",
|
|
26
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Export Recruiting model families."""
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Export validated Recruiting request and response models."""
|
|
2
|
+
|
|
3
|
+
from .requests import (
|
|
4
|
+
RecruitingGroupsRequest,
|
|
5
|
+
RecruitingPlayersRequest,
|
|
6
|
+
RecruitingTeamsRequest,
|
|
7
|
+
)
|
|
8
|
+
from .responses import (
|
|
9
|
+
AggregatedTeamRecruiting,
|
|
10
|
+
Recruit,
|
|
11
|
+
RecruitHometown,
|
|
12
|
+
TeamRecruitingRanking,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"AggregatedTeamRecruiting",
|
|
17
|
+
"Recruit",
|
|
18
|
+
"RecruitHometown",
|
|
19
|
+
"RecruitingGroupsRequest",
|
|
20
|
+
"RecruitingPlayersRequest",
|
|
21
|
+
"RecruitingTeamsRequest",
|
|
22
|
+
"TeamRecruitingRanking",
|
|
23
|
+
]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Validate request parameters for implemented Recruiting endpoints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Self
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
8
|
+
|
|
9
|
+
from cfb_data.enums import RecruitClassification
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _RecruitingRequest(BaseModel):
|
|
13
|
+
"""Apply the closed-object contract shared by Recruiting requests."""
|
|
14
|
+
|
|
15
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RecruitingPlayersRequest(_RecruitingRequest):
|
|
19
|
+
"""Validate filters accepted by ``GET /recruiting/players``."""
|
|
20
|
+
|
|
21
|
+
year: int | None = Field(default=None, ge=1869)
|
|
22
|
+
team: str | None = Field(default=None, min_length=1)
|
|
23
|
+
position: str | None = Field(default=None, min_length=1)
|
|
24
|
+
state: str | None = Field(default=None, min_length=1)
|
|
25
|
+
classification: RecruitClassification | None = None
|
|
26
|
+
|
|
27
|
+
@model_validator(mode="after")
|
|
28
|
+
def validate_selectors(self) -> Self:
|
|
29
|
+
"""Require a recruiting year or committed team."""
|
|
30
|
+
if self.year is None and self.team is None:
|
|
31
|
+
raise ValueError("year is required when team is not specified")
|
|
32
|
+
return self
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class RecruitingTeamsRequest(_RecruitingRequest):
|
|
36
|
+
"""Validate filters accepted by ``GET /recruiting/teams``."""
|
|
37
|
+
|
|
38
|
+
year: int | None = Field(default=None, ge=1869)
|
|
39
|
+
team: str | None = Field(default=None, min_length=1)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class RecruitingGroupsRequest(_RecruitingRequest):
|
|
43
|
+
"""Validate filters accepted by ``GET /recruiting/groups``."""
|
|
44
|
+
|
|
45
|
+
team: str | None = Field(default=None, min_length=1)
|
|
46
|
+
conference: str | None = Field(default=None, min_length=1)
|
|
47
|
+
recruit_type: RecruitClassification | None = Field(
|
|
48
|
+
default=None, alias="recruitType"
|
|
49
|
+
)
|
|
50
|
+
start_year: int | None = Field(default=None, alias="startYear", ge=1869)
|
|
51
|
+
end_year: int | None = Field(default=None, alias="endYear", ge=1869)
|
|
52
|
+
|
|
53
|
+
@model_validator(mode="after")
|
|
54
|
+
def validate_year_range(self) -> Self:
|
|
55
|
+
"""Require the recruiting year range to be ascending."""
|
|
56
|
+
if (
|
|
57
|
+
self.start_year is not None
|
|
58
|
+
and self.end_year is not None
|
|
59
|
+
and self.start_year > self.end_year
|
|
60
|
+
):
|
|
61
|
+
raise ValueError("start_year must be less than or equal to end_year")
|
|
62
|
+
return self
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
__all__ = [
|
|
66
|
+
"RecruitingGroupsRequest",
|
|
67
|
+
"RecruitingPlayersRequest",
|
|
68
|
+
"RecruitingTeamsRequest",
|
|
69
|
+
]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Validate responses from implemented CFBD Recruiting endpoints."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
4
|
+
|
|
5
|
+
from cfb_data.enums import RecruitClassification
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _ResponseModel(BaseModel):
|
|
9
|
+
"""Apply the upstream closed-object contract to Recruiting responses."""
|
|
10
|
+
|
|
11
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RecruitHometown(_ResponseModel):
|
|
15
|
+
"""Represent normalized geographic details for a recruit's hometown."""
|
|
16
|
+
|
|
17
|
+
latitude: float | None
|
|
18
|
+
longitude: float | None
|
|
19
|
+
fips_code: str | None = Field(alias="fipsCode")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Recruit(_ResponseModel):
|
|
23
|
+
"""Represent one ranked recruiting prospect."""
|
|
24
|
+
|
|
25
|
+
id: str
|
|
26
|
+
athlete_id: str | None = Field(alias="athleteId")
|
|
27
|
+
recruit_type: RecruitClassification = Field(alias="recruitType")
|
|
28
|
+
year: int = Field(ge=1869)
|
|
29
|
+
ranking: int | None = Field(ge=1)
|
|
30
|
+
name: str
|
|
31
|
+
school: str | None
|
|
32
|
+
committed_to: str | None = Field(alias="committedTo")
|
|
33
|
+
position: str | None
|
|
34
|
+
height: float | None = Field(ge=0)
|
|
35
|
+
weight: int | None = Field(ge=0)
|
|
36
|
+
stars: int | None = Field(ge=0, le=5)
|
|
37
|
+
rating: float | None = Field(ge=0)
|
|
38
|
+
city: str | None
|
|
39
|
+
state_province: str | None = Field(alias="stateProvince")
|
|
40
|
+
country: str | None
|
|
41
|
+
hometown_info: RecruitHometown = Field(alias="hometownInfo")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TeamRecruitingRanking(_ResponseModel):
|
|
45
|
+
"""Represent one team's recruiting class ranking."""
|
|
46
|
+
|
|
47
|
+
year: int = Field(ge=1869)
|
|
48
|
+
rank: int = Field(ge=1)
|
|
49
|
+
team: str
|
|
50
|
+
points: float
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class AggregatedTeamRecruiting(_ResponseModel):
|
|
54
|
+
"""Represent recruiting quality aggregated by team and position group."""
|
|
55
|
+
|
|
56
|
+
team: str
|
|
57
|
+
conference: str
|
|
58
|
+
position_group: str | None = Field(alias="positionGroup")
|
|
59
|
+
average_rating: float = Field(alias="averageRating")
|
|
60
|
+
total_rating: float = Field(alias="totalRating")
|
|
61
|
+
commits: int = Field(ge=0)
|
|
62
|
+
average_stars: float = Field(alias="averageStars", ge=0, le=5)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
__all__ = [
|
|
66
|
+
"AggregatedTeamRecruiting",
|
|
67
|
+
"Recruit",
|
|
68
|
+
"RecruitHometown",
|
|
69
|
+
"TeamRecruitingRanking",
|
|
70
|
+
]
|