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,43 @@
1
+ """Export the supported Plays namespace and public contracts."""
2
+
3
+ from cfb_data.enums import Classification, SeasonType
4
+
5
+ from .models.pydantic import (
6
+ DownType,
7
+ HomeAway,
8
+ LiveGame,
9
+ LiveGameDrive,
10
+ LiveGamePlay,
11
+ LiveGameTeam,
12
+ LivePlaysRequest,
13
+ Play,
14
+ PlayClock,
15
+ PlaysRequest,
16
+ PlayStat,
17
+ PlayStatsRequest,
18
+ PlayStatType,
19
+ PlayType,
20
+ RushPass,
21
+ )
22
+ from .resource import PlaysResource
23
+
24
+ __all__ = [
25
+ "Classification",
26
+ "DownType",
27
+ "HomeAway",
28
+ "LiveGame",
29
+ "LiveGameDrive",
30
+ "LiveGamePlay",
31
+ "LiveGameTeam",
32
+ "LivePlaysRequest",
33
+ "Play",
34
+ "PlayClock",
35
+ "PlayStat",
36
+ "PlayStatsRequest",
37
+ "PlayStatType",
38
+ "PlayType",
39
+ "PlaysRequest",
40
+ "PlaysResource",
41
+ "RushPass",
42
+ "SeasonType",
43
+ ]
@@ -0,0 +1 @@
1
+ """Provide authoritative Pydantic models for Plays endpoints."""
@@ -0,0 +1,35 @@
1
+ """Export Pydantic models for Plays endpoints."""
2
+
3
+ from .requests import LivePlaysRequest, PlaysRequest, PlayStatsRequest
4
+ from .responses import (
5
+ DownType,
6
+ HomeAway,
7
+ LiveGame,
8
+ LiveGameDrive,
9
+ LiveGamePlay,
10
+ LiveGameTeam,
11
+ Play,
12
+ PlayClock,
13
+ PlayStat,
14
+ PlayStatType,
15
+ PlayType,
16
+ RushPass,
17
+ )
18
+
19
+ __all__ = [
20
+ "DownType",
21
+ "HomeAway",
22
+ "LiveGame",
23
+ "LiveGameDrive",
24
+ "LiveGamePlay",
25
+ "LiveGameTeam",
26
+ "LivePlaysRequest",
27
+ "Play",
28
+ "PlayClock",
29
+ "PlayStat",
30
+ "PlayStatsRequest",
31
+ "PlayStatType",
32
+ "PlayType",
33
+ "PlaysRequest",
34
+ "RushPass",
35
+ ]
@@ -0,0 +1,85 @@
1
+ """Validate request parameters for implemented Plays endpoints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+ from cfb_data.enums import Classification, SeasonType
8
+
9
+
10
+ class PlaysRequest(BaseModel):
11
+ """Validate filters accepted by ``GET /plays``.
12
+
13
+ :param year: Required season year.
14
+ :param week: Required non-negative season week.
15
+ :param team: Team appearing on either side of the play.
16
+ :param offense: Offensive-team selector.
17
+ :param defense: Defensive-team selector.
18
+ :param offense_conference: Offensive-team conference selector.
19
+ :param defense_conference: Defensive-team conference selector.
20
+ :param conference: Conference appearing on either side of the play.
21
+ :param play_type: Play-type abbreviation.
22
+ :param season_type: Optional season phase.
23
+ :param classification: Division classification selector.
24
+ """
25
+
26
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
27
+
28
+ year: int = Field(ge=1869)
29
+ week: int = Field(ge=0)
30
+ team: str | None = None
31
+ offense: str | None = None
32
+ defense: str | None = None
33
+ offense_conference: str | None = Field(
34
+ default=None,
35
+ alias="offenseConference",
36
+ )
37
+ defense_conference: str | None = Field(
38
+ default=None,
39
+ alias="defenseConference",
40
+ )
41
+ conference: str | None = None
42
+ play_type: str | None = Field(default=None, alias="playType")
43
+ season_type: SeasonType | None = Field(default=None, alias="seasonType")
44
+ classification: Classification | None = None
45
+
46
+
47
+ class PlayStatsRequest(BaseModel):
48
+ """Validate filters accepted by ``GET /plays/stats``.
49
+
50
+ The upstream endpoint permits an unfiltered request and limits every
51
+ response to 2,000 rows.
52
+
53
+ :param year: Optional season year.
54
+ :param week: Optional non-negative season week.
55
+ :param team: Team selector.
56
+ :param game_id: Positive game identifier.
57
+ :param athlete_id: Positive athlete identifier.
58
+ :param stat_type_id: Positive play-stat type identifier.
59
+ :param season_type: Optional season phase.
60
+ :param conference: Conference name or abbreviation.
61
+ """
62
+
63
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
64
+
65
+ year: int | None = Field(default=None, ge=1869)
66
+ week: int | None = Field(default=None, ge=0)
67
+ team: str | None = None
68
+ game_id: int | None = Field(default=None, gt=0, serialization_alias="gameId")
69
+ athlete_id: int | None = Field(default=None, gt=0, serialization_alias="athleteId")
70
+ stat_type_id: int | None = Field(
71
+ default=None, gt=0, serialization_alias="statTypeId"
72
+ )
73
+ season_type: SeasonType | None = Field(default=None, alias="seasonType")
74
+ conference: str | None = None
75
+
76
+
77
+ class LivePlaysRequest(BaseModel):
78
+ """Validate filters accepted by ``GET /live/plays``.
79
+
80
+ :param game_id: Positive game identifier.
81
+ """
82
+
83
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
84
+
85
+ game_id: int = Field(gt=0, serialization_alias="gameId")
@@ -0,0 +1,231 @@
1
+ """Validate responses from implemented CFBD Plays 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 response models."""
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
+
21
+ :param value: Validated response field value.
22
+ :return: UTC-normalized datetime or the unchanged non-datetime value.
23
+ :raises ValueError: If a datetime does not identify an instant.
24
+ """
25
+ if not isinstance(value, datetime):
26
+ return value
27
+ if value.tzinfo is None or value.utcoffset() is None:
28
+ raise ValueError("Response timestamps must be timezone-aware")
29
+ return value.astimezone(UTC)
30
+
31
+
32
+ class PlayClock(_ResponseModel):
33
+ """Represent time remaining in a play's period."""
34
+
35
+ minutes: int | None = Field(ge=0)
36
+ seconds: int | None = Field(ge=0)
37
+
38
+
39
+ class Play(_ResponseModel):
40
+ """Represent one historical play returned by ``GET /plays``."""
41
+
42
+ id: str
43
+ drive_id: str = Field(alias="driveId")
44
+ game_id: int = Field(alias="gameId", gt=0)
45
+ drive_number: int | None = Field(alias="driveNumber", ge=0)
46
+ play_number: int | None = Field(alias="playNumber", ge=0)
47
+ offense: str
48
+ offense_conference: str | None = Field(alias="offenseConference")
49
+ offense_score: int = Field(alias="offenseScore", ge=0)
50
+ defense: str
51
+ home: str
52
+ away: str
53
+ defense_conference: str | None = Field(alias="defenseConference")
54
+ defense_score: int = Field(alias="defenseScore", ge=0)
55
+ period: int = Field(ge=0)
56
+ clock: PlayClock
57
+ offense_timeouts: int | None = Field(alias="offenseTimeouts")
58
+ defense_timeouts: int | None = Field(alias="defenseTimeouts")
59
+ yardline: int = Field(ge=0)
60
+ yards_to_goal: int = Field(alias="yardsToGoal", ge=0)
61
+ down: int = Field(ge=0)
62
+ distance: int = Field(ge=0)
63
+ yards_gained: int = Field(alias="yardsGained")
64
+ scoring: bool
65
+ play_type: str = Field(alias="playType")
66
+ play_text: str | None = Field(alias="playText")
67
+ ppa: float | None
68
+ wallclock: datetime | None
69
+
70
+
71
+ class PlayType(_ResponseModel):
72
+ """Represent one available historical play type."""
73
+
74
+ id: int = Field(gt=0)
75
+ text: str
76
+ abbreviation: str | None
77
+
78
+
79
+ class PlayStat(_ResponseModel):
80
+ """Represent one athlete statistic associated with a play."""
81
+
82
+ game_id: int = Field(alias="gameId", gt=0)
83
+ season: int = Field(ge=1869)
84
+ week: int = Field(ge=0)
85
+ team: str
86
+ conference: str
87
+ opponent: str
88
+ team_score: int = Field(alias="teamScore", ge=0)
89
+ opponent_score: int = Field(alias="opponentScore", ge=0)
90
+ drive_id: str = Field(alias="driveId")
91
+ play_id: str = Field(alias="playId")
92
+ period: int = Field(ge=0)
93
+ clock: PlayClock
94
+ yards_to_goal: int = Field(alias="yardsToGoal", ge=0)
95
+ down: int = Field(ge=0)
96
+ distance: int = Field(ge=0)
97
+ athlete_id: str = Field(alias="athleteId")
98
+ athlete_name: str = Field(alias="athleteName")
99
+ stat_type: str = Field(alias="statType")
100
+ stat: int
101
+
102
+
103
+ class PlayStatType(_ResponseModel):
104
+ """Represent one available athlete play-stat type."""
105
+
106
+ id: int = Field(gt=0)
107
+ name: str
108
+
109
+
110
+ class HomeAway(StrEnum):
111
+ """Identify whether a live-game team is home or away."""
112
+
113
+ home = "home"
114
+ away = "away"
115
+
116
+
117
+ class RushPass(StrEnum):
118
+ """Classify a live play by its offensive action."""
119
+
120
+ rush = "rush"
121
+ pass_ = "pass"
122
+ other = "other"
123
+
124
+
125
+ class DownType(StrEnum):
126
+ """Classify a live play's down and distance."""
127
+
128
+ passing = "passing"
129
+ standard = "standard"
130
+
131
+
132
+ class LiveGamePlay(_ResponseModel):
133
+ """Represent one play nested within a live drive."""
134
+
135
+ id: str
136
+ home_score: int = Field(alias="homeScore", ge=0)
137
+ away_score: int = Field(alias="awayScore", ge=0)
138
+ period: int = Field(ge=0)
139
+ clock: str | None
140
+ wall_clock: datetime = Field(alias="wallClock")
141
+ team_id: int = Field(alias="teamId", gt=0)
142
+ team: str
143
+ down: int = Field(ge=0)
144
+ distance: int = Field(ge=0)
145
+ yards_to_goal: int = Field(alias="yardsToGoal", ge=0)
146
+ yards_gained: int = Field(alias="yardsGained")
147
+ play_type_id: int = Field(alias="playTypeId", gt=0)
148
+ play_type: str = Field(alias="playType")
149
+ epa: float | None
150
+ garbage_time: bool = Field(alias="garbageTime")
151
+ success: bool
152
+ rush_pass: RushPass = Field(alias="rushPass")
153
+ down_type: DownType = Field(alias="downType")
154
+ play_text: str = Field(alias="playText")
155
+
156
+
157
+ class LiveGameDrive(_ResponseModel):
158
+ """Represent one drive nested within a live game."""
159
+
160
+ id: str
161
+ offense_id: int = Field(alias="offenseId", gt=0)
162
+ offense: str
163
+ defense_id: int = Field(alias="defenseId", gt=0)
164
+ defense: str
165
+ play_count: int = Field(alias="playCount", ge=0)
166
+ yards: int
167
+ start_period: int = Field(alias="startPeriod", ge=0)
168
+ start_clock: str | None = Field(alias="startClock")
169
+ start_yards_to_goal: int = Field(alias="startYardsToGoal", ge=0)
170
+ end_period: int | None = Field(alias="endPeriod", ge=0)
171
+ end_clock: str | None = Field(alias="endClock")
172
+ end_yards_to_goal: int | None = Field(alias="endYardsToGoal", ge=0)
173
+ duration: str | None
174
+ scoring_opportunity: bool = Field(alias="scoringOpportunity")
175
+ result: str
176
+ points_gained: int = Field(alias="pointsGained")
177
+ plays: list[LiveGamePlay]
178
+
179
+
180
+ class LiveGameTeam(_ResponseModel):
181
+ """Represent one team's live-game aggregate metrics."""
182
+
183
+ team_id: int = Field(alias="teamId", gt=0)
184
+ team: str
185
+ home_away: HomeAway = Field(alias="homeAway")
186
+ line_scores: list[int] = Field(alias="lineScores")
187
+ points: int = Field(ge=0)
188
+ drives: int = Field(ge=0)
189
+ scoring_opportunities: int = Field(alias="scoringOpportunities", ge=0)
190
+ points_per_opportunity: float = Field(alias="pointsPerOpportunity")
191
+ average_start_yard_line: float | None = Field(alias="averageStartYardLine")
192
+ plays: int = Field(ge=0)
193
+ line_yards: float = Field(alias="lineYards")
194
+ line_yards_per_rush: float = Field(alias="lineYardsPerRush")
195
+ second_level_yards: float = Field(alias="secondLevelYards")
196
+ second_level_yards_per_rush: float = Field(alias="secondLevelYardsPerRush")
197
+ open_field_yards: float = Field(alias="openFieldYards")
198
+ open_field_yards_per_rush: float = Field(alias="openFieldYardsPerRush")
199
+ epa_per_play: float = Field(alias="epaPerPlay")
200
+ total_epa: float = Field(alias="totalEpa")
201
+ passing_epa: float = Field(alias="passingEpa")
202
+ epa_per_pass: float = Field(alias="epaPerPass")
203
+ rushing_epa: float = Field(alias="rushingEpa")
204
+ epa_per_rush: float = Field(alias="epaPerRush")
205
+ success_rate: float = Field(alias="successRate", ge=0, le=1)
206
+ standard_down_success_rate: float = Field(
207
+ alias="standardDownSuccessRate", ge=0, le=1
208
+ )
209
+ passing_down_success_rate: float = Field(alias="passingDownSuccessRate", ge=0, le=1)
210
+ explosiveness: float
211
+ deserve_to_win: float | None = Field(
212
+ default=None,
213
+ alias="deserveToWin",
214
+ ge=0,
215
+ le=1,
216
+ )
217
+
218
+
219
+ class LiveGame(_ResponseModel):
220
+ """Represent the nested response returned by ``GET /live/plays``."""
221
+
222
+ id: int = Field(gt=0)
223
+ status: str
224
+ period: int | None = Field(ge=0)
225
+ clock: str
226
+ possession: str
227
+ down: int | None = Field(ge=0)
228
+ distance: int | None = Field(ge=0)
229
+ yards_to_goal: int | None = Field(alias="yardsToGoal", ge=0)
230
+ teams: list[LiveGameTeam]
231
+ drives: list[LiveGameDrive]
@@ -0,0 +1,249 @@
1
+ """Expose typed Plays endpoints through the primary client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Literal, overload
6
+
7
+ from pydantic import BaseModel, ConfigDict, TypeAdapter
8
+
9
+ from cfb_data._dataframes import _DataFrameAdapter
10
+ from cfb_data._executor import _EndpointExecutor
11
+ from cfb_data._requests import _resolve_request
12
+ from cfb_data.enums import Classification, SeasonType
13
+ from cfb_data.plays.models.pydantic.requests import (
14
+ LivePlaysRequest,
15
+ PlaysRequest,
16
+ PlayStatsRequest,
17
+ )
18
+ from cfb_data.plays.models.pydantic.responses import (
19
+ LiveGame,
20
+ Play,
21
+ PlayStat,
22
+ PlayStatType,
23
+ PlayType,
24
+ )
25
+
26
+ type _SeasonTypeArgument = (
27
+ SeasonType
28
+ | Literal[
29
+ "regular",
30
+ "postseason",
31
+ "both",
32
+ "allstar",
33
+ "spring_regular",
34
+ "spring_postseason",
35
+ ]
36
+ )
37
+ type _ClassificationArgument = Classification | Literal["fbs", "fcs", "ii", "iii"]
38
+
39
+ _PLAY_ROWS = TypeAdapter(list[Play])
40
+ _PLAY_TYPE_ROWS = TypeAdapter(list[PlayType])
41
+ _PLAY_STAT_ROWS = TypeAdapter(list[PlayStat])
42
+ _PLAY_STAT_TYPE_ROWS = TypeAdapter(list[PlayStatType])
43
+ _LIVE_GAME = TypeAdapter(LiveGame)
44
+
45
+
46
+ class _EmptyRequest(BaseModel):
47
+ """Represent a route with no query parameters."""
48
+
49
+ model_config = ConfigDict(extra="forbid")
50
+
51
+
52
+ _EMPTY_REQUEST = _EmptyRequest()
53
+
54
+
55
+ class PlaysResource[FrameT]:
56
+ """Provide validated Plays endpoints with backend-specific frame results."""
57
+
58
+ def __init__(
59
+ self,
60
+ executor: _EndpointExecutor,
61
+ dataframe_adapter: _DataFrameAdapter[FrameT],
62
+ ) -> None:
63
+ """Bind the namespace to shared execution and presentation services."""
64
+ self._executor = executor
65
+ self._dataframe_adapter = dataframe_adapter
66
+
67
+ @overload
68
+ async def list(self, request: PlaysRequest, /) -> FrameT: ...
69
+
70
+ @overload
71
+ async def list(
72
+ self,
73
+ request: None = None,
74
+ /,
75
+ *,
76
+ year: int,
77
+ week: int,
78
+ team: str | None = None,
79
+ offense: str | None = None,
80
+ defense: str | None = None,
81
+ offense_conference: str | None = None,
82
+ defense_conference: str | None = None,
83
+ conference: str | None = None,
84
+ play_type: str | None = None,
85
+ season_type: _SeasonTypeArgument | None = None,
86
+ classification: _ClassificationArgument | None = None,
87
+ ) -> FrameT: ...
88
+
89
+ async def list(
90
+ self,
91
+ request: PlaysRequest | None = None,
92
+ /,
93
+ **filters: object,
94
+ ) -> FrameT:
95
+ """Return historical plays as the selected DataFrame type.
96
+
97
+ :param request: Validated request model, mutually exclusive with filters.
98
+ :param filters: Explicit snake-case endpoint filters.
99
+ :return: Eager frame containing validated ``Play`` rows.
100
+ :raises TypeError: If request styles are mixed or the model type is wrong.
101
+ :raises CFBDError: If request, transport, response, or conversion fails.
102
+ """
103
+ endpoint = "/plays"
104
+ validated = _resolve_request(
105
+ endpoint=endpoint,
106
+ request_type=PlaysRequest,
107
+ request=request,
108
+ filters=filters,
109
+ )
110
+ rows = await self._executor.fetch_many(
111
+ endpoint=endpoint,
112
+ request=validated,
113
+ response_adapter=_PLAY_ROWS,
114
+ )
115
+ return self._dataframe_adapter.from_models(
116
+ endpoint=endpoint,
117
+ row_model=Play,
118
+ models=rows,
119
+ )
120
+
121
+ async def types(self) -> FrameT:
122
+ """Return available play types as the selected DataFrame type.
123
+
124
+ :return: Eager frame containing validated ``PlayType`` rows.
125
+ :raises CFBDError: If transport, response, or conversion fails.
126
+ """
127
+ endpoint = "/plays/types"
128
+ rows = await self._executor.fetch_many(
129
+ endpoint=endpoint,
130
+ request=_EMPTY_REQUEST,
131
+ response_adapter=_PLAY_TYPE_ROWS,
132
+ )
133
+ return self._dataframe_adapter.from_models(
134
+ endpoint=endpoint,
135
+ row_model=PlayType,
136
+ models=rows,
137
+ )
138
+
139
+ @overload
140
+ async def stats(self, request: PlayStatsRequest, /) -> FrameT: ...
141
+
142
+ @overload
143
+ async def stats(
144
+ self,
145
+ request: None = None,
146
+ /,
147
+ *,
148
+ year: int | None = None,
149
+ week: int | None = None,
150
+ team: str | None = None,
151
+ game_id: int | None = None,
152
+ athlete_id: int | None = None,
153
+ stat_type_id: int | None = None,
154
+ season_type: _SeasonTypeArgument | None = None,
155
+ conference: str | None = None,
156
+ ) -> FrameT: ...
157
+
158
+ async def stats(
159
+ self,
160
+ request: PlayStatsRequest | None = None,
161
+ /,
162
+ **filters: object,
163
+ ) -> FrameT:
164
+ """Return athlete play statistics as the selected DataFrame type.
165
+
166
+ The upstream endpoint limits responses to 2,000 rows.
167
+
168
+ :param request: Validated request model, mutually exclusive with filters.
169
+ :param filters: Explicit snake-case endpoint filters.
170
+ :return: Eager frame containing validated ``PlayStat`` rows.
171
+ :raises TypeError: If request styles are mixed or the model type is wrong.
172
+ :raises CFBDError: If request, transport, response, or conversion fails.
173
+ """
174
+ endpoint = "/plays/stats"
175
+ validated = _resolve_request(
176
+ endpoint=endpoint,
177
+ request_type=PlayStatsRequest,
178
+ request=request,
179
+ filters=filters,
180
+ )
181
+ rows = await self._executor.fetch_many(
182
+ endpoint=endpoint,
183
+ request=validated,
184
+ response_adapter=_PLAY_STAT_ROWS,
185
+ )
186
+ return self._dataframe_adapter.from_models(
187
+ endpoint=endpoint,
188
+ row_model=PlayStat,
189
+ models=rows,
190
+ )
191
+
192
+ async def stat_types(self) -> FrameT:
193
+ """Return athlete play-stat types as the selected DataFrame type.
194
+
195
+ :return: Eager frame containing validated ``PlayStatType`` rows.
196
+ :raises CFBDError: If transport, response, or conversion fails.
197
+ """
198
+ endpoint = "/plays/stats/types"
199
+ rows = await self._executor.fetch_many(
200
+ endpoint=endpoint,
201
+ request=_EMPTY_REQUEST,
202
+ response_adapter=_PLAY_STAT_TYPE_ROWS,
203
+ )
204
+ return self._dataframe_adapter.from_models(
205
+ endpoint=endpoint,
206
+ row_model=PlayStatType,
207
+ models=rows,
208
+ )
209
+
210
+ @overload
211
+ async def live(self, request: LivePlaysRequest, /) -> LiveGame: ...
212
+
213
+ @overload
214
+ async def live(
215
+ self,
216
+ request: None = None,
217
+ /,
218
+ *,
219
+ game_id: int,
220
+ ) -> LiveGame: ...
221
+
222
+ async def live(
223
+ self,
224
+ request: LivePlaysRequest | None = None,
225
+ /,
226
+ **filters: object,
227
+ ) -> LiveGame:
228
+ """Return live play-by-play and metrics as one validated model.
229
+
230
+ The upstream route requires Patreon Tier 2 access.
231
+
232
+ :param request: Validated request model, mutually exclusive with filters.
233
+ :param filters: Explicit snake-case endpoint filters.
234
+ :return: Validated nested live game, team, drive, and play data.
235
+ :raises TypeError: If request styles are mixed or the model type is wrong.
236
+ :raises CFBDError: If request, transport, decode, or validation fails.
237
+ """
238
+ endpoint = "/live/plays"
239
+ validated = _resolve_request(
240
+ endpoint=endpoint,
241
+ request_type=LivePlaysRequest,
242
+ request=request,
243
+ filters=filters,
244
+ )
245
+ return await self._executor.fetch_one(
246
+ endpoint=endpoint,
247
+ request=validated,
248
+ response_adapter=_LIVE_GAME,
249
+ )
cfb_data/py.typed ADDED
File without changes
@@ -0,0 +1,16 @@
1
+ """Export the supported Rankings namespace and public contracts."""
2
+
3
+ from cfb_data.enums import RankingPoll, SeasonType
4
+
5
+ from .models.pydantic import Poll, PollRank, PollWeek, RankingsRequest
6
+ from .resource import RankingsResource
7
+
8
+ __all__ = [
9
+ "Poll",
10
+ "PollRank",
11
+ "PollWeek",
12
+ "RankingPoll",
13
+ "RankingsRequest",
14
+ "RankingsResource",
15
+ "SeasonType",
16
+ ]
@@ -0,0 +1 @@
1
+ """Export Rankings model families."""
@@ -0,0 +1,6 @@
1
+ """Export validated Rankings request and response models."""
2
+
3
+ from .requests import RankingsRequest
4
+ from .responses import Poll, PollRank, PollWeek
5
+
6
+ __all__ = ["Poll", "PollRank", "PollWeek", "RankingsRequest"]