theoddsapi-client 1.0.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.
theoddsapi/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""theoddsapi: official Python client for TheOddsAPI (theoddsapi.com).
|
|
2
|
+
|
|
3
|
+
Zero dependencies (standard library only). One class, one method per endpoint.
|
|
4
|
+
|
|
5
|
+
from theoddsapi import TheOddsAPI
|
|
6
|
+
client = TheOddsAPI("YOUR_API_KEY")
|
|
7
|
+
odds = client.odds("baseball_mlb", oddsFormat="decimal")
|
|
8
|
+
"""
|
|
9
|
+
from .client import TheOddsAPI, TheOddsAPIError
|
|
10
|
+
|
|
11
|
+
__all__ = ["TheOddsAPI", "TheOddsAPIError"]
|
|
12
|
+
__version__ = "1.0.1"
|
theoddsapi/client.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""HTTP client for TheOddsAPI. Standard library only, retries on 5xx."""
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
import urllib.error
|
|
5
|
+
import urllib.parse
|
|
6
|
+
import urllib.request
|
|
7
|
+
|
|
8
|
+
BASE_URL = "https://api.theoddsapi.com"
|
|
9
|
+
RETRY_STATUSES = {502, 503, 504}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TheOddsAPIError(Exception):
|
|
13
|
+
"""Raised for any non-2xx API response. Carries .status and .detail."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, status, detail):
|
|
16
|
+
self.status = status
|
|
17
|
+
self.detail = detail
|
|
18
|
+
super().__init__(f"HTTP {status}: {detail}")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TheOddsAPI:
|
|
22
|
+
"""Thin client for TheOddsAPI. Docs: https://theoddsapi.com/docs/"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, api_key, base_url=BASE_URL, timeout=15, max_retries=3):
|
|
25
|
+
self.api_key = api_key
|
|
26
|
+
self.base_url = base_url.rstrip("/")
|
|
27
|
+
self.timeout = timeout
|
|
28
|
+
self.max_retries = max_retries
|
|
29
|
+
|
|
30
|
+
# ------------------------------------------------------------- core ----
|
|
31
|
+
def _get(self, path, params=None):
|
|
32
|
+
query = urllib.parse.urlencode(
|
|
33
|
+
{k: v for k, v in (params or {}).items() if v is not None})
|
|
34
|
+
url = f"{self.base_url}{path}" + (f"?{query}" if query else "")
|
|
35
|
+
req = urllib.request.Request(url, headers={
|
|
36
|
+
"x-api-key": self.api_key,
|
|
37
|
+
"User-Agent": "theoddsapi-python/1.0.1",
|
|
38
|
+
})
|
|
39
|
+
delay = 1.0
|
|
40
|
+
for attempt in range(self.max_retries + 1):
|
|
41
|
+
try:
|
|
42
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
43
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
44
|
+
except urllib.error.HTTPError as e:
|
|
45
|
+
if e.code in RETRY_STATUSES and attempt < self.max_retries:
|
|
46
|
+
time.sleep(delay)
|
|
47
|
+
delay = min(delay * 2, 30)
|
|
48
|
+
continue
|
|
49
|
+
try:
|
|
50
|
+
detail = json.loads(e.read().decode("utf-8")).get("detail", e.reason)
|
|
51
|
+
except Exception:
|
|
52
|
+
detail = e.reason
|
|
53
|
+
raise TheOddsAPIError(e.code, detail) from None
|
|
54
|
+
except urllib.error.URLError as e:
|
|
55
|
+
if attempt < self.max_retries:
|
|
56
|
+
time.sleep(delay)
|
|
57
|
+
delay = min(delay * 2, 30)
|
|
58
|
+
continue
|
|
59
|
+
raise TheOddsAPIError(0, f"connection failed: {e.reason}") from None
|
|
60
|
+
|
|
61
|
+
# -------------------------------------------------------- core data ----
|
|
62
|
+
def sports(self):
|
|
63
|
+
"""List the sports available on your plan."""
|
|
64
|
+
return self._get("/sports/")
|
|
65
|
+
|
|
66
|
+
def odds(self, sport_key, **params):
|
|
67
|
+
"""Current multi-book odds for a sport. Params: markets, regions,
|
|
68
|
+
bookmakers, event_id, commenceTimeFrom, commenceTimeTo, oddsFormat."""
|
|
69
|
+
return self._get("/odds/", {"sport_key": sport_key, **params})
|
|
70
|
+
|
|
71
|
+
def events(self, sport_key, **params):
|
|
72
|
+
"""Upcoming events for a sport."""
|
|
73
|
+
return self._get("/events/", {"sport_key": sport_key, **params})
|
|
74
|
+
|
|
75
|
+
def best_lines(self, sport_key, **params):
|
|
76
|
+
"""Best available price per outcome across all tracked books."""
|
|
77
|
+
return self._get("/best-lines/", {"sport_key": sport_key, **params})
|
|
78
|
+
|
|
79
|
+
# ---------------------------------------------- business endpoints ----
|
|
80
|
+
def props(self, sport_key, **params):
|
|
81
|
+
"""Player props (Business plan)."""
|
|
82
|
+
return self._get("/props/", {"sport_key": sport_key, **params})
|
|
83
|
+
|
|
84
|
+
def edges(self, sport_key, **params):
|
|
85
|
+
"""Pinnacle-anchored cross-book edges (Business plan)."""
|
|
86
|
+
return self._get("/edges/", {"sport_key": sport_key, **params})
|
|
87
|
+
|
|
88
|
+
def fair_odds(self, sport_key, **params):
|
|
89
|
+
"""Vig-removed fair odds from sharp-anchored consensus (Business)."""
|
|
90
|
+
return self._get("/intelligence/fair-odds", {"sport_key": sport_key, **params})
|
|
91
|
+
|
|
92
|
+
def consensus(self, sport_key, **params):
|
|
93
|
+
"""Median/mean market center per outcome (Business)."""
|
|
94
|
+
return self._get("/intelligence/consensus", {"sport_key": sport_key, **params})
|
|
95
|
+
|
|
96
|
+
def arbitrage(self, sport_key, **params):
|
|
97
|
+
"""Precomputed cross-book arbitrage opportunities (Business)."""
|
|
98
|
+
return self._get("/intelligence/arbitrage", {"sport_key": sport_key, **params})
|
|
99
|
+
|
|
100
|
+
def value_bets(self, sport_key, **params):
|
|
101
|
+
"""Value bets vs vig-removed consensus (Business)."""
|
|
102
|
+
return self._get("/intelligence/value", {"sport_key": sport_key, **params})
|
|
103
|
+
|
|
104
|
+
def historical_odds(self, sport_key, **params):
|
|
105
|
+
"""Historical odds snapshots (Business). Params: from/to (use
|
|
106
|
+
from_/to_ or pass via dict), event_id, bookmakers, market,
|
|
107
|
+
limit, offset, oddsFormat."""
|
|
108
|
+
clean = {k.rstrip("_"): v for k, v in params.items()}
|
|
109
|
+
return self._get("/historical/odds", {"sport_key": sport_key, **clean})
|
|
110
|
+
|
|
111
|
+
def historical_settlements(self, sport_key, **params):
|
|
112
|
+
"""Graded edge settlements joined with final scores (Business)."""
|
|
113
|
+
return self._get("/historical/settlements", {"sport_key": sport_key, **params})
|
|
114
|
+
|
|
115
|
+
# ------------------------------------------------------------ account --
|
|
116
|
+
def me(self):
|
|
117
|
+
"""Your key's tier and usage."""
|
|
118
|
+
return self._get("/me/")
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: theoddsapi-client
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Official Python client for TheOddsAPI: real-time sports odds from 50+ sportsbooks across 26 sports, plus player props, Pinnacle-anchored edges, fair odds, and historical snapshots.
|
|
5
|
+
Project-URL: Homepage, https://theoddsapi.com
|
|
6
|
+
Project-URL: Documentation, https://theoddsapi.com/docs/
|
|
7
|
+
Project-URL: Changelog, https://theoddsapi.com/changelog
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: api,arbitrage,betting,odds,props,sports,sportsbook
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# theoddsapi
|
|
19
|
+
|
|
20
|
+
Official Python client for [TheOddsAPI](https://theoddsapi.com): real-time sports odds from 50+ sportsbooks across 26 sports, normalized into one JSON shape, plus player props, Pinnacle-anchored edge detection, fair odds, and historical snapshots.
|
|
21
|
+
|
|
22
|
+
Zero dependencies. Python 3.8+.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
pip install theoddsapi
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from theoddsapi import TheOddsAPI
|
|
34
|
+
|
|
35
|
+
client = TheOddsAPI("YOUR_API_KEY")
|
|
36
|
+
|
|
37
|
+
for sport in client.sports()["data"]:
|
|
38
|
+
print(sport)
|
|
39
|
+
|
|
40
|
+
odds = client.odds("baseball_mlb", oddsFormat="decimal")
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Get a free key in minutes at [theoddsapi.com/start](https://theoddsapi.com/start). No credit card.
|
|
44
|
+
|
|
45
|
+
## Endpoints
|
|
46
|
+
|
|
47
|
+
Every endpoint is one method. Core data: `sports()`, `odds()`, `events()`, `best_lines()`. Business plan: `props()`, `edges()`, `fair_odds()`, `consensus()`, `arbitrage()`, `value_bets()`, `historical_odds()`, `historical_settlements()`. Account: `me()`.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
edges = client.edges("basketball_nba", min_edge=50)
|
|
51
|
+
history = client.historical_odds("baseball_mlb", bookmakers="pinnacle", limit=500)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Errors
|
|
55
|
+
|
|
56
|
+
Non-2xx responses raise `TheOddsAPIError` with `.status` and `.detail`. Transient 5xx responses are retried with exponential backoff automatically.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from theoddsapi import TheOddsAPI, TheOddsAPIError
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
client.props("baseball_mlb")
|
|
63
|
+
except TheOddsAPIError as e:
|
|
64
|
+
if e.status == 403:
|
|
65
|
+
print("Props require the Business plan:", e.detail)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Links
|
|
69
|
+
|
|
70
|
+
Full reference: [theoddsapi.com/docs](https://theoddsapi.com/docs/) · Guides: [theoddsapi.com/guides](https://theoddsapi.com/guides/) · Free calculators: [theoddsapi.com/tools](https://theoddsapi.com/tools/)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
theoddsapi/__init__.py,sha256=VpPSodf0JpJR4RDo4sYpp4BYKq8ZpBqg8cvT9VxsoJE,410
|
|
2
|
+
theoddsapi/client.py,sha256=FlFjVZWkwPBkkZIilZcLtuygWm6mKB5954YDA4d1PeU,5098
|
|
3
|
+
theoddsapi_client-1.0.1.dist-info/METADATA,sha256=eLxPnsNYqxtW4KvT1LcfpoEBrHPEXHmD4MIrGC-WbNc,2464
|
|
4
|
+
theoddsapi_client-1.0.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
5
|
+
theoddsapi_client-1.0.1.dist-info/RECORD,,
|