sutro 0.1.19__py3-none-any.whl → 0.1.20__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.
Potentially problematic release.
This version of sutro might be problematic. Click here for more details.
- sutro/sdk.py +75 -129
- {sutro-0.1.19.dist-info → sutro-0.1.20.dist-info}/METADATA +1 -1
- sutro-0.1.20.dist-info/RECORD +8 -0
- sutro-0.1.19.dist-info/RECORD +0 -8
- {sutro-0.1.19.dist-info → sutro-0.1.20.dist-info}/WHEEL +0 -0
- {sutro-0.1.19.dist-info → sutro-0.1.20.dist-info}/entry_points.txt +0 -0
- {sutro-0.1.19.dist-info → sutro-0.1.20.dist-info}/licenses/LICENSE +0 -0
sutro/sdk.py
CHANGED
|
@@ -114,7 +114,6 @@ class Sutro:
|
|
|
114
114
|
):
|
|
115
115
|
self.api_key = api_key or self.check_for_api_key()
|
|
116
116
|
self.base_url = base_url
|
|
117
|
-
self.HEARTBEAT_INTERVAL_SECONDS = 15 # Keep in sync w what the backend expects
|
|
118
117
|
|
|
119
118
|
def check_for_api_key(self):
|
|
120
119
|
"""
|
|
@@ -286,45 +285,51 @@ class Sutro:
|
|
|
286
285
|
job_id = None
|
|
287
286
|
t = f"Creating {'[dry run] ' if dry_run else ''}priority {job_priority} job"
|
|
288
287
|
spinner_text = to_colored_text(t)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
response_data = response.json()
|
|
294
|
-
if response.status_code != 200:
|
|
295
|
-
spinner.write(
|
|
296
|
-
to_colored_text(f"Error: {response.status_code}", state="fail")
|
|
288
|
+
try:
|
|
289
|
+
with yaspin(SPINNER, text=spinner_text, color=YASPIN_COLOR) as spinner:
|
|
290
|
+
response = requests.post(
|
|
291
|
+
endpoint, data=json.dumps(payload), headers=headers
|
|
297
292
|
)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
return None
|
|
301
|
-
else:
|
|
302
|
-
job_id = response_data["results"]
|
|
303
|
-
if dry_run:
|
|
293
|
+
response_data = response.json()
|
|
294
|
+
if response.status_code != 200:
|
|
304
295
|
spinner.write(
|
|
305
|
-
to_colored_text(f"
|
|
296
|
+
to_colored_text(f"Error: {response.status_code}", state="fail")
|
|
306
297
|
)
|
|
307
298
|
spinner.stop()
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
spinner.write(
|
|
311
|
-
to_colored_text(f"✔ Cost estimates retrieved for job {job_id}: ${cost_estimate}", state="success")
|
|
312
|
-
)
|
|
313
|
-
return job_id
|
|
299
|
+
print(to_colored_text(response.json(), state="fail"))
|
|
300
|
+
return None
|
|
314
301
|
else:
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
state="
|
|
302
|
+
job_id = response_data["results"]
|
|
303
|
+
if dry_run:
|
|
304
|
+
spinner.write(
|
|
305
|
+
to_colored_text(f"Awaiting cost estimates with job ID: {job_id}. You can safely detach and retrieve the cost estimates later.", state="info")
|
|
319
306
|
)
|
|
320
|
-
|
|
321
|
-
|
|
307
|
+
spinner.stop()
|
|
308
|
+
self.await_job_completion(job_id, obtain_results=False)
|
|
309
|
+
cost_estimate = self._get_job_cost_estimate(job_id)
|
|
310
|
+
spinner.write(
|
|
311
|
+
to_colored_text(f"✔ Cost estimates retrieved for job {job_id}: ${cost_estimate}", state="success")
|
|
312
|
+
)
|
|
313
|
+
return job_id
|
|
314
|
+
else:
|
|
322
315
|
spinner.write(
|
|
323
316
|
to_colored_text(
|
|
324
|
-
f"
|
|
325
|
-
|
|
317
|
+
f"🛠 Priority {job_priority} Job created with ID: {job_id}.",
|
|
318
|
+
state="success",
|
|
326
319
|
)
|
|
327
|
-
|
|
320
|
+
)
|
|
321
|
+
if not stay_attached:
|
|
322
|
+
spinner.write(
|
|
323
|
+
to_colored_text(
|
|
324
|
+
f"Use `so.get_job_status('{job_id}')` to check the status of the job."
|
|
325
|
+
)
|
|
326
|
+
)
|
|
327
|
+
return job_id
|
|
328
|
+
except KeyboardInterrupt:
|
|
329
|
+
pass
|
|
330
|
+
finally:
|
|
331
|
+
if spinner:
|
|
332
|
+
spinner.stop()
|
|
328
333
|
|
|
329
334
|
success = False
|
|
330
335
|
if stay_attached and job_id is not None:
|
|
@@ -335,20 +340,12 @@ class Sutro:
|
|
|
335
340
|
failure_reason = self._get_failure_reason(job_id)
|
|
336
341
|
spinner.write(to_colored_text(f"Failure reason: {failure_reason['message']}", "fail"))
|
|
337
342
|
return None
|
|
338
|
-
|
|
339
343
|
s = requests.Session()
|
|
340
|
-
payload = {
|
|
341
|
-
"job_id": job_id,
|
|
342
|
-
}
|
|
343
344
|
pbar = None
|
|
344
345
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
# Use the heartbeat session context manager
|
|
349
|
-
with self.stream_heartbeat_session(job_id, session_token) as s:
|
|
350
|
-
with s.get(
|
|
351
|
-
f"{self.base_url}/stream-job-progress/{job_id}?request_session_token={session_token}",
|
|
346
|
+
try:
|
|
347
|
+
with requests.get(
|
|
348
|
+
f"{self.base_url}/stream-job-progress/{job_id}",
|
|
352
349
|
headers=headers,
|
|
353
350
|
stream=True,
|
|
354
351
|
) as streaming_response:
|
|
@@ -359,6 +356,13 @@ class Sutro:
|
|
|
359
356
|
color=YASPIN_COLOR,
|
|
360
357
|
)
|
|
361
358
|
spinner.start()
|
|
359
|
+
|
|
360
|
+
token_state = {
|
|
361
|
+
'input_tokens': 0,
|
|
362
|
+
'output_tokens': 0,
|
|
363
|
+
'total_tokens_processed_per_second': 0
|
|
364
|
+
}
|
|
365
|
+
|
|
362
366
|
for line in streaming_response.iter_lines():
|
|
363
367
|
if line:
|
|
364
368
|
try:
|
|
@@ -381,12 +385,30 @@ class Sutro:
|
|
|
381
385
|
pbar.update(json_obj["result"] - pbar.n)
|
|
382
386
|
pbar.refresh()
|
|
383
387
|
if json_obj["result"] == len(input_data):
|
|
384
|
-
pbar.close()
|
|
385
388
|
success = True
|
|
386
389
|
elif json_obj["update_type"] == "tokens":
|
|
390
|
+
# Update only the values that are present in this update
|
|
391
|
+
# Currently, the way the progress stream endpoint is defined,
|
|
392
|
+
# its possible to have updates come in that only have 1 or 2 fields
|
|
393
|
+
new = {
|
|
394
|
+
k: v for k, v in json_obj.get('result', {}).items()
|
|
395
|
+
if k in token_state and v >= token_state[k]
|
|
396
|
+
}
|
|
397
|
+
token_state.update(new)
|
|
398
|
+
|
|
387
399
|
if pbar is not None:
|
|
388
|
-
pbar.postfix = f"Input tokens processed: {
|
|
400
|
+
pbar.postfix = f"Input tokens processed: {token_state['input_tokens']}, Output tokens generated: {token_state['output_tokens']}, Total tokens/s: {token_state['total_tokens_processed_per_second']}"
|
|
389
401
|
pbar.refresh()
|
|
402
|
+
|
|
403
|
+
except KeyboardInterrupt:
|
|
404
|
+
pass
|
|
405
|
+
finally:
|
|
406
|
+
# Need to clean these up on keyboard exit otherwise it causes
|
|
407
|
+
# an error
|
|
408
|
+
if pbar is not None:
|
|
409
|
+
pbar.close()
|
|
410
|
+
if spinner is not None:
|
|
411
|
+
spinner.stop()
|
|
390
412
|
if success:
|
|
391
413
|
spinner.text = to_colored_text(
|
|
392
414
|
"✔ Job succeeded. Obtaining results...", state="success"
|
|
@@ -451,87 +473,6 @@ class Sutro:
|
|
|
451
473
|
return None
|
|
452
474
|
return None
|
|
453
475
|
|
|
454
|
-
def register_stream_listener(self, job_id: str) -> str:
|
|
455
|
-
"""Register a new stream listener and get a session token."""
|
|
456
|
-
headers = {
|
|
457
|
-
"Authorization": f"Key {self.api_key}",
|
|
458
|
-
"Content-Type": "application/json",
|
|
459
|
-
}
|
|
460
|
-
with requests.post(
|
|
461
|
-
f"{self.base_url}/register-stream-listener/{job_id}",
|
|
462
|
-
headers=headers,
|
|
463
|
-
) as response:
|
|
464
|
-
response.raise_for_status()
|
|
465
|
-
data = response.json()
|
|
466
|
-
return data["request_session_token"]
|
|
467
|
-
|
|
468
|
-
# This is a best effort action and is ok if it sometimes doesn't complete etc
|
|
469
|
-
def unregister_stream_listener(self, job_id: str, session_token: str):
|
|
470
|
-
"""Explicitly unregister a stream listener."""
|
|
471
|
-
headers = {
|
|
472
|
-
"Authorization": f"Key {self.api_key}",
|
|
473
|
-
"Content-Type": "application/json",
|
|
474
|
-
}
|
|
475
|
-
with requests.post(
|
|
476
|
-
f"{self.base_url}/unregister-stream-listener/{job_id}",
|
|
477
|
-
headers=headers,
|
|
478
|
-
json={"request_session_token": session_token},
|
|
479
|
-
) as response:
|
|
480
|
-
response.raise_for_status()
|
|
481
|
-
|
|
482
|
-
def start_heartbeat(
|
|
483
|
-
self,
|
|
484
|
-
job_id: str,
|
|
485
|
-
session_token: str,
|
|
486
|
-
session: requests.Session,
|
|
487
|
-
stop_event: threading.Event
|
|
488
|
-
):
|
|
489
|
-
"""Send heartbeats until stopped."""
|
|
490
|
-
while not stop_event.is_set():
|
|
491
|
-
try:
|
|
492
|
-
headers = {
|
|
493
|
-
"Authorization": f"Key {self.api_key}",
|
|
494
|
-
"Content-Type": "application/json",
|
|
495
|
-
}
|
|
496
|
-
response = session.post(
|
|
497
|
-
f"{self.base_url}/stream-heartbeat/{job_id}",
|
|
498
|
-
headers=headers,
|
|
499
|
-
params={"request_session_token": session_token},
|
|
500
|
-
)
|
|
501
|
-
response.raise_for_status()
|
|
502
|
-
except Exception as e:
|
|
503
|
-
if not stop_event.is_set(): # Only log if we weren't stopping anyway
|
|
504
|
-
print(f"Heartbeat failed for job {job_id}: {e}")
|
|
505
|
-
|
|
506
|
-
for _ in range(self.HEARTBEAT_INTERVAL_SECONDS):
|
|
507
|
-
if stop_event.is_set():
|
|
508
|
-
break
|
|
509
|
-
time.sleep(1)
|
|
510
|
-
|
|
511
|
-
@contextmanager
|
|
512
|
-
def stream_heartbeat_session(self, job_id: str, session_token: str) -> Generator[requests.Session, None, None]:
|
|
513
|
-
"""Context manager that handles session registration and heartbeat."""
|
|
514
|
-
session = requests.Session()
|
|
515
|
-
stop_heartbeat = threading.Event()
|
|
516
|
-
|
|
517
|
-
# Run this concurrently in a thread so we can not block main SDK path/behavior
|
|
518
|
-
# but still run heartbeat requests
|
|
519
|
-
with ThreadPoolExecutor(max_workers=1) as executor:
|
|
520
|
-
executor.submit(
|
|
521
|
-
self.start_heartbeat,
|
|
522
|
-
job_id,
|
|
523
|
-
session_token,
|
|
524
|
-
session,
|
|
525
|
-
stop_heartbeat
|
|
526
|
-
)
|
|
527
|
-
|
|
528
|
-
try:
|
|
529
|
-
yield session
|
|
530
|
-
finally:
|
|
531
|
-
# Signal stop and cleanup
|
|
532
|
-
stop_heartbeat.set()
|
|
533
|
-
self.unregister_stream_listener(job_id, session_token)
|
|
534
|
-
session.close()
|
|
535
476
|
|
|
536
477
|
def attach(self, job_id):
|
|
537
478
|
"""
|
|
@@ -596,11 +537,9 @@ class Sutro:
|
|
|
596
537
|
total_rows = job["num_rows"]
|
|
597
538
|
success = False
|
|
598
539
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
with self.stream_heartbeat_session(job_id, session_token) as s:
|
|
540
|
+
try:
|
|
602
541
|
with s.get(
|
|
603
|
-
f"{self.base_url}/stream-job-progress/{job_id}
|
|
542
|
+
f"{self.base_url}/stream-job-progress/{job_id}",
|
|
604
543
|
headers=headers,
|
|
605
544
|
stream=True,
|
|
606
545
|
) as streaming_response:
|
|
@@ -649,6 +588,13 @@ class Sutro:
|
|
|
649
588
|
)
|
|
650
589
|
)
|
|
651
590
|
spinner.stop()
|
|
591
|
+
except KeyboardInterrupt:
|
|
592
|
+
pass
|
|
593
|
+
finally:
|
|
594
|
+
if pbar:
|
|
595
|
+
pbar.close()
|
|
596
|
+
if spinner:
|
|
597
|
+
spinner.stop()
|
|
652
598
|
|
|
653
599
|
|
|
654
600
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
sutro/__init__.py,sha256=yUiVwcZ8QamSqDdRHgzoANyTZ-x3cPzlt2Fs5OllR_w,402
|
|
2
|
+
sutro/cli.py,sha256=6Qy9Vwaaho92HeO8YA_z1De4zp1dEFkSX3bEnLvdbkE,13203
|
|
3
|
+
sutro/sdk.py,sha256=ct-uGpVXeWq4oaFrXy0jtxQ5LmhuI5tNqo7pISxlPPo,50485
|
|
4
|
+
sutro-0.1.20.dist-info/METADATA,sha256=uZ3H5UCqRD7m5MCmDY7eAOGj8T-VzK5Zfa3zxgthCOM,669
|
|
5
|
+
sutro-0.1.20.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
+
sutro-0.1.20.dist-info/entry_points.txt,sha256=eXvr4dvMV4UmZgR0zmrY8KOmNpo64cJkhNDywiadRFM,40
|
|
7
|
+
sutro-0.1.20.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
8
|
+
sutro-0.1.20.dist-info/RECORD,,
|
sutro-0.1.19.dist-info/RECORD
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
sutro/__init__.py,sha256=yUiVwcZ8QamSqDdRHgzoANyTZ-x3cPzlt2Fs5OllR_w,402
|
|
2
|
-
sutro/cli.py,sha256=6Qy9Vwaaho92HeO8YA_z1De4zp1dEFkSX3bEnLvdbkE,13203
|
|
3
|
-
sutro/sdk.py,sha256=1FLepL3M7afptWwudF310KWmPQ9ZDQ51LiT0Xoh1S_o,52705
|
|
4
|
-
sutro-0.1.19.dist-info/METADATA,sha256=6Irt5RX_DaIyRC6ig4XvHr0p0CKnFK0nmWA23oi1mCA,669
|
|
5
|
-
sutro-0.1.19.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
-
sutro-0.1.19.dist-info/entry_points.txt,sha256=eXvr4dvMV4UmZgR0zmrY8KOmNpo64cJkhNDywiadRFM,40
|
|
7
|
-
sutro-0.1.19.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
8
|
-
sutro-0.1.19.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|