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,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."""
|