mfethuls 0.3.1__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.
- mfethuls/__init__.py +26 -0
- mfethuls/_files.py +22 -0
- mfethuls/app/__init__.py +6 -0
- mfethuls/app/backend.py +186 -0
- mfethuls/app/launch.py +21 -0
- mfethuls/app/page.py +775 -0
- mfethuls/app/register.py +292 -0
- mfethuls/app/streamlit_app.py +6 -0
- mfethuls/app/welcome.py +526 -0
- mfethuls/characterizers/__init__.py +0 -0
- mfethuls/characterizers/dsc.py +84 -0
- mfethuls/characterizers/tga.py +29 -0
- mfethuls/comparison.py +171 -0
- mfethuls/config/__init__.py +0 -0
- mfethuls/config/instrument_params.json +126 -0
- mfethuls/config/loader.py +313 -0
- mfethuls/config/schemas/dma.json +91 -0
- mfethuls/config/schemas/dsc.json +97 -0
- mfethuls/config/schemas/fluorescence.json +27 -0
- mfethuls/config/schemas/ftir.json +32 -0
- mfethuls/config/schemas/ms.json +27 -0
- mfethuls/config/schemas/nmr.json +27 -0
- mfethuls/config/schemas/rheometer.json +91 -0
- mfethuls/config/schemas/saxs.json +37 -0
- mfethuls/config/schemas/sec.json +36 -0
- mfethuls/config/schemas/tga.json +60 -0
- mfethuls/config/schemas/uv_vis.json +47 -0
- mfethuls/dataset.py +49 -0
- mfethuls/discovery.py +415 -0
- mfethuls/examples/__init__.py +102 -0
- mfethuls/examples/data/DSC/poly1.txt +6034 -0
- mfethuls/examples/data/DSC/poly2.txt +8194 -0
- mfethuls/examples/data/FTIR/poly1.csv +3403 -0
- mfethuls/examples/data/FTIR/poly2.csv +3403 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__0__18-11-09-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__10__18-11-19-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__11__18-11-20-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__12__18-11-21-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__13__18-11-22-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__14__18-11-23-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__15__18-11-24-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__16__18-11-25-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__17__18-11-26-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__18__18-11-27-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__19__18-11-28-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__1__18-11-10-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__20__18-11-29-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__21__18-11-30-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__22__18-11-31-744.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__23__18-11-32-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__24__18-11-33-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__25__18-11-34-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__26__18-11-35-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__27__18-11-36-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__28__18-11-37-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__29__18-11-38-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__2__18-11-11-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__30__18-11-39-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__31__18-11-40-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__32__18-11-41-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__33__18-11-42-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__34__18-11-43-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__35__18-11-44-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__36__18-11-45-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__37__18-11-46-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__38__18-11-47-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__39__18-11-48-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__3__18-11-12-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__40__18-11-49-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__41__18-11-50-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__42__18-11-51-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__43__18-11-52-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__44__18-11-53-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__45__18-11-54-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__46__18-11-55-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__47__18-11-56-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__48__18-11-57-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__49__18-11-58-758.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__4__18-11-13-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__5__18-11-14-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__6__18-11-15-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__7__18-11-16-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__8__18-11-17-757.txt +1058 -0
- mfethuls/examples/data/Fluorescence/poly3/poly3__9__18-11-18-757.txt +1058 -0
- mfethuls/examples/data/TGA/poly1.txt +2854 -0
- mfethuls/examples/data/TGA/poly2.txt +2854 -0
- mfethuls/examples/experiments_registry.csv +8 -0
- mfethuls/experiments.py +427 -0
- mfethuls/factory.py +134 -0
- mfethuls/instruments/__init__.py +0 -0
- mfethuls/instruments/base.py +6 -0
- mfethuls/instruments/generic.py +17 -0
- mfethuls/main.py +154 -0
- mfethuls/manifest.py +325 -0
- mfethuls/parsers/__init__.py +3 -0
- mfethuls/parsers/dma.py +127 -0
- mfethuls/parsers/dsc.py +439 -0
- mfethuls/parsers/ftir.py +81 -0
- mfethuls/parsers/ingestion.py +80 -0
- mfethuls/parsers/ms.py +116 -0
- mfethuls/parsers/nmr.py +141 -0
- mfethuls/parsers/registry.py +35 -0
- mfethuls/parsers/rheometer.py +113 -0
- mfethuls/parsers/saxs.py +85 -0
- mfethuls/parsers/sec.py +110 -0
- mfethuls/parsers/tga.py +136 -0
- mfethuls/parsers/uv_vis.py +189 -0
- mfethuls/plotting/__init__.py +46 -0
- mfethuls/plotting/backend.py +51 -0
- mfethuls/plotting/comparison.py +296 -0
- mfethuls/plotting/core.py +398 -0
- mfethuls/plotting/dma.py +97 -0
- mfethuls/plotting/dsc.py +180 -0
- mfethuls/plotting/fluorescence.py +56 -0
- mfethuls/plotting/ftir.py +65 -0
- mfethuls/plotting/labels.py +148 -0
- mfethuls/plotting/ms.py +53 -0
- mfethuls/plotting/nmr.py +55 -0
- mfethuls/plotting/render_mpl.py +193 -0
- mfethuls/plotting/render_plotly.py +212 -0
- mfethuls/plotting/rheology.py +97 -0
- mfethuls/plotting/saxs.py +55 -0
- mfethuls/plotting/sec.py +93 -0
- mfethuls/plotting/spec.py +118 -0
- mfethuls/plotting/style.py +67 -0
- mfethuls/plotting/tga.py +60 -0
- mfethuls/plotting/uv_vis.py +63 -0
- mfethuls/py.typed +0 -0
- mfethuls/reconcile.py +419 -0
- mfethuls/registry_validator.py +481 -0
- mfethuls/schema_normalization.py +207 -0
- mfethuls/settings.py +204 -0
- mfethuls/storage/__init__.py +23 -0
- mfethuls/storage/backends.py +105 -0
- mfethuls/storage/catalog.py +82 -0
- mfethuls/storage/config.py +67 -0
- mfethuls/storage/duckdb_backend.py +293 -0
- mfethuls/storage/manager.py +102 -0
- mfethuls/storage/notebook.py +26 -0
- mfethuls/storage/provenance.py +88 -0
- mfethuls/storage/types.py +83 -0
- mfethuls/stores.py +50 -0
- mfethuls/tutorials/__init__.py +53 -0
- mfethuls/tutorials/local_tutorial.py +345 -0
- mfethuls-0.3.1.dist-info/METADATA +166 -0
- mfethuls-0.3.1.dist-info/RECORD +151 -0
- mfethuls-0.3.1.dist-info/WHEEL +5 -0
- mfethuls-0.3.1.dist-info/entry_points.txt +4 -0
- mfethuls-0.3.1.dist-info/licenses/LICENSE +202 -0
- mfethuls-0.3.1.dist-info/licenses/NOTICE +5 -0
- mfethuls-0.3.1.dist-info/top_level.txt +1 -0
mfethuls/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from .comparison import ComparisonSet, load_experiments, load_samples
|
|
2
|
+
from .settings import use_test_env
|
|
3
|
+
from .experiments import Registry
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def plot_experiments(*args, **kwargs):
|
|
7
|
+
from .plotting.comparison import plot_experiments as _plot_experiments
|
|
8
|
+
|
|
9
|
+
return _plot_experiments(*args, **kwargs)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def set_plot_backend(backend):
|
|
13
|
+
"""Default plotting backend: "matplotlib" (publication, vector SVG) or "plotly" (interactive)."""
|
|
14
|
+
from .plotting.backend import set_default_backend
|
|
15
|
+
|
|
16
|
+
set_default_backend(backend)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"ComparisonSet",
|
|
20
|
+
"Registry",
|
|
21
|
+
"load_experiments",
|
|
22
|
+
"load_samples",
|
|
23
|
+
"plot_experiments",
|
|
24
|
+
"set_plot_backend",
|
|
25
|
+
"use_test_env",
|
|
26
|
+
]
|
mfethuls/_files.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Small file helpers shared by the manifest and the saved settings."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def replace_with_retry(source: str, target: str, attempts: int = 10) -> None:
|
|
10
|
+
"""``os.replace``, retried briefly: on Windows it fails while another process has the target open.
|
|
11
|
+
|
|
12
|
+
Virus scanners, indexers and sync clients (OneDrive) open freshly written files for
|
|
13
|
+
a moment; waiting a little and trying again is enough.
|
|
14
|
+
"""
|
|
15
|
+
for attempt in range(attempts):
|
|
16
|
+
try:
|
|
17
|
+
os.replace(source, target)
|
|
18
|
+
return
|
|
19
|
+
except PermissionError:
|
|
20
|
+
if attempt == attempts - 1:
|
|
21
|
+
raise
|
|
22
|
+
time.sleep(0.05 * (attempt + 1))
|
mfethuls/app/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""The mfethuls Streamlit app: ``mfethuls-app`` starts it on this computer.
|
|
2
|
+
|
|
3
|
+
- :mod:`mfethuls.app.page`: the page, drawn by ``run(backend)``
|
|
4
|
+
- :mod:`mfethuls.app.backend`: where its data comes from (:class:`LocalBackend`)
|
|
5
|
+
- :mod:`mfethuls.app.register`: the "Register new data" dialog
|
|
6
|
+
"""
|
mfethuls/app/backend.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Where the app gets its data: the backend :func:`mfethuls.app.page.run` draws the page for.
|
|
2
|
+
|
|
3
|
+
:class:`LocalBackend` works on this computer: the registry file, the data folder and
|
|
4
|
+
the local stores (manifest, Parquet files, DuckDB catalog). Other backends subclass
|
|
5
|
+
it and override what differs, e.g. reading datasets through an API.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
import pandas as pd
|
|
14
|
+
import streamlit as st
|
|
15
|
+
|
|
16
|
+
from mfethuls.app.register import clear_registry_caches, new_data_prompt, registry_frame
|
|
17
|
+
from mfethuls.app.welcome import is_set_up
|
|
18
|
+
from mfethuls.settings import get_settings
|
|
19
|
+
|
|
20
|
+
_CATALOG_TEXT_FIELDS = (
|
|
21
|
+
"experiment_id", "experiment_name", "raw_data_filename", "instrument_name",
|
|
22
|
+
"instrument_type", "instrument_model", "sample_id", "run_id", "measurement_profile",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def normalise_dataset(row: Dict[str, Any]) -> Dict[str, Any]:
|
|
27
|
+
"""A catalog row with empty strings for missing text, and its view name as ``name``."""
|
|
28
|
+
dataset = {
|
|
29
|
+
"name": row.get("name") or row.get("table_name") or "",
|
|
30
|
+
"storage_path": row.get("storage_path") or "",
|
|
31
|
+
"registered_at": str(row.get("registered_at") or ""),
|
|
32
|
+
"rows": row.get("rows"),
|
|
33
|
+
}
|
|
34
|
+
dataset.update({field: row.get(field) or "" for field in _CATALOG_TEXT_FIELDS})
|
|
35
|
+
return dataset
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def local_catalog_path() -> Optional[str]:
|
|
39
|
+
"""The DuckDB catalog file, if it exists on this computer."""
|
|
40
|
+
from mfethuls.storage import _get_duckdb_path
|
|
41
|
+
|
|
42
|
+
path = _get_duckdb_path()
|
|
43
|
+
return path if os.path.exists(path) else None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def list_local_datasets() -> List[Dict[str, Any]]:
|
|
47
|
+
"""Ingested datasets from the local DuckDB catalog (cached for 30 s, per catalog)."""
|
|
48
|
+
db_path = local_catalog_path()
|
|
49
|
+
return _list_catalog(db_path) if db_path else []
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@st.cache_data(show_spinner=False, ttl=30)
|
|
53
|
+
def _list_catalog(db_path: str) -> List[Dict[str, Any]]:
|
|
54
|
+
from mfethuls.storage import duckdb_session
|
|
55
|
+
|
|
56
|
+
with duckdb_session(db_path=db_path, read_only=True) as catalog:
|
|
57
|
+
return [normalise_dataset(row) for row in catalog.list_registered()]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Cached for 10 minutes: whole datasets are expensive to reload. Refresh clears it.
|
|
61
|
+
def query_local_dataset(name: str, limit: Optional[int] = None, offset: int = 0) -> pd.DataFrame:
|
|
62
|
+
"""Rows of a dataset from the local catalog; ``limit=None`` loads all of them."""
|
|
63
|
+
return _query_catalog(local_catalog_path(), name, limit, offset)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@st.cache_data(show_spinner=False, ttl=600)
|
|
67
|
+
def _query_catalog(db_path: str, name: str, limit: Optional[int], offset: int) -> pd.DataFrame:
|
|
68
|
+
from mfethuls.storage import duckdb_session
|
|
69
|
+
|
|
70
|
+
with duckdb_session(db_path=db_path, read_only=True) as catalog:
|
|
71
|
+
safe = name.replace('"', '""')
|
|
72
|
+
if limit is None:
|
|
73
|
+
return catalog.query(f'SELECT * FROM "{safe}" OFFSET ?', [offset])
|
|
74
|
+
return catalog.query(f'SELECT * FROM "{safe}" LIMIT ? OFFSET ?', [limit, offset])
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class LocalBackend:
|
|
78
|
+
"""The app on this computer: it reads and writes the registry file and the local stores."""
|
|
79
|
+
|
|
80
|
+
# The "Register new data" dialog may append rows to the registry file.
|
|
81
|
+
writes_registry = True
|
|
82
|
+
# The getting-started page offers to switch to a copy of the bundled examples.
|
|
83
|
+
offers_example = True
|
|
84
|
+
|
|
85
|
+
# -- sidebar ------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
def render_status(self) -> None:
|
|
88
|
+
"""Anything shown at the top of the sidebar (nothing locally)."""
|
|
89
|
+
|
|
90
|
+
def render_ingest(self) -> str:
|
|
91
|
+
"""The Ingest sidebar section; returns the registry path in use."""
|
|
92
|
+
registry_path = get_settings().registry_path or ""
|
|
93
|
+
if not is_set_up():
|
|
94
|
+
st.caption("Choose your data folder and registry first: **⚙ Settings** → Data folder and registry.")
|
|
95
|
+
return registry_path
|
|
96
|
+
st.caption(f"Registry: `{os.path.basename(registry_path)}`")
|
|
97
|
+
refresh_ingest = st.checkbox("Re-parse even if cached", value=False)
|
|
98
|
+
|
|
99
|
+
experiment_names: List[str] = []
|
|
100
|
+
if registry_path:
|
|
101
|
+
new_data_prompt(self, registry_path)
|
|
102
|
+
try:
|
|
103
|
+
experiment_names = registry_frame(registry_path).get("name", pd.Series(dtype=str)).dropna().astype(str).tolist()
|
|
104
|
+
except Exception as exc:
|
|
105
|
+
st.error(f"Could not load registry: {exc}")
|
|
106
|
+
|
|
107
|
+
selected: List[str] = []
|
|
108
|
+
if experiment_names:
|
|
109
|
+
select_all = st.checkbox("Select all", value=False)
|
|
110
|
+
selected = experiment_names if select_all else st.multiselect("Experiments", options=experiment_names)
|
|
111
|
+
st.caption(f"{len(selected)} selected.")
|
|
112
|
+
elif registry_path:
|
|
113
|
+
st.info("No experiments found.")
|
|
114
|
+
|
|
115
|
+
if st.button("Ingest", disabled=not selected, width="stretch"):
|
|
116
|
+
progress_bar = st.progress(0)
|
|
117
|
+
results: List[tuple] = []
|
|
118
|
+
for i, name in enumerate(selected):
|
|
119
|
+
results.extend(self.ingest([name], registry_path, refresh=refresh_ingest))
|
|
120
|
+
progress_bar.progress((i + 1) / len(selected))
|
|
121
|
+
|
|
122
|
+
counts: Dict[str, int] = {}
|
|
123
|
+
for name, result in results:
|
|
124
|
+
status = result.get("status", "unknown")
|
|
125
|
+
counts[status] = counts.get(status, 0) + 1
|
|
126
|
+
if status == "error":
|
|
127
|
+
st.error(f"{name}: {result.get('error', '')}")
|
|
128
|
+
st.success("Done — " + ", ".join(f"{k}: {v}" for k, v in counts.items()))
|
|
129
|
+
self.clear_caches()
|
|
130
|
+
st.rerun()
|
|
131
|
+
return registry_path
|
|
132
|
+
|
|
133
|
+
# -- data -----------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
def list_datasets(self) -> List[Dict[str, Any]]:
|
|
136
|
+
return list_local_datasets()
|
|
137
|
+
|
|
138
|
+
def query_dataset(self, name: str, limit: Optional[int] = None, offset: int = 0) -> pd.DataFrame:
|
|
139
|
+
return query_local_dataset(name, limit, offset)
|
|
140
|
+
|
|
141
|
+
def clear_caches(self) -> None:
|
|
142
|
+
_list_catalog.clear()
|
|
143
|
+
_query_catalog.clear()
|
|
144
|
+
|
|
145
|
+
def ingest(self, names: List[str], registry_path: str, refresh: bool = False) -> List[tuple]:
|
|
146
|
+
"""Ingest experiments into the local stores; returns ``(name, result)`` pairs."""
|
|
147
|
+
from mfethuls.config.loader import ingest_experiment_dataset
|
|
148
|
+
from mfethuls.experiments import Registry
|
|
149
|
+
from mfethuls.stores import open_local_stores
|
|
150
|
+
|
|
151
|
+
registry = Registry.load(registry_path)
|
|
152
|
+
results = []
|
|
153
|
+
with open_local_stores() as stores:
|
|
154
|
+
for name in names:
|
|
155
|
+
try:
|
|
156
|
+
result = ingest_experiment_dataset(name, refresh=refresh, registry=registry, stores=stores)
|
|
157
|
+
results.append((name, result or {}))
|
|
158
|
+
except Exception as exc:
|
|
159
|
+
results.append((name, {"status": "error", "error": str(exc)}))
|
|
160
|
+
_list_catalog.clear()
|
|
161
|
+
return results
|
|
162
|
+
|
|
163
|
+
# -- clean up ---------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
def reconcile_plan(self, registry_path: str) -> Dict[str, Any]:
|
|
166
|
+
from mfethuls.experiments import Registry
|
|
167
|
+
from mfethuls.reconcile import plan
|
|
168
|
+
from mfethuls.stores import open_local_stores
|
|
169
|
+
|
|
170
|
+
with open_local_stores() as stores:
|
|
171
|
+
return plan(Registry.load(registry_path), stores).to_dict()
|
|
172
|
+
|
|
173
|
+
def reconcile_apply(self, registry_path: str, fingerprint: str, force: bool = False) -> List[Dict[str, Any]]:
|
|
174
|
+
"""Apply the reviewed plan; RuntimeError if it changed since the check or is blocked."""
|
|
175
|
+
from mfethuls.experiments import Registry
|
|
176
|
+
from mfethuls.reconcile import PlanChanged, ReconcileBlocked, apply_reviewed
|
|
177
|
+
from mfethuls.stores import open_local_stores
|
|
178
|
+
|
|
179
|
+
with open_local_stores() as stores:
|
|
180
|
+
try:
|
|
181
|
+
results = apply_reviewed(Registry.load(registry_path), stores, fingerprint=fingerprint, force=force)
|
|
182
|
+
except (PlanChanged, ReconcileBlocked) as exc:
|
|
183
|
+
raise RuntimeError(str(exc)) from exc
|
|
184
|
+
clear_registry_caches()
|
|
185
|
+
self.clear_caches()
|
|
186
|
+
return results
|
mfethuls/app/launch.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""``mfethuls-app``: start the Streamlit app. Extra arguments are passed to ``streamlit run``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run_streamlit(script: Path, argv: list[str] | None = None) -> int:
|
|
11
|
+
"""Run ``streamlit run script`` with this Python; returns its exit code."""
|
|
12
|
+
args = sys.argv[1:] if argv is None else argv
|
|
13
|
+
return subprocess.call([sys.executable, "-m", "streamlit", "run", str(script), *args])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main(argv: list[str] | None = None) -> int:
|
|
17
|
+
return run_streamlit(Path(__file__).with_name("streamlit_app.py"), argv)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
raise SystemExit(main())
|