Skip to content

Commit 49decec

Browse files
committed
Merge pull request #1 from sahmk-sa/fix/v0.6.1-patch
v0.6.1: Patch — validation fixes and Beta classifier
2 parents b4cb830 + d97f7a2 commit 49decec

10 files changed

Lines changed: 251 additions & 87 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@ All notable changes to the `sahmk` Python SDK will be documented in this file.
44

55
This project follows [Semantic Versioning](https://semver.org/).
66

7+
## [0.6.1] — 2026-04-02
8+
9+
### Fixed
10+
11+
- **CLI `--compact` flag** now works in both positions: `sahmk --compact quote 2222` and `sahmk quote 2222 --compact` (previously only the first form worked)
12+
- **Non-JSON 200 responses** (e.g. proxy HTML errors) are now wrapped in `SahmkError` instead of raising a raw `ValueError`
13+
- **`on_reconnect` docstring** corrected — it fires before a reconnect attempt (after backoff delay), not after a successful reconnection
14+
- **Test fixtures** aligned with real API response shapes (market summary, company, financials, dividends, events)
15+
- **`quotes([])` guard** — calling `quotes()` with an empty list now raises `ValueError` immediately instead of sending an invalid request
16+
- **Redundant 429** removed from internal `_RETRIABLE_STATUS_CODES` (429 is handled by its own dedicated branch)
17+
18+
### Changed
19+
20+
- **PyPI classifier** updated from "3 - Alpha" to "4 - Beta"
21+
722
## [0.6.0] — 2026-04-02
823

924
### Added

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "sahmk"
7-
version = "0.6.0"
7+
version = "0.6.1"
88
description = "Lightweight Python client for the SAHMK Developer API."
99
readme = "README.md"
1010
requires-python = ">=3.9"
@@ -14,7 +14,7 @@ authors = [
1414
]
1515
keywords = ["stocks", "tadawul", "market-data", "websocket", "api"]
1616
classifiers = [
17-
"Development Status :: 3 - Alpha",
17+
"Development Status :: 4 - Beta",
1818
"Intended Audience :: Developers",
1919
"Operating System :: OS Independent",
2020
"Programming Language :: Python :: 3",

sahmk/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
Liquidity,
2727
)
2828

29-
__version__ = "0.6.0"
29+
__version__ = "0.6.1"
3030
__all__ = [
3131
"SahmkClient",
3232
"SahmkError",

sahmk/cli.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@
1111
from .client import SahmkClient, SahmkError
1212

1313

14+
def _compact_arg(parser):
15+
"""Add --compact flag to a parser."""
16+
parser.add_argument(
17+
"--compact",
18+
action="store_true",
19+
help="Print compact JSON output.",
20+
)
21+
22+
1423
def _build_parser():
1524
parser = argparse.ArgumentParser(
1625
prog="sahmk",
@@ -31,16 +40,12 @@ def _build_parser():
3140
default=30,
3241
help="Request timeout in seconds (default: 30).",
3342
)
34-
parser.add_argument(
35-
"--compact",
36-
action="store_true",
37-
help="Print compact JSON output.",
38-
)
3943

4044
subparsers = parser.add_subparsers(dest="command", required=True)
4145

4246
quote_parser = subparsers.add_parser("quote", help="Get a single stock quote.")
4347
quote_parser.add_argument("symbol", help='Stock symbol (e.g., "2222").')
48+
_compact_arg(quote_parser)
4449

4550
quotes_parser = subparsers.add_parser(
4651
"quotes", help="Get quotes for multiple symbols."
@@ -49,6 +54,7 @@ def _build_parser():
4954
"symbols",
5055
help='Comma-separated symbols, e.g. "2222,1120,2010".',
5156
)
57+
_compact_arg(quotes_parser)
5258

5359
market_parser = subparsers.add_parser("market", help="Market overview endpoints.")
5460
market_parser.add_argument(
@@ -61,6 +67,7 @@ def _build_parser():
6167
type=int,
6268
help="Optional limit for gainers/losers/volume/value.",
6369
)
70+
_compact_arg(market_parser)
6471

6572
historical_parser = subparsers.add_parser(
6673
"historical", help="Get historical OHLCV data."
@@ -73,21 +80,25 @@ def _build_parser():
7380
choices=["1d", "1w", "1m"],
7481
help='Interval: "1d", "1w", or "1m".',
7582
)
83+
_compact_arg(historical_parser)
7684

7785
company_parser = subparsers.add_parser(
7886
"company", help="Get company info (tiered by plan)."
7987
)
8088
company_parser.add_argument("symbol", help='Stock symbol (e.g., "2222").')
89+
_compact_arg(company_parser)
8190

8291
financials_parser = subparsers.add_parser(
8392
"financials", help="Get financial statements (Starter+ plan)."
8493
)
8594
financials_parser.add_argument("symbol", help='Stock symbol (e.g., "2222").')
95+
_compact_arg(financials_parser)
8696

8797
dividends_parser = subparsers.add_parser(
8898
"dividends", help="Get dividend history and yield (Starter+ plan)."
8999
)
90100
dividends_parser.add_argument("symbol", help='Stock symbol (e.g., "2222").')
101+
_compact_arg(dividends_parser)
91102

92103
events_parser = subparsers.add_parser(
93104
"events", help="Get AI-generated stock events (Pro+ plan)."
@@ -103,6 +114,7 @@ def _build_parser():
103114
default=None,
104115
help="Number of events to return.",
105116
)
117+
_compact_arg(events_parser)
106118

107119
stream_parser = subparsers.add_parser(
108120
"stream", help="Stream real-time quotes via WebSocket (Pro+ plan)."

sahmk/client.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
logger = logging.getLogger("sahmk")
1818

19-
_RETRIABLE_STATUS_CODES = frozenset({429, 500, 502, 503, 504})
19+
_RETRIABLE_STATUS_CODES = frozenset({500, 502, 503, 504})
2020

2121

2222
class SahmkError(Exception):
@@ -154,7 +154,14 @@ def _request(self, method, endpoint, params=None):
154154
if response.status_code != 200:
155155
raise self._build_api_error(response)
156156

157-
return response.json()
157+
try:
158+
return response.json()
159+
except (ValueError, TypeError) as e:
160+
raise SahmkError(
161+
f"Unexpected non-JSON response: {e}",
162+
status_code=response.status_code,
163+
response=response,
164+
)
158165

159166
raise last_exc # pragma: no cover
160167

@@ -256,6 +263,8 @@ def quotes(self, symbols):
256263
BatchQuotesResponse with .quotes list and .count
257264
"""
258265
from .models import BatchQuotesResponse
266+
if not symbols:
267+
raise ValueError("At least one symbol is required")
259268
if len(symbols) > 50:
260269
raise SahmkError("Maximum 50 symbols per batch request")
261270
data = self._request(
@@ -478,8 +487,9 @@ async def stream(
478487
on_error: Async callback — on_error(error_data)
479488
on_disconnect: Async callback — on_disconnect(reason) called when
480489
the connection drops. Receives a string reason.
481-
on_reconnect: Async callback — on_reconnect(attempt) called after
482-
a successful reconnection. Receives the attempt number.
490+
on_reconnect: Async callback — on_reconnect(attempt) called before
491+
a reconnect attempt (after the backoff delay).
492+
Receives the attempt number.
483493
ping_interval: Seconds between keep-alive pings (default: 30)
484494
max_reconnect_attempts: Maximum reconnection attempts. 0 means
485495
unlimited reconnection (default). Set to -1

0 commit comments

Comments
 (0)