spacr.qt.screens.project_browser¶
Workflow inputs and outputs¶
Project Browser¶
Locate projects and inspect their stage, last run, disk use and stale outputs.
Open: the application’s Help/tools menus.
Inputs and outputs below include conditional alternatives. The guidance and handoff notes say which route applies.
Inputs
Run history and artifacts — Project run records, settings, output paths, artifact provenance, status and logs.
Outputs
Run history and artifacts — Project run records, settings, output paths, artifact provenance, status and logs.
N4 — the Project Browser: every project on disk, in one table.
Navigating by folder is how spaCR has always been used, and it is why nobody can answer “which plates have been measured?”, “which one is the 400 GB?” or “which results no longer match the masks under them?” without opening six windows. This screen is that answer as a list.
It computes nothing. Every column comes from spacr.projects, which in
turn assembles spacr.data_manager.scan_project() (the size and the
unaccounted-for bytes), spacr.ports.declared_outputs() (the stage),
spacr.artifacts.Registry.is_stale() (what is out of date) and
spacr.chaining.next_steps() (what could run next). A browser with its
own opinion about any of those is a browser that disagrees with the screen
the user opens next.
Two things about it are load-bearing rather than cosmetic.
A project the registry has never seen is still listed. Projects are found by walking the disk, so a folder copied from a colleague appears the moment the browser is pointed at its parent — with its stage, its size and the date its files were last written. What it does not show is “0 stale”, which would read as clean: with no provenance there is nothing to compare against, so the state column says “unknown — nothing recorded” and the note says why.
The scan never blocks the window. Walking a plate folder is tens of
thousands of stat calls, and doing it on the GUI thread is a frozen
application for however long the filesystem takes. Everything goes through
spacr.qt.job_runner.JobRunner, whose completion handlers here are
bound methods — read that module’s docstring for what a closure connected
to thread.finished costs. The background activity spinner follows the run
registry on its own, so nothing here has to drive it.
And neither does opening the screen, nor opening its folder chooser.
Two things here take a path the user typed on some other screen: the search
folders make_project_browser_screen() seeds from the recent-source list,
and the folder the “Add folder…” dialog starts in. Both used to be settled
with an os.path.isdir on the GUI thread. Measured on one workstation, one
such folder was under an autofs mount whose
share was asleep and a single isdir on it had not returned after TWENTY
SECONDS — which is not a slow screen, it is the whole application frozen with
no traceback, and it was reported as “opening the project browser crashes
spacr”. Both now ask spacr.qt.path_probe, which answers from a cache
and stats in the background. Those two are the whole inventory: the walk
runs on a worker, and every column, note and detail line the screen draws is
rendered from the frozen spacr.projects.ProjectSummary that walk
already returned, so filling the table and the detail pane reads nothing.
register() is not called at import; read its docstring.
Classes¶
The browser: a roots picker, a table of projects, and a detail pane. |
Functions¶
|
Factory handed to |
|
Put the Project Browser in the app registry. Idempotent. |
Module Contents¶
- class spacr.qt.screens.project_browser.ProjectBrowserScreen(parent: PySide6.QtWidgets.QWidget | None = None, *, threaded: bool = True, roots: Tuple[str, ...] = ())[source]¶
Bases:
PySide6.QtWidgets.QWidgetThe browser: a roots picker, a table of projects, and a detail pane.
- Parameters:
threaded –
Falseruns the scan inline through the sameJobRunnercode path, so a test drives the real thing synchronously.roots – folders to search on the first scan.
parent – parent widget; ownership only.
Build the project browser.
The project table (“Projects”) and the detail pane (“Project details”) fold by their headings and share a draggable edge in a
CollapsibleSplitter.- Parameters:
parent – parent widget, or
None.threaded – scan on a worker thread. Set
Falsein tests so a refresh finishes before it returns.roots – folders to scan for projects.
- add_root(path: str, *, scan: bool = True) bool[source]¶
Add a folder to search.
Truewhen it was not already there.- Parameters:
path – folder to add;
~is expanded and the path made absolute. It is also recorded as a recent folder.
- choose_root() None[source]¶
Ask for a folder and add it.
The dialog opens on a folder the probe cache has already confirmed rather than simply on the last one searched;
_start_directory()says why that distinction is the difference between a chooser and a frozen window.
- closeEvent(event)[source]¶
Stop background work and unlink before going away.
- Parameters:
event – the Qt close event.
- show_detail(root: str) str[source]¶
Draw the detail pane for one project. Returns what it drew.
- Parameters:
root – absolute project root as listed; a root with no listed summary clears the pane and returns
"".
- summaries() Tuple[source]¶
Every
spacr.projects.ProjectSummarycurrently listed.
- spacr.qt.screens.project_browser.make_project_browser_screen(app_key: str | None = None) PySide6.QtWidgets.QWidget[source]¶
Factory handed to
spacr.qt.app.register_app().Seeds the search folders from the ones the user last pointed any source-taking screen at, so the browser is useful on first open instead of empty.
- spacr.qt.screens.project_browser.register() bool[source]¶
Put the Project Browser in the app registry. Idempotent.
Called from
spacr.qt.SELF_REGISTERING_MODULES, whichspacr.qt.run()runs afterspacr.qt.appis fully executed and beforeMainWindow.__init__reads the registry.The row itself – the key, the name, the blurb, the section, the “no headless run” sentence, the API doc link and the nine translations of the display name – is declared in
spacr.qt.app_catalog.spacr.qt.app.register_app()distributes those into the four tables each used to need a hand-edit in, and this function’s whole job is to name which row. That is what lets the app be registered without importing this module at all: the launch reads the table, and the screen is imported when somebody opens it.- Returns:
Trueif this call is what registered it. Safe to call again: a module imported twice, or a test that re-imports it, must not raise on the duplicate key.