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,175 @@
|
|
|
1
|
+
"""Expose typed Conferences 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 Literal, 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.conferences.models.pydantic.requests import (
|
|
15
|
+
ConferenceAffiliationsRequest,
|
|
16
|
+
ConferenceChangesRequest,
|
|
17
|
+
ConferencesRequest,
|
|
18
|
+
)
|
|
19
|
+
from cfb_data.conferences.models.pydantic.responses import (
|
|
20
|
+
Conference,
|
|
21
|
+
ConferenceClassification,
|
|
22
|
+
TeamConferenceAffiliation,
|
|
23
|
+
TeamConferenceChange,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
_RequestT = TypeVar("_RequestT", bound=BaseModel)
|
|
27
|
+
_RowT = TypeVar("_RowT", bound=BaseModel)
|
|
28
|
+
type _ClassificationArgument = (
|
|
29
|
+
ConferenceClassification | Literal["fbs", "fcs", "ii", "ii/iii", "iii"]
|
|
30
|
+
)
|
|
31
|
+
_CONFERENCE_ROWS = TypeAdapter(list[Conference])
|
|
32
|
+
_CHANGE_ROWS = TypeAdapter(list[TeamConferenceChange])
|
|
33
|
+
_AFFILIATION_ROWS = TypeAdapter(list[TeamConferenceAffiliation])
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ConferencesResource[FrameT]:
|
|
37
|
+
"""Provide Conferences endpoints with backend-specific 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: ConferencesRequest, /) -> FrameT: ...
|
|
50
|
+
|
|
51
|
+
@overload
|
|
52
|
+
async def list(
|
|
53
|
+
self,
|
|
54
|
+
request: None = None,
|
|
55
|
+
/,
|
|
56
|
+
*,
|
|
57
|
+
year: int | None = None,
|
|
58
|
+
classification: _ClassificationArgument | None = None,
|
|
59
|
+
) -> FrameT: ...
|
|
60
|
+
|
|
61
|
+
async def list(
|
|
62
|
+
self, request: ConferencesRequest | None = None, /, **filters: object
|
|
63
|
+
) -> FrameT:
|
|
64
|
+
"""Return conferences as the selected DataFrame type.
|
|
65
|
+
|
|
66
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
67
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
68
|
+
:return: Eager frame containing validated ``Conference`` rows.
|
|
69
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
70
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
71
|
+
"""
|
|
72
|
+
return await self._fetch_many(
|
|
73
|
+
endpoint="/conferences",
|
|
74
|
+
request_type=ConferencesRequest,
|
|
75
|
+
request=request,
|
|
76
|
+
filters=filters,
|
|
77
|
+
response_adapter=_CONFERENCE_ROWS,
|
|
78
|
+
row_model=Conference,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@overload
|
|
82
|
+
async def changes(self, request: ConferenceChangesRequest, /) -> FrameT: ...
|
|
83
|
+
|
|
84
|
+
@overload
|
|
85
|
+
async def changes(self, request: None = None, /, *, year: int) -> FrameT: ...
|
|
86
|
+
|
|
87
|
+
async def changes(
|
|
88
|
+
self, request: ConferenceChangesRequest | None = None, /, **filters: object
|
|
89
|
+
) -> FrameT:
|
|
90
|
+
"""Return team conference changes as the selected DataFrame type.
|
|
91
|
+
|
|
92
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
93
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
94
|
+
:return: Eager frame containing validated change rows.
|
|
95
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
96
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
97
|
+
"""
|
|
98
|
+
return await self._fetch_many(
|
|
99
|
+
endpoint="/conferences/changes",
|
|
100
|
+
request_type=ConferenceChangesRequest,
|
|
101
|
+
request=request,
|
|
102
|
+
filters=filters,
|
|
103
|
+
response_adapter=_CHANGE_ROWS,
|
|
104
|
+
row_model=TeamConferenceChange,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
@overload
|
|
108
|
+
async def affiliations(
|
|
109
|
+
self, request: ConferenceAffiliationsRequest, /
|
|
110
|
+
) -> FrameT: ...
|
|
111
|
+
|
|
112
|
+
@overload
|
|
113
|
+
async def affiliations(
|
|
114
|
+
self,
|
|
115
|
+
request: None = None,
|
|
116
|
+
/,
|
|
117
|
+
*,
|
|
118
|
+
team: str | None = None,
|
|
119
|
+
conference: str | None = None,
|
|
120
|
+
year: int | None = None,
|
|
121
|
+
min_year: int | None = None,
|
|
122
|
+
max_year: int | None = None,
|
|
123
|
+
classification: _ClassificationArgument | None = None,
|
|
124
|
+
) -> FrameT: ...
|
|
125
|
+
|
|
126
|
+
async def affiliations(
|
|
127
|
+
self,
|
|
128
|
+
request: ConferenceAffiliationsRequest | None = None,
|
|
129
|
+
/,
|
|
130
|
+
**filters: object,
|
|
131
|
+
) -> FrameT:
|
|
132
|
+
"""Return historical affiliations as the selected DataFrame type.
|
|
133
|
+
|
|
134
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
135
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
136
|
+
:return: Eager frame containing validated affiliation rows.
|
|
137
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
138
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
139
|
+
"""
|
|
140
|
+
return await self._fetch_many(
|
|
141
|
+
endpoint="/conferences/affiliations",
|
|
142
|
+
request_type=ConferenceAffiliationsRequest,
|
|
143
|
+
request=request,
|
|
144
|
+
filters=filters,
|
|
145
|
+
response_adapter=_AFFILIATION_ROWS,
|
|
146
|
+
row_model=TeamConferenceAffiliation,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
async def _fetch_many(
|
|
150
|
+
self,
|
|
151
|
+
*,
|
|
152
|
+
endpoint: str,
|
|
153
|
+
request_type: type[_RequestT],
|
|
154
|
+
request: _RequestT | None,
|
|
155
|
+
filters: Mapping[str, object],
|
|
156
|
+
response_adapter: TypeAdapter[builtins.list[_RowT]],
|
|
157
|
+
row_model: type[_RowT],
|
|
158
|
+
) -> FrameT:
|
|
159
|
+
"""Validate, fetch, and convert one Conferences list route."""
|
|
160
|
+
validated = _resolve_request(
|
|
161
|
+
endpoint=endpoint,
|
|
162
|
+
request_type=request_type,
|
|
163
|
+
request=request,
|
|
164
|
+
filters=filters,
|
|
165
|
+
)
|
|
166
|
+
rows = await self._executor.fetch_many(
|
|
167
|
+
endpoint=endpoint,
|
|
168
|
+
request=validated,
|
|
169
|
+
response_adapter=response_adapter,
|
|
170
|
+
)
|
|
171
|
+
return self._dataframe_adapter.from_models(
|
|
172
|
+
endpoint=endpoint,
|
|
173
|
+
row_model=row_model,
|
|
174
|
+
models=rows,
|
|
175
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Export the supported Draft namespace and public contracts."""
|
|
2
|
+
|
|
3
|
+
from .models.pydantic import (
|
|
4
|
+
DraftPick,
|
|
5
|
+
DraftPickHometown,
|
|
6
|
+
DraftPicksRequest,
|
|
7
|
+
DraftPosition,
|
|
8
|
+
DraftTeam,
|
|
9
|
+
)
|
|
10
|
+
from .resource import DraftResource
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"DraftPick",
|
|
14
|
+
"DraftPickHometown",
|
|
15
|
+
"DraftPicksRequest",
|
|
16
|
+
"DraftPosition",
|
|
17
|
+
"DraftResource",
|
|
18
|
+
"DraftTeam",
|
|
19
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Export Draft model families."""
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Export validated Draft request and response models."""
|
|
2
|
+
|
|
3
|
+
from .requests import DraftPicksRequest
|
|
4
|
+
from .responses import DraftPick, DraftPickHometown, DraftPosition, DraftTeam
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"DraftPick",
|
|
8
|
+
"DraftPickHometown",
|
|
9
|
+
"DraftPicksRequest",
|
|
10
|
+
"DraftPosition",
|
|
11
|
+
"DraftTeam",
|
|
12
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Validate request parameters for implemented Draft endpoints."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DraftPicksRequest(BaseModel):
|
|
7
|
+
"""Validate filters accepted by ``GET /draft/picks``."""
|
|
8
|
+
|
|
9
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
10
|
+
|
|
11
|
+
year: int | None = Field(default=None, ge=1936)
|
|
12
|
+
team: str | None = Field(default=None, min_length=1)
|
|
13
|
+
school: str | None = Field(default=None, min_length=1)
|
|
14
|
+
conference: str | None = Field(default=None, min_length=1)
|
|
15
|
+
position: str | None = Field(default=None, min_length=1)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = ["DraftPicksRequest"]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Validate responses from implemented CFBD Draft endpoints."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class _ResponseModel(BaseModel):
|
|
7
|
+
"""Apply the upstream closed-object contract to Draft responses."""
|
|
8
|
+
|
|
9
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DraftTeam(_ResponseModel):
|
|
13
|
+
"""Represent an NFL team present in the historical draft data."""
|
|
14
|
+
|
|
15
|
+
location: str
|
|
16
|
+
nickname: str | None
|
|
17
|
+
display_name: str | None = Field(alias="displayName")
|
|
18
|
+
logo: str | None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DraftPosition(_ResponseModel):
|
|
22
|
+
"""Represent an NFL Draft position category."""
|
|
23
|
+
|
|
24
|
+
name: str
|
|
25
|
+
abbreviation: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DraftPickHometown(_ResponseModel):
|
|
29
|
+
"""Represent the recorded hometown of an NFL Draft pick."""
|
|
30
|
+
|
|
31
|
+
city: str | None
|
|
32
|
+
state: str | None
|
|
33
|
+
country: str | None
|
|
34
|
+
latitude: str | None
|
|
35
|
+
longitude: str | None
|
|
36
|
+
county_fips: str | None = Field(alias="countyFips")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DraftPick(_ResponseModel):
|
|
40
|
+
"""Represent one historical NFL Draft selection."""
|
|
41
|
+
|
|
42
|
+
college_athlete_id: int | None = Field(alias="collegeAthleteId", gt=0)
|
|
43
|
+
nfl_athlete_id: int = Field(alias="nflAthleteId", gt=0)
|
|
44
|
+
college_id: int = Field(alias="collegeId", gt=0)
|
|
45
|
+
college_team: str = Field(alias="collegeTeam")
|
|
46
|
+
college_conference: str | None = Field(alias="collegeConference")
|
|
47
|
+
nfl_team_id: int = Field(alias="nflTeamId", gt=0)
|
|
48
|
+
nfl_team: str = Field(alias="nflTeam")
|
|
49
|
+
year: int = Field(ge=1936)
|
|
50
|
+
overall: int = Field(gt=0)
|
|
51
|
+
round: int = Field(gt=0)
|
|
52
|
+
pick: int = Field(gt=0)
|
|
53
|
+
name: str
|
|
54
|
+
position: str
|
|
55
|
+
height: float | None = Field(ge=0)
|
|
56
|
+
weight: int | None = Field(ge=0)
|
|
57
|
+
pre_draft_ranking: int | None = Field(alias="preDraftRanking", gt=0)
|
|
58
|
+
pre_draft_position_ranking: int | None = Field(
|
|
59
|
+
alias="preDraftPositionRanking", gt=0
|
|
60
|
+
)
|
|
61
|
+
pre_draft_grade: int | None = Field(alias="preDraftGrade", ge=0)
|
|
62
|
+
hometown_info: DraftPickHometown = Field(alias="hometownInfo")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
__all__ = ["DraftPick", "DraftPickHometown", "DraftPosition", "DraftTeam"]
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Expose typed Draft endpoints through the primary client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TypeVar, 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.draft.models.pydantic.requests import DraftPicksRequest
|
|
13
|
+
from cfb_data.draft.models.pydantic.responses import (
|
|
14
|
+
DraftPick,
|
|
15
|
+
DraftPosition,
|
|
16
|
+
DraftTeam,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
_RowT = TypeVar("_RowT", bound=BaseModel)
|
|
20
|
+
|
|
21
|
+
_DRAFT_TEAM_ROWS = TypeAdapter(list[DraftTeam])
|
|
22
|
+
_DRAFT_POSITION_ROWS = TypeAdapter(list[DraftPosition])
|
|
23
|
+
_DRAFT_PICK_ROWS = TypeAdapter(list[DraftPick])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _EmptyRequest(BaseModel):
|
|
27
|
+
"""Represent an endpoint that accepts no filters."""
|
|
28
|
+
|
|
29
|
+
model_config = ConfigDict(extra="forbid")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_EMPTY_REQUEST = _EmptyRequest()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DraftResource[FrameT]:
|
|
36
|
+
"""Provide validated Draft endpoints with selected frame results."""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
executor: _EndpointExecutor,
|
|
41
|
+
dataframe_adapter: _DataFrameAdapter[FrameT],
|
|
42
|
+
) -> None:
|
|
43
|
+
"""Bind the namespace to shared execution and presentation services."""
|
|
44
|
+
self._executor = executor
|
|
45
|
+
self._dataframe_adapter = dataframe_adapter
|
|
46
|
+
|
|
47
|
+
async def teams(self) -> FrameT:
|
|
48
|
+
"""Return NFL teams represented in historical draft data.
|
|
49
|
+
|
|
50
|
+
:return: Eager frame containing validated NFL team rows.
|
|
51
|
+
:raises CFBDError: If transport, response, or conversion fails.
|
|
52
|
+
"""
|
|
53
|
+
return await self._fetch_without_filters(
|
|
54
|
+
endpoint="/draft/teams",
|
|
55
|
+
response_adapter=_DRAFT_TEAM_ROWS,
|
|
56
|
+
row_model=DraftTeam,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
async def positions(self) -> FrameT:
|
|
60
|
+
"""Return position categories used in NFL Draft data.
|
|
61
|
+
|
|
62
|
+
:return: Eager frame containing validated position rows.
|
|
63
|
+
:raises CFBDError: If transport, response, or conversion fails.
|
|
64
|
+
"""
|
|
65
|
+
return await self._fetch_without_filters(
|
|
66
|
+
endpoint="/draft/positions",
|
|
67
|
+
response_adapter=_DRAFT_POSITION_ROWS,
|
|
68
|
+
row_model=DraftPosition,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
@overload
|
|
72
|
+
async def picks(self, request: DraftPicksRequest, /) -> FrameT: ...
|
|
73
|
+
|
|
74
|
+
@overload
|
|
75
|
+
async def picks(
|
|
76
|
+
self,
|
|
77
|
+
request: None = None,
|
|
78
|
+
/,
|
|
79
|
+
*,
|
|
80
|
+
year: int | None = None,
|
|
81
|
+
team: str | None = None,
|
|
82
|
+
school: str | None = None,
|
|
83
|
+
conference: str | None = None,
|
|
84
|
+
position: str | None = None,
|
|
85
|
+
) -> FrameT: ...
|
|
86
|
+
|
|
87
|
+
async def picks(
|
|
88
|
+
self, request: DraftPicksRequest | None = None, /, **filters: object
|
|
89
|
+
) -> FrameT:
|
|
90
|
+
"""Return historical NFL Draft picks.
|
|
91
|
+
|
|
92
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
93
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
94
|
+
:return: Eager frame containing validated draft-pick rows.
|
|
95
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
96
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
97
|
+
"""
|
|
98
|
+
endpoint = "/draft/picks"
|
|
99
|
+
validated = _resolve_request(
|
|
100
|
+
endpoint=endpoint,
|
|
101
|
+
request_type=DraftPicksRequest,
|
|
102
|
+
request=request,
|
|
103
|
+
filters=filters,
|
|
104
|
+
)
|
|
105
|
+
rows = await self._executor.fetch_many(
|
|
106
|
+
endpoint=endpoint,
|
|
107
|
+
request=validated,
|
|
108
|
+
response_adapter=_DRAFT_PICK_ROWS,
|
|
109
|
+
)
|
|
110
|
+
return self._dataframe_adapter.from_models(
|
|
111
|
+
endpoint=endpoint, row_model=DraftPick, models=rows
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
async def _fetch_without_filters(
|
|
115
|
+
self,
|
|
116
|
+
*,
|
|
117
|
+
endpoint: str,
|
|
118
|
+
response_adapter: TypeAdapter[list[_RowT]],
|
|
119
|
+
row_model: type[_RowT],
|
|
120
|
+
) -> FrameT:
|
|
121
|
+
"""Fetch and tabularize one filterless Draft endpoint."""
|
|
122
|
+
rows = await self._executor.fetch_many(
|
|
123
|
+
endpoint=endpoint,
|
|
124
|
+
request=_EMPTY_REQUEST,
|
|
125
|
+
response_adapter=response_adapter,
|
|
126
|
+
)
|
|
127
|
+
return self._dataframe_adapter.from_models(
|
|
128
|
+
endpoint=endpoint, row_model=row_model, models=rows
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
__all__ = ["DraftResource"]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Export the supported Drives namespace and request contract."""
|
|
2
|
+
|
|
3
|
+
from cfb_data.enums import Classification, SeasonType
|
|
4
|
+
|
|
5
|
+
from .models.pydantic.requests import DrivesRequest
|
|
6
|
+
from .resource import DrivesResource
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Classification",
|
|
10
|
+
"DrivesRequest",
|
|
11
|
+
"DrivesResource",
|
|
12
|
+
"SeasonType",
|
|
13
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Provide authoritative Pydantic models for Drives endpoints."""
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Pydantic models for drives endpoints."""
|
|
2
|
+
|
|
3
|
+
from .requests import (
|
|
4
|
+
DrivesRequest,
|
|
5
|
+
)
|
|
6
|
+
from .responses import (
|
|
7
|
+
Drive,
|
|
8
|
+
DriveTime,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
# Request models
|
|
13
|
+
"DrivesRequest",
|
|
14
|
+
# Response models
|
|
15
|
+
"DriveTime",
|
|
16
|
+
"Drive",
|
|
17
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Validate request parameters for the implemented Drives endpoint."""
|
|
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 DrivesRequest(BaseModel):
|
|
11
|
+
"""Validate filters accepted by ``GET /drives``.
|
|
12
|
+
|
|
13
|
+
:param year: Required season year.
|
|
14
|
+
:param season_type: Optional season phase.
|
|
15
|
+
:param week: Optional non-negative season week.
|
|
16
|
+
:param team: Team appearing on offense or defense.
|
|
17
|
+
:param offense: Offensive-team selector.
|
|
18
|
+
:param defense: Defensive-team selector.
|
|
19
|
+
:param conference: Conference appearing on offense or defense.
|
|
20
|
+
:param offense_conference: Offensive-team conference selector.
|
|
21
|
+
:param defense_conference: Defensive-team conference selector.
|
|
22
|
+
:param classification: Division classification selector.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
26
|
+
|
|
27
|
+
year: int = Field(ge=1869)
|
|
28
|
+
season_type: SeasonType | None = Field(default=None, alias="seasonType")
|
|
29
|
+
week: int | None = Field(default=None, ge=0)
|
|
30
|
+
team: str | None = None
|
|
31
|
+
offense: str | None = None
|
|
32
|
+
defense: str | None = None
|
|
33
|
+
conference: str | None = None
|
|
34
|
+
offense_conference: str | None = Field(
|
|
35
|
+
default=None,
|
|
36
|
+
alias="offenseConference",
|
|
37
|
+
)
|
|
38
|
+
defense_conference: str | None = Field(
|
|
39
|
+
default=None,
|
|
40
|
+
alias="defenseConference",
|
|
41
|
+
)
|
|
42
|
+
classification: Classification | None = None
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Pydantic models for drives endpoint responses."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DriveTime(BaseModel):
|
|
9
|
+
"""Time remaining in a period."""
|
|
10
|
+
|
|
11
|
+
seconds: int | None = Field(ge=0)
|
|
12
|
+
minutes: int | None = Field(ge=0)
|
|
13
|
+
|
|
14
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Drive(BaseModel):
|
|
18
|
+
"""Drive data model for `/drives` endpoint."""
|
|
19
|
+
|
|
20
|
+
offense: str = Field(alias="offense")
|
|
21
|
+
offense_conference: str | None = Field(alias="offenseConference")
|
|
22
|
+
defense: str = Field(alias="defense")
|
|
23
|
+
defense_conference: str | None = Field(alias="defenseConference")
|
|
24
|
+
game_id: int = Field(alias="gameId", ge=0)
|
|
25
|
+
id: str = Field(alias="id")
|
|
26
|
+
drive_number: int | None = Field(alias="driveNumber", ge=0)
|
|
27
|
+
scoring: bool = Field(alias="scoring")
|
|
28
|
+
start_period: int = Field(alias="startPeriod", ge=0)
|
|
29
|
+
start_yardline: int = Field(alias="startYardline", ge=0)
|
|
30
|
+
start_yards_to_goal: int = Field(alias="startYardsToGoal", ge=0)
|
|
31
|
+
start_time: DriveTime = Field(alias="startTime")
|
|
32
|
+
end_period: int = Field(alias="endPeriod", ge=0)
|
|
33
|
+
end_yardline: int = Field(alias="endYardline", ge=0)
|
|
34
|
+
end_yards_to_goal: int = Field(alias="endYardsToGoal", ge=0)
|
|
35
|
+
end_time: DriveTime = Field(alias="endTime")
|
|
36
|
+
elapsed: DriveTime
|
|
37
|
+
plays: int = Field(alias="plays", ge=0)
|
|
38
|
+
yards: int = Field(alias="yards")
|
|
39
|
+
drive_result: str = Field(alias="driveResult")
|
|
40
|
+
is_home_offense: bool = Field(alias="isHomeOffense")
|
|
41
|
+
start_offense_score: int = Field(alias="startOffenseScore", ge=0)
|
|
42
|
+
start_defense_score: int = Field(alias="startDefenseScore", ge=0)
|
|
43
|
+
end_offense_score: int = Field(alias="endOffenseScore", ge=0)
|
|
44
|
+
end_defense_score: int = Field(alias="endDefenseScore", ge=0)
|
|
45
|
+
|
|
46
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Expose typed drive 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.drives.models.pydantic.requests import DrivesRequest
|
|
13
|
+
from cfb_data.drives.models.pydantic.responses import Drive
|
|
14
|
+
from cfb_data.enums import Classification, SeasonType
|
|
15
|
+
|
|
16
|
+
type _SeasonTypeArgument = (
|
|
17
|
+
SeasonType
|
|
18
|
+
| Literal[
|
|
19
|
+
"regular",
|
|
20
|
+
"postseason",
|
|
21
|
+
"both",
|
|
22
|
+
"allstar",
|
|
23
|
+
"spring_regular",
|
|
24
|
+
"spring_postseason",
|
|
25
|
+
]
|
|
26
|
+
)
|
|
27
|
+
type _ClassificationArgument = Classification | Literal["fbs", "fcs", "ii", "iii"]
|
|
28
|
+
_DRIVE_ROWS = TypeAdapter(list[Drive])
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DrivesResource[FrameT]:
|
|
32
|
+
"""Provide validated Drives endpoints with backend-specific frame results."""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
executor: _EndpointExecutor,
|
|
37
|
+
dataframe_adapter: _DataFrameAdapter[FrameT],
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Bind the namespace to shared execution and presentation services."""
|
|
40
|
+
self._executor = executor
|
|
41
|
+
self._dataframe_adapter = dataframe_adapter
|
|
42
|
+
|
|
43
|
+
@overload
|
|
44
|
+
async def list(self, request: DrivesRequest, /) -> FrameT: ...
|
|
45
|
+
|
|
46
|
+
@overload
|
|
47
|
+
async def list(
|
|
48
|
+
self,
|
|
49
|
+
request: None = None,
|
|
50
|
+
/,
|
|
51
|
+
*,
|
|
52
|
+
year: int,
|
|
53
|
+
season_type: _SeasonTypeArgument | None = None,
|
|
54
|
+
week: int | None = None,
|
|
55
|
+
team: str | None = None,
|
|
56
|
+
offense: str | None = None,
|
|
57
|
+
defense: str | None = None,
|
|
58
|
+
conference: str | None = None,
|
|
59
|
+
offense_conference: str | None = None,
|
|
60
|
+
defense_conference: str | None = None,
|
|
61
|
+
classification: _ClassificationArgument | None = None,
|
|
62
|
+
) -> FrameT: ...
|
|
63
|
+
|
|
64
|
+
async def list(
|
|
65
|
+
self,
|
|
66
|
+
request: DrivesRequest | None = None,
|
|
67
|
+
/,
|
|
68
|
+
**filters: object,
|
|
69
|
+
) -> FrameT:
|
|
70
|
+
"""Return drives in upstream order as the selected DataFrame type.
|
|
71
|
+
|
|
72
|
+
:param request: Validated request model, mutually exclusive with filters.
|
|
73
|
+
:param filters: Explicit snake-case endpoint filters.
|
|
74
|
+
:return: Eager frame containing validated ``Drive`` rows.
|
|
75
|
+
:raises TypeError: If request styles are mixed or the model type is wrong.
|
|
76
|
+
:raises CFBDError: If request, transport, response, or conversion fails.
|
|
77
|
+
"""
|
|
78
|
+
endpoint = "/drives"
|
|
79
|
+
validated = _resolve_request(
|
|
80
|
+
endpoint=endpoint,
|
|
81
|
+
request_type=DrivesRequest,
|
|
82
|
+
request=request,
|
|
83
|
+
filters=filters,
|
|
84
|
+
)
|
|
85
|
+
rows = await self._executor.fetch_many(
|
|
86
|
+
endpoint=endpoint,
|
|
87
|
+
request=validated,
|
|
88
|
+
response_adapter=_DRIVE_ROWS,
|
|
89
|
+
)
|
|
90
|
+
return self._dataframe_adapter.from_models(
|
|
91
|
+
endpoint=endpoint,
|
|
92
|
+
row_model=Drive,
|
|
93
|
+
models=rows,
|
|
94
|
+
)
|