sharpapi 0.2.1__py3-none-any.whl → 0.2.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/_base.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import random
|
|
6
|
+
|
|
5
7
|
import httpx
|
|
6
8
|
|
|
7
9
|
from .exceptions import (
|
|
@@ -15,7 +17,25 @@ from .models import APIResponse, RateLimitInfo, ResponseMeta
|
|
|
15
17
|
|
|
16
18
|
DEFAULT_BASE_URL = "https://api.sharpapi.io"
|
|
17
19
|
DEFAULT_TIMEOUT = 30.0
|
|
18
|
-
USER_AGENT = "sharpapi-python/0.2.
|
|
20
|
+
USER_AGENT = "sharpapi-python/0.2.2"
|
|
21
|
+
|
|
22
|
+
RETRY_STATUSES = frozenset({502, 503, 504})
|
|
23
|
+
RETRY_MAX_ATTEMPTS = 3
|
|
24
|
+
RETRY_BASE_DELAY = 0.5
|
|
25
|
+
RETRY_MAX_DELAY = 4.0
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def should_retry(response: httpx.Response | None, exc: Exception | None) -> bool:
|
|
29
|
+
"""True for transient upstream failures worth retrying."""
|
|
30
|
+
if exc is not None:
|
|
31
|
+
return isinstance(exc, (httpx.ConnectError, httpx.ReadError, httpx.RemoteProtocolError))
|
|
32
|
+
return response is not None and response.status_code in RETRY_STATUSES
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def retry_delay(attempt: int) -> float:
|
|
36
|
+
"""Exponential backoff with full jitter. attempt is 1-indexed."""
|
|
37
|
+
ceiling = min(RETRY_BASE_DELAY * (2 ** (attempt - 1)), RETRY_MAX_DELAY)
|
|
38
|
+
return random.uniform(0, ceiling)
|
|
19
39
|
|
|
20
40
|
|
|
21
41
|
def parse_response(raw: dict, model_class: type) -> APIResponse:
|
sharpapi/async_client.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import asyncio
|
|
5
6
|
from typing import Any, Optional, Union
|
|
6
7
|
|
|
7
8
|
import httpx
|
|
@@ -9,10 +10,13 @@ import httpx
|
|
|
9
10
|
from ._base import (
|
|
10
11
|
DEFAULT_BASE_URL,
|
|
11
12
|
DEFAULT_TIMEOUT,
|
|
13
|
+
RETRY_MAX_ATTEMPTS,
|
|
12
14
|
handle_errors,
|
|
13
15
|
make_headers,
|
|
14
16
|
parse_rate_limit,
|
|
15
17
|
parse_response,
|
|
18
|
+
retry_delay,
|
|
19
|
+
should_retry,
|
|
16
20
|
)
|
|
17
21
|
from ._utils import _clean_params
|
|
18
22
|
from .models import (
|
|
@@ -89,11 +93,26 @@ class AsyncSharpAPI:
|
|
|
89
93
|
return self._last_rate_limit
|
|
90
94
|
|
|
91
95
|
async def _request(self, method: str, path: str, params: dict | None = None, **kwargs) -> Any:
|
|
92
|
-
"""Make an async API request and return parsed JSON."""
|
|
96
|
+
"""Make an async API request and return parsed JSON. Retries 502/503/504 with jittered backoff."""
|
|
93
97
|
if params:
|
|
94
98
|
params = _clean_params(params)
|
|
95
99
|
|
|
96
|
-
response
|
|
100
|
+
response: httpx.Response | None = None
|
|
101
|
+
for attempt in range(1, RETRY_MAX_ATTEMPTS + 1):
|
|
102
|
+
exc: Exception | None = None
|
|
103
|
+
try:
|
|
104
|
+
response = await self._http.request(method, path, params=params, **kwargs)
|
|
105
|
+
except (httpx.ConnectError, httpx.ReadError, httpx.RemoteProtocolError) as e:
|
|
106
|
+
exc = e
|
|
107
|
+
|
|
108
|
+
if attempt < RETRY_MAX_ATTEMPTS and should_retry(response, exc):
|
|
109
|
+
await asyncio.sleep(retry_delay(attempt))
|
|
110
|
+
continue
|
|
111
|
+
if exc is not None:
|
|
112
|
+
raise exc
|
|
113
|
+
break
|
|
114
|
+
|
|
115
|
+
assert response is not None
|
|
97
116
|
self._last_rate_limit = parse_rate_limit(response)
|
|
98
117
|
handle_errors(response)
|
|
99
118
|
return response.json()
|
sharpapi/client.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import time
|
|
5
6
|
from typing import Any, Optional, Union
|
|
6
7
|
|
|
7
8
|
import httpx
|
|
@@ -9,10 +10,13 @@ import httpx
|
|
|
9
10
|
from ._base import (
|
|
10
11
|
DEFAULT_BASE_URL,
|
|
11
12
|
DEFAULT_TIMEOUT,
|
|
13
|
+
RETRY_MAX_ATTEMPTS,
|
|
12
14
|
handle_errors,
|
|
13
15
|
make_headers,
|
|
14
16
|
parse_rate_limit,
|
|
15
17
|
parse_response,
|
|
18
|
+
retry_delay,
|
|
19
|
+
should_retry,
|
|
16
20
|
)
|
|
17
21
|
from ._utils import _clean_params
|
|
18
22
|
from .models import (
|
|
@@ -99,11 +103,26 @@ class SharpAPI:
|
|
|
99
103
|
return self._last_rate_limit
|
|
100
104
|
|
|
101
105
|
def _request(self, method: str, path: str, params: dict | None = None, **kwargs) -> Any:
|
|
102
|
-
"""Make an API request and return parsed JSON."""
|
|
106
|
+
"""Make an API request and return parsed JSON. Retries 502/503/504 with jittered backoff."""
|
|
103
107
|
if params:
|
|
104
108
|
params = _clean_params(params)
|
|
105
109
|
|
|
106
|
-
response
|
|
110
|
+
response: httpx.Response | None = None
|
|
111
|
+
for attempt in range(1, RETRY_MAX_ATTEMPTS + 1):
|
|
112
|
+
exc: Exception | None = None
|
|
113
|
+
try:
|
|
114
|
+
response = self._http.request(method, path, params=params, **kwargs)
|
|
115
|
+
except (httpx.ConnectError, httpx.ReadError, httpx.RemoteProtocolError) as e:
|
|
116
|
+
exc = e
|
|
117
|
+
|
|
118
|
+
if attempt < RETRY_MAX_ATTEMPTS and should_retry(response, exc):
|
|
119
|
+
time.sleep(retry_delay(attempt))
|
|
120
|
+
continue
|
|
121
|
+
if exc is not None:
|
|
122
|
+
raise exc
|
|
123
|
+
break
|
|
124
|
+
|
|
125
|
+
assert response is not None
|
|
107
126
|
self._last_rate_limit = parse_rate_limit(response)
|
|
108
127
|
handle_errors(response)
|
|
109
128
|
return response.json()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sharpapi
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.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
|
|
@@ -13,14 +13,13 @@ Classifier: Development Status :: 4 - Beta
|
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
20
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
21
|
Classifier: Typing :: Typed
|
|
23
|
-
Requires-Python: >=3.
|
|
22
|
+
Requires-Python: >=3.10
|
|
24
23
|
Requires-Dist: httpx>=0.25.0
|
|
25
24
|
Requires-Dist: pydantic>=2.0.0
|
|
26
25
|
Provides-Extra: pandas
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
sharpapi/__init__.py,sha256=dCb0vGKwVAMd8nCaLTfo_m5IyV_yymY4N9bjTborKLU,1974
|
|
2
|
-
sharpapi/_base.py,sha256=
|
|
2
|
+
sharpapi/_base.py,sha256=lSV5MiGI02rB_fMX3oXlbqrckwkFyRbwecX7NyAep7A,4045
|
|
3
3
|
sharpapi/_utils.py,sha256=nQU1gNkzepAIr93HDYX455aRO2yhc6BeBbaWDnpI5lw,1143
|
|
4
|
-
sharpapi/async_client.py,sha256=
|
|
5
|
-
sharpapi/client.py,sha256=
|
|
4
|
+
sharpapi/async_client.py,sha256=yT45nbCWMxkMba11AtLM8gbst55JDM99ooEGhYQkl10,15825
|
|
5
|
+
sharpapi/client.py,sha256=f_PTMUBy8bFs_Q-PKDRIe-CfmfHOMG9Ln4HdLmwExaU,22973
|
|
6
6
|
sharpapi/exceptions.py,sha256=nseJ4BboGjSWIfDtMYQpXPIaOUxbPAJLsxUPbERZqpY,1272
|
|
7
7
|
sharpapi/models.py,sha256=z9IIQd2dQKeEQTzZ8-qF8J5_L0GwT-8k2r3Y3ozXJ0U,12951
|
|
8
8
|
sharpapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
sharpapi/streaming.py,sha256=smQi9F-m7ET7s7V_psdg3S-butiuG_bIq3CnOE1zG8M,6971
|
|
10
|
-
sharpapi-0.2.
|
|
11
|
-
sharpapi-0.2.
|
|
12
|
-
sharpapi-0.2.
|
|
10
|
+
sharpapi-0.2.2.dist-info/METADATA,sha256=YuRZC64xa9GMLLaE4-qL-QbqerLNwrAMD2aU78ODwpQ,5715
|
|
11
|
+
sharpapi-0.2.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
12
|
+
sharpapi-0.2.2.dist-info/RECORD,,
|
|
File without changes
|