sharpapi 0.1.0__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.
@@ -0,0 +1,464 @@
1
+ """SharpAPI asynchronous Python client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional, Union
6
+
7
+ import httpx
8
+
9
+ from ._base import (
10
+ DEFAULT_BASE_URL,
11
+ DEFAULT_TIMEOUT,
12
+ handle_errors,
13
+ make_headers,
14
+ parse_rate_limit,
15
+ parse_response,
16
+ )
17
+ from ._utils import _clean_params
18
+ from .models import (
19
+ APIResponse,
20
+ AccountInfo,
21
+ ArbitrageOpportunity,
22
+ EVOpportunity,
23
+ Event,
24
+ League,
25
+ LowHoldOpportunity,
26
+ MiddleOpportunity,
27
+ OddsLine,
28
+ RateLimitInfo,
29
+ Sportsbook,
30
+ Sport,
31
+ )
32
+
33
+
34
+ class AsyncSharpAPI:
35
+ """Async SharpAPI Python client.
36
+
37
+ Provides typed access to odds, +EV, arbitrage, middles, and streaming
38
+ endpoints using ``async``/``await``.
39
+
40
+ Example::
41
+
42
+ import asyncio
43
+ from sharpapi import AsyncSharpAPI
44
+
45
+ async def main():
46
+ async with AsyncSharpAPI("sk_live_xxx") as client:
47
+ arbs = await client.arbitrage.get(min_profit=1.0)
48
+ for arb in arbs.data:
49
+ print(f"{arb.profit_percent}% — {arb.event_name}")
50
+
51
+ asyncio.run(main())
52
+ """
53
+
54
+ def __init__(
55
+ self,
56
+ api_key: str,
57
+ *,
58
+ base_url: str = DEFAULT_BASE_URL,
59
+ timeout: float = DEFAULT_TIMEOUT,
60
+ ):
61
+ if not api_key:
62
+ raise ValueError("api_key is required")
63
+
64
+ self._api_key = api_key
65
+ self._base_url = base_url.rstrip("/")
66
+ self._timeout = timeout
67
+ self._http = httpx.AsyncClient(
68
+ base_url=f"{self._base_url}/api/v1",
69
+ headers=make_headers(api_key),
70
+ timeout=timeout,
71
+ )
72
+ self._last_rate_limit = RateLimitInfo()
73
+
74
+ # Resource namespaces
75
+ self.odds = _AsyncOddsResource(self)
76
+ self.ev = _AsyncEVResource(self)
77
+ self.arbitrage = _AsyncArbitrageResource(self)
78
+ self.middles = _AsyncMiddlesResource(self)
79
+ self.low_hold = _AsyncLowHoldResource(self)
80
+ self.sports = _AsyncSportsResource(self)
81
+ self.leagues = _AsyncLeaguesResource(self)
82
+ self.sportsbooks = _AsyncSportsbooksResource(self)
83
+ self.events = _AsyncEventsResource(self)
84
+ self.account = _AsyncAccountResource(self)
85
+
86
+ @property
87
+ def rate_limit(self) -> RateLimitInfo:
88
+ """Rate limit info from the last request."""
89
+ return self._last_rate_limit
90
+
91
+ async def _request(self, method: str, path: str, params: dict | None = None, **kwargs) -> Any:
92
+ """Make an async API request and return parsed JSON."""
93
+ if params:
94
+ params = _clean_params(params)
95
+
96
+ response = await self._http.request(method, path, params=params, **kwargs)
97
+ self._last_rate_limit = parse_rate_limit(response)
98
+ handle_errors(response)
99
+ return response.json()
100
+
101
+ async def _get(self, path: str, params: dict | None = None) -> Any:
102
+ return await self._request("GET", path, params)
103
+
104
+ async def _post(self, path: str, json_body: Any = None, params: dict | None = None) -> Any:
105
+ return await self._request("POST", path, params, json=json_body)
106
+
107
+ async def close(self) -> None:
108
+ """Close the async HTTP client."""
109
+ await self._http.aclose()
110
+
111
+ async def __aenter__(self):
112
+ return self
113
+
114
+ async def __aexit__(self, *args):
115
+ await self.close()
116
+
117
+
118
+ # =============================================================================
119
+ # Async Resource Namespaces
120
+ # =============================================================================
121
+
122
+
123
+ class _AsyncOddsResource:
124
+ """Async access to odds data."""
125
+
126
+ def __init__(self, client: AsyncSharpAPI):
127
+ self._client = client
128
+
129
+ async def get(
130
+ self,
131
+ *,
132
+ sportsbook: Optional[Union[str, list[str]]] = None,
133
+ add_sportsbook: Optional[Union[str, list[str]]] = None,
134
+ sport: Optional[Union[str, list[str]]] = None,
135
+ league: Optional[Union[str, list[str]]] = None,
136
+ market: Optional[Union[str, list[str]]] = None,
137
+ event: Optional[Union[str, list[str]]] = None,
138
+ live: Optional[bool] = None,
139
+ sort: Optional[str] = None,
140
+ group_by: Optional[str] = None,
141
+ fields: Optional[Union[str, list[str]]] = None,
142
+ limit: Optional[int] = None,
143
+ offset: Optional[int] = None,
144
+ ) -> APIResponse[list[OddsLine]]:
145
+ """Get current odds snapshot."""
146
+ data = await self._client._get("/odds", {
147
+ "sportsbook": sportsbook,
148
+ "add_sportsbook": add_sportsbook,
149
+ "sport": sport,
150
+ "league": league,
151
+ "market": market,
152
+ "event": event,
153
+ "live": live,
154
+ "sort": sort,
155
+ "group_by": group_by,
156
+ "fields": fields,
157
+ "limit": limit,
158
+ "offset": offset,
159
+ })
160
+ return parse_response(data, OddsLine)
161
+
162
+ async def best(
163
+ self,
164
+ *,
165
+ sport: Optional[Union[str, list[str]]] = None,
166
+ league: Optional[Union[str, list[str]]] = None,
167
+ market: Optional[Union[str, list[str]]] = None,
168
+ event: Optional[Union[str, list[str]]] = None,
169
+ live: Optional[bool] = None,
170
+ sportsbook: Optional[Union[str, list[str]]] = None,
171
+ add_sportsbook: Optional[Union[str, list[str]]] = None,
172
+ limit: Optional[int] = None,
173
+ offset: Optional[int] = None,
174
+ ) -> APIResponse[list[OddsLine]]:
175
+ """Get best odds per selection across all sportsbooks."""
176
+ data = await self._client._get("/odds/best", {
177
+ "sport": sport,
178
+ "league": league,
179
+ "market": market,
180
+ "event": event,
181
+ "live": live,
182
+ "sportsbook": sportsbook,
183
+ "add_sportsbook": add_sportsbook,
184
+ "limit": limit,
185
+ "offset": offset,
186
+ })
187
+ return parse_response(data, OddsLine)
188
+
189
+ async def comparison(
190
+ self,
191
+ event_id: str,
192
+ *,
193
+ market: Optional[str] = None,
194
+ ) -> APIResponse[list[OddsLine]]:
195
+ """Get side-by-side odds comparison for an event."""
196
+ data = await self._client._get("/odds/comparison", {
197
+ "event_id": event_id,
198
+ "market": market,
199
+ })
200
+ return parse_response(data, OddsLine)
201
+
202
+ async def batch(self, event_ids: list[str]) -> APIResponse[list[OddsLine]]:
203
+ """Batch odds lookup for multiple events."""
204
+ data = await self._client._post("/odds/batch", {"event_ids": event_ids})
205
+ return parse_response(data, OddsLine)
206
+
207
+
208
+ class _AsyncEVResource:
209
+ """Async access to +EV opportunities."""
210
+
211
+ def __init__(self, client: AsyncSharpAPI):
212
+ self._client = client
213
+
214
+ async def get(
215
+ self,
216
+ *,
217
+ sport: Optional[Union[str, list[str]]] = None,
218
+ league: Optional[Union[str, list[str]]] = None,
219
+ sportsbook: Optional[Union[str, list[str]]] = None,
220
+ add_sportsbook: Optional[Union[str, list[str]]] = None,
221
+ market: Optional[Union[str, list[str]]] = None,
222
+ min_ev: Optional[float] = None,
223
+ max_ev: Optional[float] = None,
224
+ min_market_width: Optional[float] = None,
225
+ max_market_width: Optional[float] = None,
226
+ max_odds_age: Optional[int] = None,
227
+ date_range: Optional[str] = None,
228
+ live: Optional[bool] = None,
229
+ sort: Optional[str] = None,
230
+ limit: Optional[int] = None,
231
+ offset: Optional[int] = None,
232
+ ) -> APIResponse[list[EVOpportunity]]:
233
+ """Get +EV opportunities. Requires Pro tier or higher."""
234
+ data = await self._client._get("/opportunities/ev", {
235
+ "sport": sport,
236
+ "league": league,
237
+ "sportsbook": sportsbook,
238
+ "add_sportsbook": add_sportsbook,
239
+ "market": market,
240
+ "min_ev": min_ev,
241
+ "max_ev": max_ev,
242
+ "min_market_width": min_market_width,
243
+ "max_market_width": max_market_width,
244
+ "max_odds_age": max_odds_age,
245
+ "date_range": date_range,
246
+ "live": live,
247
+ "sort": sort,
248
+ "limit": limit,
249
+ "offset": offset,
250
+ })
251
+ return parse_response(data, EVOpportunity)
252
+
253
+
254
+ class _AsyncArbitrageResource:
255
+ """Async access to arbitrage opportunities."""
256
+
257
+ def __init__(self, client: AsyncSharpAPI):
258
+ self._client = client
259
+
260
+ async def get(
261
+ self,
262
+ *,
263
+ sport: Optional[Union[str, list[str]]] = None,
264
+ league: Optional[Union[str, list[str]]] = None,
265
+ sportsbook: Optional[Union[str, list[str]]] = None,
266
+ add_sportsbook: Optional[Union[str, list[str]]] = None,
267
+ market: Optional[Union[str, list[str]]] = None,
268
+ min_profit: Optional[float] = None,
269
+ max_odds_age: Optional[int] = None,
270
+ live: Optional[bool] = None,
271
+ sort: Optional[str] = None,
272
+ group: Optional[str] = None,
273
+ limit: Optional[int] = None,
274
+ offset: Optional[int] = None,
275
+ ) -> APIResponse[list[ArbitrageOpportunity]]:
276
+ """Get arbitrage opportunities. Requires Hobby tier or higher."""
277
+ data = await self._client._get("/opportunities/arbitrage", {
278
+ "sport": sport,
279
+ "league": league,
280
+ "sportsbook": sportsbook,
281
+ "add_sportsbook": add_sportsbook,
282
+ "market": market,
283
+ "min_profit": min_profit,
284
+ "max_odds_age": max_odds_age,
285
+ "live": live,
286
+ "sort": sort,
287
+ "group": group,
288
+ "limit": limit,
289
+ "offset": offset,
290
+ })
291
+ return parse_response(data, ArbitrageOpportunity)
292
+
293
+
294
+ class _AsyncMiddlesResource:
295
+ """Async access to middle opportunities."""
296
+
297
+ def __init__(self, client: AsyncSharpAPI):
298
+ self._client = client
299
+
300
+ async def get(
301
+ self,
302
+ *,
303
+ sport: Optional[Union[str, list[str]]] = None,
304
+ league: Optional[Union[str, list[str]]] = None,
305
+ sportsbook: Optional[Union[str, list[str]]] = None,
306
+ market: Optional[Union[str, list[str]]] = None,
307
+ min_size: Optional[float] = None,
308
+ max_odds_age: Optional[int] = None,
309
+ live: Optional[bool] = None,
310
+ state: Optional[str] = None,
311
+ sort: Optional[str] = None,
312
+ limit: Optional[int] = None,
313
+ offset: Optional[int] = None,
314
+ ) -> APIResponse[list[MiddleOpportunity]]:
315
+ """Get middle opportunities. Requires Pro tier or higher."""
316
+ data = await self._client._get("/opportunities/middles", {
317
+ "sport": sport,
318
+ "league": league,
319
+ "sportsbook": sportsbook,
320
+ "market": market,
321
+ "min_size": min_size,
322
+ "max_odds_age": max_odds_age,
323
+ "live": live,
324
+ "state": state,
325
+ "sort": sort,
326
+ "limit": limit,
327
+ "offset": offset,
328
+ })
329
+ return parse_response(data, MiddleOpportunity)
330
+
331
+
332
+ class _AsyncLowHoldResource:
333
+ """Async access to low-hold opportunities."""
334
+
335
+ def __init__(self, client: AsyncSharpAPI):
336
+ self._client = client
337
+
338
+ async def get(
339
+ self,
340
+ *,
341
+ sport: Optional[Union[str, list[str]]] = None,
342
+ league: Optional[Union[str, list[str]]] = None,
343
+ sportsbook: Optional[Union[str, list[str]]] = None,
344
+ market: Optional[Union[str, list[str]]] = None,
345
+ max_hold: Optional[float] = None,
346
+ live: Optional[bool] = None,
347
+ state: Optional[str] = None,
348
+ sort: Optional[str] = None,
349
+ limit: Optional[int] = None,
350
+ offset: Optional[int] = None,
351
+ ) -> APIResponse[list[LowHoldOpportunity]]:
352
+ """Get low-hold opportunities."""
353
+ data = await self._client._get("/opportunities/low_hold", {
354
+ "sport": sport,
355
+ "league": league,
356
+ "sportsbook": sportsbook,
357
+ "market": market,
358
+ "max_hold": max_hold,
359
+ "live": live,
360
+ "state": state,
361
+ "sort": sort,
362
+ "limit": limit,
363
+ "offset": offset,
364
+ })
365
+ return parse_response(data, LowHoldOpportunity)
366
+
367
+
368
+ class _AsyncSportsResource:
369
+ def __init__(self, client: AsyncSharpAPI):
370
+ self._client = client
371
+
372
+ async def list(self) -> APIResponse[list[Sport]]:
373
+ """List all available sports."""
374
+ data = await self._client._get("/sports")
375
+ return parse_response(data, Sport)
376
+
377
+ async def get(self, sport_id: str) -> Sport:
378
+ """Get a specific sport."""
379
+ data = await self._client._get(f"/sports/{sport_id}")
380
+ raw = data.get("data", data)
381
+ return Sport.model_validate(raw)
382
+
383
+
384
+ class _AsyncLeaguesResource:
385
+ def __init__(self, client: AsyncSharpAPI):
386
+ self._client = client
387
+
388
+ async def list(self, *, sport: Optional[str] = None) -> APIResponse[list[League]]:
389
+ """List all leagues, optionally filtered by sport."""
390
+ data = await self._client._get("/leagues", {"sport": sport})
391
+ return parse_response(data, League)
392
+
393
+ async def get(self, league_id: str) -> League:
394
+ """Get a specific league."""
395
+ data = await self._client._get(f"/leagues/{league_id}")
396
+ raw = data.get("data", data)
397
+ return League.model_validate(raw)
398
+
399
+
400
+ class _AsyncSportsbooksResource:
401
+ def __init__(self, client: AsyncSharpAPI):
402
+ self._client = client
403
+
404
+ async def list(self) -> APIResponse[list[Sportsbook]]:
405
+ """List all active sportsbooks."""
406
+ data = await self._client._get("/sportsbooks")
407
+ return parse_response(data, Sportsbook)
408
+
409
+ async def get(self, book_id: str) -> Sportsbook:
410
+ """Get a specific sportsbook."""
411
+ data = await self._client._get(f"/sportsbooks/{book_id}")
412
+ raw = data.get("data", data)
413
+ return Sportsbook.model_validate(raw)
414
+
415
+
416
+ class _AsyncEventsResource:
417
+ def __init__(self, client: AsyncSharpAPI):
418
+ self._client = client
419
+
420
+ async def list(
421
+ self,
422
+ *,
423
+ sport: Optional[str] = None,
424
+ league: Optional[Union[str, list[str]]] = None,
425
+ live: Optional[bool] = None,
426
+ limit: Optional[int] = None,
427
+ offset: Optional[int] = None,
428
+ ) -> APIResponse[list[Event]]:
429
+ """List events."""
430
+ data = await self._client._get("/events", {
431
+ "sport": sport,
432
+ "league": league,
433
+ "live": live,
434
+ "limit": limit,
435
+ "offset": offset,
436
+ })
437
+ return parse_response(data, Event)
438
+
439
+ async def get(self, event_id: str) -> Event:
440
+ """Get a specific event."""
441
+ data = await self._client._get(f"/events/{event_id}")
442
+ raw = data.get("data", data)
443
+ return Event.model_validate(raw)
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
+
451
+ class _AsyncAccountResource:
452
+ def __init__(self, client: AsyncSharpAPI):
453
+ self._client = client
454
+
455
+ async def me(self) -> AccountInfo:
456
+ """Get current account info (tier, limits, features)."""
457
+ data = await self._client._get("/account")
458
+ raw = data.get("data", data)
459
+ return AccountInfo.model_validate(raw)
460
+
461
+ async def usage(self) -> dict:
462
+ """Get current usage stats."""
463
+ data = await self._client._get("/account/usage")
464
+ return data.get("data", data)