Skip to content

Commit edbd000

Browse files
Mlaz-codeclaude
andcommitted
feat: v0.2.0 — use canonical field names with backwards compat
Rename EVOpportunity model fields to match canonical API names: - ev_percent → ev_percentage - true_probability → fair_probability - devig_book → sharp_book - kelly_fraction → kelly_percent Uses Pydantic AliasChoices so the SDK accepts both old and new field names from API responses, maintaining full backwards compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5b3995b commit edbd000

11 files changed

Lines changed: 40 additions & 28 deletions

File tree

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ for arb in arbs.data:
2727
# --- +EV opportunities ---
2828
evs = client.ev.get(min_ev=3.0, sport="basketball")
2929
for opp in evs.data:
30-
print(f"+{opp.ev_percent:.1f}% EV on {opp.selection} @ {opp.sportsbook}")
31-
if opp.kelly_fraction:
32-
print(f" Kelly: {opp.kelly_fraction:.1%} of bankroll")
30+
print(f"+{opp.ev_percentage:.1f}% EV on {opp.selection} @ {opp.sportsbook}")
31+
if opp.kelly_percent:
32+
print(f" Kelly: {opp.kelly_percent:.1%} of bankroll")
3333

3434
# --- Best odds across books ---
3535
odds = client.odds.best(league="nba", market="moneyline")
@@ -47,7 +47,7 @@ stream = client.stream.opportunities(league="nba")
4747
@stream.on("ev:detected")
4848
def on_ev(data):
4949
for opp in data:
50-
print(f"+EV: {opp['selection']} {opp['ev_percent']}% @ {opp['sportsbook']}")
50+
print(f"+EV: {opp['selection']} {opp['ev_percentage']}% @ {opp['sportsbook']}")
5151

5252
@stream.on("arb:detected")
5353
def on_arb(data):

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "sharpapi"
7-
version = "0.1.0"
7+
version = "0.2.0"
88
description = "Official Python SDK for the SharpAPI real-time sports betting odds API"
99
readme = "README.md"
1010
license = "MIT"
@@ -56,3 +56,5 @@ testpaths = ["tests"]
5656
[tool.pyright]
5757
pythonVersion = "3.9"
5858
typeCheckingMode = "standard"
59+
venvPath = "."
60+
venv = ".venv"

src/sharpapi/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# +EV opportunities
1515
evs = client.ev.get(min_ev=3.0, league="nba")
1616
for opp in evs.data:
17-
print(f"+{opp.ev_percent}% on {opp.selection} @ {opp.sportsbook}")
17+
print(f"+{opp.ev_percentage}% on {opp.selection} @ {opp.sportsbook}")
1818
"""
1919

2020
from .async_client import AsyncSharpAPI
@@ -51,7 +51,7 @@
5151
from .streaming import EventStream
5252
from ._utils import american_to_decimal, american_to_probability, decimal_to_american
5353

54-
__version__ = "0.1.0"
54+
__version__ = "0.2.0"
5555

5656
__all__ = [
5757
# Clients

src/sharpapi/_base.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
from __future__ import annotations
44

5-
from typing import Any
6-
75
import httpx
86

97
from .exceptions import (
@@ -17,7 +15,7 @@
1715

1816
DEFAULT_BASE_URL = "https://api.sharpapi.io"
1917
DEFAULT_TIMEOUT = 30.0
20-
USER_AGENT = "sharpapi-python/0.1.0"
18+
USER_AGENT = "sharpapi-python/0.2.0"
2119

2220

2321
def parse_response(raw: dict, model_class: type) -> APIResponse:

src/sharpapi/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class SharpAPI:
5252
# Get +EV opportunities
5353
evs = client.ev.get(min_ev=3.0, league="nba")
5454
for opp in evs.data:
55-
print(f"+{opp.ev_percent}% on {opp.selection} @ {opp.sportsbook}")
55+
print(f"+{opp.ev_percentage}% on {opp.selection} @ {opp.sportsbook}")
5656
5757
# Stream real-time updates
5858
stream = client.stream.opportunities(league="nba")

src/sharpapi/models.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from typing import Any, Generic, List, Optional, TypeVar
66

7-
from pydantic import BaseModel, Field
7+
from pydantic import AliasChoices, BaseModel, Field
88

99
T = TypeVar("T")
1010

@@ -171,14 +171,22 @@ class EVOpportunity(BaseModel):
171171
odds_american: int | float
172172
odds_decimal: float
173173
no_vig_odds: Optional[float] = None
174-
true_probability: Optional[float] = None
175-
ev_percent: float = Field(alias="ev_percent")
176-
kelly_fraction: Optional[float] = None
174+
fair_probability: Optional[float] = Field(
175+
None, validation_alias=AliasChoices("fair_probability", "true_probability")
176+
)
177+
ev_percentage: float = Field(
178+
validation_alias=AliasChoices("ev_percentage", "ev_percent")
179+
)
180+
kelly_percent: Optional[float] = Field(
181+
None, validation_alias=AliasChoices("kelly_percent", "kelly_fraction")
182+
)
177183
confidence_score: Optional[float] = None
178184
book_count: Optional[int] = None
179185
market_width: Optional[float] = None
180186
devig_method: Optional[str] = None
181-
devig_book: Optional[str] = None
187+
sharp_book: Optional[str] = Field(
188+
None, validation_alias=AliasChoices("sharp_book", "devig_book")
189+
)
182190
sharp_odds_american: Optional[int | float] = None
183191
sharp_odds_decimal: Optional[float] = None
184192
line: Optional[float] = None

tests/conftest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,17 @@
9191
"odds_american": -105,
9292
"odds_decimal": 1.952,
9393
"no_vig_odds": 1.912,
94+
"fair_probability": 0.523,
9495
"true_probability": 0.523,
96+
"ev_percentage": 4.2,
9597
"ev_percent": 4.2,
98+
"kelly_percent": 0.021,
9699
"kelly_fraction": 0.021,
97100
"confidence_score": 87,
98101
"book_count": 8,
99102
"market_width": 0.043,
100103
"devig_method": "power",
104+
"sharp_book": "pinnacle",
101105
"devig_book": "pinnacle",
102106
"is_live": False,
103107
"possibly_stale": False,

tests/test_async_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ async def test_get_ev(self):
174174
result = await client.ev.get(min_ev=3.0)
175175
assert len(result.data) == 1
176176
assert isinstance(result.data[0], EVOpportunity)
177-
assert result.data[0].ev_percent == 4.2
177+
assert result.data[0].ev_percentage == 4.2
178178

179179

180180
# =============================================================================

tests/test_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,8 @@ def test_get_ev(self):
260260
assert len(result.data) == 1
261261
ev = result.data[0]
262262
assert isinstance(ev, EVOpportunity)
263-
assert ev.ev_percent == 4.2
264-
assert ev.kelly_fraction == 0.021
263+
assert ev.ev_percentage == 4.2
264+
assert ev.kelly_percent == 0.021
265265

266266

267267
class TestMiddlesResource:

tests/test_dataframe.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def test_ev_dataframe(self):
2222
result = parse_response(EV_RESPONSE, EVOpportunity)
2323
df = result.to_dataframe()
2424
assert len(df) == 1
25-
assert df.iloc[0]["ev_percent"] == 4.2
25+
assert df.iloc[0]["ev_percentage"] == 4.2
2626
assert df.iloc[0]["sportsbook"] == "draftkings"
2727

2828
def test_flattens_nested_dicts(self):
@@ -60,24 +60,24 @@ def test_multiple_rows(self):
6060
"id": "ev_1", "sport": "basketball", "league": "nba",
6161
"selection": "A", "sportsbook": "dk",
6262
"odds_american": -110, "odds_decimal": 1.909,
63-
"ev_percent": 4.2, "possibly_stale": False, "warnings": [],
63+
"ev_percentage": 4.2, "possibly_stale": False, "warnings": [],
6464
},
6565
{
6666
"id": "ev_2", "sport": "basketball", "league": "nba",
6767
"selection": "B", "sportsbook": "fd",
6868
"odds_american": 130, "odds_decimal": 2.3,
69-
"ev_percent": 2.8, "possibly_stale": False, "warnings": [],
69+
"ev_percentage": 2.8, "possibly_stale": False, "warnings": [],
7070
},
7171
],
7272
}
7373
result = parse_response(multi, EVOpportunity)
7474
df = result.to_dataframe()
7575
assert len(df) == 2
76-
assert list(df["ev_percent"]) == [4.2, 2.8]
76+
assert list(df["ev_percentage"]) == [4.2, 2.8]
7777
assert list(df["sportsbook"]) == ["dk", "fd"]
7878

7979
def test_dataframe_dtypes(self):
8080
result = parse_response(EV_RESPONSE, EVOpportunity)
8181
df = result.to_dataframe()
82-
assert df["ev_percent"].dtype == "float64"
82+
assert df["ev_percentage"].dtype == "float64"
8383
assert "str" in str(df["sportsbook"].dtype).lower() or df["sportsbook"].dtype == "object"

0 commit comments

Comments
 (0)