pypeit.dashboard.runlock module

The single-run lock for the PypeIt Dashboard (design X1).

At most one PypeIt run (run_pypeit or pypeit_run_to_calibstep) may be active at a time, and the Dashboard must refuse to launch a second. It becomes “locked” in two situations:

  • a run the Dashboard launched is still in progress (driven by the Launcher via RunLock.set_dashboard_running()); and

  • a run started outside the Dashboard is detected by watching the reduction .log file — PypeIt writes to it continuously while running, so a log whose modification time is recent means a run is in progress.

The mtime test (RunLock._is_recent()) is a pure, unit-testable function; the polling QTimer only calls it. The lock emits RunLock.lockChanged when it transitions, which the views connect to so they can enable/disable their launch controls.

Generated by JXP and Claude.

class pypeit.dashboard.runlock.RunLock(log_path=None, state_path=None, parent=None)[source]

Bases: QObject

Single-run lock with .log-mtime external-run detection (design X1), and the live-monitoring change signal (design R14, Stage 5).

The one polling timer serves both roles: it detects whether a run is active (the .log mtime is recent) and, while active, whether the reduction state file has changed (its mtime advanced) — emitting stateChanged so the views can refresh live (S5-Q3).

Generated by JXP and Claude.

Parameters:
  • log_path (str, pathlib.Path, optional) – The reduction .log file to watch. If None, only Dashboard-launched runs lock (no external detection).

  • state_path (str, pathlib.Path, optional) – The *_state.json file to watch for live updates. If None, stateChanged never fires.

  • parent (QObject, optional) – The Qt parent.

Variables:
  • lockChanged (Signal) – Emitted with the new locked state (bool) on each transition.

  • stateChanged (Signal) – Emitted (no args) when the state file’s mtime advances while a run is active (Stage 5 live monitoring).

ACTIVE_WINDOW_S = 10.0

A .log modified within this many seconds means a run is active (PypeIt writes to it continuously; a quiet log means it finished or stalled).

POLL_MS = 2500

Polling cadence for the external-run + state-change check (S4-Q5/S5-Q2: ~2–3 s; one timer for both).

_emit_if_changed(was_locked)[source]

Emit lockChanged if the locked state differs from was.

Generated by JXP and Claude.

Parameters:

was_locked (bool) – The locked state before the change.

Returns:

None.

static _is_recent(mtime, now, window)[source]

Whether a file modified at mtime counts as “being written now”.

Pure and unit-testable: True when the log was touched within window seconds of now.

Generated by JXP and Claude.

Parameters:
  • mtime (float) – The file modification time (epoch seconds).

  • now (float) – The current time (epoch seconds).

  • window (float) – The “recent” window, in seconds.

Returns:

True if now - mtime < window.

Return type:

bool

_state_mtime()[source]

The state file’s modification time, or None if absent/unreadable.

Generated by JXP and Claude.

Returns:

The *_state.json mtime (epoch seconds).

Return type:

float or None

is_locked()[source]

Whether a run is active (Dashboard-launched or external).

Generated by JXP and Claude.

Returns:

True if the Dashboard should refuse to launch a run.

Return type:

bool

lockChanged

Emitted with the new locked state (bool) whenever it changes.

poll()[source]

Re-check the .log mtime for an external run (emitting lockChanged on a transition) and the *_state.json mtime for a live update (emitting stateChanged while active, Stage 5).

Generated by JXP and Claude.

Returns:

None.

set_dashboard_running(running)[source]

Record whether a run the Dashboard launched is in progress, emitting lockChanged if the locked state changes.

Generated by JXP and Claude.

Parameters:

running (bool) – True while our launched run is active.

Returns:

None.

start()[source]

Start polling the .log for external runs (and check once now).

Generated by JXP and Claude.

Returns:

None.

stateChanged

Emitted when the state file changes while a run is active (Stage 5).

stop()[source]

Stop polling.

Generated by JXP and Claude.

Returns:

None.