ask-shell 0.5.0__tar.gz → 0.5.2__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.
Files changed (28) hide show
  1. {ask_shell-0.5.0 → ask_shell-0.5.2}/PKG-INFO +1 -1
  2. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/__init__.py +1 -1
  3. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/_internal/_run.py +22 -0
  4. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/_internal/models.py +3 -0
  5. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/_internal/rich_progress.py +12 -2
  6. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/test_docs.py +1 -1
  7. {ask_shell-0.5.0 → ask_shell-0.5.2}/pyproject.toml +1 -1
  8. {ask_shell-0.5.0 → ask_shell-0.5.2}/.gitignore +0 -0
  9. {ask_shell-0.5.0 → ask_shell-0.5.2}/LICENSE +0 -0
  10. {ask_shell-0.5.0 → ask_shell-0.5.2}/README.md +0 -0
  11. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/__main__.py +0 -0
  12. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/_internal/__init__.py +0 -0
  13. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/_internal/_run_env.py +0 -0
  14. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/_internal/events.py +0 -0
  15. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/_internal/global_callbacks.py +0 -0
  16. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/_internal/interactive.py +0 -0
  17. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/_internal/rich_live.py +0 -0
  18. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/_internal/rich_live_callback.py +0 -0
  19. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/_internal/rich_run_state.py +0 -0
  20. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/_internal/run_pool.py +0 -0
  21. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/_internal/typer_command.py +0 -0
  22. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/ask.py +0 -0
  23. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/console.py +0 -0
  24. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/py.typed +0 -0
  25. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/settings.py +0 -0
  26. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/shell.py +0 -0
  27. {ask_shell-0.5.0 → ask_shell-0.5.2}/ask_shell/shell_events.py +0 -0
  28. {ask_shell-0.5.0 → ask_shell-0.5.2}/scripts/fix_source_links.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ask-shell
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Summary: CLIs with prompts, shell runs, and testable flows
5
5
  Author-email: EspenAlbert <espen.albert1@gmail.com>
6
6
  License-Expression: MIT
@@ -6,7 +6,7 @@ from ask_shell import shell_events
6
6
  from ask_shell import ask
7
7
  from ask_shell.settings import AskShellSettings
8
8
 
9
- VERSION = "0.5.0"
9
+ VERSION = "0.5.2"
10
10
  __all__ = [
11
11
  "shell",
12
12
  "console",
@@ -13,6 +13,7 @@ from __future__ import annotations
13
13
  import atexit
14
14
  import logging
15
15
  import os
16
+ import random
16
17
  import signal
17
18
  import subprocess
18
19
  import sys
@@ -382,11 +383,20 @@ def _eval_should_retry(config: ShellConfig, result: ShellRun) -> bool:
382
383
  return False
383
384
 
384
385
 
386
+ def _backoff_wait(attempt: int, config: ShellConfig) -> float:
387
+ """wait = min(initial * 2^(attempt-2), max_wait) + uniform(0, jitter)"""
388
+ base = min(config.retry_initial_wait * 2 ** (attempt - 2), config.retry_max_wait)
389
+ return base + random.uniform(0, config.retry_jitter)
390
+
391
+
385
392
  def _run_attempts(shell_run: ShellRun, output_dir: Path) -> BaseException | None:
386
393
  config = shell_run.config
387
394
  queue = shell_run._queue
388
395
  for attempt in range(1, config.attempts + 1):
389
396
  if attempt > 1:
397
+ wait_seconds = _backoff_wait(attempt, config)
398
+ logger.info(f"Backoff {wait_seconds:.2f}s before attempt {attempt}/{config.attempts}")
399
+ time.sleep(wait_seconds)
390
400
  queue.put_nowait(ShellRunRetryAttempt(attempt=attempt))
391
401
  logger.warning(f"Retrying run {shell_run} attempt {attempt} of {config.attempts}")
392
402
  attempt_log_prefix = config.run_log_stem(attempt)
@@ -459,6 +469,9 @@ def run(
459
469
  is_binary_call: bool | None = None,
460
470
  message_callbacks: list[Callable[[ShellRunEventT], bool]] | None = None,
461
471
  print_prefix: str | None = None,
472
+ retry_initial_wait: float | None = None,
473
+ retry_jitter: float | None = None,
474
+ retry_max_wait: float | None = None,
462
475
  run_log_stem_prefix: str | None = None,
463
476
  run_output_dir: Path | None | None = None,
464
477
  settings: AskShellSettings | None = None,
@@ -483,6 +496,9 @@ def run(
483
496
  is_binary_call=is_binary_call,
484
497
  message_callbacks=message_callbacks,
485
498
  print_prefix=print_prefix,
499
+ retry_initial_wait=retry_initial_wait,
500
+ retry_jitter=retry_jitter,
501
+ retry_max_wait=retry_max_wait,
486
502
  run_log_stem_prefix=run_log_stem_prefix,
487
503
  run_output_dir=run_output_dir,
488
504
  settings=settings,
@@ -518,6 +534,9 @@ def run_and_wait(
518
534
  is_binary_call: bool | None = None,
519
535
  message_callbacks: list[Callable[[ShellRunEventT], bool]] | None = None,
520
536
  print_prefix: str | None = None,
537
+ retry_initial_wait: float | None = None,
538
+ retry_jitter: float | None = None,
539
+ retry_max_wait: float | None = None,
521
540
  run_log_stem_prefix: str | None = None,
522
541
  run_output_dir: Path | None | None = None,
523
542
  settings: AskShellSettings | None = None,
@@ -542,6 +561,9 @@ def run_and_wait(
542
561
  is_binary_call=is_binary_call,
543
562
  message_callbacks=message_callbacks,
544
563
  print_prefix=print_prefix,
564
+ retry_initial_wait=retry_initial_wait,
565
+ retry_jitter=retry_jitter,
566
+ retry_max_wait=retry_max_wait,
545
567
  run_log_stem_prefix=run_log_stem_prefix,
546
568
  run_output_dir=run_output_dir,
547
569
  settings=settings,
@@ -151,6 +151,9 @@ class ShellConfig(Entity):
151
151
  # retry args
152
152
  attempts: int = 1
153
153
  should_retry: Callable[[ShellRun], bool] = always_retry
154
+ retry_initial_wait: float = Field(default=5, description="Seconds before first retry, only used when attempts > 1")
155
+ retry_max_wait: float = Field(default=60, description="Cap on wait between retries")
156
+ retry_jitter: float = Field(default=5, description="Max random jitter added to wait")
154
157
 
155
158
  # logging/output
156
159
  print_prefix: str = Field(default=None, description="Use cwd+binary_name+first_arg if not provided") # type: ignore
@@ -31,6 +31,15 @@ def transient_progress() -> Progress:
31
31
  )
32
32
 
33
33
 
34
+ def _is_clean_exit(exc: BaseException | None) -> bool:
35
+ if exc is None:
36
+ return True
37
+ if isinstance(exc, SystemExit) and exc.code in (None, 0):
38
+ return True
39
+ exit_code = getattr(exc, "exit_code", None)
40
+ return exit_code == 0
41
+
42
+
34
43
  def log_task_done(
35
44
  task: new_task,
36
45
  *,
@@ -206,5 +215,6 @@ class new_task:
206
215
  return self
207
216
 
208
217
  def __exit__(self, exc_type, exc_val, exc_tb):
209
- self.complete() # Ensure we mark it as complete
210
- self.manager.remove_task(self, error=exc_val)
218
+ self.complete()
219
+ error = exc_val if not _is_clean_exit(exc_val) else None
220
+ self.manager.remove_task(self, error=error)
@@ -8,7 +8,7 @@ EXAMPLES_DIR = Path(__file__).parent.parent / "docs" / "examples"
8
8
 
9
9
  @pytest.mark.parametrize("example", find_examples(EXAMPLES_DIR), ids=str)
10
10
  def test_examples(example: CodeExample, eval_example: EvalExample):
11
- eval_example.set_config(line_length=120, target_version="py313", ruff_ignore=["T"]) # pyright: ignore[reportArgumentType]
11
+ eval_example.set_config(line_length=120, target_version="py313", ruff_ignore=["I001", "T"]) # pyright: ignore[reportArgumentType]
12
12
  prefix = example.prefix_settings()
13
13
  if prefix.get("test", "").startswith("skip"):
14
14
  pytest.skip(prefix["test"])
@@ -2,7 +2,7 @@
2
2
 
3
3
  [project]
4
4
  name = "ask-shell"
5
- version = "0.5.0"
5
+ version = "0.5.2"
6
6
  description = "CLIs with prompts, shell runs, and testable flows"
7
7
  requires-python = ">=3.13"
8
8
  license = "MIT"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes