evidome 1.0__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.
- evidome/__init__.py +9 -0
- evidome/checkpoint.py +111 -0
- evidome/cli.py +873 -0
- evidome/collisions.py +137 -0
- evidome/combine.py +320 -0
- evidome/crosswalk.py +412 -0
- evidome/database_value.py +748 -0
- evidome/docs/INPUT_FORMAT.md +466 -0
- evidome/enrich.py +1061 -0
- evidome/errors.py +304 -0
- evidome/figures.py +751 -0
- evidome/gene_ids.py +322 -0
- evidome/genesets.py +417 -0
- evidome/genomes.py +266 -0
- evidome/human_tables.py +319 -0
- evidome/identity.py +35 -0
- evidome/jsonio.py +104 -0
- evidome/parallel.py +77 -0
- evidome/pathway_signs.py +413 -0
- evidome/pipeline.py +94 -0
- evidome/reference.py +790 -0
- evidome/regulators.py +529 -0
- evidome/report.py +779 -0
- evidome/sister.py +139 -0
- evidome/slurm.py +67 -0
- evidome/species.py +73 -0
- evidome/utility.py +229 -0
- evidome/version.py +12 -0
- evidome/web/evidence_browser.html +725 -0
- evidome/wide_stats.py +545 -0
- evidome/xlsx.py +192 -0
- evidome-1.0.dist-info/METADATA +114 -0
- evidome-1.0.dist-info/RECORD +38 -0
- evidome-1.0.dist-info/WHEEL +5 -0
- evidome-1.0.dist-info/entry_points.txt +2 -0
- evidome-1.0.dist-info/licenses/LICENSE +661 -0
- evidome-1.0.dist-info/licenses/NOTICE +85 -0
- evidome-1.0.dist-info/top_level.txt +1 -0
evidome/__init__.py
ADDED
evidome/checkpoint.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
""""Is this stage already done?" -- answered here, and only here.
|
|
2
|
+
|
|
3
|
+
This module exists because of Paralome's most expensive recurring bug. There,
|
|
4
|
+
"is this stage done" was answered in four independent places (the stage's own
|
|
5
|
+
checkpoint check, the fan-out wrapper, the index payload filter, and the
|
|
6
|
+
orchestrator/supervisor) that never consulted each other. A fix at one layer
|
|
7
|
+
left the other three wrong, so fixes shipped one layer at a time, each
|
|
8
|
+
"verified", each insufficient.
|
|
9
|
+
|
|
10
|
+
The rule here: every caller that needs to know whether a stage can be skipped
|
|
11
|
+
-- the stage itself, any runner, any status command, any future supervisor --
|
|
12
|
+
calls ``reuse_decision``. Nobody reads ``complete: true`` out of a checkpoint
|
|
13
|
+
and draws their own conclusion.
|
|
14
|
+
|
|
15
|
+
``reuse_decision`` checks three things, in this order, and returns the FIRST
|
|
16
|
+
reason to recompute as a sentence (never a bare bool -- "recomputing because X"
|
|
17
|
+
is actionable; "recomputing" is not):
|
|
18
|
+
|
|
19
|
+
1. version -- a checkpoint written by any other program version is stale.
|
|
20
|
+
Safe because the version only bumps on a behaviour change.
|
|
21
|
+
2. parameters -- every parameter that affects the output must match.
|
|
22
|
+
3. outputs -- every recorded output file must still exist. Deleting results
|
|
23
|
+
and re-running is the routine case, not the edge case.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any, Dict, Iterable, Mapping, Optional
|
|
31
|
+
|
|
32
|
+
from .jsonio import VERSION_KEY, read_json, write_json
|
|
33
|
+
from .version import __version__
|
|
34
|
+
|
|
35
|
+
COMPLETE_KEY = "complete"
|
|
36
|
+
PARAMS_KEY = "parameters"
|
|
37
|
+
OUTPUTS_KEY = "outputs"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class ReuseDecision:
|
|
42
|
+
reuse: bool
|
|
43
|
+
reason: str
|
|
44
|
+
checkpoint: Dict[str, Any]
|
|
45
|
+
|
|
46
|
+
def __bool__(self) -> bool: # `if decision:` reads as "can reuse"
|
|
47
|
+
return self.reuse
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def reuse_decision(
|
|
51
|
+
checkpoint_path: Path,
|
|
52
|
+
parameters: Mapping[str, Any],
|
|
53
|
+
*,
|
|
54
|
+
version: Optional[str] = None,
|
|
55
|
+
) -> ReuseDecision:
|
|
56
|
+
"""Whether the checkpoint at ``checkpoint_path`` may stand in for a re-run."""
|
|
57
|
+
path = Path(checkpoint_path)
|
|
58
|
+
if not path.exists():
|
|
59
|
+
return ReuseDecision(False, f"no checkpoint at {path}", {})
|
|
60
|
+
previous = read_json(path)
|
|
61
|
+
if not isinstance(previous, dict): # valid JSON, but not a checkpoint
|
|
62
|
+
return ReuseDecision(False, f"{path} is not a checkpoint object", {})
|
|
63
|
+
if not previous.get(COMPLETE_KEY):
|
|
64
|
+
return ReuseDecision(False, "checkpoint is not marked complete", previous)
|
|
65
|
+
|
|
66
|
+
running = version if version is not None else __version__
|
|
67
|
+
recorded = previous.get(VERSION_KEY)
|
|
68
|
+
if recorded != running:
|
|
69
|
+
return ReuseDecision(False, f"written by version {recorded}, running {running}", previous)
|
|
70
|
+
|
|
71
|
+
recorded_params = previous.get(PARAMS_KEY, {})
|
|
72
|
+
for key in sorted(set(parameters) | set(recorded_params)):
|
|
73
|
+
if _normalise(parameters.get(key)) != _normalise(recorded_params.get(key)):
|
|
74
|
+
return ReuseDecision(
|
|
75
|
+
False,
|
|
76
|
+
f"parameter {key!r} changed: {recorded_params.get(key)!r} -> {parameters.get(key)!r}",
|
|
77
|
+
previous,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
for name, output in sorted(previous.get(OUTPUTS_KEY, {}).items()):
|
|
81
|
+
if output and not Path(output).exists():
|
|
82
|
+
return ReuseDecision(False, f"recorded output {name!r} no longer exists: {output}", previous)
|
|
83
|
+
|
|
84
|
+
return ReuseDecision(True, "version, parameters and outputs all match", previous)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def write_checkpoint(
|
|
88
|
+
checkpoint_path: Path,
|
|
89
|
+
parameters: Mapping[str, Any],
|
|
90
|
+
outputs: Mapping[str, Optional[str]],
|
|
91
|
+
**extra: Any,
|
|
92
|
+
) -> Path:
|
|
93
|
+
"""Record a completed stage. ``outputs`` maps output kind -> path."""
|
|
94
|
+
payload: Dict[str, Any] = dict(extra)
|
|
95
|
+
payload[COMPLETE_KEY] = True
|
|
96
|
+
payload[PARAMS_KEY] = {k: _normalise(v) for k, v in parameters.items()}
|
|
97
|
+
payload[OUTPUTS_KEY] = {k: (str(v) if v is not None else None) for k, v in outputs.items()}
|
|
98
|
+
return write_json(checkpoint_path, payload)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _normalise(value: Any) -> Any:
|
|
102
|
+
"""Make recorded and requested parameters comparable after a JSON round trip."""
|
|
103
|
+
if isinstance(value, Path):
|
|
104
|
+
return str(value)
|
|
105
|
+
if isinstance(value, tuple):
|
|
106
|
+
return [_normalise(v) for v in value]
|
|
107
|
+
if isinstance(value, list):
|
|
108
|
+
return [_normalise(v) for v in value]
|
|
109
|
+
if isinstance(value, Mapping):
|
|
110
|
+
return {str(k): _normalise(v) for k, v in value.items()}
|
|
111
|
+
return value
|