starplast.paths
Where data lives — resolved in one place, so no module ever opens a relative path again.
Before this module existed, build_graph.py computed its dataset root as the repository's parent
directory. That is true on exactly one machine. Measured from a clean checkout, 1 of 16 registry paths
resolved; the other 15 were two directories away, and nothing said so. Any reader who cloned the
repository and followed the instructions got fifteen missing files and no explanation, which for a paper
whose whole claim is reproducibility is the wrong first impression.
There are two separate roots and confusing them is the mistake this module exists to prevent:
the CACHE `starplast/data/` — built artefacts, committed, 17 MB, ships inside the package.
The application needs this and nothing else. It must resolve offline, from an
installed wheel, with no configuration, forever.
the DATASETS `datasets/` — raw published inputs, tens of gigabytes, NOT committed. Only
`build_graph.py` needs it. It is allowed to be missing, and when it is missing the
answer is to fetch it, not to fail.
So data_dir() must never fail and dataset_root() is allowed to return a directory that is empty.
Resolution order
Both roots take an explicit override first, because a user who sets one has a reason:
$STARPLAST_DATA the raw datasets tree
$STARPLAST_CACHE the built cache
then the layouts that actually occur, in order, and finally the platform cache directory, which is
where anything downloaded lands. describe() prints which candidate won, since a resolver that silently
picks the wrong directory is harder to debug than one that fails.
Platform cache directory. Downloads land here, so it must be writable and per-user.
The built cache. Returns the first candidate that holds a real cache, else the best guess.
Never raises. A missing cache is reported by check() with an instruction, because an exception
from an import is the least useful way to tell someone a file is absent.
Absolute path to one file in the built cache, resolved through the search chain.
Resolve a wheel file or the active downloaded pack, never falling back across species.
(ok, message). What the application needs, and what to do when it is not there.
Every dataset root that exists, in priority order.
All of them, not just the first: after the move to datasets/<level>/<type>/<PMID>/ some files
live under the new taxonomy and some under the old, and a resolver that stopped at the first
existing root silently lost the screens once already.
The primary dataset root. Falls back to the cache directory, which is always writable.
Search every dataset root for a file. None when it is genuinely absent.
Returning None rather than raising is deliberate: a missing raw dataset is a normal state that the caller decides about -- fetch it, skip it, or report it -- and a resolver is the wrong layer to make that decision.
What resolved to where. A resolver that picks silently is harder to debug than one that fails.