Skip to content

Commit 4925e44

Browse files
authored
Feature: Model Pred Endpoints (#2)
* Feature: Model Pred Endpoints Adds model.rankings() : Player Rankings Adds model.pre_tournament_pred : Pre Tournament Model Predictions * Feature - Model Prediction Endpoints Adds model.pre_tournament_pred_archive, model.player_skill_decompositions, model.player_skill_ratings, model.detailed_appraoch_skill, model.fantasy_projection. Change: Changes how the query_parameters are passed to the HTTP client and helpers, this is no longer a big ugly string, but a dict. Adds helpers in http.py to build final query parameter * version bump to 0.3.0
1 parent 8b5406f commit 4925e44

7 files changed

Lines changed: 279 additions & 43 deletions

File tree

data_golf/api/general.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,25 @@ def player_list(self, format: str = "json") -> List[dict]:
1212
"""
1313
return self.client.get(resource="/get-player-list", format=format)
1414

15-
def tour_schedule(self, tour: str = "all", format: str = "json") -> dict:
15+
def tour_schedule(self, tour: str = "all", f_format: str = "json") -> dict:
1616
"""
17-
17+
Current season schedule for PGA Tour, European Tour, Korn Ferry Tour, and LIV.
1818
:param tour: str optional defaults to 'all', the tour you want the schedule for. values: all, pga, euro, kft, alt, liv
19-
:param format:
19+
:param f_format:
2020
:return:
2121
"""
22-
return self.client.get(resource=f"/get-schedule?tour={tour}", format=format)
22+
return self.client.get(
23+
resource="/get-schedule", params={"tour": tour}, format=f_format
24+
)
2325

24-
def field_updates(self, tour: str = None, format: str = "json") -> List[dict]:
26+
def field_updates(self, tour: str = "pga", f_format: str = "json") -> List[dict]:
2527
"""
2628
Up-to-the-minute field updates on WDs, Monday Qualifiers, tee times, and fantasy salaries for PGA Tour,
2729
European Tour, and Korn Ferry Tour events. Includes data golf IDs and tour-specific IDs for
2830
each player in the field.
2931
:return:
3032
"""
31-
q = f"?tour={tour}" if tour else ""
32-
return self.client.get(resource=f"/field-updates{q}", format=format)
33+
34+
return self.client.get(
35+
resource="/field-updates", params={"tour": tour}, format=f_format
36+
)

data_golf/api/model.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
class Model:
2+
def __init__(self, client):
3+
self.client = client
4+
self._path = "/preds"
5+
6+
def rankings(self, f_format: str = "json") -> dict:
7+
"""
8+
Returns top 500 players according to DG model predictions.
9+
:return: dict
10+
"""
11+
return self.client.get(
12+
resource=f"{self._path}/get-dg-rankings", format=f_format
13+
)
14+
15+
def pre_tournament_pred(
16+
self,
17+
tour: str = "pga",
18+
add_position: str = None,
19+
dead_heat: bool = True,
20+
odds_format: str = "percent",
21+
f_format: str = "json",
22+
) -> dict:
23+
"""
24+
25+
:param tour: pga, euro, kft, opp, alt
26+
:param add_position: 1, 2, 3 (csv separated values)
27+
:param dead_heat: bool - Adjust odds for dead-heat rules.
28+
:param odds_format: percent (default), american, decimal, fraction
29+
:param f_format: json (default)
30+
:return:
31+
"""
32+
query_p = {"tour": tour}
33+
if add_position:
34+
query_p["add_position"] = add_position
35+
36+
query_p["dead_heat"] = "yes" if dead_heat else "no"
37+
query_p["odds_format"] = odds_format
38+
39+
return self.client.get(
40+
resource=f"{self._path}/pre-tournament?", params=query_p, format=f_format
41+
)
42+
43+
def pre_tournament_pred_archive(
44+
self,
45+
event_id: str = None,
46+
year: str = None,
47+
odd_format: str = "percent",
48+
f_format="json",
49+
) -> dict:
50+
"""
51+
Returns pre-tournament predictions for a specific event or year.
52+
:param event_id: The event id for the tournament.
53+
:param year: The year for the tournament.
54+
:param odd_format: percent (default), american, decimal, fraction
55+
:param f_format: json (default)
56+
:return: dict
57+
"""
58+
59+
query_p = {}
60+
61+
if event_id:
62+
query_p["event_id"] = event_id
63+
if year:
64+
query_p["year"] = year
65+
query_p["odds_format"] = odd_format
66+
query_p["file_format"] = f_format
67+
68+
return self.client.get(
69+
resource=f"{self._path}/pre-tournament-archive",
70+
params=query_p,
71+
format=f_format,
72+
)
73+
74+
def player_skill_decompositions(
75+
self, tour: str = "pga", f_format: str = "json"
76+
) -> dict:
77+
"""
78+
Returns player skill decompositions for a specific tour.
79+
:param tour: pga, euro, kft, opp, alt
80+
:param f_format: json (default)
81+
:return: dict
82+
"""
83+
query_p = {"tour": tour}
84+
return self.client.get(
85+
resource=f"{self._path}/player-decompositions",
86+
params=query_p,
87+
format=f_format,
88+
)
89+
90+
def player_skill_ratings(
91+
self, display: str = "value", f_format: str = "json"
92+
) -> dict:
93+
"""
94+
Returns our estimate and rank for each skill for all players with sufficient Shotlink measured rounds (at least 30 rounds in the last year or 50 in the last 2 years).
95+
:param display: value, rank
96+
:param f_format: json (default)
97+
:return: dict
98+
"""
99+
query_p = {"display": display}
100+
return self.client.get(
101+
resource=f"{self._path}/skill-ratings",
102+
params=query_p,
103+
format=f_format,
104+
)
105+
106+
def detailed_approach_skill(
107+
self, period: str = "l24", f_format: str = "json"
108+
) -> dict:
109+
"""
110+
Returns detailed player-level approach performance stats (strokes-gained per shot, proximity, GIR, good shot rate, poor shot avoidance rate) across various yardage/lie buckets.
111+
:param period: l24 (last 24 months) (default), l12 (last 12 months), ytd (year to date)
112+
:param f_format: json (default)
113+
:return: dict
114+
"""
115+
query_p = {"period": period}
116+
return self.client.get(
117+
resource=f"{self._path}/approach-skill",
118+
params=query_p,
119+
format=f_format,
120+
)
121+
122+
def fantasy_projection(
123+
self,
124+
tour: str = "pga",
125+
slate: str = "main",
126+
site: str = "draftkings",
127+
f_format: str = "json",
128+
) -> dict:
129+
"""
130+
Returns our fantasy projections for a specific tour and slate.
131+
:param tour: pga (default), euro, opp (opposite field PGA TOUR event), alt
132+
:param slate: main (default), showdown, showdown_late, weekend, captain
133+
:param site: draftkings (default), fanduel, yahoo
134+
:param f_format: json (default)
135+
:return: dict
136+
"""
137+
query_p = {"tour": tour, "slate": slate, "site": site}
138+
return self.client.get(
139+
resource=f"{self._path}/fantasy-projection-defaults",
140+
params=query_p,
141+
format=f_format,
142+
)

data_golf/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from data_golf.api.model import Model
12
from data_golf.config import DGConfig
23
from data_golf.http import HttpClient
34
from data_golf.api.general import General
@@ -24,6 +25,7 @@ def __init__(
2425

2526
# Endpoints
2627
self.general = General(self._http_client)
28+
self.model = Model(self._http_client)
2729

2830
def _validate_api_key(self, api_key: str) -> None:
2931
"""

data_golf/http.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Tuple
2+
13
from data_golf.request_helpers import RequestHelpers
24

35
import httpx
@@ -10,35 +12,43 @@ def __init__(self, config) -> None:
1012
if self._config.verbose:
1113
logging.basicConfig(level=logging.INFO)
1214

13-
def _build_url(self, resource: str, format: str):
15+
def _build_request(
16+
self, resource: str, query_params: dict, format: str
17+
) -> Tuple[str, dict]:
1418
"""
1519
Private method to build the URL for the Data Golf API.
1620
:param resource:
1721
:param format:
1822
:return:
1923
"""
20-
params = [f"key={self._config.api_key}", f"file_format={format}"]
21-
url = ""
24+
query_params["key"] = self._config.api_key
25+
query_params["file_format"] = format
26+
27+
url = f"{self._config.base_url}{resource}?"
2228

23-
if len(resource.split("?")) > 1:
24-
url = f"{self._config.base_url}{resource}&{'&'.join(params)}"
25-
else:
26-
url = f"{self._config.base_url}{resource}?{'&'.join(params)}"
27-
return url
29+
return url, query_params
2830

2931
@RequestHelpers.prepare_request
30-
def get(self, resource: str, format: str = "json", **kwargs) -> httpx.request:
32+
def get(
33+
self, resource: str, params: dict = None, format: str = "json", **kwargs
34+
) -> httpx.request:
3135
"""
3236
Private method to make a get request to the Data Golf API. This wraps the lib httpx functionality.
37+
:param params:
3338
:param format:
3439
:param resource:
3540
:return:
3641
"""
3742
with httpx.Client(
3843
verify=self._config.ssl_verify, timeout=self._config.timeout
3944
) as client:
45+
url, q = self._build_request(
46+
resource=resource, query_params=params if params else {}, format=format
47+
)
4048
r: httpx.request = client.get(
41-
url=self._build_url(resource, format), **kwargs
49+
url=url,
50+
params=q,
51+
**kwargs,
4252
)
4353

4454
if self._config.verbose:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "data_golf"
3-
version = "0.2.1"
3+
version = "0.3.0"
44
description = "API Wrapper for Data golf endpoints"
55
authors = ["Corey Schaf <cschaf@gmail.com>"]
66
readme = "README.md"

tests/api/test_general.py

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,8 @@ def test_request_gets_json_header(d_m, dg_client):
1414
def test_api_key_appends_to_request(d_m, dg_client):
1515
dg_client.general.player_list()
1616
d_m.assert_called_once()
17-
assert (
18-
d_m.call_args[1]["url"]
19-
== "https://feeds.datagolf.com/get-player-list?key=test_key&file_format=json"
20-
)
21-
assert "key=test_key" in d_m.call_args[1]["url"]
17+
assert "https://feeds.datagolf.com/get-player-list?" in d_m.call_args[1]["url"]
18+
assert d_m.call_args[1]["params"]["key"] == "test_key"
2219

2320

2421
def test_request_will_err_on_csv_format(dg_client):
@@ -30,47 +27,42 @@ def test_request_will_err_on_csv_format(dg_client):
3027
def test_player_list(d_m, dg_client):
3128
dg_client.general.player_list()
3229
d_m.assert_called_once()
33-
assert (
34-
d_m.call_args[1]["url"]
35-
== "https://feeds.datagolf.com/get-player-list?key=test_key&file_format=json"
36-
)
30+
assert "https://feeds.datagolf.com/get-player-list?" in d_m.call_args[1]["url"]
31+
assert d_m.call_args[1]["params"]["key"] == "test_key"
3732

3833

3934
@mock.patch("httpx.Client.get")
4035
def test_tour_schedule(d_m, dg_client):
4136
dg_client.general.tour_schedule()
4237
d_m.assert_called_once()
43-
assert (
44-
d_m.call_args[1]["url"]
45-
== "https://feeds.datagolf.com/get-schedule?tour=all&key=test_key&file_format=json"
46-
)
38+
assert "https://feeds.datagolf.com/get-schedule?" in d_m.call_args[1]["url"]
39+
40+
assert d_m.call_args[1]["params"]["tour"] == "all"
41+
assert d_m.call_args[1]["params"]["key"] == "test_key"
4742

4843

4944
@mock.patch("httpx.Client.get")
5045
def test_tour_schedule_for_tour(d_m, dg_client):
5146
dg_client.general.tour_schedule(tour="kft")
5247
d_m.assert_called_once()
53-
assert (
54-
d_m.call_args[1]["url"]
55-
== "https://feeds.datagolf.com/get-schedule?tour=kft&key=test_key&file_format=json"
56-
)
48+
assert "https://feeds.datagolf.com/get-schedule?" in d_m.call_args[1]["url"]
49+
assert d_m.call_args[1]["params"]["tour"] == "kft"
50+
assert d_m.call_args[1]["params"]["key"] == "test_key"
5751

5852

5953
@mock.patch("httpx.Client.get")
6054
def test_field_updates(d_m, dg_client):
6155
dg_client.general.field_updates()
6256
d_m.assert_called_once()
63-
assert (
64-
d_m.call_args[1]["url"]
65-
== "https://feeds.datagolf.com/field-updates?key=test_key&file_format=json"
66-
)
57+
assert "https://feeds.datagolf.com/field-updates?" in d_m.call_args[1]["url"]
58+
assert d_m.call_args[1]["params"]["key"] == "test_key"
6759

6860

6961
@mock.patch("httpx.Client.get")
7062
def test_field_updates_with_tour_euro(d_m, dg_client):
7163
dg_client.general.field_updates(tour="euro")
7264
d_m.assert_called_once()
73-
assert (
74-
d_m.call_args[1]["url"]
75-
== "https://feeds.datagolf.com/field-updates?tour=euro&key=test_key&file_format=json"
76-
)
65+
assert "https://feeds.datagolf.com/field-updates?" in d_m.call_args[1]["url"]
66+
67+
assert d_m.call_args[1]["params"]["key"] == "test_key"
68+
assert d_m.call_args[1]["params"]["tour"] == "euro"

0 commit comments

Comments
 (0)