Skip to content

Commit f352ea0

Browse files
committed
Put settings file in /processing
1 parent 7d5912c commit f352ea0

2 files changed

Lines changed: 50 additions & 23 deletions

File tree

‎src/dlstbx/services/trigger_xchem.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
from dlstbx.util.prometheus_metrics import BasePrometheusMetrics, NoMetrics
4747
from dlstbx.util.soakdb import find_xchem_visit_dir
4848
from dlstbx.util.stage_reprocess import stage_existing_modeldir
49-
from dlstbx.util.xchem_config import CONFIG_FILENAME, load_visit_config
49+
from dlstbx.util.xchem_config import CACHE_FILE, SETTINGS_FILE, load_visit_config
5050

5151
INDUSTRIAL_PROPOSAL_CODES = frozenset({"in", "sw"})
5252
BATCH_DCIDS = ".batch_dcids.json" # {dcid: dtag} cached while PanDDA2 waits
@@ -407,7 +407,9 @@ def trigger_modelbuilding(
407407
)
408408
return {"success": True}
409409

410-
if not (xchem_visit_dir / CONFIG_FILENAME).is_file():
410+
if not any(
411+
(xchem_visit_dir / name).is_file() for name in (CACHE_FILE, SETTINGS_FILE)
412+
):
411413
self.log.debug("Exiting PanDDA2/Pipedream trigger: visit not registered")
412414
return {"success": True}
413415

‎src/dlstbx/util/xchem_config.py‎

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
"""Per-visit settings for the XChem autoprocessing pipelines.
22
3-
A labxchem visit may hold a `.user.yaml` naming its target and steering how
4-
it is processed::
3+
Settings live in a `.user.yaml` the users own, under a visit's `processing`
4+
directory, which the pipeline only ever reads::
55
6-
data:
7-
acronym: A71EV2A # cached by the pipeline
6+
# <visit>/processing/.user.yaml
87
autoprocessing:
98
enabled: true # process this visit at all?
109
comparator_threshold: 150 # datasets PanDDA2 waits for before starting
@@ -13,6 +12,16 @@
1312
high_res_lower_limit: 2.5
1413
notify: someone@diamond.ac.uk # who to mail when collate finishes
1514
15+
The target acronym is cached separately, in a `.user.yaml` at the top of the
16+
visit, because that is the one the pipeline can write::
17+
18+
# <visit>/.user.yaml
19+
data:
20+
acronym: A71EV2A
21+
22+
Visits predating the split keep their settings alongside the cached acronym,
23+
and are still read from there when they have no `processing` file.
24+
1625
A recipe that set the parameter explicitly beats the file, the file
1726
beats the parameter model's default.
1827
"""
@@ -25,7 +34,10 @@
2534
import pydantic
2635
import yaml
2736

28-
CONFIG_FILENAME = ".user.yaml"
37+
# written by the pipeline, holding only the cached acronym
38+
CACHE_FILE = ".user.yaml"
39+
# written by users; the pipeline never writes here
40+
SETTINGS_FILE = "processing/.user.yaml"
2941

3042

3143
class VisitConfig(pydantic.BaseModel):
@@ -62,43 +74,56 @@ def _mapping(value) -> dict:
6274
return value if isinstance(value, dict) else {}
6375

6476

65-
def _read(visit_dir, logger) -> tuple[Path, dict | None]:
66-
"""A visit config file's path and parsed contents, the latter None if the
67-
file is there but could not be read."""
68-
path = Path(visit_dir) / CONFIG_FILENAME
77+
def _read(path: Path, logger) -> dict | None:
78+
"""A config file's parsed contents, or None if it is there but unreadable."""
6979
try:
7080
text = path.read_text() if path.is_file() else ""
71-
return path, _mapping(yaml.safe_load(text))
81+
return _mapping(yaml.safe_load(text))
7282
except Exception as e:
7383
logger.warning(f"Ignoring unreadable visit config {path}: {e}")
74-
return path, None
84+
return None
7585

7686

7787
def load_visit_config(visit_dir, logger) -> VisitConfig:
78-
"""Read a visit's config file, or an all-defaults config if it has none.
88+
"""Read a visit's settings and cached acronym, or an all-defaults config.
7989
8090
A setting that fails validation is dropped on its own and the rest of the
8191
file still applies, so one typo cannot cost a visit its whole config.
8292
"""
83-
path, raw = _read(visit_dir, logger)
84-
raw = raw or {}
85-
values = {str(k): v for k, v in _mapping(raw.get("autoprocessing")).items()}
86-
values["acronym"] = _mapping(raw.get("data")).get("acronym")
87-
values["notify"] = raw.get("notify")
93+
visit_dir = Path(visit_dir)
94+
cache_path = visit_dir / CACHE_FILE
95+
cache = _read(cache_path, logger) or {}
96+
97+
# visits predating the split keep their settings in the cache file
98+
settings_path = visit_dir / SETTINGS_FILE
99+
if settings_path.is_file():
100+
settings = _read(settings_path, logger) or {}
101+
else:
102+
settings_path, settings = cache_path, cache
103+
104+
values = {str(k): v for k, v in _mapping(settings.get("autoprocessing")).items()}
105+
values["acronym"] = _mapping(cache.get("data")).get("acronym")
106+
values["notify"] = settings.get("notify")
88107
try:
89108
return VisitConfig(**values)
90109
except pydantic.ValidationError as e:
91110
# only return settings that validate
92111
bad = {str(err["loc"][0]) for err in e.errors() if err["loc"]}
93-
logger.warning(f"Ignoring invalid {', '.join(sorted(bad))} in {path}: {e}")
112+
logger.warning(
113+
f"Ignoring invalid {', '.join(sorted(bad))} in {settings_path}: {e}"
114+
)
94115
return VisitConfig(**{k: v for k, v in values.items() if k not in bad})
95116

96117

97118
def cache_acronym(visit_dir, acronym: str, logger) -> None:
98-
"""Cache a visit's target acronym to its config file so that a beamline visit
99-
can be linked with a labxchem one.
119+
"""Cache a visit's target acronym so that a beamline visit can be linked
120+
with a labxchem one. Does nothing if an acronym is already recorded.
121+
122+
Written at the top of the visit, not under `processing`, because that is
123+
where the pipeline can write.
100124
"""
101-
path, raw = _read(visit_dir, logger)
125+
path = Path(visit_dir) / CACHE_FILE
126+
raw = _read(path, logger)
102127
if raw is None:
103128
return
104129
data = _mapping(raw.get("data"))

0 commit comments

Comments
 (0)