sharpapi 0.2.5__py3-none-any.whl → 0.3.2__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.
- sharpapi/__init__.py +9 -1
- sharpapi/async_client.py +37 -0
- sharpapi/client.py +49 -0
- sharpapi/exceptions.py +19 -10
- sharpapi/models.py +158 -15
- {sharpapi-0.2.5.dist-info → sharpapi-0.3.2.dist-info}/METADATA +5 -4
- sharpapi-0.3.2.dist-info/RECORD +13 -0
- sharpapi-0.3.2.dist-info/licenses/LICENSE +21 -0
- sharpapi-0.2.5.dist-info/RECORD +0 -12
- {sharpapi-0.2.5.dist-info → sharpapi-0.3.2.dist-info}/WHEEL +0 -0
sharpapi/__init__.py
CHANGED
|
@@ -38,6 +38,7 @@ from .models import (
|
|
|
38
38
|
ArbitrageOpportunity,
|
|
39
39
|
ClosingOddsLine,
|
|
40
40
|
ClosingSnapshot,
|
|
41
|
+
EntityRef,
|
|
41
42
|
Event,
|
|
42
43
|
EVOpportunity,
|
|
43
44
|
GameState,
|
|
@@ -53,11 +54,14 @@ from .models import (
|
|
|
53
54
|
RateLimitInfo,
|
|
54
55
|
ResponseMeta,
|
|
55
56
|
Sport,
|
|
57
|
+
SportRef,
|
|
56
58
|
Sportsbook,
|
|
59
|
+
Team,
|
|
60
|
+
TeamRef,
|
|
57
61
|
)
|
|
58
62
|
from .streaming import EventStream
|
|
59
63
|
|
|
60
|
-
__version__ = "0.
|
|
64
|
+
__version__ = "0.3.1"
|
|
61
65
|
|
|
62
66
|
__all__ = [
|
|
63
67
|
# Clients
|
|
@@ -71,6 +75,7 @@ __all__ = [
|
|
|
71
75
|
"ArbitrageOpportunity",
|
|
72
76
|
"ClosingOddsLine",
|
|
73
77
|
"ClosingSnapshot",
|
|
78
|
+
"EntityRef",
|
|
74
79
|
"EVOpportunity",
|
|
75
80
|
"Event",
|
|
76
81
|
"GameState",
|
|
@@ -86,7 +91,10 @@ __all__ = [
|
|
|
86
91
|
"RateLimitInfo",
|
|
87
92
|
"ResponseMeta",
|
|
88
93
|
"Sport",
|
|
94
|
+
"SportRef",
|
|
89
95
|
"Sportsbook",
|
|
96
|
+
"Team",
|
|
97
|
+
"TeamRef",
|
|
90
98
|
# Streaming
|
|
91
99
|
"EventStream",
|
|
92
100
|
# Exceptions
|
sharpapi/async_client.py
CHANGED
|
@@ -29,6 +29,7 @@ from .models import (
|
|
|
29
29
|
ClosingSnapshot,
|
|
30
30
|
Event,
|
|
31
31
|
EVOpportunity,
|
|
32
|
+
GameState,
|
|
32
33
|
League,
|
|
33
34
|
LowHoldOpportunity,
|
|
34
35
|
Market,
|
|
@@ -103,6 +104,7 @@ class AsyncSharpAPI:
|
|
|
103
104
|
self.arbitrage = _AsyncArbitrageResource(self)
|
|
104
105
|
self.middles = _AsyncMiddlesResource(self)
|
|
105
106
|
self.low_hold = _AsyncLowHoldResource(self)
|
|
107
|
+
self.gamestate = _AsyncGameStateResource(self)
|
|
106
108
|
self.sports = _AsyncSportsResource(self)
|
|
107
109
|
self.leagues = _AsyncLeaguesResource(self)
|
|
108
110
|
self.sportsbooks = _AsyncSportsbooksResource(self)
|
|
@@ -426,6 +428,41 @@ class _AsyncLowHoldResource:
|
|
|
426
428
|
return parse_response(data, LowHoldOpportunity)
|
|
427
429
|
|
|
428
430
|
|
|
431
|
+
class _AsyncGameStateResource:
|
|
432
|
+
"""Async access to live game state — scores, period, clock —
|
|
433
|
+
merged across sportsbooks.
|
|
434
|
+
|
|
435
|
+
Requires the Game State add-on ($79/mo) or Enterprise tier.
|
|
436
|
+
"""
|
|
437
|
+
|
|
438
|
+
def __init__(self, client: AsyncSharpAPI):
|
|
439
|
+
self._client = client
|
|
440
|
+
|
|
441
|
+
async def get(self, sport: str | None = None) -> dict[str, dict[str, GameState]]:
|
|
442
|
+
"""Fetch the current game state.
|
|
443
|
+
|
|
444
|
+
Args:
|
|
445
|
+
sport: Limit to a single sport (e.g. ``"basketball"``).
|
|
446
|
+
Omit to fetch every sport at once.
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
Nested mapping ``{sport: {event_id: GameState}}``.
|
|
450
|
+
"""
|
|
451
|
+
path = f"/gamestate/{sport}" if sport else "/gamestate"
|
|
452
|
+
data = await self._client._get(path)
|
|
453
|
+
raw = data.get("data", {}) or {}
|
|
454
|
+
result: dict[str, dict[str, GameState]] = {}
|
|
455
|
+
for sport_key, events in raw.items():
|
|
456
|
+
if not isinstance(events, dict):
|
|
457
|
+
continue
|
|
458
|
+
result[sport_key] = {
|
|
459
|
+
eid: GameState.model_validate(state)
|
|
460
|
+
for eid, state in events.items()
|
|
461
|
+
if isinstance(state, dict)
|
|
462
|
+
}
|
|
463
|
+
return result
|
|
464
|
+
|
|
465
|
+
|
|
429
466
|
class _AsyncSportsResource:
|
|
430
467
|
def __init__(self, client: AsyncSharpAPI):
|
|
431
468
|
self._client = client
|
sharpapi/client.py
CHANGED
|
@@ -29,6 +29,7 @@ from .models import (
|
|
|
29
29
|
ClosingSnapshot,
|
|
30
30
|
Event,
|
|
31
31
|
EVOpportunity,
|
|
32
|
+
GameState,
|
|
32
33
|
League,
|
|
33
34
|
LowHoldOpportunity,
|
|
34
35
|
Market,
|
|
@@ -111,6 +112,7 @@ class SharpAPI:
|
|
|
111
112
|
self.arbitrage = _ArbitrageResource(self)
|
|
112
113
|
self.middles = _MiddlesResource(self)
|
|
113
114
|
self.low_hold = _LowHoldResource(self)
|
|
115
|
+
self.gamestate = _GameStateResource(self)
|
|
114
116
|
self.sports = _SportsResource(self)
|
|
115
117
|
self.leagues = _LeaguesResource(self)
|
|
116
118
|
self.sportsbooks = _SportsbooksResource(self)
|
|
@@ -542,6 +544,44 @@ class _LowHoldResource:
|
|
|
542
544
|
return _parse_response(data, LowHoldOpportunity)
|
|
543
545
|
|
|
544
546
|
|
|
547
|
+
class _GameStateResource:
|
|
548
|
+
"""Live game state — scores, period, clock — merged across sportsbooks.
|
|
549
|
+
|
|
550
|
+
Requires the Game State add-on ($79/mo) or Enterprise tier.
|
|
551
|
+
Pair with EV / arb / low-hold rows: those endpoints no longer carry
|
|
552
|
+
``game_state`` themselves — look up the row's ``event_id`` here.
|
|
553
|
+
"""
|
|
554
|
+
|
|
555
|
+
def __init__(self, client: SharpAPI):
|
|
556
|
+
self._client = client
|
|
557
|
+
|
|
558
|
+
def get(self, sport: str | None = None) -> dict[str, dict[str, GameState]]:
|
|
559
|
+
"""Fetch the current game state.
|
|
560
|
+
|
|
561
|
+
Args:
|
|
562
|
+
sport: Limit to a single sport (e.g. ``"basketball"``,
|
|
563
|
+
``"football"``). Omit to fetch every sport at once.
|
|
564
|
+
|
|
565
|
+
Returns:
|
|
566
|
+
Nested mapping ``{sport: {event_id: GameState}}``. Look up an
|
|
567
|
+
opportunity's state with
|
|
568
|
+
``result.get(opp.sport, {}).get(opp.event_id)``.
|
|
569
|
+
"""
|
|
570
|
+
path = f"/gamestate/{sport}" if sport else "/gamestate"
|
|
571
|
+
data = self._client._get(path)
|
|
572
|
+
raw = data.get("data", {}) or {}
|
|
573
|
+
result: dict[str, dict[str, GameState]] = {}
|
|
574
|
+
for sport_key, events in raw.items():
|
|
575
|
+
if not isinstance(events, dict):
|
|
576
|
+
continue
|
|
577
|
+
result[sport_key] = {
|
|
578
|
+
eid: GameState.model_validate(state)
|
|
579
|
+
for eid, state in events.items()
|
|
580
|
+
if isinstance(state, dict)
|
|
581
|
+
}
|
|
582
|
+
return result
|
|
583
|
+
|
|
584
|
+
|
|
545
585
|
class _SportsResource:
|
|
546
586
|
def __init__(self, client: SharpAPI):
|
|
547
587
|
self._client = client
|
|
@@ -781,6 +821,15 @@ class _StreamResource:
|
|
|
781
821
|
"market": market,
|
|
782
822
|
})
|
|
783
823
|
|
|
824
|
+
def gamestate(self) -> EventStream:
|
|
825
|
+
"""Stream live game state updates (scores, period, clock).
|
|
826
|
+
|
|
827
|
+
Emits ``gamestate:snapshot`` (initial dump on connect) and
|
|
828
|
+
``gamestate:update`` / ``gamestate:removed`` events. Requires the
|
|
829
|
+
Game State add-on or Enterprise tier.
|
|
830
|
+
"""
|
|
831
|
+
return self._build_stream("/stream/gamestate")
|
|
832
|
+
|
|
784
833
|
|
|
785
834
|
# =============================================================================
|
|
786
835
|
# Helpers
|
sharpapi/exceptions.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""SharpAPI exceptions and canonical error-code registry.
|
|
2
2
|
|
|
3
|
-
The
|
|
4
|
-
|
|
5
|
-
sync when new codes are added upstream.
|
|
3
|
+
The codes here mirror the canonical set the SharpAPI server emits.
|
|
4
|
+
Keep this file in sync when new codes are added upstream.
|
|
6
5
|
"""
|
|
7
6
|
|
|
8
7
|
from __future__ import annotations
|
|
@@ -65,9 +64,10 @@ class StreamError(SharpAPIError):
|
|
|
65
64
|
# =============================================================================
|
|
66
65
|
# Canonical error-code registry
|
|
67
66
|
#
|
|
68
|
-
# Mirrors
|
|
69
|
-
# add it here too and update the matching description. Each
|
|
70
|
-
# Python exception class that ``handle_errors`` (in
|
|
67
|
+
# Mirrors the canonical SharpAPI server error-code set. When upstream adds
|
|
68
|
+
# a new code, add it here too and update the matching description. Each
|
|
69
|
+
# code maps to the Python exception class that ``handle_errors`` (in
|
|
70
|
+
# ``_base.py``) raises for it.
|
|
71
71
|
# =============================================================================
|
|
72
72
|
|
|
73
73
|
# HTTP error codes — emitted via REST handlers (httputil.WriteJSONError).
|
|
@@ -82,6 +82,8 @@ INVALID_TOKEN = "invalid_token"
|
|
|
82
82
|
METHOD_NOT_ALLOWED = "method_not_allowed"
|
|
83
83
|
MISSING_API_KEY = "missing_api_key"
|
|
84
84
|
NOT_FOUND = "not_found"
|
|
85
|
+
NOT_READY = "not_ready"
|
|
86
|
+
OFFSET_TOO_LARGE = "offset_too_large"
|
|
85
87
|
RATE_LIMITED = "rate_limited"
|
|
86
88
|
SERVICE_UNAVAILABLE = "service_unavailable"
|
|
87
89
|
TIER_RESTRICTED = "tier_restricted"
|
|
@@ -113,6 +115,11 @@ ERROR_CODE_DESCRIPTIONS: dict[str, str] = {
|
|
|
113
115
|
METHOD_NOT_ALLOWED: "HTTP method not allowed on this endpoint.",
|
|
114
116
|
MISSING_API_KEY: "No API key provided.",
|
|
115
117
|
NOT_FOUND: "Resource not found.",
|
|
118
|
+
NOT_READY: "A required backing store is not yet ready to serve this request; retry shortly.",
|
|
119
|
+
OFFSET_TOO_LARGE: (
|
|
120
|
+
"offset exceeds the per-endpoint maximum; "
|
|
121
|
+
"use cursor-based pagination or advance `since`."
|
|
122
|
+
),
|
|
116
123
|
RATE_LIMITED: "Rate limit exceeded; see Retry-After header.",
|
|
117
124
|
SERVICE_UNAVAILABLE: "Service is temporarily unavailable.",
|
|
118
125
|
TIER_RESTRICTED: "Current subscription tier does not include this feature.",
|
|
@@ -148,6 +155,7 @@ ERROR_CODE_TO_EXCEPTION: dict[str, type[SharpAPIError]] = {
|
|
|
148
155
|
TOO_MANY_STREAMS: RateLimitedError,
|
|
149
156
|
# Validation
|
|
150
157
|
VALIDATION_ERROR: ValidationError,
|
|
158
|
+
OFFSET_TOO_LARGE: ValidationError,
|
|
151
159
|
# Streaming frames
|
|
152
160
|
WS_ALREADY_AUTHENTICATED: StreamError,
|
|
153
161
|
WS_INVALID_MESSAGE: StreamError,
|
|
@@ -160,15 +168,16 @@ ERROR_CODE_TO_EXCEPTION: dict[str, type[SharpAPIError]] = {
|
|
|
160
168
|
INTERNAL_ERROR: SharpAPIError,
|
|
161
169
|
METHOD_NOT_ALLOWED: SharpAPIError,
|
|
162
170
|
NOT_FOUND: SharpAPIError,
|
|
171
|
+
NOT_READY: SharpAPIError,
|
|
163
172
|
SERVICE_UNAVAILABLE: SharpAPIError,
|
|
164
173
|
UNKNOWN_ENDPOINT: SharpAPIError,
|
|
165
174
|
UPSTREAM_ERROR: SharpAPIError,
|
|
166
175
|
}
|
|
167
176
|
|
|
168
|
-
# Deprecated aliases. ``bad_request`` and ``invalid_request`` were both
|
|
169
|
-
# into ``validation_error``
|
|
170
|
-
# responses (or user code still checking these strings) resolve
|
|
171
|
-
#
|
|
177
|
+
# Deprecated aliases. ``bad_request`` and ``invalid_request`` were both
|
|
178
|
+
# collapsed into ``validation_error`` server-side. Kept here so that older
|
|
179
|
+
# API responses (or user code still checking these strings) resolve
|
|
180
|
+
# correctly. Will be removed after 2026-10.
|
|
172
181
|
DEPRECATED_CODE_ALIASES: dict[str, str] = {
|
|
173
182
|
"bad_request": VALIDATION_ERROR,
|
|
174
183
|
"invalid_request": VALIDATION_ERROR,
|
sharpapi/models.py
CHANGED
|
@@ -22,6 +22,67 @@ class OddsValue(BaseModel):
|
|
|
22
22
|
probability: float
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
# =============================================================================
|
|
26
|
+
# Nested reference objects
|
|
27
|
+
# =============================================================================
|
|
28
|
+
#
|
|
29
|
+
# These structured ref objects ship alongside the legacy flat fields on every
|
|
30
|
+
# odds row, opportunity row, and reference-list row. All fields are optional
|
|
31
|
+
# and additive — clients on older API versions simply receive ``None``.
|
|
32
|
+
#
|
|
33
|
+
# Wire format uses snake_case (``sport_ref``, ``league_ref``, ``market_ref``,
|
|
34
|
+
# ``sportsbook_ref``) which Python attribute names match directly.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TeamRef(BaseModel):
|
|
38
|
+
"""Structured team reference attached to ``home`` / ``away``.
|
|
39
|
+
|
|
40
|
+
``abbreviation`` is only present for ~1500 team-sport entities; absent
|
|
41
|
+
for individual-sport competitors (tennis players, MMA fighters, etc).
|
|
42
|
+
|
|
43
|
+
Optional metadata fields (``logo``, ``city``, ``mascot``, ``conference``,
|
|
44
|
+
``division``) are populated for the majority of major-league teams
|
|
45
|
+
(~93% coverage on ``logo``, similar on the rest). Unmapped rows simply
|
|
46
|
+
leave the field absent rather than emitting null.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
id: str | None = None
|
|
50
|
+
numerical_id: int | None = None
|
|
51
|
+
name: str | None = None
|
|
52
|
+
abbreviation: str | None = None
|
|
53
|
+
logo: str | None = None
|
|
54
|
+
city: str | None = None
|
|
55
|
+
mascot: str | None = None
|
|
56
|
+
conference: str | None = None
|
|
57
|
+
division: str | None = None
|
|
58
|
+
|
|
59
|
+
model_config = {"extra": "allow"}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class SportRef(BaseModel):
|
|
63
|
+
"""Structured sport reference attached to ``sport_ref``."""
|
|
64
|
+
|
|
65
|
+
id: str | None = None
|
|
66
|
+
name: str | None = None
|
|
67
|
+
numerical_id: int | None = None
|
|
68
|
+
|
|
69
|
+
model_config = {"extra": "allow"}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class EntityRef(BaseModel):
|
|
73
|
+
"""Structured reference for league / market / sportsbook objects.
|
|
74
|
+
|
|
75
|
+
Used by ``league_ref``, ``market_ref``, and ``sportsbook_ref`` on
|
|
76
|
+
every odds, opportunity, and reference row.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
id: str | None = None
|
|
80
|
+
label: str | None = None
|
|
81
|
+
numerical_id: int | None = None
|
|
82
|
+
|
|
83
|
+
model_config = {"extra": "allow"}
|
|
84
|
+
|
|
85
|
+
|
|
25
86
|
class Pagination(BaseModel):
|
|
26
87
|
limit: int
|
|
27
88
|
offset: int
|
|
@@ -60,9 +121,9 @@ class APIResponse(BaseModel, Generic[T]):
|
|
|
60
121
|
Requires ``pip install sharpapi[pandas]``.
|
|
61
122
|
|
|
62
123
|
Args:
|
|
63
|
-
flatten: If True (default), flatten nested objects
|
|
64
|
-
|
|
65
|
-
|
|
124
|
+
flatten: If True (default), flatten nested objects into
|
|
125
|
+
underscore-joined columns. Nested lists (like ``legs``)
|
|
126
|
+
remain as-is.
|
|
66
127
|
|
|
67
128
|
Returns:
|
|
68
129
|
pandas.DataFrame with one row per item in ``data``.
|
|
@@ -109,12 +170,29 @@ def _flatten_dict(d: dict, parent_key: str = "", sep: str = "_") -> dict:
|
|
|
109
170
|
|
|
110
171
|
|
|
111
172
|
class GameState(BaseModel):
|
|
112
|
-
"""Live game state.
|
|
173
|
+
"""Live game state for a single event, merged across sportsbooks.
|
|
113
174
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
175
|
+
Returned by ``/api/v1/gamestate`` and the ``gamestate`` stream channel.
|
|
176
|
+
Scores are consensus-merged with period-guarded outlier rejection;
|
|
177
|
+
period/clock are picked from the most-advanced book. Not present on
|
|
178
|
+
EV / arb / low-hold opportunity rows — correlate by ``event_id``.
|
|
179
|
+
|
|
180
|
+
``extra="allow"`` lets adapter-specific fields pass through unchanged.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
home_score: int | None = None
|
|
184
|
+
away_score: int | None = None
|
|
185
|
+
game_period: str | None = None
|
|
186
|
+
game_clock: str | None = None
|
|
187
|
+
home_team: str | None = None
|
|
188
|
+
away_team: str | None = None
|
|
189
|
+
sport: str | None = None
|
|
190
|
+
primary_book: str | None = None
|
|
191
|
+
book_count: int | None = None
|
|
192
|
+
stale: bool = False
|
|
193
|
+
aggregator_stale: bool = False
|
|
194
|
+
|
|
195
|
+
model_config = {"extra": "allow"}
|
|
118
196
|
|
|
119
197
|
|
|
120
198
|
# =============================================================================
|
|
@@ -146,10 +224,13 @@ class OddsLine(BaseModel):
|
|
|
146
224
|
deep_link: str | None = None
|
|
147
225
|
player_name: str | None = None
|
|
148
226
|
stat_category: str | None = None
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
227
|
+
# Optional structured refs (additive, non-breaking).
|
|
228
|
+
home: TeamRef | None = None
|
|
229
|
+
away: TeamRef | None = None
|
|
230
|
+
sport_ref: SportRef | None = None
|
|
231
|
+
league_ref: EntityRef | None = None
|
|
232
|
+
market_ref: EntityRef | None = None
|
|
233
|
+
sportsbook_ref: EntityRef | None = None
|
|
153
234
|
|
|
154
235
|
|
|
155
236
|
# =============================================================================
|
|
@@ -205,6 +286,13 @@ class EVOpportunity(BaseModel):
|
|
|
205
286
|
detected_at: str | None = None
|
|
206
287
|
external_event_id: str | None = None
|
|
207
288
|
selection_id: str | None = None
|
|
289
|
+
# Optional structured refs (additive, non-breaking).
|
|
290
|
+
home: TeamRef | None = None
|
|
291
|
+
away: TeamRef | None = None
|
|
292
|
+
sport_ref: SportRef | None = None
|
|
293
|
+
league_ref: EntityRef | None = None
|
|
294
|
+
market_ref: EntityRef | None = None
|
|
295
|
+
sportsbook_ref: EntityRef | None = None
|
|
208
296
|
|
|
209
297
|
model_config = {"populate_by_name": True}
|
|
210
298
|
|
|
@@ -227,6 +315,8 @@ class ArbitrageLeg(BaseModel):
|
|
|
227
315
|
external_event_id: str | None = None
|
|
228
316
|
selection_id: str | None = None
|
|
229
317
|
market_id: str | None = None
|
|
318
|
+
# Optional structured book ref on each leg.
|
|
319
|
+
sportsbook_ref: EntityRef | None = None
|
|
230
320
|
|
|
231
321
|
|
|
232
322
|
class ArbitrageOpportunity(BaseModel):
|
|
@@ -248,7 +338,6 @@ class ArbitrageOpportunity(BaseModel):
|
|
|
248
338
|
possibly_stale: bool = False
|
|
249
339
|
oldest_odds_age_seconds: float | None = None
|
|
250
340
|
warnings: list[str] = Field(default_factory=list)
|
|
251
|
-
game_state: GameState | None = None
|
|
252
341
|
ev_available: bool | None = None
|
|
253
342
|
ev_percentage: float | None = None
|
|
254
343
|
is_player_prop: bool = False
|
|
@@ -256,6 +345,12 @@ class ArbitrageOpportunity(BaseModel):
|
|
|
256
345
|
stat_category: str | None = None
|
|
257
346
|
legs: list[ArbitrageLeg]
|
|
258
347
|
detected_at: str | None = None
|
|
348
|
+
# Optional structured refs (additive, non-breaking).
|
|
349
|
+
home: TeamRef | None = None
|
|
350
|
+
away: TeamRef | None = None
|
|
351
|
+
sport_ref: SportRef | None = None
|
|
352
|
+
league_ref: EntityRef | None = None
|
|
353
|
+
market_ref: EntityRef | None = None
|
|
259
354
|
|
|
260
355
|
|
|
261
356
|
# =============================================================================
|
|
@@ -304,7 +399,6 @@ class MiddleOpportunity(BaseModel):
|
|
|
304
399
|
quality_score: float | None = None
|
|
305
400
|
market_overround: float | None = None
|
|
306
401
|
is_live: bool = False
|
|
307
|
-
game_state: GameState | None = None
|
|
308
402
|
is_player_prop: bool = False
|
|
309
403
|
player_name: str | None = None
|
|
310
404
|
stat_category: str | None = None
|
|
@@ -315,6 +409,12 @@ class MiddleOpportunity(BaseModel):
|
|
|
315
409
|
gap_size: float | None = Field(None, alias="gapSize")
|
|
316
410
|
potential_profit: float | None = Field(None, alias="potentialProfit")
|
|
317
411
|
legs: list[ArbitrageLeg] | None = None
|
|
412
|
+
# Optional structured refs (additive, non-breaking).
|
|
413
|
+
home: TeamRef | None = None
|
|
414
|
+
away: TeamRef | None = None
|
|
415
|
+
sport_ref: SportRef | None = None
|
|
416
|
+
league_ref: EntityRef | None = None
|
|
417
|
+
market_ref: EntityRef | None = None
|
|
318
418
|
|
|
319
419
|
model_config = {"populate_by_name": True}
|
|
320
420
|
|
|
@@ -352,7 +452,6 @@ class LowHoldOpportunity(BaseModel):
|
|
|
352
452
|
side2: LowHoldSide | None = None
|
|
353
453
|
side3: LowHoldSide | None = None
|
|
354
454
|
is_live: bool = False
|
|
355
|
-
game_state: GameState | None = None
|
|
356
455
|
is_alternate_line: bool = False
|
|
357
456
|
all_books: list[str] | None = None
|
|
358
457
|
confidence: float | None = None
|
|
@@ -362,6 +461,12 @@ class LowHoldOpportunity(BaseModel):
|
|
|
362
461
|
player_name: str | None = None
|
|
363
462
|
stat_category: str | None = None
|
|
364
463
|
detected_at: str | None = None
|
|
464
|
+
# Optional structured refs (additive, non-breaking).
|
|
465
|
+
home: TeamRef | None = None
|
|
466
|
+
away: TeamRef | None = None
|
|
467
|
+
sport_ref: SportRef | None = None
|
|
468
|
+
league_ref: EntityRef | None = None
|
|
469
|
+
market_ref: EntityRef | None = None
|
|
365
470
|
|
|
366
471
|
|
|
367
472
|
# =============================================================================
|
|
@@ -375,6 +480,8 @@ class Sport(BaseModel):
|
|
|
375
480
|
slug: str
|
|
376
481
|
active: bool
|
|
377
482
|
event_count: int | None = None
|
|
483
|
+
# Optional integer numerical ID, additive.
|
|
484
|
+
numerical_id: int | None = None
|
|
378
485
|
|
|
379
486
|
|
|
380
487
|
class League(BaseModel):
|
|
@@ -384,6 +491,8 @@ class League(BaseModel):
|
|
|
384
491
|
sport_id: str | None = None
|
|
385
492
|
country: str | None = None
|
|
386
493
|
active: bool
|
|
494
|
+
# Optional integer numerical ID, additive.
|
|
495
|
+
numerical_id: int | None = None
|
|
387
496
|
|
|
388
497
|
|
|
389
498
|
class Sportsbook(BaseModel):
|
|
@@ -393,6 +502,25 @@ class Sportsbook(BaseModel):
|
|
|
393
502
|
active: bool
|
|
394
503
|
regions: list[str] | None = None
|
|
395
504
|
features: list[str] | None = None
|
|
505
|
+
# Optional integer numerical ID, additive.
|
|
506
|
+
numerical_id: int | None = None
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
class Team(BaseModel):
|
|
510
|
+
"""A team / competitor returned by the ``/teams`` reference endpoint.
|
|
511
|
+
|
|
512
|
+
``abbreviation`` is only present for ~1500 team-sport entities and is
|
|
513
|
+
absent for individual-sport competitors.
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
id: str
|
|
517
|
+
name: str | None = None
|
|
518
|
+
sport: str | None = None
|
|
519
|
+
league: str | None = None
|
|
520
|
+
abbreviation: str | None = None
|
|
521
|
+
numerical_id: int | None = None
|
|
522
|
+
|
|
523
|
+
model_config = {"extra": "allow"}
|
|
396
524
|
|
|
397
525
|
|
|
398
526
|
class Event(BaseModel):
|
|
@@ -404,6 +532,11 @@ class Event(BaseModel):
|
|
|
404
532
|
start_time: str | None = None
|
|
405
533
|
is_live: bool = False
|
|
406
534
|
status: str | None = None
|
|
535
|
+
# Optional structured refs (additive, non-breaking).
|
|
536
|
+
home: TeamRef | None = None
|
|
537
|
+
away: TeamRef | None = None
|
|
538
|
+
sport_ref: SportRef | None = None
|
|
539
|
+
league_ref: EntityRef | None = None
|
|
407
540
|
|
|
408
541
|
|
|
409
542
|
class Market(BaseModel):
|
|
@@ -414,6 +547,8 @@ class Market(BaseModel):
|
|
|
414
547
|
selection_count: int | None = None
|
|
415
548
|
book_count: int | None = None
|
|
416
549
|
books: list[str] | None = None
|
|
550
|
+
# Optional integer numerical ID, additive.
|
|
551
|
+
numerical_id: int | None = None
|
|
417
552
|
|
|
418
553
|
|
|
419
554
|
# =============================================================================
|
|
@@ -433,6 +568,9 @@ class ClosingOddsLine(BaseModel):
|
|
|
433
568
|
line: float | None = None
|
|
434
569
|
player_name: str | None = None
|
|
435
570
|
stat_category: str | None = None
|
|
571
|
+
# Optional structured refs (additive, non-breaking).
|
|
572
|
+
market_ref: EntityRef | None = None
|
|
573
|
+
sportsbook_ref: EntityRef | None = None
|
|
436
574
|
|
|
437
575
|
|
|
438
576
|
class ClosingSnapshot(BaseModel):
|
|
@@ -446,6 +584,11 @@ class ClosingSnapshot(BaseModel):
|
|
|
446
584
|
event_start_time: str | None = None
|
|
447
585
|
captured_at: str | None = None
|
|
448
586
|
books: dict[str, list[ClosingOddsLine]] = Field(default_factory=dict)
|
|
587
|
+
# Optional structured refs (additive, non-breaking).
|
|
588
|
+
home: TeamRef | None = None
|
|
589
|
+
away: TeamRef | None = None
|
|
590
|
+
sport_ref: SportRef | None = None
|
|
591
|
+
league_ref: EntityRef | None = None
|
|
449
592
|
|
|
450
593
|
|
|
451
594
|
# =============================================================================
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sharpapi
|
|
3
|
-
Version: 0.2
|
|
3
|
+
Version: 0.3.2
|
|
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/Sharp-API/
|
|
8
|
-
Project-URL: Changelog, https://github.com/Sharp-API/
|
|
9
|
-
Author-email: SharpAPI <
|
|
7
|
+
Project-URL: Repository, https://github.com/Sharp-API/SharpAPI-Python
|
|
8
|
+
Project-URL: Changelog, https://github.com/Sharp-API/SharpAPI-Python/releases
|
|
9
|
+
Author-email: SharpAPI <hello@sharpapi.io>
|
|
10
10
|
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
11
12
|
Keywords: api,arbitrage,ev,odds,pinnacle,real-time,sports-betting
|
|
12
13
|
Classifier: Development Status :: 4 - Beta
|
|
13
14
|
Classifier: Intended Audience :: Developers
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
sharpapi/__init__.py,sha256=7m3CaUCg8wxNBihnmEsdflKCuTryceJnn-eW8zund3Y,2372
|
|
2
|
+
sharpapi/_base.py,sha256=D47zn_qwhD3UlE0Wez7rvbEq2r21ZAnSmmoeAJ4dURA,5841
|
|
3
|
+
sharpapi/_utils.py,sha256=nQU1gNkzepAIr93HDYX455aRO2yhc6BeBbaWDnpI5lw,1143
|
|
4
|
+
sharpapi/async_client.py,sha256=s_lkwvOctMSEE3WkNGXiZk1ulnrkVm5LVsrkOiqsCkw,19757
|
|
5
|
+
sharpapi/client.py,sha256=823nvQwsQ1k9vzQz5_zwXzie6FvrfAzaiqBaOcN6pss,27833
|
|
6
|
+
sharpapi/exceptions.py,sha256=AqZmr5ceXKyEVKdVA8ua-D0klH6zrXEMgs9Rxn7O2eE,7095
|
|
7
|
+
sharpapi/models.py,sha256=TIRbb7uaW9yTAk6kKZuCSkCILqQaoYyA289N_3ettrQ,19325
|
|
8
|
+
sharpapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
sharpapi/streaming.py,sha256=sSdVE9HzuiDYnYH_Lmr4NHq0CKvwbUIELJvbSdqaTCw,8940
|
|
10
|
+
sharpapi-0.3.2.dist-info/METADATA,sha256=cFrChp78mFzbdLl3DpxzEIP_-wexOi-OCIZ7kByGz7U,5735
|
|
11
|
+
sharpapi-0.3.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
12
|
+
sharpapi-0.3.2.dist-info/licenses/LICENSE,sha256=EfZuzcYtp0eJC9ky4Nei3os5r0cFBYQmc-TpamV5OPw,1069
|
|
13
|
+
sharpapi-0.3.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SharpAPI LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sharpapi-0.2.5.dist-info/RECORD
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
sharpapi/__init__.py,sha256=zAGsy2BLJmqNZ76cxD2sk9WD1U1bvTM83MkkRHU-LWo,2260
|
|
2
|
-
sharpapi/_base.py,sha256=D47zn_qwhD3UlE0Wez7rvbEq2r21ZAnSmmoeAJ4dURA,5841
|
|
3
|
-
sharpapi/_utils.py,sha256=nQU1gNkzepAIr93HDYX455aRO2yhc6BeBbaWDnpI5lw,1143
|
|
4
|
-
sharpapi/async_client.py,sha256=xi5ueJGe09jSrsQ2Bb203-gfYVJGSoWX4l7-m2sySuY,18514
|
|
5
|
-
sharpapi/client.py,sha256=vjsWjE36s-TisG0xAb9eors2lqq-9sQLoZKno3xrMHc,26006
|
|
6
|
-
sharpapi/exceptions.py,sha256=0wRzrDEFhqP2_lWAbUJ5IcQaa7pi-QKB6TgNYuoYnVU,6790
|
|
7
|
-
sharpapi/models.py,sha256=CW8p2VDyZo60bFaJoY5OCYnvCg5OqRYaA0xLLf0pR88,14240
|
|
8
|
-
sharpapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
sharpapi/streaming.py,sha256=sSdVE9HzuiDYnYH_Lmr4NHq0CKvwbUIELJvbSdqaTCw,8940
|
|
10
|
-
sharpapi-0.2.5.dist-info/METADATA,sha256=Wdu3-Ka4F_GmE985GOf6kufl303PwHfKPAsm-OBjVvY,5715
|
|
11
|
-
sharpapi-0.2.5.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
12
|
-
sharpapi-0.2.5.dist-info/RECORD,,
|
|
File without changes
|