Skip to content

Commit

Permalink
Feature: Model Pred Endpoints (#2)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
coreyjs authored Jun 11, 2024
1 parent 8b5406f commit 4925e44
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 43 deletions.
18 changes: 11 additions & 7 deletions data_golf/api/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,25 @@ def player_list(self, format: str = "json") -> List[dict]:
"""
return self.client.get(resource="/get-player-list", format=format)

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

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

return self.client.get(
resource="/field-updates", params={"tour": tour}, format=f_format
)
142 changes: 142 additions & 0 deletions data_golf/api/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
class Model:
def __init__(self, client):
self.client = client
self._path = "/preds"

def rankings(self, f_format: str = "json") -> dict:
"""
Returns top 500 players according to DG model predictions.
:return: dict
"""
return self.client.get(
resource=f"{self._path}/get-dg-rankings", format=f_format
)

def pre_tournament_pred(
self,
tour: str = "pga",
add_position: str = None,
dead_heat: bool = True,
odds_format: str = "percent",
f_format: str = "json",
) -> dict:
"""
:param tour: pga, euro, kft, opp, alt
:param add_position: 1, 2, 3 (csv separated values)
:param dead_heat: bool - Adjust odds for dead-heat rules.
:param odds_format: percent (default), american, decimal, fraction
:param f_format: json (default)
:return:
"""
query_p = {"tour": tour}
if add_position:
query_p["add_position"] = add_position

query_p["dead_heat"] = "yes" if dead_heat else "no"
query_p["odds_format"] = odds_format

return self.client.get(
resource=f"{self._path}/pre-tournament?", params=query_p, format=f_format
)

def pre_tournament_pred_archive(
self,
event_id: str = None,
year: str = None,
odd_format: str = "percent",
f_format="json",
) -> dict:
"""
Returns pre-tournament predictions for a specific event or year.
:param event_id: The event id for the tournament.
:param year: The year for the tournament.
:param odd_format: percent (default), american, decimal, fraction
:param f_format: json (default)
:return: dict
"""

query_p = {}

if event_id:
query_p["event_id"] = event_id
if year:
query_p["year"] = year
query_p["odds_format"] = odd_format
query_p["file_format"] = f_format

return self.client.get(
resource=f"{self._path}/pre-tournament-archive",
params=query_p,
format=f_format,
)

def player_skill_decompositions(
self, tour: str = "pga", f_format: str = "json"
) -> dict:
"""
Returns player skill decompositions for a specific tour.
:param tour: pga, euro, kft, opp, alt
:param f_format: json (default)
:return: dict
"""
query_p = {"tour": tour}
return self.client.get(
resource=f"{self._path}/player-decompositions",
params=query_p,
format=f_format,
)

def player_skill_ratings(
self, display: str = "value", f_format: str = "json"
) -> dict:
"""
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).
:param display: value, rank
:param f_format: json (default)
:return: dict
"""
query_p = {"display": display}
return self.client.get(
resource=f"{self._path}/skill-ratings",
params=query_p,
format=f_format,
)

def detailed_approach_skill(
self, period: str = "l24", f_format: str = "json"
) -> dict:
"""
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.
:param period: l24 (last 24 months) (default), l12 (last 12 months), ytd (year to date)
:param f_format: json (default)
:return: dict
"""
query_p = {"period": period}
return self.client.get(
resource=f"{self._path}/approach-skill",
params=query_p,
format=f_format,
)

def fantasy_projection(
self,
tour: str = "pga",
slate: str = "main",
site: str = "draftkings",
f_format: str = "json",
) -> dict:
"""
Returns our fantasy projections for a specific tour and slate.
:param tour: pga (default), euro, opp (opposite field PGA TOUR event), alt
:param slate: main (default), showdown, showdown_late, weekend, captain
:param site: draftkings (default), fanduel, yahoo
:param f_format: json (default)
:return: dict
"""
query_p = {"tour": tour, "slate": slate, "site": site}
return self.client.get(
resource=f"{self._path}/fantasy-projection-defaults",
params=query_p,
format=f_format,
)
2 changes: 2 additions & 0 deletions data_golf/client.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from data_golf.api.model import Model
from data_golf.config import DGConfig
from data_golf.http import HttpClient
from data_golf.api.general import General
Expand All @@ -24,6 +25,7 @@ def __init__(

# Endpoints
self.general = General(self._http_client)
self.model = Model(self._http_client)

def _validate_api_key(self, api_key: str) -> None:
"""
Expand Down
30 changes: 20 additions & 10 deletions data_golf/http.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Tuple

from data_golf.request_helpers import RequestHelpers

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

def _build_url(self, resource: str, format: str):
def _build_request(
self, resource: str, query_params: dict, format: str
) -> Tuple[str, dict]:
"""
Private method to build the URL for the Data Golf API.
:param resource:
:param format:
:return:
"""
params = [f"key={self._config.api_key}", f"file_format={format}"]
url = ""
query_params["key"] = self._config.api_key
query_params["file_format"] = format

url = f"{self._config.base_url}{resource}?"

if len(resource.split("?")) > 1:
url = f"{self._config.base_url}{resource}&{'&'.join(params)}"
else:
url = f"{self._config.base_url}{resource}?{'&'.join(params)}"
return url
return url, query_params

@RequestHelpers.prepare_request
def get(self, resource: str, format: str = "json", **kwargs) -> httpx.request:
def get(
self, resource: str, params: dict = None, format: str = "json", **kwargs
) -> httpx.request:
"""
Private method to make a get request to the Data Golf API. This wraps the lib httpx functionality.
:param params:
:param format:
:param resource:
:return:
"""
with httpx.Client(
verify=self._config.ssl_verify, timeout=self._config.timeout
) as client:
url, q = self._build_request(
resource=resource, query_params=params if params else {}, format=format
)
r: httpx.request = client.get(
url=self._build_url(resource, format), **kwargs
url=url,
params=q,
**kwargs,
)

if self._config.verbose:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "data_golf"
version = "0.2.1"
version = "0.3.0"
description = "API Wrapper for Data golf endpoints"
authors = ["Corey Schaf <cschaf@gmail.com>"]
readme = "README.md"
Expand Down
42 changes: 17 additions & 25 deletions tests/api/test_general.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@ def test_request_gets_json_header(d_m, dg_client):
def test_api_key_appends_to_request(d_m, dg_client):
dg_client.general.player_list()
d_m.assert_called_once()
assert (
d_m.call_args[1]["url"]
== "https://feeds.datagolf.com/get-player-list?key=test_key&file_format=json"
)
assert "key=test_key" in d_m.call_args[1]["url"]
assert "https://feeds.datagolf.com/get-player-list?" in d_m.call_args[1]["url"]
assert d_m.call_args[1]["params"]["key"] == "test_key"


def test_request_will_err_on_csv_format(dg_client):
Expand All @@ -30,47 +27,42 @@ def test_request_will_err_on_csv_format(dg_client):
def test_player_list(d_m, dg_client):
dg_client.general.player_list()
d_m.assert_called_once()
assert (
d_m.call_args[1]["url"]
== "https://feeds.datagolf.com/get-player-list?key=test_key&file_format=json"
)
assert "https://feeds.datagolf.com/get-player-list?" in d_m.call_args[1]["url"]
assert d_m.call_args[1]["params"]["key"] == "test_key"


@mock.patch("httpx.Client.get")
def test_tour_schedule(d_m, dg_client):
dg_client.general.tour_schedule()
d_m.assert_called_once()
assert (
d_m.call_args[1]["url"]
== "https://feeds.datagolf.com/get-schedule?tour=all&key=test_key&file_format=json"
)
assert "https://feeds.datagolf.com/get-schedule?" in d_m.call_args[1]["url"]

assert d_m.call_args[1]["params"]["tour"] == "all"
assert d_m.call_args[1]["params"]["key"] == "test_key"


@mock.patch("httpx.Client.get")
def test_tour_schedule_for_tour(d_m, dg_client):
dg_client.general.tour_schedule(tour="kft")
d_m.assert_called_once()
assert (
d_m.call_args[1]["url"]
== "https://feeds.datagolf.com/get-schedule?tour=kft&key=test_key&file_format=json"
)
assert "https://feeds.datagolf.com/get-schedule?" in d_m.call_args[1]["url"]
assert d_m.call_args[1]["params"]["tour"] == "kft"
assert d_m.call_args[1]["params"]["key"] == "test_key"


@mock.patch("httpx.Client.get")
def test_field_updates(d_m, dg_client):
dg_client.general.field_updates()
d_m.assert_called_once()
assert (
d_m.call_args[1]["url"]
== "https://feeds.datagolf.com/field-updates?key=test_key&file_format=json"
)
assert "https://feeds.datagolf.com/field-updates?" in d_m.call_args[1]["url"]
assert d_m.call_args[1]["params"]["key"] == "test_key"


@mock.patch("httpx.Client.get")
def test_field_updates_with_tour_euro(d_m, dg_client):
dg_client.general.field_updates(tour="euro")
d_m.assert_called_once()
assert (
d_m.call_args[1]["url"]
== "https://feeds.datagolf.com/field-updates?tour=euro&key=test_key&file_format=json"
)
assert "https://feeds.datagolf.com/field-updates?" in d_m.call_args[1]["url"]

assert d_m.call_args[1]["params"]["key"] == "test_key"
assert d_m.call_args[1]["params"]["tour"] == "euro"
Loading

0 comments on commit 4925e44

Please sign in to comment.