verifiers 0.2.1.dev1__py3-none-any.whl → 0.2.1.dev2__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.
@@ -9,6 +9,8 @@ from collections.abc import Callable, Iterator
9
9
  from rich.console import Group
10
10
  from rich.live import Live
11
11
 
12
+ from verifiers.v1.utils.interrupt import cleaning_up
13
+
12
14
  try: # POSIX-only terminal control; absent on Windows, where key reading is skipped.
13
15
  import termios
14
16
  import tty
@@ -82,10 +84,17 @@ async def live_view(
82
84
  """Refresh `render()` every 0.25s until the block exits, then draw one final frame. When
83
85
  `on_key` is given, left/right arrow presses are dispatched to it (and redraw at once)."""
84
86
  with Live(render(), auto_refresh=False) as live:
87
+ stopping = False
85
88
 
86
89
  async def loop() -> None:
87
90
  while True:
88
- await asyncio.sleep(0.25)
91
+ try:
92
+ await asyncio.sleep(0.25)
93
+ except asyncio.CancelledError:
94
+ # Ctrl-C cancels every task at once; keep refreshing so the cleanup notice
95
+ # stays live through teardown, stopping only when live_view tears down.
96
+ if stopping or not cleaning_up():
97
+ raise
89
98
  live.update(render(), refresh=True)
90
99
 
91
100
  task = asyncio.create_task(loop())
@@ -93,6 +102,7 @@ async def live_view(
93
102
  try:
94
103
  yield
95
104
  finally:
105
+ stopping = True
96
106
  task.cancel()
97
107
  with contextlib.suppress(asyncio.CancelledError):
98
108
  await task
@@ -14,6 +14,7 @@ from rich.text import Text
14
14
 
15
15
  from verifiers.v1.cli.dashboard.base import live_view
16
16
  from verifiers.v1.cli.output import output_path
17
+ from verifiers.v1.utils.interrupt import cleaning_up
17
18
  from verifiers.v1.configs.eval import EvalConfig
18
19
  from verifiers.v1.rollout import Phase, Rollout
19
20
  from verifiers.v1.trace import Trace
@@ -110,6 +111,22 @@ def _aligned(rows: list[list[str]]) -> list[str]:
110
111
  ]
111
112
 
112
113
 
114
+ def _interrupt_footer() -> Group | None:
115
+ """The graceful-shutdown notice under the rollouts once Ctrl-C has begun teardown — the
116
+ on-screen echo of the warning (console logging is silenced in rich mode); a further Ctrl-C
117
+ is ignored while it shows. Sits beside the `--push` line, mirroring its placement."""
118
+ if not cleaning_up():
119
+ return None
120
+ return Group(
121
+ Rule(style="dim"),
122
+ Text(
123
+ "interrupted — cleaning up, tearing down containers/sandboxes. "
124
+ "please wait; a further ctrl-c is ignored.",
125
+ style="yellow",
126
+ ),
127
+ )
128
+
129
+
113
130
  def _warning(config: EvalConfig) -> Text | None:
114
131
  """A local-runtime caution for a code-running harness (none for the tool-less `null`),
115
132
  shown above the overview rather than as a row in it."""
@@ -561,11 +578,11 @@ def _render(
561
578
  now = time.time()
562
579
  warning = _warning(config)
563
580
  header = Group(warning, Text(""), Overview(config)) if warning else Overview(config)
564
- # The --push status line appears under the rollouts once the upload starts (None during the run
565
- # / when off). Measure the fixed top (header + progress + rule) and the footer so the rollout
566
- # rows fill what's left; page through them (timer / arrows) when they'd overflow (else rich
567
- # truncates).
568
- footer = _push_footer(push)
581
+ # The --push status line (and, on Ctrl-C, the cleanup notice) appear under the rollouts. Measure
582
+ # the fixed top (header + progress + rule) and the footer so the rollout rows fill what's left;
583
+ # page through them (timer / arrows) when they'd overflow (else rich truncates).
584
+ footers = [f for f in (_push_footer(push), _interrupt_footer()) if f is not None]
585
+ footer = Group(*footers) if footers else None
569
586
  top = Group(header, Progress(rollouts, start), Rule(style="dim"))
570
587
  reserved = len(_CONSOLE.render_lines(top))
571
588
  if footer is not None:
@@ -2,12 +2,12 @@
2
2
 
3
3
  import asyncio
4
4
  import logging
5
- import signal
6
5
  import sys
7
6
 
8
7
  from pydantic_config import cli
9
8
 
10
9
  import verifiers.v1 as vf
10
+ from verifiers.v1.utils.interrupt import install as install_interrupt
11
11
  from verifiers.v1.utils.logging import setup_logging
12
12
  from verifiers.v1.cli.output import output_path, write_config
13
13
  from verifiers.v1.cli.resolve import (
@@ -80,21 +80,30 @@ def main(argv: list[str] | None = None) -> None:
80
80
  logging.lastResort = None
81
81
  else:
82
82
  setup_logging(level, log_file=log_file, console=True)
83
- # Make SIGTERM behave like Ctrl-C (SIGINT) so a killed/timed-out eval still runs each
84
- # rollout's `finally` (tears down containers/sandboxes) and any worker pool it spawned.
85
- signal.signal(signal.SIGTERM, lambda *_: (_ for _ in ()).throw(KeyboardInterrupt()))
83
+ # First Ctrl-C / SIGTERM warns and raises KeyboardInterrupt so a killed/timed-out eval still
84
+ # runs each rollout's `finally` (tears down containers/sandboxes) and any worker pool it
85
+ # spawned; further signals during that cleanup are swallowed so an impatient second Ctrl-C
86
+ # can't orphan those resources.
87
+ install_interrupt()
86
88
 
87
- if config.is_legacy: # v0 backwards-compat: run the classic env, bridged to Traces
88
- from verifiers.v1.legacy import run_legacy_eval
89
+ try:
90
+ if (
91
+ config.is_legacy
92
+ ): # v0 backwards-compat: run the classic env, bridged to Traces
93
+ from verifiers.v1.legacy import run_legacy_eval
89
94
 
90
- traces = asyncio.run(run_legacy_eval(config))
91
- elif config.server: # opt-in: drive rollouts through the env-server worker pool
92
- from verifiers.v1.cli.eval.runner import run_eval_server
95
+ traces = asyncio.run(run_legacy_eval(config))
96
+ elif config.server: # opt-in: drive rollouts through the env-server worker pool
97
+ from verifiers.v1.cli.eval.runner import run_eval_server
93
98
 
94
- traces = asyncio.run(run_eval_server(config))
95
- else: # in-process (default), with or without the live dashboard
96
- env = vf.Environment(config)
97
- traces = asyncio.run(run_eval(env, config))
99
+ traces = asyncio.run(run_eval_server(config))
100
+ else: # in-process (default), with or without the live dashboard
101
+ env = vf.Environment(config)
102
+ traces = asyncio.run(run_eval(env, config))
103
+ except KeyboardInterrupt:
104
+ # Graceful cleanup has already run (each rollout's `finally`); partial results are on
105
+ # disk. Exit on the conventional Ctrl-C code without a traceback.
106
+ raise SystemExit(130)
98
107
  if config.push and not rich:
99
108
  from verifiers.v1.push import push_traces
100
109
 
@@ -0,0 +1,33 @@
1
+ """Graceful-shutdown signal handling for the eval CLI: the first Ctrl-C/SIGTERM warns and
2
+ raises KeyboardInterrupt so asyncio unwinds each rollout's teardown `finally`; further signals
3
+ are swallowed so a second Ctrl-C can't orphan containers/sandboxes mid-cleanup."""
4
+
5
+ import logging
6
+ import signal
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ _cleaning_up = False
11
+
12
+
13
+ def cleaning_up() -> bool:
14
+ """Whether a shutdown signal has begun graceful cleanup (read by the dashboard)."""
15
+ return _cleaning_up
16
+
17
+
18
+ def install() -> None:
19
+ """Route SIGINT/SIGTERM through the graceful-shutdown handler."""
20
+
21
+ def handle(*_) -> None:
22
+ global _cleaning_up
23
+ first, _cleaning_up = not _cleaning_up, True
24
+ logger.warning(
25
+ "interrupted — cleaning up, please wait..."
26
+ if first
27
+ else "cleanup in progress — please wait (ctrl-c ignored)"
28
+ )
29
+ if first:
30
+ raise KeyboardInterrupt
31
+
32
+ signal.signal(signal.SIGINT, handle)
33
+ signal.signal(signal.SIGTERM, handle)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: verifiers
3
- Version: 0.2.1.dev1
3
+ Version: 0.2.1.dev2
4
4
  Summary: Verifiers: Environments for LLM Reinforcement Learning
5
5
  Project-URL: Homepage, https://github.com/primeintellect-ai/verifiers
6
6
  Project-URL: Documentation, https://github.com/primeintellect-ai/verifiers
@@ -225,12 +225,12 @@ verifiers/v1/cli/resolve.py,sha256=7xPR4enanS574I0GIcv30Hg0xX8OmC9GzVq93Mb9LS8,3
225
225
  verifiers/v1/cli/serve.py,sha256=7T_pQdMihmGj7CdsEyVqlro_fBgdJVRkb3ak2qCkZgc,2240
226
226
  verifiers/v1/cli/validate.py,sha256=CjjNYXeS8z8Ow0eKcdiKCNVDiMCbOv-tH1mo2mutgpM,9513
227
227
  verifiers/v1/cli/dashboard/__init__.py,sha256=nWhXVYuAw-eF4TZbhtSoKWRQytv67WzIEnGISQnRHqY,227
228
- verifiers/v1/cli/dashboard/base.py,sha256=IMgpzJMsEzg2HUn4BTopzWf2yeYaBULaQlWGwllkz-s,3093
229
- verifiers/v1/cli/dashboard/eval.py,sha256=-wsqRhX0svFzfYRgVDkxUvsZ8oAY30NZ1jI9hx0nykk,25427
228
+ verifiers/v1/cli/dashboard/base.py,sha256=kUP93zJSIVLptSbnWX6MOy8kGg63fpeAzPqakCpPyDc,3547
229
+ verifiers/v1/cli/dashboard/eval.py,sha256=hYRC_2HVlia87yR1MOGjMwzet1rVpeQzxZaKmo3wjRg,26166
230
230
  verifiers/v1/cli/dashboard/replay.py,sha256=4SmCFx7UbDbdRKszrPkOKSZQ303ezc9SqJLbAbClL6s,3119
231
231
  verifiers/v1/cli/dashboard/validate.py,sha256=1EyP2wkm0KMwl1Xf32my-ulmHwHHWzlHNgtbf-3ZQXs,3355
232
232
  verifiers/v1/cli/eval/__init__.py,sha256=64KcYxE9GDt_TB1CNuRI5uOx3A5npitYRKFuCHbV4DM,22
233
- verifiers/v1/cli/eval/main.py,sha256=7l5YOS12tW6zmo0MnxLMlBr1xcGJbUyV3oS8ly1iddY,4504
233
+ verifiers/v1/cli/eval/main.py,sha256=48dGLSq8CZ48pjCiMRd8p1gRFcYXVwVinDVdYdThzfU,4931
234
234
  verifiers/v1/cli/eval/resume.py,sha256=CWKtKYmU6vtHD_rd4dfV4YV7ccgRNs7jBtA0zeOfHzA,5077
235
235
  verifiers/v1/cli/eval/runner.py,sha256=iSnocgsuZALpVCX0TewlHhisGFgnRoaIxePET70c7nM,10769
236
236
  verifiers/v1/clients/__init__.py,sha256=EgDgl5RYcjQ5W5ROjleL91eJoVfI36SsV9pSLrWVMVI,595
@@ -307,10 +307,11 @@ verifiers/v1/utils/aio.py,sha256=ZKTHeURNbWhTpHMyYuqMLsdPWVHyn5anfG1IJs5y-Zg,148
307
307
  verifiers/v1/utils/format.py,sha256=NfQo9M5KMzWCZpQaet5YtsJx56vCKAZPfbjZZJLJhhM,2187
308
308
  verifiers/v1/utils/generic.py,sha256=cpiyt_GIhFcHVK3dh7eBDqQJ6tQFXSQyHUuPKRLZfj4,647
309
309
  verifiers/v1/utils/install.py,sha256=fWNsyKrw_PyhC0Qhqv5Ri0adFm5Ddx4AVy_hbsra1GU,1390
310
+ verifiers/v1/utils/interrupt.py,sha256=OTb6zQ9lpS3xQV5j4VDNiSB1N20fllaDbY8glI8Akx4,1027
310
311
  verifiers/v1/utils/logging.py,sha256=OcMHA6NsYux3oIzjPuI95rDWmFBHNcHDjZeNIhXTX-Y,2084
311
312
  verifiers/v1/utils/memory.py,sha256=ZkIvGk6uITAH5sKon65LifKPbvZr8mJ__PVc23FZpOQ,1835
312
- verifiers-0.2.1.dev1.dist-info/METADATA,sha256=KLAE-jA_yHTgBkgxjcPlDRXoGC_SHLV88ra_G9KpKg4,5135
313
- verifiers-0.2.1.dev1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
314
- verifiers-0.2.1.dev1.dist-info/entry_points.txt,sha256=vYJT3h4I0-pEnXGaizK-6J56h-qrh-HvMtBTiATK06w,517
315
- verifiers-0.2.1.dev1.dist-info/licenses/LICENSE,sha256=v0RrUsdV3IDoZhrRce297IXS3xMHNJ-_LdLpFAUWb9k,1072
316
- verifiers-0.2.1.dev1.dist-info/RECORD,,
313
+ verifiers-0.2.1.dev2.dist-info/METADATA,sha256=EG-uGv9lIPLOh3mU4JY9GFSV2-_-HR7rrxkGE4JYKMs,5135
314
+ verifiers-0.2.1.dev2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
315
+ verifiers-0.2.1.dev2.dist-info/entry_points.txt,sha256=vYJT3h4I0-pEnXGaizK-6J56h-qrh-HvMtBTiATK06w,517
316
+ verifiers-0.2.1.dev2.dist-info/licenses/LICENSE,sha256=v0RrUsdV3IDoZhrRce297IXS3xMHNJ-_LdLpFAUWb9k,1072
317
+ verifiers-0.2.1.dev2.dist-info/RECORD,,