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,261 @@
|
|
|
1
|
+
"""Validate responses from implemented CFBD Coaches endpoints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import UTC, datetime
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _ResponseModel(BaseModel):
|
|
12
|
+
"""Apply the upstream closed-object contract to Coaches responses."""
|
|
13
|
+
|
|
14
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
15
|
+
|
|
16
|
+
@field_validator("*", mode="after", check_fields=False)
|
|
17
|
+
@classmethod
|
|
18
|
+
def require_utc_datetimes(cls, value: object) -> object:
|
|
19
|
+
"""Require aware response timestamps and normalize them to UTC."""
|
|
20
|
+
if not isinstance(value, datetime):
|
|
21
|
+
return value
|
|
22
|
+
if value.tzinfo is None or value.utcoffset() is None:
|
|
23
|
+
raise ValueError("Response timestamps must be timezone-aware")
|
|
24
|
+
return value.astimezone(UTC)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CoachSeason(_ResponseModel):
|
|
28
|
+
"""Represent one season in the historical coach summary route."""
|
|
29
|
+
|
|
30
|
+
team_id: int = Field(alias="teamId", gt=0)
|
|
31
|
+
school: str
|
|
32
|
+
conference: str | None
|
|
33
|
+
year: int = Field(ge=1869)
|
|
34
|
+
games: int = Field(ge=0)
|
|
35
|
+
wins: int = Field(ge=0)
|
|
36
|
+
losses: int = Field(ge=0)
|
|
37
|
+
ties: int = Field(ge=0)
|
|
38
|
+
win_percentage: float | None = Field(alias="winPercentage", ge=0, le=1)
|
|
39
|
+
preseason_rank: int | None = Field(alias="preseasonRank", ge=1)
|
|
40
|
+
postseason_rank: int | None = Field(alias="postseasonRank", ge=1)
|
|
41
|
+
srs: float | None
|
|
42
|
+
sp_overall: float | None = Field(alias="spOverall")
|
|
43
|
+
sp_offense: float | None = Field(alias="spOffense")
|
|
44
|
+
sp_defense: float | None = Field(alias="spDefense")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Coach(_ResponseModel):
|
|
48
|
+
"""Represent one historical head coach and selected seasons."""
|
|
49
|
+
|
|
50
|
+
id: int = Field(gt=0)
|
|
51
|
+
first_name: str = Field(alias="firstName")
|
|
52
|
+
last_name: str = Field(alias="lastName")
|
|
53
|
+
hire_date: datetime | None = Field(alias="hireDate")
|
|
54
|
+
seasons: list[CoachSeason]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CoachRecord(_ResponseModel):
|
|
58
|
+
"""Represent an attributed coaching win-loss record."""
|
|
59
|
+
|
|
60
|
+
games: int = Field(ge=0)
|
|
61
|
+
wins: int = Field(ge=0)
|
|
62
|
+
losses: int = Field(ge=0)
|
|
63
|
+
ties: int = Field(ge=0)
|
|
64
|
+
win_percentage: float | None = Field(alias="winPercentage", ge=0, le=1)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class CoachReference(_ResponseModel):
|
|
68
|
+
"""Represent the canonical identity of a coach."""
|
|
69
|
+
|
|
70
|
+
id: int = Field(gt=0)
|
|
71
|
+
first_name: str = Field(alias="firstName")
|
|
72
|
+
last_name: str = Field(alias="lastName")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class CoachTeamReference(_ResponseModel):
|
|
76
|
+
"""Represent a team associated with a coach."""
|
|
77
|
+
|
|
78
|
+
id: int = Field(gt=0)
|
|
79
|
+
school: str
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class CoachSeasonTeamReference(CoachTeamReference):
|
|
83
|
+
"""Represent a team and conference within one coaching season."""
|
|
84
|
+
|
|
85
|
+
conference: str | None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class CoachCareer(CoachRecord):
|
|
89
|
+
"""Represent career totals in a coach profile."""
|
|
90
|
+
|
|
91
|
+
seasons: int = Field(ge=0)
|
|
92
|
+
teams: int = Field(ge=0)
|
|
93
|
+
first_year: int = Field(alias="firstYear", ge=1869)
|
|
94
|
+
last_year: int = Field(alias="lastYear", ge=1869)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class CoachAlmaMater(_ResponseModel):
|
|
98
|
+
"""Represent a coach's alma mater."""
|
|
99
|
+
|
|
100
|
+
id: int = Field(gt=0)
|
|
101
|
+
school: str
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class CoachProfile(_ResponseModel):
|
|
105
|
+
"""Represent canonical identity and career totals for one coach."""
|
|
106
|
+
|
|
107
|
+
id: int = Field(gt=0)
|
|
108
|
+
first_name: str = Field(alias="firstName")
|
|
109
|
+
last_name: str = Field(alias="lastName")
|
|
110
|
+
display_name: str | None = Field(alias="displayName")
|
|
111
|
+
current_team: CoachSeasonTeamReference | None = Field(alias="currentTeam")
|
|
112
|
+
career: CoachCareer
|
|
113
|
+
birth_date: str | None = Field(alias="birthDate")
|
|
114
|
+
alma_mater: CoachAlmaMater | None = Field(alias="almaMater")
|
|
115
|
+
graduation_year: int | None = Field(alias="graduationYear", ge=1869)
|
|
116
|
+
wikidata_id: str | None = Field(alias="wikidataId")
|
|
117
|
+
hall_of_fame_year: int | None = Field(alias="hallOfFameYear", ge=1869)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class CoachTenure(_ResponseModel):
|
|
121
|
+
"""Represent one continuous head-coaching tenure."""
|
|
122
|
+
|
|
123
|
+
id: int = Field(gt=0)
|
|
124
|
+
coach: CoachReference
|
|
125
|
+
team: CoachTeamReference
|
|
126
|
+
hire_date: str | None = Field(alias="hireDate")
|
|
127
|
+
start_year: int = Field(alias="startYear", ge=1869)
|
|
128
|
+
end_year: int | None = Field(alias="endYear", ge=1869)
|
|
129
|
+
effective_start: datetime | None = Field(alias="effectiveStart")
|
|
130
|
+
effective_end: datetime | None = Field(alias="effectiveEnd")
|
|
131
|
+
is_interim: bool = Field(alias="isInterim")
|
|
132
|
+
active: bool
|
|
133
|
+
seasons: int = Field(ge=0)
|
|
134
|
+
record: CoachRecord
|
|
135
|
+
attribution_complete: bool = Field(alias="attributionComplete")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class CoachYearOverYear(_ResponseModel):
|
|
139
|
+
"""Represent change from the preceding team season."""
|
|
140
|
+
|
|
141
|
+
wins: int | None
|
|
142
|
+
srs: float | None
|
|
143
|
+
sp_overall: float | None = Field(alias="spOverall")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class CoachRatingContext(_ResponseModel):
|
|
147
|
+
"""Represent team rating context for a coaching season."""
|
|
148
|
+
|
|
149
|
+
sp_special_teams: float | None = Field(alias="spSpecialTeams")
|
|
150
|
+
strength_of_schedule: float | None = Field(alias="strengthOfSchedule")
|
|
151
|
+
second_order_wins: float | None = Field(alias="secondOrderWins")
|
|
152
|
+
fpi: float | None
|
|
153
|
+
year_over_year: CoachYearOverYear = Field(alias="yearOverYear")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class CoachRecruitingContext(_ResponseModel):
|
|
157
|
+
"""Represent recruiting context for a coaching season."""
|
|
158
|
+
|
|
159
|
+
rank: int | None = Field(ge=1)
|
|
160
|
+
points: float | None
|
|
161
|
+
talent: float | None
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class CoachPollResume(_ResponseModel):
|
|
165
|
+
"""Represent poll résumé totals for a coaching season."""
|
|
166
|
+
|
|
167
|
+
preseason_rank: int | None = Field(alias="preseasonRank", ge=1)
|
|
168
|
+
postseason_rank: int | None = Field(alias="postseasonRank", ge=1)
|
|
169
|
+
best_rank: int | None = Field(alias="bestRank", ge=1)
|
|
170
|
+
weeks_ranked: int = Field(alias="weeksRanked", ge=0)
|
|
171
|
+
weeks_top_ten: int = Field(alias="weeksTopTen", ge=0)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class CoachRecordSplits(_ResponseModel):
|
|
175
|
+
"""Represent coaching records split by game context."""
|
|
176
|
+
|
|
177
|
+
conference: CoachRecord
|
|
178
|
+
postseason: CoachRecord
|
|
179
|
+
home: CoachRecord
|
|
180
|
+
away: CoachRecord
|
|
181
|
+
neutral: CoachRecord
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class CoachScoring(_ResponseModel):
|
|
185
|
+
"""Represent aggregate scoring under a coach for one season."""
|
|
186
|
+
|
|
187
|
+
points_for: int = Field(alias="pointsFor", ge=0)
|
|
188
|
+
points_against: int = Field(alias="pointsAgainst", ge=0)
|
|
189
|
+
average_point_differential: float | None = Field(alias="averagePointDifferential")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class CoachCfpOutcome(StrEnum):
|
|
193
|
+
"""Identify a coach's College Football Playoff season outcome."""
|
|
194
|
+
|
|
195
|
+
active = "active"
|
|
196
|
+
eliminated = "eliminated"
|
|
197
|
+
champion = "champion"
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class CoachCfpContext(_ResponseModel):
|
|
201
|
+
"""Represent College Football Playoff context for a coaching season."""
|
|
202
|
+
|
|
203
|
+
appeared: bool
|
|
204
|
+
seed: int | None = Field(ge=1)
|
|
205
|
+
outcome: CoachCfpOutcome | None
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class CoachDraftContext(_ResponseModel):
|
|
209
|
+
"""Represent draft outcomes following a coaching season."""
|
|
210
|
+
|
|
211
|
+
year: int = Field(ge=1936)
|
|
212
|
+
total_picks: int = Field(alias="totalPicks", ge=0)
|
|
213
|
+
first_round_picks: int = Field(alias="firstRoundPicks", ge=0)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class DetailedCoachSeason(CoachRecord):
|
|
217
|
+
"""Represent one coach-season with results and team context."""
|
|
218
|
+
|
|
219
|
+
coach: CoachReference
|
|
220
|
+
team: CoachSeasonTeamReference
|
|
221
|
+
year: int = Field(ge=1869)
|
|
222
|
+
preseason_rank: int | None = Field(alias="preseasonRank", ge=1)
|
|
223
|
+
postseason_rank: int | None = Field(alias="postseasonRank", ge=1)
|
|
224
|
+
srs: float | None
|
|
225
|
+
sp_overall: float | None = Field(alias="spOverall")
|
|
226
|
+
sp_offense: float | None = Field(alias="spOffense")
|
|
227
|
+
sp_defense: float | None = Field(alias="spDefense")
|
|
228
|
+
team_metrics: CoachRatingContext = Field(alias="teamMetrics")
|
|
229
|
+
recruiting: CoachRecruitingContext
|
|
230
|
+
poll_resume: CoachPollResume | None = Field(alias="pollResume")
|
|
231
|
+
attribution_complete: bool = Field(alias="attributionComplete")
|
|
232
|
+
record_splits: CoachRecordSplits | None = Field(alias="recordSplits")
|
|
233
|
+
scoring: CoachScoring | None
|
|
234
|
+
cfp: CoachCfpContext
|
|
235
|
+
draft_following_season: CoachDraftContext | None = Field(
|
|
236
|
+
alias="draftFollowingSeason"
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
__all__ = [
|
|
241
|
+
"Coach",
|
|
242
|
+
"CoachAlmaMater",
|
|
243
|
+
"CoachCareer",
|
|
244
|
+
"CoachCfpContext",
|
|
245
|
+
"CoachCfpOutcome",
|
|
246
|
+
"CoachDraftContext",
|
|
247
|
+
"CoachPollResume",
|
|
248
|
+
"CoachProfile",
|
|
249
|
+
"CoachRatingContext",
|
|
250
|
+
"CoachRecord",
|
|
251
|
+
"CoachRecordSplits",
|
|
252
|
+
"CoachRecruitingContext",
|
|
253
|
+
"CoachReference",
|
|
254
|
+
"CoachScoring",
|
|
255
|
+
"CoachSeason",
|
|
256
|
+
"CoachSeasonTeamReference",
|
|
257
|
+
"CoachTeamReference",
|
|
258
|
+
"CoachTenure",
|
|
259
|
+
"CoachYearOverYear",
|
|
260
|
+
"DetailedCoachSeason",
|
|
261
|
+
]
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Expose typed Coaches endpoints through the primary client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import builtins
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from typing import TypeVar, overload
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, TypeAdapter
|
|
10
|
+
|
|
11
|
+
from cfb_data._dataframes import _DataFrameAdapter
|
|
12
|
+
from cfb_data._executor import _EndpointExecutor
|
|
13
|
+
from cfb_data._requests import _resolve_request
|
|
14
|
+
from cfb_data.coaches.models.pydantic.requests import (
|
|
15
|
+
CoachesRequest,
|
|
16
|
+
CoachProfileRequest,
|
|
17
|
+
CoachSeasonsRequest,
|
|
18
|
+
CoachTenuresRequest,
|
|
19
|
+
)
|
|
20
|
+
from cfb_data.coaches.models.pydantic.responses import (
|
|
21
|
+
Coach,
|
|
22
|
+
CoachProfile,
|
|
23
|
+
CoachTenure,
|
|
24
|
+
DetailedCoachSeason,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
_RequestT = TypeVar("_RequestT", bound=BaseModel)
|
|
28
|
+
_RowT = TypeVar("_RowT", bound=BaseModel)
|
|
29
|
+
|
|
30
|
+
_COACH_ROWS = TypeAdapter(list[Coach])
|
|
31
|
+
_COACH_PROFILE = TypeAdapter(CoachProfile)
|
|
32
|
+
_COACH_SEASON_ROWS = TypeAdapter(list[DetailedCoachSeason])
|
|
33
|
+
_COACH_TENURE_ROWS = TypeAdapter(list[CoachTenure])
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CoachesResource[FrameT]:
|
|
37
|
+
"""Provide validated Coaches endpoints with selected frame results."""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
executor: _EndpointExecutor,
|
|
42
|
+
dataframe_adapter: _DataFrameAdapter[FrameT],
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Bind the namespace to shared execution and presentation services."""
|
|
45
|
+
self._executor = executor
|
|
46
|
+
self._dataframe_adapter = dataframe_adapter
|
|
47
|
+
|
|
48
|
+
@overload
|
|
49
|
+
async def list(self, request: CoachesRequest, /) -> FrameT: ...
|
|
50
|
+
|
|
51
|
+
@overload
|
|
52
|
+
async def list(
|
|
53
|
+
self,
|
|
54
|
+
request: None = None,
|
|
55
|
+
/,
|
|
56
|
+
*,
|
|
57
|
+
first_name: str | None = None,
|
|
58
|
+
last_name: str | None = None,
|
|
59
|
+
team: str | None = None,
|
|
60
|
+
year: int | None = None,
|
|
61
|
+
min_year: int | None = None,
|
|
62
|
+
max_year: int | None = None,
|
|
63
|
+
) -> FrameT: ...
|
|
64
|
+
|
|
65
|
+
async def list(
|
|
66
|
+
self, request: CoachesRequest | None = None, /, **filters: object
|
|
67
|
+
) -> FrameT:
|
|
68
|
+
"""Return historical head coaches with nested season summaries.
|
|
69
|
+
|
|
70
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
71
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
72
|
+
:return: Eager frame containing validated coach rows.
|
|
73
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
74
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
75
|
+
"""
|
|
76
|
+
return await self._fetch_many(
|
|
77
|
+
endpoint="/coaches",
|
|
78
|
+
request_type=CoachesRequest,
|
|
79
|
+
request=request,
|
|
80
|
+
filters=filters,
|
|
81
|
+
response_adapter=_COACH_ROWS,
|
|
82
|
+
row_model=Coach,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@overload
|
|
86
|
+
async def profile(self, request: CoachProfileRequest, /) -> FrameT: ...
|
|
87
|
+
|
|
88
|
+
@overload
|
|
89
|
+
async def profile(self, request: None = None, /, *, coach_id: int) -> FrameT: ...
|
|
90
|
+
|
|
91
|
+
async def profile(
|
|
92
|
+
self, request: CoachProfileRequest | None = None, /, **filters: object
|
|
93
|
+
) -> FrameT:
|
|
94
|
+
"""Return one canonical coach profile as a one-row frame.
|
|
95
|
+
|
|
96
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
97
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
98
|
+
:return: One-row eager frame containing the validated coach profile.
|
|
99
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
100
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
101
|
+
"""
|
|
102
|
+
endpoint = "/coaches/profile"
|
|
103
|
+
validated = _resolve_request(
|
|
104
|
+
endpoint=endpoint,
|
|
105
|
+
request_type=CoachProfileRequest,
|
|
106
|
+
request=request,
|
|
107
|
+
filters=filters,
|
|
108
|
+
)
|
|
109
|
+
profile = await self._executor.fetch_one(
|
|
110
|
+
endpoint=endpoint,
|
|
111
|
+
request=validated,
|
|
112
|
+
response_adapter=_COACH_PROFILE,
|
|
113
|
+
)
|
|
114
|
+
return self._dataframe_adapter.from_models(
|
|
115
|
+
endpoint=endpoint, row_model=CoachProfile, models=[profile]
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
@overload
|
|
119
|
+
async def seasons(self, request: CoachSeasonsRequest, /) -> FrameT: ...
|
|
120
|
+
|
|
121
|
+
@overload
|
|
122
|
+
async def seasons(
|
|
123
|
+
self,
|
|
124
|
+
request: None = None,
|
|
125
|
+
/,
|
|
126
|
+
*,
|
|
127
|
+
coach_id: int | None = None,
|
|
128
|
+
team: str | None = None,
|
|
129
|
+
year: int | None = None,
|
|
130
|
+
min_year: int | None = None,
|
|
131
|
+
max_year: int | None = None,
|
|
132
|
+
) -> FrameT: ...
|
|
133
|
+
|
|
134
|
+
async def seasons(
|
|
135
|
+
self, request: CoachSeasonsRequest | None = None, /, **filters: object
|
|
136
|
+
) -> FrameT:
|
|
137
|
+
"""Return coach-season records with attributed results and context.
|
|
138
|
+
|
|
139
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
140
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
141
|
+
:return: Eager frame containing validated detailed coach seasons.
|
|
142
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
143
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
144
|
+
"""
|
|
145
|
+
return await self._fetch_many(
|
|
146
|
+
endpoint="/coaches/seasons",
|
|
147
|
+
request_type=CoachSeasonsRequest,
|
|
148
|
+
request=request,
|
|
149
|
+
filters=filters,
|
|
150
|
+
response_adapter=_COACH_SEASON_ROWS,
|
|
151
|
+
row_model=DetailedCoachSeason,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
@overload
|
|
155
|
+
async def tenures(self, request: CoachTenuresRequest, /) -> FrameT: ...
|
|
156
|
+
|
|
157
|
+
@overload
|
|
158
|
+
async def tenures(
|
|
159
|
+
self,
|
|
160
|
+
request: None = None,
|
|
161
|
+
/,
|
|
162
|
+
*,
|
|
163
|
+
coach_id: int | None = None,
|
|
164
|
+
team: str | None = None,
|
|
165
|
+
year: int | None = None,
|
|
166
|
+
active: bool | None = None,
|
|
167
|
+
) -> FrameT: ...
|
|
168
|
+
|
|
169
|
+
async def tenures(
|
|
170
|
+
self, request: CoachTenuresRequest | None = None, /, **filters: object
|
|
171
|
+
) -> FrameT:
|
|
172
|
+
"""Return continuous head-coaching tenures and attributed records.
|
|
173
|
+
|
|
174
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
175
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
176
|
+
:return: Eager frame containing validated coaching tenure rows.
|
|
177
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
178
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
179
|
+
"""
|
|
180
|
+
return await self._fetch_many(
|
|
181
|
+
endpoint="/coaches/tenures",
|
|
182
|
+
request_type=CoachTenuresRequest,
|
|
183
|
+
request=request,
|
|
184
|
+
filters=filters,
|
|
185
|
+
response_adapter=_COACH_TENURE_ROWS,
|
|
186
|
+
row_model=CoachTenure,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
async def _fetch_many(
|
|
190
|
+
self,
|
|
191
|
+
*,
|
|
192
|
+
endpoint: str,
|
|
193
|
+
request_type: type[_RequestT],
|
|
194
|
+
request: _RequestT | None,
|
|
195
|
+
filters: Mapping[str, object],
|
|
196
|
+
response_adapter: TypeAdapter[builtins.list[_RowT]],
|
|
197
|
+
row_model: type[_RowT],
|
|
198
|
+
) -> FrameT:
|
|
199
|
+
"""Resolve, validate, fetch, and tabularize one list endpoint."""
|
|
200
|
+
validated = _resolve_request(
|
|
201
|
+
endpoint=endpoint,
|
|
202
|
+
request_type=request_type,
|
|
203
|
+
request=request,
|
|
204
|
+
filters=filters,
|
|
205
|
+
)
|
|
206
|
+
rows: builtins.list[_RowT] = await self._executor.fetch_many(
|
|
207
|
+
endpoint=endpoint,
|
|
208
|
+
request=validated,
|
|
209
|
+
response_adapter=response_adapter,
|
|
210
|
+
)
|
|
211
|
+
return self._dataframe_adapter.from_models(
|
|
212
|
+
endpoint=endpoint, row_model=row_model, models=rows
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
__all__ = ["CoachesResource"]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Export the supported Conferences namespace and public contracts."""
|
|
2
|
+
|
|
3
|
+
from .models.pydantic import (
|
|
4
|
+
Conference,
|
|
5
|
+
ConferenceAffiliationsRequest,
|
|
6
|
+
ConferenceChangesRequest,
|
|
7
|
+
ConferenceClassification,
|
|
8
|
+
ConferencesRequest,
|
|
9
|
+
TeamConferenceAffiliation,
|
|
10
|
+
TeamConferenceChange,
|
|
11
|
+
)
|
|
12
|
+
from .resource import ConferencesResource
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"Conference",
|
|
16
|
+
"ConferenceAffiliationsRequest",
|
|
17
|
+
"ConferenceChangesRequest",
|
|
18
|
+
"ConferenceClassification",
|
|
19
|
+
"ConferencesRequest",
|
|
20
|
+
"ConferencesResource",
|
|
21
|
+
"TeamConferenceAffiliation",
|
|
22
|
+
"TeamConferenceChange",
|
|
23
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Provide authoritative Pydantic models for Conferences endpoints."""
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Export Pydantic models for Conferences endpoints."""
|
|
2
|
+
|
|
3
|
+
from .requests import (
|
|
4
|
+
ConferenceAffiliationsRequest,
|
|
5
|
+
ConferenceChangesRequest,
|
|
6
|
+
ConferencesRequest,
|
|
7
|
+
)
|
|
8
|
+
from .responses import (
|
|
9
|
+
Conference,
|
|
10
|
+
ConferenceClassification,
|
|
11
|
+
TeamConferenceAffiliation,
|
|
12
|
+
TeamConferenceChange,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"Conference",
|
|
17
|
+
"ConferenceAffiliationsRequest",
|
|
18
|
+
"ConferenceChangesRequest",
|
|
19
|
+
"ConferenceClassification",
|
|
20
|
+
"ConferencesRequest",
|
|
21
|
+
"TeamConferenceAffiliation",
|
|
22
|
+
"TeamConferenceChange",
|
|
23
|
+
]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Validate request parameters for implemented Conferences 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 .responses import ConferenceClassification
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ConferencesRequest(BaseModel):
|
|
13
|
+
"""Validate filters accepted by ``GET /conferences``.
|
|
14
|
+
|
|
15
|
+
:param year: Season used to calculate membership.
|
|
16
|
+
:param classification: Conference division classification.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
20
|
+
|
|
21
|
+
year: int | None = Field(default=None, ge=1869)
|
|
22
|
+
classification: ConferenceClassification | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ConferenceChangesRequest(BaseModel):
|
|
26
|
+
"""Validate filters accepted by ``GET /conferences/changes``.
|
|
27
|
+
|
|
28
|
+
:param year: Season whose effective conference changes are returned.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
32
|
+
|
|
33
|
+
year: int = Field(ge=1869)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ConferenceAffiliationsRequest(BaseModel):
|
|
37
|
+
"""Validate filters accepted by ``GET /conferences/affiliations``.
|
|
38
|
+
|
|
39
|
+
:param team: Team school name or abbreviation.
|
|
40
|
+
:param conference: Conference name or abbreviation.
|
|
41
|
+
:param year: Exact active-affiliation season.
|
|
42
|
+
:param min_year: Earliest overlapping affiliation season.
|
|
43
|
+
:param max_year: Latest overlapping affiliation season.
|
|
44
|
+
:param classification: Conference division classification.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
48
|
+
|
|
49
|
+
team: str | None = None
|
|
50
|
+
conference: str | None = None
|
|
51
|
+
year: int | None = Field(default=None, ge=1869)
|
|
52
|
+
min_year: int | None = Field(default=None, ge=1869, alias="minYear")
|
|
53
|
+
max_year: int | None = Field(default=None, ge=1869, alias="maxYear")
|
|
54
|
+
classification: ConferenceClassification | None = None
|
|
55
|
+
|
|
56
|
+
@model_validator(mode="after")
|
|
57
|
+
def validate_year_filters(self) -> Self:
|
|
58
|
+
"""Reject mutually exclusive and reversed season filters."""
|
|
59
|
+
if self.year is not None and (
|
|
60
|
+
self.min_year is not None or self.max_year is not None
|
|
61
|
+
):
|
|
62
|
+
raise ValueError("year cannot be combined with min_year or max_year")
|
|
63
|
+
if (
|
|
64
|
+
self.min_year is not None
|
|
65
|
+
and self.max_year is not None
|
|
66
|
+
and self.min_year > self.max_year
|
|
67
|
+
):
|
|
68
|
+
raise ValueError("min_year cannot be greater than max_year")
|
|
69
|
+
return self
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Validate responses from implemented CFBD Conferences endpoints."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ConferenceClassification(StrEnum):
|
|
9
|
+
"""Identify an official conference division classification."""
|
|
10
|
+
|
|
11
|
+
fbs = "fbs"
|
|
12
|
+
fcs = "fcs"
|
|
13
|
+
ii = "ii"
|
|
14
|
+
ii_or_iii = "ii/iii"
|
|
15
|
+
iii = "iii"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _ResponseModel(BaseModel):
|
|
19
|
+
"""Apply the upstream closed-object contract to response models."""
|
|
20
|
+
|
|
21
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Conference(_ResponseModel):
|
|
25
|
+
"""Represent a conference and its membership count."""
|
|
26
|
+
|
|
27
|
+
id: int = Field(gt=0)
|
|
28
|
+
name: str
|
|
29
|
+
short_name: str | None = Field(alias="shortName")
|
|
30
|
+
abbreviation: str | None
|
|
31
|
+
classification: ConferenceClassification | None
|
|
32
|
+
member_count: int = Field(alias="memberCount", ge=0)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TeamConferenceAffiliation(_ResponseModel):
|
|
36
|
+
"""Represent one historical team-to-conference affiliation."""
|
|
37
|
+
|
|
38
|
+
team_id: int = Field(alias="teamId", gt=0)
|
|
39
|
+
team: str
|
|
40
|
+
conference_id: int = Field(alias="conferenceId", gt=0)
|
|
41
|
+
conference: str
|
|
42
|
+
conference_abbreviation: str | None = Field(alias="conferenceAbbreviation")
|
|
43
|
+
classification: ConferenceClassification | None
|
|
44
|
+
conference_division: str | None = Field(alias="conferenceDivision")
|
|
45
|
+
start_year: int = Field(alias="startYear", ge=1869)
|
|
46
|
+
end_year: int | None = Field(alias="endYear", ge=1869)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TeamConferenceChange(_ResponseModel):
|
|
50
|
+
"""Represent one team's conference change for a season."""
|
|
51
|
+
|
|
52
|
+
team_id: int = Field(alias="teamId", gt=0)
|
|
53
|
+
team: str
|
|
54
|
+
from_conference_id: int = Field(alias="fromConferenceId", gt=0)
|
|
55
|
+
from_conference: str = Field(alias="fromConference")
|
|
56
|
+
from_conference_abbreviation: str | None = Field(alias="fromConferenceAbbreviation")
|
|
57
|
+
from_classification: ConferenceClassification | None = Field(
|
|
58
|
+
alias="fromClassification"
|
|
59
|
+
)
|
|
60
|
+
to_conference_id: int = Field(alias="toConferenceId", gt=0)
|
|
61
|
+
to_conference: str = Field(alias="toConference")
|
|
62
|
+
to_conference_abbreviation: str | None = Field(alias="toConferenceAbbreviation")
|
|
63
|
+
to_classification: ConferenceClassification | None = Field(alias="toClassification")
|
|
64
|
+
effective_year: int = Field(alias="effectiveYear", ge=1869)
|