helius-python 0.5.0__tar.gz → 0.5.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.
- {helius_python-0.5.0 → helius_python-0.5.1}/PKG-INFO +1 -1
- {helius_python-0.5.0 → helius_python-0.5.1}/pyproject.toml +1 -1
- {helius_python-0.5.0 → helius_python-0.5.1}/test_examples.py +83 -22
- {helius_python-0.5.0 → helius_python-0.5.1}/.editorconfig +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/.github/workflows/python-package.yml +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/.github/workflows/python-publish.yml +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/.gitignore +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/AGENTS.md +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/CLAUDE.md +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/CONTRIBUTING.md +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/LICENSE +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/README.md +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/TODO.md +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/examples/laserstream/websocket_logs.py +0 -0
- {helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/address_transactions.py +0 -0
- {helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/address_transfers.py +0 -0
- {helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/block_explorer.py +0 -0
- {helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/devnet_airdrop.py +0 -0
- {helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/network_status.py +0 -0
- {helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/priority_fees.py +0 -0
- {helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/stake_overview.py +0 -0
- {helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/token_inspector.py +0 -0
- {helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/transaction_inspector.py +0 -0
- {helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/wallet_tracker.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/examples/webhooks/webhook_crud.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/examples/webhooks/webhook_receiver.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/requirements.txt +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/src/helius/__init__.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/src/helius/admin/__init__.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/src/helius/admin/admin.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/src/helius/laserstream/websockets.py +21 -21
- {helius_python-0.5.0 → helius_python-0.5.1}/src/helius/rpc/__init__.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/src/helius/rpc/client.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/src/helius/rpc/models.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/src/helius/utils/__init__.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/src/helius/utils/json_rpc_request.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/src/helius/webhooks/__init__.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/src/helius/webhooks/webhooks.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/tests/fixtures/account.json +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/tests/fixtures/supply.json +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/tests/unit/admin/test_admin.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/tests/unit/lasterstream/test_websockets.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/tests/unit/solana_rpc/test_client.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/tests/unit/solana_rpc/test_models.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/tests/unit/utils/test_json_rpc_request.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/tests/unit/webhooks/test_webhook.py +0 -0
- {helius_python-0.5.0 → helius_python-0.5.1}/tests/unit/webhooks/test_webhooks_api_client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: helius-python
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Typed Python client for the Helius API
|
|
5
5
|
Project-URL: Homepage, https://github.com/markosnarinian/helius-python
|
|
6
6
|
Project-URL: Issues, https://github.com/markosnarinian/helius-python/issues
|
|
@@ -9,13 +9,15 @@ Usage:
|
|
|
9
9
|
.venv/bin/python test_examples.py
|
|
10
10
|
|
|
11
11
|
The runner expects `HELIUS_API_KEY` to be available in the environment or in
|
|
12
|
-
`.env`, matching the examples themselves.
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
`.env`, matching the examples themselves. Missing/unauthorized API key failures
|
|
13
|
+
are reported as "auth" instead of as example runtime bugs. Some Helius endpoints
|
|
14
|
+
are plan-gated or network-gated; other failures in that class are reported as
|
|
15
|
+
"external".
|
|
15
16
|
"""
|
|
16
17
|
|
|
17
18
|
from __future__ import annotations
|
|
18
19
|
|
|
20
|
+
import argparse
|
|
19
21
|
import os
|
|
20
22
|
import subprocess
|
|
21
23
|
import sys
|
|
@@ -28,6 +30,7 @@ USE_COLOR = "NO_COLOR" not in os.environ
|
|
|
28
30
|
|
|
29
31
|
GREEN = "\033[32m"
|
|
30
32
|
YELLOW = "\033[33m"
|
|
33
|
+
ORANGE = "\033[38;5;208m"
|
|
31
34
|
RED = "\033[31m"
|
|
32
35
|
BOLD = "\033[1m"
|
|
33
36
|
RESET = "\033[0m"
|
|
@@ -37,6 +40,16 @@ SMALL_MINT = "J5iyNuTa6zqqA62Xe4h1VBvcBW5CTSNNva3QPh8DU5RV"
|
|
|
37
40
|
KNOWN_SIGNATURE = "eqRntqi1tjXv1zEGBM5btQGWoxWc73XXGDJXjxLE65Atj6T6qzNnJf5LyTbUoGXHS9TzeAnQniAre48SjcJft9f"
|
|
38
41
|
DEVNET_ADDRESS = "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"
|
|
39
42
|
|
|
43
|
+
AUTH_FAILURE_MARKERS = (
|
|
44
|
+
"No API key provided.",
|
|
45
|
+
"HTTP 401",
|
|
46
|
+
"401 Unauthorized",
|
|
47
|
+
"HTTP 403",
|
|
48
|
+
"403 Forbidden",
|
|
49
|
+
"Unauthorized",
|
|
50
|
+
"Forbidden",
|
|
51
|
+
)
|
|
52
|
+
|
|
40
53
|
|
|
41
54
|
@dataclass(frozen=True)
|
|
42
55
|
class ExampleTest:
|
|
@@ -118,8 +131,6 @@ TESTS = [
|
|
|
118
131
|
timeout=45,
|
|
119
132
|
external_failure_markers=(
|
|
120
133
|
"TimeoutError: timed out",
|
|
121
|
-
"403 Forbidden",
|
|
122
|
-
"HTTP 403",
|
|
123
134
|
),
|
|
124
135
|
),
|
|
125
136
|
ExampleTest(
|
|
@@ -141,6 +152,41 @@ def color(text: str, ansi_color: str) -> str:
|
|
|
141
152
|
return f"{ansi_color}{text}{RESET}"
|
|
142
153
|
|
|
143
154
|
|
|
155
|
+
def parse_args() -> argparse.Namespace:
|
|
156
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
157
|
+
parser.add_argument(
|
|
158
|
+
"-v",
|
|
159
|
+
action="count",
|
|
160
|
+
default=0,
|
|
161
|
+
dest="verbose_count",
|
|
162
|
+
help="increase verbosity; use -v for level 1 or -vv for level 2",
|
|
163
|
+
)
|
|
164
|
+
parser.add_argument(
|
|
165
|
+
"--verbose",
|
|
166
|
+
type=int,
|
|
167
|
+
choices=(0, 1, 2),
|
|
168
|
+
default=None,
|
|
169
|
+
help=(
|
|
170
|
+
"0: only results and failure names; "
|
|
171
|
+
"1: include script output for non-passing tests; "
|
|
172
|
+
"2: include script output for all tests"
|
|
173
|
+
),
|
|
174
|
+
)
|
|
175
|
+
args = parser.parse_args()
|
|
176
|
+
args.verbose = (
|
|
177
|
+
args.verbose if args.verbose is not None else min(args.verbose_count, 2)
|
|
178
|
+
)
|
|
179
|
+
return args
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def output_preview(output: str) -> str:
|
|
183
|
+
lines = output.strip().splitlines()
|
|
184
|
+
preview = "\n".join(lines[:30])
|
|
185
|
+
if len(lines) > 30:
|
|
186
|
+
preview += f"\n... ({len(lines) - 30} more lines)"
|
|
187
|
+
return preview
|
|
188
|
+
|
|
189
|
+
|
|
144
190
|
def run_example(test: ExampleTest) -> tuple[str, str]:
|
|
145
191
|
env = os.environ.copy()
|
|
146
192
|
env["PYTHONPATH"] = (
|
|
@@ -167,47 +213,60 @@ def run_example(test: ExampleTest) -> tuple[str, str]:
|
|
|
167
213
|
output = result.stdout + result.stderr
|
|
168
214
|
if result.returncode == 0:
|
|
169
215
|
return "passed", output
|
|
216
|
+
if any(marker in output for marker in AUTH_FAILURE_MARKERS):
|
|
217
|
+
return "auth", output
|
|
170
218
|
if any(marker in output for marker in test.external_failure_markers):
|
|
171
219
|
return "external", output
|
|
172
220
|
return "failed", output
|
|
173
221
|
|
|
174
222
|
|
|
175
223
|
def main() -> int:
|
|
224
|
+
args = parse_args()
|
|
176
225
|
passed: list[str] = []
|
|
226
|
+
auth: list[str] = []
|
|
177
227
|
external: list[str] = []
|
|
178
228
|
failed: list[tuple[str, str]] = []
|
|
179
229
|
|
|
180
230
|
for test in TESTS:
|
|
181
|
-
print(f"\n=== {test.name} ===", flush=True)
|
|
182
231
|
status, output = run_example(test)
|
|
183
232
|
if status == "passed":
|
|
184
233
|
passed.append(test.name)
|
|
185
|
-
|
|
234
|
+
result = color("PASS", GREEN + BOLD)
|
|
235
|
+
elif status == "auth":
|
|
236
|
+
auth.append(test.name)
|
|
237
|
+
result = (
|
|
238
|
+
color("AUTH", ORANGE + BOLD)
|
|
239
|
+
+ " - missing API key or endpoint is not authorized for this key"
|
|
240
|
+
)
|
|
186
241
|
elif status == "external":
|
|
187
242
|
external.append(test.name)
|
|
188
|
-
|
|
189
|
-
color(
|
|
190
|
-
|
|
191
|
-
YELLOW + BOLD,
|
|
192
|
-
)
|
|
193
|
-
+ ": endpoint, plan, or network prevented a live success"
|
|
243
|
+
result = (
|
|
244
|
+
color("EXTERNAL", YELLOW + BOLD)
|
|
245
|
+
+ " - endpoint, plan, or network prevented a live success"
|
|
194
246
|
)
|
|
195
247
|
else:
|
|
196
248
|
failed.append((test.name, output))
|
|
197
|
-
|
|
249
|
+
result = color("FAIL", RED + BOLD)
|
|
198
250
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
251
|
+
print(f"{result}: {test.name}", flush=True)
|
|
252
|
+
|
|
253
|
+
should_print_output = output.strip() and (
|
|
254
|
+
args.verbose == 2 or (args.verbose == 1 and status != "passed")
|
|
255
|
+
)
|
|
256
|
+
if should_print_output:
|
|
257
|
+
print(output_preview(output))
|
|
205
258
|
|
|
206
259
|
print("\n=== Summary ===")
|
|
207
260
|
print(f"{color('Passed', GREEN)} : {len(passed)}")
|
|
261
|
+
print(f"{color('Auth', ORANGE)} : {len(auth)}")
|
|
208
262
|
print(f"{color('External', YELLOW)} : {len(external)}")
|
|
209
263
|
print(f"{color('Failed', RED)} : {len(failed)}")
|
|
210
264
|
|
|
265
|
+
if auth:
|
|
266
|
+
print("\nAuth failures:")
|
|
267
|
+
for name in auth:
|
|
268
|
+
print(f" - {name}")
|
|
269
|
+
|
|
211
270
|
if external:
|
|
212
271
|
print("\nExternal failures:")
|
|
213
272
|
for name in external:
|
|
@@ -216,8 +275,10 @@ def main() -> int:
|
|
|
216
275
|
if failed:
|
|
217
276
|
print("\nUnexpected failures:")
|
|
218
277
|
for name, output in failed:
|
|
219
|
-
print(f"
|
|
220
|
-
|
|
278
|
+
print(f" - {name}")
|
|
279
|
+
if args.verbose >= 1:
|
|
280
|
+
print(f"\n--- {name} ---")
|
|
281
|
+
print(output.strip())
|
|
221
282
|
return 1
|
|
222
283
|
|
|
223
284
|
return 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/address_transactions.py
RENAMED
|
File without changes
|
{helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/address_transfers.py
RENAMED
|
File without changes
|
{helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/block_explorer.py
RENAMED
|
File without changes
|
{helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/devnet_airdrop.py
RENAMED
|
File without changes
|
{helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/network_status.py
RENAMED
|
File without changes
|
{helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/priority_fees.py
RENAMED
|
File without changes
|
{helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/stake_overview.py
RENAMED
|
File without changes
|
{helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/token_inspector.py
RENAMED
|
File without changes
|
|
File without changes
|
{helius_python-0.5.0/examples/solana_rpc → helius_python-0.5.1/examples/rpc}/wallet_tracker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -163,6 +163,27 @@ class WebSocketClient:
|
|
|
163
163
|
response = self._websocket.recv()
|
|
164
164
|
return json.loads(response)
|
|
165
165
|
|
|
166
|
+
def receive(self) -> tuple[dict | None, Notification, int]:
|
|
167
|
+
response = json.loads(self._websocket.recv())
|
|
168
|
+
model = self.MODELS[response["method"]]
|
|
169
|
+
result = response["params"]["result"]
|
|
170
|
+
subscription = response["params"]["subscription"]
|
|
171
|
+
if isinstance(result, dict):
|
|
172
|
+
context = result.get("context")
|
|
173
|
+
value = result.get("value")
|
|
174
|
+
else:
|
|
175
|
+
context, value = None, None
|
|
176
|
+
if value is not None:
|
|
177
|
+
notification = model.model_validate(value)
|
|
178
|
+
else:
|
|
179
|
+
notification = model.model_validate(result)
|
|
180
|
+
return context, notification, subscription
|
|
181
|
+
|
|
182
|
+
def listen(self):
|
|
183
|
+
while True:
|
|
184
|
+
context, notification, subscription = self.receive()
|
|
185
|
+
yield context, notification, subscription
|
|
186
|
+
|
|
166
187
|
def _unsubscribe(self, subscription_type, subscription) -> bool:
|
|
167
188
|
request = (
|
|
168
189
|
JsonRpcRequest(method=f"{subscription_type}Unsubscribe")
|
|
@@ -376,24 +397,3 @@ class WebSocketClient:
|
|
|
376
397
|
|
|
377
398
|
def vote_unsubscribe(self, subscription) -> bool:
|
|
378
399
|
return self._unsubscribe("vote", subscription)
|
|
379
|
-
|
|
380
|
-
def receive(self) -> tuple[dict | None, Notification, int]:
|
|
381
|
-
response = json.loads(self._websocket.recv())
|
|
382
|
-
model = self.MODELS[response["method"]]
|
|
383
|
-
result = response["params"]["result"]
|
|
384
|
-
subscription = response["params"]["subscription"]
|
|
385
|
-
if isinstance(result, dict):
|
|
386
|
-
context = result.get("context")
|
|
387
|
-
value = result.get("value")
|
|
388
|
-
else:
|
|
389
|
-
context, value = None, None
|
|
390
|
-
if value is not None:
|
|
391
|
-
notification = model.model_validate(value)
|
|
392
|
-
else:
|
|
393
|
-
notification = model.model_validate(result)
|
|
394
|
-
return context, notification, subscription
|
|
395
|
-
|
|
396
|
-
def listen(self):
|
|
397
|
-
while True:
|
|
398
|
-
context, notification, subscription = self.receive()
|
|
399
|
-
yield context, notification, subscription
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|