sharpapi 0.2.2__tar.gz → 0.3.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.
@@ -10,3 +10,9 @@ build/
10
10
  .ruff_cache/
11
11
  *.egg
12
12
  .mypy_cache/
13
+ .coverage
14
+ htmlcov/
15
+ .claude/
16
+ .DS_Store
17
+ tests/
18
+ .benchmarks/
@@ -0,0 +1,58 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `sharpapi` Python SDK are documented here.
4
+
5
+ ## 0.3.1 — 2026-05-06
6
+
7
+ ### Added — TeamRef metadata
8
+
9
+ `TeamRef` now exposes five additional optional fields:
10
+
11
+ - `logo` — full CDN URL. ~93% of teams are populated.
12
+ - `city` — e.g. `"Arizona"` for the Diamondbacks.
13
+ - `mascot` — e.g. `"Diamondbacks"`.
14
+ - `conference` — e.g. `"NL"`, `"AFC"`, `"Western"`.
15
+ - `division` — e.g. `"West Division"`, `"NL East"`, `"Pacific Division"`.
16
+
17
+ All five default to `None` and are additive — existing 0.3.0 code keeps
18
+ working unchanged.
19
+
20
+ ## 0.3.0 — 2026-05-06
21
+
22
+ ### Added — nested refs
23
+
24
+ Every odds row, opportunity row, and reference-list row may now carry
25
+ optional structured reference objects alongside the existing flat fields.
26
+ All new fields are **optional and additive** — clients on older API
27
+ versions (or talking to older API servers) see `None` and behave
28
+ identically.
29
+
30
+ New models:
31
+
32
+ - `TeamRef` — `id`, `numerical_id`, `name`, `abbreviation` (latter only on
33
+ team-sport competitors)
34
+ - `SportRef` — `id`, `name`, `numerical_id`
35
+ - `EntityRef` — `id`, `label`, `numerical_id` (used for league / market /
36
+ sportsbook refs)
37
+
38
+ New optional fields:
39
+
40
+ - `OddsLine`, `EVOpportunity`, `ArbitrageOpportunity`, `MiddleOpportunity`,
41
+ `LowHoldOpportunity` — all gain `home`, `away`, `sport_ref`, `league_ref`,
42
+ `market_ref`, `sportsbook_ref` (legs / opps without a single book skip
43
+ `sportsbook_ref`).
44
+ - `ArbitrageLeg` — gains `sportsbook_ref`.
45
+ - `ClosingOddsLine` — gains `market_ref`, `sportsbook_ref`.
46
+ - `ClosingSnapshot` — gains `home`, `away`, `sport_ref`, `league_ref`.
47
+ - `Sport`, `League`, `Sportsbook`, `Market` — gain `numerical_id`.
48
+ - `Event` — gains `home`, `away`, `sport_ref`, `league_ref`.
49
+
50
+ New reference model:
51
+
52
+ - `Team` — for the `/teams` reference endpoint, includes optional
53
+ `abbreviation` and `numerical_id`.
54
+
55
+ ### Backward compatibility
56
+
57
+ No existing field was renamed, retyped, or removed. Code that does not
58
+ reference the new attributes continues to work without changes.
sharpapi-0.3.1/LICENSE ADDED
@@ -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.
@@ -1,13 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sharpapi
3
- Version: 0.2.2
3
+ Version: 0.3.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
7
  Project-URL: Repository, https://github.com/Sharp-API/sharpapi-python
8
8
  Project-URL: Changelog, https://github.com/Sharp-API/sharpapi-python/releases
9
- Author-email: SharpAPI <support@sharpapi.io>
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,25 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ If you believe you have found a security vulnerability in this SDK or in
6
+ the SharpAPI service, please report it privately to:
7
+
8
+ **hello@sharpapi.io** (subject line: `[SECURITY] <short summary>`)
9
+
10
+ Please do not open a public GitHub issue for security reports.
11
+
12
+ We will acknowledge receipt within 72 hours and aim to provide a status
13
+ update within 7 days. If the issue is confirmed, we will work with you on
14
+ disclosure timing.
15
+
16
+ ## Scope
17
+
18
+ In scope:
19
+ - This SDK package and its published artifact on PyPI
20
+ - The SharpAPI HTTP and WebSocket APIs (`api.sharpapi.io`, `ws.sharpapi.io`)
21
+
22
+ Out of scope:
23
+ - Findings in third-party dependencies (please report those upstream)
24
+ - Denial of service via brute-force or volumetric attacks against the API
25
+ - Issues that require physical access to a user's device
@@ -4,12 +4,12 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sharpapi"
7
- version = "0.2.2"
7
+ version = "0.3.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"
11
11
  requires-python = ">=3.10"
12
- authors = [{ name = "SharpAPI", email = "support@sharpapi.io" }]
12
+ authors = [{ name = "SharpAPI", email = "hello@sharpapi.io" }]
13
13
  keywords = ["sports-betting", "odds", "arbitrage", "ev", "api", "real-time", "pinnacle"]
14
14
  classifiers = [
15
15
  "Development Status :: 4 - Beta",
@@ -41,6 +41,25 @@ Changelog = "https://github.com/Sharp-API/sharpapi-python/releases"
41
41
  [tool.hatch.build.targets.wheel]
42
42
  packages = ["src/sharpapi"]
43
43
 
44
+ # Explicit sdist whitelist. Hatchling's default sdist would otherwise
45
+ # include everything not gitignored (CI workflows, dependabot config,
46
+ # .gitignore itself), all of which would land on PyPI on every release.
47
+ # Keep this in sync with what we actually want users who do
48
+ # `pip install --no-binary :all: sharpapi` to receive.
49
+ [tool.hatch.build.targets.sdist]
50
+ include = [
51
+ "src/sharpapi",
52
+ "README.md",
53
+ "LICENSE",
54
+ "CHANGELOG.md",
55
+ "SECURITY.md",
56
+ "pyproject.toml",
57
+ ]
58
+ exclude = [
59
+ ".gitignore",
60
+ ".github",
61
+ ]
62
+
44
63
  [tool.ruff]
45
64
  target-version = "py310"
46
65
  line-length = 100
@@ -17,9 +17,12 @@ Example::
17
17
  print(f"+{opp.ev_percentage}% on {opp.selection} @ {opp.sportsbook}")
18
18
  """
19
19
 
20
+ from ._utils import american_to_decimal, american_to_probability, decimal_to_american
20
21
  from .async_client import AsyncSharpAPI
21
22
  from .client import SharpAPI
22
23
  from .exceptions import (
24
+ ERROR_CODE_DESCRIPTIONS,
25
+ ERROR_CODE_TO_EXCEPTION,
23
26
  AuthenticationError,
24
27
  RateLimitedError,
25
28
  SharpAPIError,
@@ -28,16 +31,21 @@ from .exceptions import (
28
31
  ValidationError,
29
32
  )
30
33
  from .models import (
31
- APIResponse,
32
34
  AccountInfo,
35
+ APIKey,
36
+ APIResponse,
33
37
  ArbitrageLeg,
34
38
  ArbitrageOpportunity,
35
- EVOpportunity,
39
+ ClosingOddsLine,
40
+ ClosingSnapshot,
41
+ EntityRef,
36
42
  Event,
43
+ EVOpportunity,
37
44
  GameState,
38
45
  League,
39
46
  LowHoldOpportunity,
40
47
  LowHoldSide,
48
+ Market,
41
49
  MiddleOpportunity,
42
50
  MiddleSide,
43
51
  OddsLine,
@@ -46,28 +54,35 @@ from .models import (
46
54
  RateLimitInfo,
47
55
  ResponseMeta,
48
56
  Sport,
57
+ SportRef,
49
58
  Sportsbook,
59
+ Team,
60
+ TeamRef,
50
61
  )
51
62
  from .streaming import EventStream
52
- from ._utils import american_to_decimal, american_to_probability, decimal_to_american
53
63
 
54
- __version__ = "0.2.1"
64
+ __version__ = "0.3.1"
55
65
 
56
66
  __all__ = [
57
67
  # Clients
58
68
  "SharpAPI",
59
69
  "AsyncSharpAPI",
60
70
  # Models
71
+ "APIKey",
61
72
  "APIResponse",
62
73
  "AccountInfo",
63
74
  "ArbitrageLeg",
64
75
  "ArbitrageOpportunity",
76
+ "ClosingOddsLine",
77
+ "ClosingSnapshot",
78
+ "EntityRef",
65
79
  "EVOpportunity",
66
80
  "Event",
67
81
  "GameState",
68
82
  "League",
69
83
  "LowHoldOpportunity",
70
84
  "LowHoldSide",
85
+ "Market",
71
86
  "MiddleOpportunity",
72
87
  "MiddleSide",
73
88
  "OddsLine",
@@ -76,7 +91,10 @@ __all__ = [
76
91
  "RateLimitInfo",
77
92
  "ResponseMeta",
78
93
  "Sport",
94
+ "SportRef",
79
95
  "Sportsbook",
96
+ "Team",
97
+ "TeamRef",
80
98
  # Streaming
81
99
  "EventStream",
82
100
  # Exceptions
@@ -86,6 +104,9 @@ __all__ = [
86
104
  "StreamError",
87
105
  "TierRestrictedError",
88
106
  "ValidationError",
107
+ # Error-code registry
108
+ "ERROR_CODE_DESCRIPTIONS",
109
+ "ERROR_CODE_TO_EXCEPTION",
89
110
  # Utilities
90
111
  "american_to_decimal",
91
112
  "american_to_probability",
@@ -3,21 +3,29 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import random
6
+ from typing import Literal
6
7
 
7
8
  import httpx
8
9
 
9
10
  from .exceptions import (
11
+ ERROR_CODE_TO_EXCEPTION,
10
12
  AuthenticationError,
11
13
  RateLimitedError,
12
14
  SharpAPIError,
13
15
  TierRestrictedError,
14
16
  ValidationError,
17
+ canonical_code,
15
18
  )
16
19
  from .models import APIResponse, RateLimitInfo, ResponseMeta
17
20
 
18
21
  DEFAULT_BASE_URL = "https://api.sharpapi.io"
19
22
  DEFAULT_TIMEOUT = 30.0
20
- USER_AGENT = "sharpapi-python/0.2.2"
23
+ USER_AGENT = "sharpapi-python/0.2.5"
24
+
25
+ # Supported REST authentication methods. SSE always uses ``?api_key=`` query
26
+ # regardless of this setting because EventSource cannot set custom headers.
27
+ AuthMethod = Literal["x-api-key", "bearer"]
28
+ DEFAULT_AUTH_METHOD: AuthMethod = "x-api-key"
21
29
 
22
30
  RETRY_STATUSES = frozenset({502, 503, 504})
23
31
  RETRY_MAX_ATTEMPTS = 3
@@ -90,6 +98,30 @@ def handle_errors(response: httpx.Response) -> None:
90
98
  code = body.get("code", "unknown_error")
91
99
  status = response.status_code
92
100
 
101
+ # Resolve deprecated code aliases (bad_request, invalid_request → validation_error).
102
+ code = canonical_code(code)
103
+
104
+ # Prefer the canonical code→exception mapping for well-known codes; fall back
105
+ # to HTTP-status-based routing for responses that omit an error code.
106
+ exc_class = ERROR_CODE_TO_EXCEPTION.get(code or "")
107
+ if exc_class is TierRestrictedError:
108
+ raise TierRestrictedError(
109
+ error_msg,
110
+ code=code,
111
+ status=status,
112
+ required_tier=body.get("required_tier"),
113
+ )
114
+ if exc_class is RateLimitedError:
115
+ raise RateLimitedError(
116
+ error_msg,
117
+ code=code,
118
+ status=status,
119
+ retry_after=body.get("retry_after"),
120
+ )
121
+ if exc_class is not None and exc_class is not SharpAPIError:
122
+ raise exc_class(error_msg, code=code, status=status)
123
+
124
+ # No canonical code match — route by HTTP status.
93
125
  if status == 401:
94
126
  raise AuthenticationError(error_msg, code=code, status=status)
95
127
  elif status == 403:
@@ -112,13 +144,28 @@ def handle_errors(response: httpx.Response) -> None:
112
144
  raise SharpAPIError(error_msg, code=code, status=status)
113
145
 
114
146
 
115
- def make_headers(api_key: str) -> dict[str, str]:
116
- """Build default request headers."""
117
- return {
118
- "X-API-Key": api_key,
147
+ def make_headers(
148
+ api_key: str,
149
+ auth_method: AuthMethod = DEFAULT_AUTH_METHOD,
150
+ ) -> dict[str, str]:
151
+ """Build default request headers.
152
+
153
+ Args:
154
+ api_key: The SharpAPI key (e.g. ``sk_live_...``).
155
+ auth_method: Either ``"x-api-key"`` (default — sends an
156
+ ``X-API-Key`` header) or ``"bearer"`` (sends
157
+ ``Authorization: Bearer <key>``). Useful when proxies, IAM
158
+ layers, or SSO gateways strip non-standard custom headers.
159
+ """
160
+ headers: dict[str, str] = {
119
161
  "Content-Type": "application/json",
120
162
  "User-Agent": USER_AGENT,
121
163
  }
164
+ if auth_method == "bearer":
165
+ headers["Authorization"] = f"Bearer {api_key}"
166
+ else:
167
+ headers["X-API-Key"] = api_key
168
+ return headers
122
169
 
123
170
 
124
171
  def _int_or_none(value: str | None) -> int | None: