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,40 @@
|
|
|
1
|
+
"""Export the supported Teams namespace and public contracts."""
|
|
2
|
+
|
|
3
|
+
from cfb_data.conferences import ConferenceClassification
|
|
4
|
+
from cfb_data.enums import Classification
|
|
5
|
+
from cfb_data.venues import Venue
|
|
6
|
+
|
|
7
|
+
from .models.pydantic import (
|
|
8
|
+
FBSTeamsRequest,
|
|
9
|
+
Matchup,
|
|
10
|
+
MatchupGame,
|
|
11
|
+
RosterPlayer,
|
|
12
|
+
RosterRequest,
|
|
13
|
+
TalentRequest,
|
|
14
|
+
Team,
|
|
15
|
+
TeamATS,
|
|
16
|
+
TeamATSRequest,
|
|
17
|
+
TeamMatchupRequest,
|
|
18
|
+
TeamsRequest,
|
|
19
|
+
TeamTalent,
|
|
20
|
+
)
|
|
21
|
+
from .resource import TeamsResource
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"Classification",
|
|
25
|
+
"ConferenceClassification",
|
|
26
|
+
"FBSTeamsRequest",
|
|
27
|
+
"Matchup",
|
|
28
|
+
"MatchupGame",
|
|
29
|
+
"RosterPlayer",
|
|
30
|
+
"RosterRequest",
|
|
31
|
+
"TalentRequest",
|
|
32
|
+
"Team",
|
|
33
|
+
"TeamATS",
|
|
34
|
+
"TeamATSRequest",
|
|
35
|
+
"TeamMatchupRequest",
|
|
36
|
+
"TeamsRequest",
|
|
37
|
+
"TeamsResource",
|
|
38
|
+
"TeamTalent",
|
|
39
|
+
"Venue",
|
|
40
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Provide authoritative Pydantic models for Teams endpoints."""
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Export Pydantic models for Teams endpoints."""
|
|
2
|
+
|
|
3
|
+
from .requests import (
|
|
4
|
+
FBSTeamsRequest,
|
|
5
|
+
RosterRequest,
|
|
6
|
+
TalentRequest,
|
|
7
|
+
TeamATSRequest,
|
|
8
|
+
TeamMatchupRequest,
|
|
9
|
+
TeamsRequest,
|
|
10
|
+
)
|
|
11
|
+
from .responses import Matchup, MatchupGame, RosterPlayer, Team, TeamATS, TeamTalent
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"FBSTeamsRequest",
|
|
15
|
+
"Matchup",
|
|
16
|
+
"MatchupGame",
|
|
17
|
+
"RosterPlayer",
|
|
18
|
+
"RosterRequest",
|
|
19
|
+
"TalentRequest",
|
|
20
|
+
"Team",
|
|
21
|
+
"TeamATS",
|
|
22
|
+
"TeamATSRequest",
|
|
23
|
+
"TeamMatchupRequest",
|
|
24
|
+
"TeamsRequest",
|
|
25
|
+
"TeamTalent",
|
|
26
|
+
]
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Validate request parameters for implemented Teams 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 Classification
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TeamsRequest(BaseModel):
|
|
13
|
+
"""Validate filters accepted by ``GET /teams``.
|
|
14
|
+
|
|
15
|
+
:param conference: Conference abbreviation.
|
|
16
|
+
:param year: Season used for historical affiliation membership.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
20
|
+
conference: str | None = None
|
|
21
|
+
year: int | None = Field(default=None, ge=1869)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class FBSTeamsRequest(BaseModel):
|
|
25
|
+
"""Validate filters accepted by ``GET /teams/fbs``.
|
|
26
|
+
|
|
27
|
+
:param year: Season used for historical FBS membership.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
31
|
+
year: int | None = Field(default=None, ge=1869)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TeamMatchupRequest(BaseModel):
|
|
35
|
+
"""Validate filters accepted by ``GET /teams/matchup``.
|
|
36
|
+
|
|
37
|
+
:param team1: First team name.
|
|
38
|
+
:param team2: Second team name.
|
|
39
|
+
:param min_year: Earliest season to include.
|
|
40
|
+
:param max_year: Latest season to include.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
44
|
+
team1: str = Field(min_length=1)
|
|
45
|
+
team2: str = Field(min_length=1)
|
|
46
|
+
min_year: int | None = Field(default=None, ge=1869, alias="minYear")
|
|
47
|
+
max_year: int | None = Field(default=None, ge=1869, alias="maxYear")
|
|
48
|
+
|
|
49
|
+
@model_validator(mode="after")
|
|
50
|
+
def validate_year_range(self) -> Self:
|
|
51
|
+
"""Reject a reversed matchup season range."""
|
|
52
|
+
if (
|
|
53
|
+
self.min_year is not None
|
|
54
|
+
and self.max_year is not None
|
|
55
|
+
and self.min_year > self.max_year
|
|
56
|
+
):
|
|
57
|
+
raise ValueError("min_year cannot be greater than max_year")
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class TeamATSRequest(BaseModel):
|
|
62
|
+
"""Validate filters accepted by ``GET /teams/ats``.
|
|
63
|
+
|
|
64
|
+
:param year: Required season year.
|
|
65
|
+
:param conference: Conference name or abbreviation.
|
|
66
|
+
:param team: Team name.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
70
|
+
year: int = Field(ge=1869)
|
|
71
|
+
conference: str | None = None
|
|
72
|
+
team: str | None = None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class RosterRequest(BaseModel):
|
|
76
|
+
"""Validate filters accepted by ``GET /roster``.
|
|
77
|
+
|
|
78
|
+
:param team: Team name.
|
|
79
|
+
:param year: Roster season, or ``None`` to retain the upstream default.
|
|
80
|
+
:param classification: Division classification selector.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
84
|
+
team: str | None = None
|
|
85
|
+
year: int | None = Field(default=None, ge=1869)
|
|
86
|
+
classification: Classification | None = None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class TalentRequest(BaseModel):
|
|
90
|
+
"""Validate filters accepted by ``GET /talent``.
|
|
91
|
+
|
|
92
|
+
:param year: Required talent-composite season.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
96
|
+
year: int = Field(ge=1869)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Validate responses from implemented CFBD Teams endpoints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import UTC, datetime
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
8
|
+
|
|
9
|
+
from cfb_data.conferences.models.pydantic.responses import ConferenceClassification
|
|
10
|
+
from cfb_data.venues.models.pydantic.responses import Venue
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _ResponseModel(BaseModel):
|
|
14
|
+
"""Apply the upstream closed-object contract to response models."""
|
|
15
|
+
|
|
16
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
17
|
+
|
|
18
|
+
@field_validator("*", mode="after", check_fields=False)
|
|
19
|
+
@classmethod
|
|
20
|
+
def require_utc_datetimes(cls, value: object) -> object:
|
|
21
|
+
"""Require aware response timestamps and normalize them to UTC."""
|
|
22
|
+
if not isinstance(value, datetime):
|
|
23
|
+
return value
|
|
24
|
+
if value.tzinfo is None or value.utcoffset() is None:
|
|
25
|
+
raise ValueError("Response timestamps must be timezone-aware")
|
|
26
|
+
return value.astimezone(UTC)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Team(_ResponseModel):
|
|
30
|
+
"""Represent one team and its current or historical affiliation."""
|
|
31
|
+
|
|
32
|
+
id: int = Field(gt=0)
|
|
33
|
+
school: str
|
|
34
|
+
mascot: str | None
|
|
35
|
+
abbreviation: str | None
|
|
36
|
+
alternate_names: list[str] | None = Field(alias="alternateNames")
|
|
37
|
+
conference: str | None
|
|
38
|
+
division: str | None
|
|
39
|
+
classification: ConferenceClassification | None
|
|
40
|
+
color: str | None
|
|
41
|
+
alternate_color: str | None = Field(alias="alternateColor")
|
|
42
|
+
logos: list[str] | None
|
|
43
|
+
twitter: str | None
|
|
44
|
+
location: Venue | None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MatchupGame(_ResponseModel):
|
|
48
|
+
"""Represent one completed game in a historical matchup."""
|
|
49
|
+
|
|
50
|
+
season: int = Field(ge=1869)
|
|
51
|
+
week: int = Field(ge=0)
|
|
52
|
+
season_type: str = Field(alias="seasonType")
|
|
53
|
+
date: datetime
|
|
54
|
+
neutral_site: bool = Field(alias="neutralSite")
|
|
55
|
+
venue: str | None
|
|
56
|
+
home_team: str = Field(alias="homeTeam")
|
|
57
|
+
home_score: int | None = Field(alias="homeScore", ge=0)
|
|
58
|
+
away_team: str = Field(alias="awayTeam")
|
|
59
|
+
away_score: int | None = Field(alias="awayScore", ge=0)
|
|
60
|
+
winner: str | None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Matchup(_ResponseModel):
|
|
64
|
+
"""Represent a matchup summary and its nested completed games."""
|
|
65
|
+
|
|
66
|
+
team1: str
|
|
67
|
+
team2: str
|
|
68
|
+
start_year: int | None = Field(default=None, alias="startYear", ge=1869)
|
|
69
|
+
end_year: int | None = Field(default=None, alias="endYear", ge=1869)
|
|
70
|
+
team1_wins: int = Field(alias="team1Wins", ge=0)
|
|
71
|
+
team2_wins: int = Field(alias="team2Wins", ge=0)
|
|
72
|
+
ties: int = Field(ge=0)
|
|
73
|
+
games: list[MatchupGame]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class RosterPlayer(_ResponseModel):
|
|
77
|
+
"""Represent one player on a historical team roster."""
|
|
78
|
+
|
|
79
|
+
id: str
|
|
80
|
+
first_name: str = Field(alias="firstName")
|
|
81
|
+
last_name: str = Field(alias="lastName")
|
|
82
|
+
team: str
|
|
83
|
+
height: float | None
|
|
84
|
+
weight: int | None = Field(ge=0)
|
|
85
|
+
jersey: int | None = Field(ge=0)
|
|
86
|
+
year: int = Field(ge=0)
|
|
87
|
+
position: str | None
|
|
88
|
+
home_city: str | None = Field(alias="homeCity")
|
|
89
|
+
home_state: str | None = Field(alias="homeState")
|
|
90
|
+
home_country: str | None = Field(alias="homeCountry")
|
|
91
|
+
home_latitude: float | None = Field(alias="homeLatitude")
|
|
92
|
+
home_longitude: float | None = Field(alias="homeLongitude")
|
|
93
|
+
home_county_fips: str | None = Field(alias="homeCountyFIPS")
|
|
94
|
+
recruit_ids: list[str] | None = Field(alias="recruitIds")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class TeamTalent(_ResponseModel):
|
|
98
|
+
"""Represent one team's 247Sports Team Talent Composite rating."""
|
|
99
|
+
|
|
100
|
+
year: int = Field(ge=1869)
|
|
101
|
+
team: str
|
|
102
|
+
talent: float
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class TeamATS(_ResponseModel):
|
|
106
|
+
"""Represent one team's against-the-spread season record."""
|
|
107
|
+
|
|
108
|
+
year: int = Field(ge=1869)
|
|
109
|
+
team_id: int = Field(alias="teamId", gt=0)
|
|
110
|
+
team: str
|
|
111
|
+
conference: str | None
|
|
112
|
+
games: int = Field(ge=0)
|
|
113
|
+
ats_wins: int = Field(alias="atsWins", ge=0)
|
|
114
|
+
ats_losses: int = Field(alias="atsLosses", ge=0)
|
|
115
|
+
ats_pushes: int = Field(alias="atsPushes", ge=0)
|
|
116
|
+
avg_cover_margin: float | None = Field(alias="avgCoverMargin")
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""Expose typed Teams endpoints through the primary client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Literal, overload
|
|
6
|
+
|
|
7
|
+
from pydantic import 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
|
|
13
|
+
from cfb_data.teams.models.pydantic.requests import (
|
|
14
|
+
FBSTeamsRequest,
|
|
15
|
+
RosterRequest,
|
|
16
|
+
TalentRequest,
|
|
17
|
+
TeamATSRequest,
|
|
18
|
+
TeamMatchupRequest,
|
|
19
|
+
TeamsRequest,
|
|
20
|
+
)
|
|
21
|
+
from cfb_data.teams.models.pydantic.responses import (
|
|
22
|
+
Matchup,
|
|
23
|
+
RosterPlayer,
|
|
24
|
+
Team,
|
|
25
|
+
TeamATS,
|
|
26
|
+
TeamTalent,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
type _ClassificationArgument = Classification | Literal["fbs", "fcs", "ii", "iii"]
|
|
30
|
+
_TEAM_ROWS = TypeAdapter(list[Team])
|
|
31
|
+
_MATCHUP = TypeAdapter(Matchup)
|
|
32
|
+
_ATS_ROWS = TypeAdapter(list[TeamATS])
|
|
33
|
+
_ROSTER_ROWS = TypeAdapter(list[RosterPlayer])
|
|
34
|
+
_TALENT_ROWS = TypeAdapter(list[TeamTalent])
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TeamsResource[FrameT]:
|
|
38
|
+
"""Provide validated Teams endpoints with backend-specific frame results."""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
executor: _EndpointExecutor,
|
|
43
|
+
dataframe_adapter: _DataFrameAdapter[FrameT],
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Bind the namespace to shared execution and presentation services."""
|
|
46
|
+
self._executor = executor
|
|
47
|
+
self._dataframe_adapter = dataframe_adapter
|
|
48
|
+
|
|
49
|
+
@overload
|
|
50
|
+
async def list(self, request: TeamsRequest, /) -> FrameT: ...
|
|
51
|
+
|
|
52
|
+
@overload
|
|
53
|
+
async def list(
|
|
54
|
+
self,
|
|
55
|
+
request: None = None,
|
|
56
|
+
/,
|
|
57
|
+
*,
|
|
58
|
+
conference: str | None = None,
|
|
59
|
+
year: int | None = None,
|
|
60
|
+
) -> FrameT: ...
|
|
61
|
+
|
|
62
|
+
async def list(
|
|
63
|
+
self, request: TeamsRequest | None = None, /, **filters: object
|
|
64
|
+
) -> FrameT:
|
|
65
|
+
"""Return teams as the selected DataFrame type.
|
|
66
|
+
|
|
67
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
68
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
69
|
+
:return: Eager frame containing validated ``Team`` rows.
|
|
70
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
71
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
72
|
+
"""
|
|
73
|
+
endpoint = "/teams"
|
|
74
|
+
validated = _resolve_request(
|
|
75
|
+
endpoint=endpoint,
|
|
76
|
+
request_type=TeamsRequest,
|
|
77
|
+
request=request,
|
|
78
|
+
filters=filters,
|
|
79
|
+
)
|
|
80
|
+
rows = await self._executor.fetch_many(
|
|
81
|
+
endpoint=endpoint, request=validated, response_adapter=_TEAM_ROWS
|
|
82
|
+
)
|
|
83
|
+
return self._dataframe_adapter.from_models(
|
|
84
|
+
endpoint=endpoint, row_model=Team, models=rows
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@overload
|
|
88
|
+
async def fbs(self, request: FBSTeamsRequest, /) -> FrameT: ...
|
|
89
|
+
|
|
90
|
+
@overload
|
|
91
|
+
async def fbs(
|
|
92
|
+
self, request: None = None, /, *, year: int | None = None
|
|
93
|
+
) -> FrameT: ...
|
|
94
|
+
|
|
95
|
+
async def fbs(
|
|
96
|
+
self, request: FBSTeamsRequest | None = None, /, **filters: object
|
|
97
|
+
) -> FrameT:
|
|
98
|
+
"""Return FBS teams as the selected DataFrame type.
|
|
99
|
+
|
|
100
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
101
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
102
|
+
:return: Eager frame containing validated ``Team`` rows.
|
|
103
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
104
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
105
|
+
"""
|
|
106
|
+
endpoint = "/teams/fbs"
|
|
107
|
+
validated = _resolve_request(
|
|
108
|
+
endpoint=endpoint,
|
|
109
|
+
request_type=FBSTeamsRequest,
|
|
110
|
+
request=request,
|
|
111
|
+
filters=filters,
|
|
112
|
+
)
|
|
113
|
+
rows = await self._executor.fetch_many(
|
|
114
|
+
endpoint=endpoint, request=validated, response_adapter=_TEAM_ROWS
|
|
115
|
+
)
|
|
116
|
+
return self._dataframe_adapter.from_models(
|
|
117
|
+
endpoint=endpoint, row_model=Team, models=rows
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
@overload
|
|
121
|
+
async def matchup(self, request: TeamMatchupRequest, /) -> FrameT: ...
|
|
122
|
+
|
|
123
|
+
@overload
|
|
124
|
+
async def matchup(
|
|
125
|
+
self,
|
|
126
|
+
request: None = None,
|
|
127
|
+
/,
|
|
128
|
+
*,
|
|
129
|
+
team1: str,
|
|
130
|
+
team2: str,
|
|
131
|
+
min_year: int | None = None,
|
|
132
|
+
max_year: int | None = None,
|
|
133
|
+
) -> FrameT: ...
|
|
134
|
+
|
|
135
|
+
async def matchup(
|
|
136
|
+
self, request: TeamMatchupRequest | None = None, /, **filters: object
|
|
137
|
+
) -> FrameT:
|
|
138
|
+
"""Return one matchup summary with nested games as a selected frame.
|
|
139
|
+
|
|
140
|
+
pandas stores ``games`` in an ``object`` column. Polars stores the same
|
|
141
|
+
logical values in a native ``List[Struct]`` column.
|
|
142
|
+
|
|
143
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
144
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
145
|
+
:return: One-row eager frame containing the validated matchup summary.
|
|
146
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
147
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
148
|
+
"""
|
|
149
|
+
endpoint = "/teams/matchup"
|
|
150
|
+
validated = _resolve_request(
|
|
151
|
+
endpoint=endpoint,
|
|
152
|
+
request_type=TeamMatchupRequest,
|
|
153
|
+
request=request,
|
|
154
|
+
filters=filters,
|
|
155
|
+
)
|
|
156
|
+
matchup = await self._executor.fetch_one(
|
|
157
|
+
endpoint=endpoint, request=validated, response_adapter=_MATCHUP
|
|
158
|
+
)
|
|
159
|
+
return self._dataframe_adapter.from_models(
|
|
160
|
+
endpoint=endpoint, row_model=Matchup, models=[matchup]
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
@overload
|
|
164
|
+
async def ats(self, request: TeamATSRequest, /) -> FrameT: ...
|
|
165
|
+
|
|
166
|
+
@overload
|
|
167
|
+
async def ats(
|
|
168
|
+
self,
|
|
169
|
+
request: None = None,
|
|
170
|
+
/,
|
|
171
|
+
*,
|
|
172
|
+
year: int,
|
|
173
|
+
conference: str | None = None,
|
|
174
|
+
team: str | None = None,
|
|
175
|
+
) -> FrameT: ...
|
|
176
|
+
|
|
177
|
+
async def ats(
|
|
178
|
+
self, request: TeamATSRequest | None = None, /, **filters: object
|
|
179
|
+
) -> FrameT:
|
|
180
|
+
"""Return team against-the-spread records as the selected frame.
|
|
181
|
+
|
|
182
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
183
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
184
|
+
:return: Eager frame containing validated ``TeamATS`` rows.
|
|
185
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
186
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
187
|
+
"""
|
|
188
|
+
endpoint = "/teams/ats"
|
|
189
|
+
validated = _resolve_request(
|
|
190
|
+
endpoint=endpoint,
|
|
191
|
+
request_type=TeamATSRequest,
|
|
192
|
+
request=request,
|
|
193
|
+
filters=filters,
|
|
194
|
+
)
|
|
195
|
+
rows = await self._executor.fetch_many(
|
|
196
|
+
endpoint=endpoint, request=validated, response_adapter=_ATS_ROWS
|
|
197
|
+
)
|
|
198
|
+
return self._dataframe_adapter.from_models(
|
|
199
|
+
endpoint=endpoint, row_model=TeamATS, models=rows
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
@overload
|
|
203
|
+
async def roster(self, request: RosterRequest, /) -> FrameT: ...
|
|
204
|
+
|
|
205
|
+
@overload
|
|
206
|
+
async def roster(
|
|
207
|
+
self,
|
|
208
|
+
request: None = None,
|
|
209
|
+
/,
|
|
210
|
+
*,
|
|
211
|
+
team: str | None = None,
|
|
212
|
+
year: int | None = None,
|
|
213
|
+
classification: _ClassificationArgument | None = None,
|
|
214
|
+
) -> FrameT: ...
|
|
215
|
+
|
|
216
|
+
async def roster(
|
|
217
|
+
self, request: RosterRequest | None = None, /, **filters: object
|
|
218
|
+
) -> FrameT:
|
|
219
|
+
"""Return historical roster players as the selected frame.
|
|
220
|
+
|
|
221
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
222
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
223
|
+
:return: Eager frame containing validated ``RosterPlayer`` rows.
|
|
224
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
225
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
226
|
+
"""
|
|
227
|
+
endpoint = "/roster"
|
|
228
|
+
validated = _resolve_request(
|
|
229
|
+
endpoint=endpoint,
|
|
230
|
+
request_type=RosterRequest,
|
|
231
|
+
request=request,
|
|
232
|
+
filters=filters,
|
|
233
|
+
)
|
|
234
|
+
rows = await self._executor.fetch_many(
|
|
235
|
+
endpoint=endpoint, request=validated, response_adapter=_ROSTER_ROWS
|
|
236
|
+
)
|
|
237
|
+
return self._dataframe_adapter.from_models(
|
|
238
|
+
endpoint=endpoint, row_model=RosterPlayer, models=rows
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
@overload
|
|
242
|
+
async def talent(self, request: TalentRequest, /) -> FrameT: ...
|
|
243
|
+
|
|
244
|
+
@overload
|
|
245
|
+
async def talent(self, request: None = None, /, *, year: int) -> FrameT: ...
|
|
246
|
+
|
|
247
|
+
async def talent(
|
|
248
|
+
self, request: TalentRequest | None = None, /, **filters: object
|
|
249
|
+
) -> FrameT:
|
|
250
|
+
"""Return team talent ratings as the selected frame.
|
|
251
|
+
|
|
252
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
253
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
254
|
+
:return: Eager frame containing validated ``TeamTalent`` rows.
|
|
255
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
256
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
257
|
+
"""
|
|
258
|
+
endpoint = "/talent"
|
|
259
|
+
validated = _resolve_request(
|
|
260
|
+
endpoint=endpoint,
|
|
261
|
+
request_type=TalentRequest,
|
|
262
|
+
request=request,
|
|
263
|
+
filters=filters,
|
|
264
|
+
)
|
|
265
|
+
rows = await self._executor.fetch_many(
|
|
266
|
+
endpoint=endpoint, request=validated, response_adapter=_TALENT_ROWS
|
|
267
|
+
)
|
|
268
|
+
return self._dataframe_adapter.from_models(
|
|
269
|
+
endpoint=endpoint, row_model=TeamTalent, models=rows
|
|
270
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Provide authoritative Pydantic models for Venues endpoints."""
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Validate responses from implemented CFBD Venues endpoints."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Venue(BaseModel):
|
|
7
|
+
"""Represent one venue or a Team's nested home location."""
|
|
8
|
+
|
|
9
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
10
|
+
|
|
11
|
+
id: int | None = Field(ge=0)
|
|
12
|
+
name: str | None
|
|
13
|
+
city: str | None
|
|
14
|
+
state: str | None
|
|
15
|
+
zip: str | None
|
|
16
|
+
country_code: str | None = Field(alias="countryCode")
|
|
17
|
+
timezone: str | None
|
|
18
|
+
latitude: float | None
|
|
19
|
+
longitude: float | None
|
|
20
|
+
elevation: str | None
|
|
21
|
+
capacity: int | None = Field(ge=0)
|
|
22
|
+
construction_year: int | None = Field(default=None, alias="constructionYear")
|
|
23
|
+
grass: bool | None = None
|
|
24
|
+
dome: bool | None = None
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Expose typed Venues endpoints through the primary client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, TypeAdapter
|
|
6
|
+
|
|
7
|
+
from cfb_data._dataframes import _DataFrameAdapter
|
|
8
|
+
from cfb_data._executor import _EndpointExecutor
|
|
9
|
+
from cfb_data.venues.models.pydantic.responses import Venue
|
|
10
|
+
|
|
11
|
+
_VENUE_ROWS = TypeAdapter(list[Venue])
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _VenuesRequest(BaseModel):
|
|
15
|
+
"""Represent the empty filter set accepted by ``GET /venues``."""
|
|
16
|
+
|
|
17
|
+
model_config = ConfigDict(extra="forbid")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
_VENUES_REQUEST = _VenuesRequest()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class VenuesResource[FrameT]:
|
|
24
|
+
"""Provide validated Venues endpoints with backend-specific frame results."""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
executor: _EndpointExecutor,
|
|
29
|
+
dataframe_adapter: _DataFrameAdapter[FrameT],
|
|
30
|
+
) -> None:
|
|
31
|
+
"""Bind the namespace to shared execution and presentation services."""
|
|
32
|
+
self._executor = executor
|
|
33
|
+
self._dataframe_adapter = dataframe_adapter
|
|
34
|
+
|
|
35
|
+
async def list(self) -> FrameT:
|
|
36
|
+
"""Return venues in upstream order as the selected DataFrame type.
|
|
37
|
+
|
|
38
|
+
:return: Eager frame containing validated ``Venue`` rows.
|
|
39
|
+
:raises CFBDError: If transport, response, or conversion fails.
|
|
40
|
+
"""
|
|
41
|
+
endpoint = "/venues"
|
|
42
|
+
rows = await self._executor.fetch_many(
|
|
43
|
+
endpoint=endpoint,
|
|
44
|
+
request=_VENUES_REQUEST,
|
|
45
|
+
response_adapter=_VENUE_ROWS,
|
|
46
|
+
)
|
|
47
|
+
return self._dataframe_adapter.from_models(
|
|
48
|
+
endpoint=endpoint,
|
|
49
|
+
row_model=Venue,
|
|
50
|
+
models=rows,
|
|
51
|
+
)
|