sharpapi 0.1.0__tar.gz → 0.2.1__tar.gz

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.
@@ -0,0 +1,27 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ id-token: write
12
+
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.12"
19
+
20
+ - name: Install build tools
21
+ run: pip install hatch
22
+
23
+ - name: Build
24
+ run: hatch build
25
+
26
+ - name: Publish to PyPI
27
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,28 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+
23
+ - name: Install dependencies
24
+ run: |
25
+ pip install -e ".[test,pandas]"
26
+
27
+ - name: Run tests
28
+ run: pytest -v
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.so
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ .env
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ *.egg
12
+ .mypy_cache/
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sharpapi
3
- Version: 0.1.0
3
+ Version: 0.2.1
4
4
  Summary: Official Python SDK for the SharpAPI real-time sports betting odds API
5
5
  Project-URL: Homepage, https://sharpapi.io
6
6
  Project-URL: Documentation, https://docs.sharpapi.io/sdks/python
7
- Project-URL: Repository, https://github.com/sharpapi/sharpapi-python
8
- Project-URL: Changelog, https://github.com/sharpapi/sharpapi-python/releases
7
+ Project-URL: Repository, https://github.com/Sharp-API/sharpapi-python
8
+ Project-URL: Changelog, https://github.com/Sharp-API/sharpapi-python/releases
9
9
  Author-email: SharpAPI <support@sharpapi.io>
10
10
  License-Expression: MIT
11
11
  Keywords: api,arbitrage,ev,odds,pinnacle,real-time,sports-betting
@@ -60,9 +60,9 @@ for arb in arbs.data:
60
60
  # --- +EV opportunities ---
61
61
  evs = client.ev.get(min_ev=3.0, sport="basketball")
62
62
  for opp in evs.data:
63
- print(f"+{opp.ev_percent:.1f}% EV on {opp.selection} @ {opp.sportsbook}")
64
- if opp.kelly_fraction:
65
- print(f" Kelly: {opp.kelly_fraction:.1%} of bankroll")
63
+ print(f"+{opp.ev_percentage:.1f}% EV on {opp.selection} @ {opp.sportsbook}")
64
+ if opp.kelly_percent:
65
+ print(f" Kelly: {opp.kelly_percent:.1%} of bankroll")
66
66
 
67
67
  # --- Best odds across books ---
68
68
  odds = client.odds.best(league="nba", market="moneyline")
@@ -80,7 +80,7 @@ stream = client.stream.opportunities(league="nba")
80
80
  @stream.on("ev:detected")
81
81
  def on_ev(data):
82
82
  for opp in data:
83
- print(f"+EV: {opp['selection']} {opp['ev_percent']}% @ {opp['sportsbook']}")
83
+ print(f"+EV: {opp['selection']} {opp['ev_percentage']}% @ {opp['sportsbook']}")
84
84
 
85
85
  @stream.on("arb:detected")
86
86
  def on_arb(data):
@@ -118,7 +118,6 @@ client.sports.list()
118
118
  client.leagues.list(sport="basketball")
119
119
  client.sportsbooks.list()
120
120
  client.events.list(league="nba", live=True)
121
- client.events.search("Lakers")
122
121
 
123
122
  # Account
124
123
  client.account.me() # Tier, limits, features
@@ -27,9 +27,9 @@ for arb in arbs.data:
27
27
  # --- +EV opportunities ---
28
28
  evs = client.ev.get(min_ev=3.0, sport="basketball")
29
29
  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")
33
33
 
34
34
  # --- Best odds across books ---
35
35
  odds = client.odds.best(league="nba", market="moneyline")
@@ -47,7 +47,7 @@ stream = client.stream.opportunities(league="nba")
47
47
  @stream.on("ev:detected")
48
48
  def on_ev(data):
49
49
  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']}")
51
51
 
52
52
  @stream.on("arb:detected")
53
53
  def on_arb(data):
@@ -85,7 +85,6 @@ client.sports.list()
85
85
  client.leagues.list(sport="basketball")
86
86
  client.sportsbooks.list()
87
87
  client.events.list(league="nba", live=True)
88
- client.events.search("Lakers")
89
88
 
90
89
  # Account
91
90
  client.account.me() # Tier, limits, features
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sharpapi"
7
- version = "0.1.0"
7
+ version = "0.2.1"
8
8
  description = "Official Python SDK for the SharpAPI real-time sports betting odds API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -36,8 +36,8 @@ test = ["pytest>=8.0", "pytest-asyncio>=0.23", "respx>=0.21"]
36
36
  [project.urls]
37
37
  Homepage = "https://sharpapi.io"
38
38
  Documentation = "https://docs.sharpapi.io/sdks/python"
39
- Repository = "https://github.com/sharpapi/sharpapi-python"
40
- Changelog = "https://github.com/sharpapi/sharpapi-python/releases"
39
+ Repository = "https://github.com/Sharp-API/sharpapi-python"
40
+ Changelog = "https://github.com/Sharp-API/sharpapi-python/releases"
41
41
 
42
42
  [tool.hatch.build.targets.wheel]
43
43
  packages = ["src/sharpapi"]
@@ -56,3 +56,5 @@ testpaths = ["tests"]
56
56
  [tool.pyright]
57
57
  pythonVersion = "3.9"
58
58
  typeCheckingMode = "standard"
59
+ venvPath = "."
60
+ venv = ".venv"
@@ -14,7 +14,7 @@ Example::
14
14
  # +EV opportunities
15
15
  evs = client.ev.get(min_ev=3.0, league="nba")
16
16
  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}")
18
18
  """
19
19
 
20
20
  from .async_client import AsyncSharpAPI
@@ -51,7 +51,7 @@ from .models import (
51
51
  from .streaming import EventStream
52
52
  from ._utils import american_to_decimal, american_to_probability, decimal_to_american
53
53
 
54
- __version__ = "0.1.0"
54
+ __version__ = "0.2.1"
55
55
 
56
56
  __all__ = [
57
57
  # Clients
@@ -2,8 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Any
6
-
7
5
  import httpx
8
6
 
9
7
  from .exceptions import (
@@ -17,7 +15,7 @@ from .models import APIResponse, RateLimitInfo, ResponseMeta
17
15
 
18
16
  DEFAULT_BASE_URL = "https://api.sharpapi.io"
19
17
  DEFAULT_TIMEOUT = 30.0
20
- USER_AGENT = "sharpapi-python/0.1.0"
18
+ USER_AGENT = "sharpapi-python/0.2.0"
21
19
 
22
20
 
23
21
  def parse_response(raw: dict, model_class: type) -> APIResponse:
@@ -442,11 +442,6 @@ class _AsyncEventsResource:
442
442
  raw = data.get("data", data)
443
443
  return Event.model_validate(raw)
444
444
 
445
- async def search(self, query: str) -> APIResponse[list[Event]]:
446
- """Search events by name."""
447
- data = await self._client._get("/events/search", {"q": query})
448
- return parse_response(data, Event)
449
-
450
445
 
451
446
  class _AsyncAccountResource:
452
447
  def __init__(self, client: AsyncSharpAPI):
@@ -52,7 +52,7 @@ class SharpAPI:
52
52
  # Get +EV opportunities
53
53
  evs = client.ev.get(min_ev=3.0, league="nba")
54
54
  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}")
56
56
 
57
57
  # Stream real-time updates
58
58
  stream = client.stream.opportunities(league="nba")
@@ -555,11 +555,6 @@ class _EventsResource:
555
555
  raw = data.get("data", data)
556
556
  return Event.model_validate(raw)
557
557
 
558
- def search(self, query: str) -> APIResponse[list[Event]]:
559
- """Search events by name."""
560
- data = self._client._get("/events/search", {"q": query})
561
- return _parse_response(data, Event)
562
-
563
558
 
564
559
  class _AccountResource:
565
560
  def __init__(self, client: SharpAPI):
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Any, Generic, List, Optional, TypeVar
6
6
 
7
- from pydantic import BaseModel, Field
7
+ from pydantic import AliasChoices, BaseModel, Field
8
8
 
9
9
  T = TypeVar("T")
10
10
 
@@ -171,14 +171,22 @@ class EVOpportunity(BaseModel):
171
171
  odds_american: int | float
172
172
  odds_decimal: float
173
173
  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
+ )
177
183
  confidence_score: Optional[float] = None
178
184
  book_count: Optional[int] = None
179
185
  market_width: Optional[float] = None
180
186
  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
+ )
182
190
  sharp_odds_american: Optional[int | float] = None
183
191
  sharp_odds_decimal: Optional[float] = None
184
192
  line: Optional[float] = None
@@ -91,13 +91,17 @@ EV_RESPONSE = {
91
91
  "odds_american": -105,
92
92
  "odds_decimal": 1.952,
93
93
  "no_vig_odds": 1.912,
94
+ "fair_probability": 0.523,
94
95
  "true_probability": 0.523,
96
+ "ev_percentage": 4.2,
95
97
  "ev_percent": 4.2,
98
+ "kelly_percent": 0.021,
96
99
  "kelly_fraction": 0.021,
97
100
  "confidence_score": 87,
98
101
  "book_count": 8,
99
102
  "market_width": 0.043,
100
103
  "devig_method": "power",
104
+ "sharp_book": "pinnacle",
101
105
  "devig_book": "pinnacle",
102
106
  "is_live": False,
103
107
  "possibly_stale": False,
@@ -174,7 +174,7 @@ class TestAsyncOpportunities:
174
174
  result = await client.ev.get(min_ev=3.0)
175
175
  assert len(result.data) == 1
176
176
  assert isinstance(result.data[0], EVOpportunity)
177
- assert result.data[0].ev_percent == 4.2
177
+ assert result.data[0].ev_percentage == 4.2
178
178
 
179
179
 
180
180
  # =============================================================================
@@ -260,8 +260,8 @@ class TestEVResource:
260
260
  assert len(result.data) == 1
261
261
  ev = result.data[0]
262
262
  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
265
265
 
266
266
 
267
267
  class TestMiddlesResource:
@@ -346,15 +346,6 @@ class TestReferenceResources:
346
346
  result = client.events.list(league="nba")
347
347
  assert len(result.data) == 1
348
348
 
349
- @respx.mock
350
- def test_events_search(self):
351
- respx.get(f"{BASE_URL}/api/v1/events/search").mock(
352
- return_value=Response(200, json=EVENTS_RESPONSE)
353
- )
354
- with SharpAPI(API_KEY) as client:
355
- result = client.events.search("Lakers")
356
- assert len(result.data) == 1
357
-
358
349
  @respx.mock
359
350
  def test_account_me(self):
360
351
  respx.get(f"{BASE_URL}/api/v1/account").mock(
@@ -22,7 +22,7 @@ class TestToDataFrame:
22
22
  result = parse_response(EV_RESPONSE, EVOpportunity)
23
23
  df = result.to_dataframe()
24
24
  assert len(df) == 1
25
- assert df.iloc[0]["ev_percent"] == 4.2
25
+ assert df.iloc[0]["ev_percentage"] == 4.2
26
26
  assert df.iloc[0]["sportsbook"] == "draftkings"
27
27
 
28
28
  def test_flattens_nested_dicts(self):
@@ -60,24 +60,24 @@ class TestToDataFrame:
60
60
  "id": "ev_1", "sport": "basketball", "league": "nba",
61
61
  "selection": "A", "sportsbook": "dk",
62
62
  "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": [],
64
64
  },
65
65
  {
66
66
  "id": "ev_2", "sport": "basketball", "league": "nba",
67
67
  "selection": "B", "sportsbook": "fd",
68
68
  "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": [],
70
70
  },
71
71
  ],
72
72
  }
73
73
  result = parse_response(multi, EVOpportunity)
74
74
  df = result.to_dataframe()
75
75
  assert len(df) == 2
76
- assert list(df["ev_percent"]) == [4.2, 2.8]
76
+ assert list(df["ev_percentage"]) == [4.2, 2.8]
77
77
  assert list(df["sportsbook"]) == ["dk", "fd"]
78
78
 
79
79
  def test_dataframe_dtypes(self):
80
80
  result = parse_response(EV_RESPONSE, EVOpportunity)
81
81
  df = result.to_dataframe()
82
- assert df["ev_percent"].dtype == "float64"
82
+ assert df["ev_percentage"].dtype == "float64"
83
83
  assert "str" in str(df["sportsbook"].dtype).lower() or df["sportsbook"].dtype == "object"
@@ -74,7 +74,7 @@ class TestEVModel:
74
74
  result = parse_response(EV_RESPONSE, EVOpportunity)
75
75
  assert len(result.data) == 1
76
76
  ev = result.data[0]
77
- assert ev.ev_percent == 4.2
77
+ assert ev.ev_percentage == 4.2
78
78
  assert ev.selection == "PHO Suns"
79
79
  assert ev.sportsbook == "draftkings"
80
80
  assert ev.odds_american == -105
@@ -84,15 +84,15 @@ class TestEVModel:
84
84
  result = parse_response(EV_RESPONSE, EVOpportunity)
85
85
  ev = result.data[0]
86
86
  assert ev.devig_method == "power"
87
- assert ev.devig_book == "pinnacle"
87
+ assert ev.sharp_book == "pinnacle"
88
88
  assert ev.no_vig_odds == 1.912
89
- assert ev.true_probability == 0.523
89
+ assert ev.fair_probability == 0.523
90
90
 
91
91
  def test_scoring(self):
92
92
  result = parse_response(EV_RESPONSE, EVOpportunity)
93
93
  ev = result.data[0]
94
94
  assert ev.confidence_score == 87
95
- assert ev.kelly_fraction == 0.021
95
+ assert ev.kelly_percent == 0.021
96
96
  assert ev.book_count == 8
97
97
 
98
98
  def test_aliased_fields(self):
File without changes
File without changes
File without changes