researchium 0.1.0__tar.gz
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.
- researchium-0.1.0/.github/workflows/ci.yml +21 -0
- researchium-0.1.0/.gitignore +13 -0
- researchium-0.1.0/LICENSE +21 -0
- researchium-0.1.0/PKG-INFO +475 -0
- researchium-0.1.0/README.md +444 -0
- researchium-0.1.0/pyproject.toml +74 -0
- researchium-0.1.0/src/researchium/__init__.py +59 -0
- researchium-0.1.0/src/researchium/cli.py +109 -0
- researchium-0.1.0/src/researchium/environment.py +179 -0
- researchium-0.1.0/src/researchium/experiments.py +213 -0
- researchium-0.1.0/src/researchium/runtime.py +182 -0
- researchium-0.1.0/tests/test_cli.py +138 -0
- researchium-0.1.0/tests/test_environment.py +70 -0
- researchium-0.1.0/tests/test_experiments.py +175 -0
- researchium-0.1.0/tests/test_runtime.py +191 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.10", "3.12", "3.14"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
- run: uv sync --extra dev
|
|
20
|
+
- run: uv run pytest
|
|
21
|
+
- run: uv run ruff check .
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Samarth Javagal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: researchium
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A research module for organizing and analyzing scientific work.
|
|
5
|
+
Project-URL: Homepage, https://github.com/samTheComputerArchitect/researchium
|
|
6
|
+
Project-URL: Repository, https://github.com/samTheComputerArchitect/researchium
|
|
7
|
+
Author: Samarth Javagal
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: analysis,experiments,research,science
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: build==1.6.1; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
29
|
+
Requires-Dist: twine==7.0.0; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# Researchium
|
|
33
|
+
|
|
34
|
+
[](https://pypi.org/project/researchium) [](https://pypi.org/project/researchium) [](LICENSE) [](https://github.com/samTheComputerArchitect/researchium/actions/workflows/ci.yml) [](https://pypi.org/project/researchium)
|
|
35
|
+
|
|
36
|
+
**Version 0.1.0**
|
|
37
|
+
|
|
38
|
+
Researchium is a small, opinionated toolkit for organizing scientific work into
|
|
39
|
+
self-contained **environments** and tracking **experiments** within them. It
|
|
40
|
+
gives every experiment a stable ID, a home folder, a runnable script, and a
|
|
41
|
+
place for its configuration and results — all stored as plain, human-readable
|
|
42
|
+
files so nothing is locked in a database.
|
|
43
|
+
|
|
44
|
+
It is built around three ideas:
|
|
45
|
+
|
|
46
|
+
1. **An environment owns a directory.** `researchium init` lays down a standard
|
|
47
|
+
structure (mirroring what `git` does with `.git` and `uv` with `uv.lock`).
|
|
48
|
+
Every subsequent command discovers the environment by walking up from the
|
|
49
|
+
current directory, so you can work from any subfolder.
|
|
50
|
+
2. **Experiments are first-class.** Each experiment is a folder in
|
|
51
|
+
`experiments/`, has a unique short ID, and keeps its configuration and
|
|
52
|
+
results in its own location.
|
|
53
|
+
3. **Run scripts fill config and results.** Write a normal Python script that
|
|
54
|
+
imports `Config` and `Results` from `researchium`. `researchium run` executes
|
|
55
|
+
it and snapshots both registries into Markdown *and* JSON files — ready for
|
|
56
|
+
humans and machines.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Table of contents
|
|
61
|
+
|
|
62
|
+
- [Quick start](#quick-start)
|
|
63
|
+
- [The environment layout](#the-environment-layout)
|
|
64
|
+
- [Command-line usage](#command-line-usage)
|
|
65
|
+
- [Writing run scripts](#writing-run-scripts)
|
|
66
|
+
- [Python API](#python-api)
|
|
67
|
+
- [File formats](#file-formats)
|
|
68
|
+
- [Error handling](#error-handling)
|
|
69
|
+
- [Development](#development)
|
|
70
|
+
- [Project layout](#project-layout)
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Quick start
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
researchium init "Climate Genomics" # lay down an environment
|
|
78
|
+
researchium create alpine-transcriptomics \
|
|
79
|
+
"RNA-seq of alpine vs lowland populations." # register an experiment
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Write a run script:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
# experiments/alpine-transcriptomics/run.py
|
|
86
|
+
from researchium import Config, Results
|
|
87
|
+
|
|
88
|
+
Config["epochs"] = 10
|
|
89
|
+
Config["learning_rate"] = 0.001
|
|
90
|
+
|
|
91
|
+
Results["mse"] = 0.032
|
|
92
|
+
Results["r2"] = 0.91
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Run it and inspect:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
researchium run alpine-transcriptomics # or by ID: researchium run 1e70f7c3
|
|
99
|
+
researchium config alpine-transcriptomics # epochs: 10
|
|
100
|
+
researchium results alpine-transcriptomics # mse: 0.032
|
|
101
|
+
researchium search alpine # find experiments and their IDs
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## The environment layout
|
|
107
|
+
|
|
108
|
+
`researchium init NAME` creates a directory with this structure:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
my-study/
|
|
112
|
+
├── .researchium/ # marker + environment metadata (like .git/)
|
|
113
|
+
│ └── environment.json
|
|
114
|
+
├── experiments/ # one folder per experiment
|
|
115
|
+
├── results/ # one folder per experiment, mirroring experiments/
|
|
116
|
+
├── src/ # reusable code for the project
|
|
117
|
+
├── experiments.md # human log of every experiment
|
|
118
|
+
└── experiments.json # machine-readable registry of experiments
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
After registering and running an experiment, an experiment looks like:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
experiments/
|
|
125
|
+
└── alpine-transcriptomics/
|
|
126
|
+
├── run.py # you write this
|
|
127
|
+
├── config.md # written by `run`
|
|
128
|
+
└── config.json
|
|
129
|
+
results/
|
|
130
|
+
└── alpine-transcriptomics/
|
|
131
|
+
├── results.md
|
|
132
|
+
└── results.json
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Key details:
|
|
136
|
+
|
|
137
|
+
- **The `.researchium/` marker is what makes a directory an environment.**
|
|
138
|
+
`discover_environment()` walks up parent directories to find it, so commands
|
|
139
|
+
work from `results/alpine-transcriptomics/`, `src/`, or anywhere below the
|
|
140
|
+
root.
|
|
141
|
+
- **Result folders are keyed by experiment name** (`results/<name>/`), which
|
|
142
|
+
keeps them readable while `researchium results <name_or_id>` still resolves
|
|
143
|
+
either form.
|
|
144
|
+
- **Results and config are stored as both Markdown (for humans) and JSON (for
|
|
145
|
+
tooling).**
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Command-line usage
|
|
150
|
+
|
|
151
|
+
Every command accepts an experiment either by **exact name** or by **ID**.
|
|
152
|
+
|
|
153
|
+
| Command | Description |
|
|
154
|
+
| -------------------------------- | ------------------------------------------------------------------ |
|
|
155
|
+
| `researchium init NAME` | Create an environment named `NAME` in the current directory. |
|
|
156
|
+
| `researchium create NAME DESC` | Register an experiment: folder + log entry + registry record. |
|
|
157
|
+
| `researchium search QUERY` | Find experiments matching name or description (case-insensitive). |
|
|
158
|
+
| `researchium run NAME_OR_ID` | Execute `experiments/<name>/run.py`, snapshot config + results. |
|
|
159
|
+
| `researchium config NAME_OR_ID` | Print the experiment config as `key: value` lines. |
|
|
160
|
+
| `researchium results NAME_OR_ID` | Print the experiment results as `key: value` lines. |
|
|
161
|
+
|
|
162
|
+
### `init`
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
$ researchium init "Climate Genomics"
|
|
166
|
+
Initialized researchium environment at /path/to/project
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Creating an environment where one already exists fails with exit code `1`.
|
|
170
|
+
|
|
171
|
+
### `create`
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
$ researchium create alpine-transcriptomics "RNA-seq of alpine vs lowland populations."
|
|
175
|
+
Created experiment alpine-transcriptomics (ID 1e70f7c3) at .../experiments/alpine-transcriptomics
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`create` refuses empty or path-traversal names (e.g. `../x`) and duplicate
|
|
179
|
+
names. It also creates the experiment's `results/<name>/` folder and appends a
|
|
180
|
+
date/description/ID entry to `experiments.md`.
|
|
181
|
+
|
|
182
|
+
### `search`
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
$ researchium search alpine
|
|
186
|
+
- alpine-transcriptomics (ID 1e70f7c3): RNA-seq of alpine vs lowland populations.
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Matching is case-insensitive over both name and description. No matches prints
|
|
190
|
+
`No experiments matching 'query'.` and still exits `0`.
|
|
191
|
+
|
|
192
|
+
### `run`
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
$ researchium run alpine-transcriptomics
|
|
196
|
+
Ran experiment alpine-transcriptomics (ID 1e70f7c3)
|
|
197
|
+
config -> experiments/alpine-transcriptomics/config.md, config.json
|
|
198
|
+
results -> results/alpine-transcriptomics/results.md, results.json
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`run` clears the previous `Config`/`Results`, executes
|
|
202
|
+
`experiments/<name>/run.py`, then writes four files. It fails with exit code
|
|
203
|
+
`1` if the script is missing or the script raises. See
|
|
204
|
+
[Writing run scripts](#writing-run-scripts).
|
|
205
|
+
|
|
206
|
+
### `config` and `results`
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
$ researchium config alpine
|
|
210
|
+
epochs: 10
|
|
211
|
+
learning_rate: 0.001
|
|
212
|
+
|
|
213
|
+
$ researchium results alpine
|
|
214
|
+
mse: 0.032
|
|
215
|
+
r2: 0.91
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Structured values (lists, dicts) print as compact JSON, e.g.
|
|
219
|
+
`metrics: {"loss": 0.2}`. Before the first run, `config` prints
|
|
220
|
+
`(no configuration yet)`; `results` falls back to a file listing of the
|
|
221
|
+
results folder.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Writing run scripts
|
|
226
|
+
|
|
227
|
+
A run script is any Python file named `run.py` inside an experiment folder. It
|
|
228
|
+
should import `Config`/`Results` and set values by key:
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
from researchium import Config, Results
|
|
232
|
+
|
|
233
|
+
import numpy as np
|
|
234
|
+
from sklearn.model_selection import train_test_split
|
|
235
|
+
|
|
236
|
+
Config["epochs"] = 10
|
|
237
|
+
Config["optimizer"] = "adam"
|
|
238
|
+
Config["layers"] = [256, 128, 64]
|
|
239
|
+
|
|
240
|
+
# ... your training code ...
|
|
241
|
+
accuracy = 0.93
|
|
242
|
+
Results["accuracy"] = accuracy
|
|
243
|
+
Results["metrics"] = {"precision": 0.91, "recall": 0.89}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
- `Config` and `Results` are plain mutable registries (dict-like). Use any key
|
|
247
|
+
you like; order of the printed/`.md` output follows insertion order.
|
|
248
|
+
- Values that are lists, tuples, or dicts are stored as JSON; any other object
|
|
249
|
+
is stored via `str()` — so non-JSON values (e.g. numpy types) never crash a
|
|
250
|
+
`run`.
|
|
251
|
+
- The registries are **cleared before every run**, so runs never leak state.
|
|
252
|
+
- `run` executes the script with `runpy`, as `__main__`. Anything the script
|
|
253
|
+
`print()`s appears on the CLI output.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Python API
|
|
258
|
+
|
|
259
|
+
Everything below is importable from the top-level `researchium` package.
|
|
260
|
+
|
|
261
|
+
### Environments
|
|
262
|
+
|
|
263
|
+
| Function | Description |
|
|
264
|
+
| ----------------------------------------- | -------------------------------------------------- |
|
|
265
|
+
| `initialize_environment(path, name=None)` | Create the standard structure + `.researchium/` marker. Returns `Environment`. |
|
|
266
|
+
| `is_initialized(path)` | `True` if `path` is directly an environment root. |
|
|
267
|
+
| `discover_environment(path)` | Nearest environment at/above `path`. Raises `EnvironmentNotFoundError`. |
|
|
268
|
+
| `Environment` (dataclass) | `root`, `name`, `created`, `researchium_version`; properties `experiments_dir`, `src_dir`, `results_dir`, `experiments_notebook`, `experiments_registry`. |
|
|
269
|
+
| `load_experiments_registry(path)` | Read experiment records from `experiments.json`. |
|
|
270
|
+
| `save_experiments_registry(path, records)`| Write experiment records to `experiments.json`. |
|
|
271
|
+
|
|
272
|
+
### Experiments
|
|
273
|
+
|
|
274
|
+
| Function | Description |
|
|
275
|
+
| ---------------------------------------------- | -------------------------------------------------- |
|
|
276
|
+
| `create_experiment(name, description, path)` | Register an experiment and return it. Raises `ExperimentExistsError`. |
|
|
277
|
+
| `list_experiments(path)` | All experiments, sorted by name. |
|
|
278
|
+
| `search_experiments(query, path)` | Experiments matching name/description. |
|
|
279
|
+
| `get_experiment(identifier, path)` | Look up by ID or name. Raises `ExperimentNotFoundError`. |
|
|
280
|
+
| `get_experiment_results(identifier, path)` | Result files (paths) of an experiment. |
|
|
281
|
+
| `Experiment` (dataclass) | `id`, `name`, `description`, `created`, `environment`; properties `path`, `results_dir`. |
|
|
282
|
+
|
|
283
|
+
### Runtime
|
|
284
|
+
|
|
285
|
+
| Function | Description |
|
|
286
|
+
| ------------------------------------- | -------------------------------------------------- |
|
|
287
|
+
| `Config`, `Results` | Mutable registries your run script fills in. |
|
|
288
|
+
| `run_experiment(identifier, path)` | Execute the run script and persist outputs. Raises `RunScriptNotFoundError` / `RunFailedError`. |
|
|
289
|
+
| `format_configuration(identifier)` | Config as `key: value` lines. |
|
|
290
|
+
| `format_run_results(identifier)` | Results as `key: value` lines (listing fallback). |
|
|
291
|
+
|
|
292
|
+
### Example
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
from researchium import (
|
|
296
|
+
Config,
|
|
297
|
+
Results,
|
|
298
|
+
create_experiment,
|
|
299
|
+
discover_environment,
|
|
300
|
+
initialize_environment,
|
|
301
|
+
run_experiment,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
env = initialize_environment("~/study", name="Climate Genomics")
|
|
305
|
+
experiment = create_experiment(
|
|
306
|
+
"climate-model", "Temperature projections for 2050.", "~/study"
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
experiment.path.joinpath("run.py").write_text(
|
|
310
|
+
"from researchium import Config, Results\n"
|
|
311
|
+
"Config['seed'] = 42\n"
|
|
312
|
+
"Results['mae'] = 1.7\n"
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
run_experiment(experiment.name, "~/study") # runs + writes files
|
|
316
|
+
with open(experiment.path / "config.json") as fh:
|
|
317
|
+
print(fh.read())
|
|
318
|
+
|
|
319
|
+
# From anywhere inside the project:
|
|
320
|
+
env = discover_environment(".") # finds it by walking up
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## File formats
|
|
326
|
+
|
|
327
|
+
### `experiments.md`
|
|
328
|
+
|
|
329
|
+
A human log. `create` appends a section per experiment:
|
|
330
|
+
|
|
331
|
+
```markdown
|
|
332
|
+
### alpine-transcriptomics
|
|
333
|
+
|
|
334
|
+
RNA-seq of alpine vs lowland populations.
|
|
335
|
+
|
|
336
|
+
- ID: `1e70f7c3`
|
|
337
|
+
- Created: 2026-09-22
|
|
338
|
+
- Folder: `experiments/alpine-transcriptomics`
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### `experiments.json`
|
|
342
|
+
|
|
343
|
+
The machine-readable registry (created empty by `init`, appended by `create`):
|
|
344
|
+
|
|
345
|
+
```json
|
|
346
|
+
{
|
|
347
|
+
"version": 1,
|
|
348
|
+
"experiments": [
|
|
349
|
+
{
|
|
350
|
+
"id": "1e70f7c3",
|
|
351
|
+
"name": "alpine-transcriptomics",
|
|
352
|
+
"description": "RNA-seq of alpine vs lowland populations.",
|
|
353
|
+
"created": "2026-09-22"
|
|
354
|
+
}
|
|
355
|
+
]
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### `config.md` / `results.md`
|
|
360
|
+
|
|
361
|
+
Human-formatted snapshots written by `run`:
|
|
362
|
+
|
|
363
|
+
```markdown
|
|
364
|
+
# alpine-transcriptomics
|
|
365
|
+
|
|
366
|
+
Last run: 2026-09-22 23:08
|
|
367
|
+
|
|
368
|
+
## Configuration
|
|
369
|
+
|
|
370
|
+
- **epochs**: 10
|
|
371
|
+
- **learning_rate**: 0.001
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### `config.json` / `results.json`
|
|
375
|
+
|
|
376
|
+
The same data as JSON, suitable for programmatic consumption:
|
|
377
|
+
|
|
378
|
+
```json
|
|
379
|
+
{
|
|
380
|
+
"epochs": 10,
|
|
381
|
+
"learning_rate": 0.001
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Note: a stale `readme.md` produced by a development build is removed
|
|
386
|
+
automatically on the next `run`.
|
|
387
|
+
|
|
388
|
+
### `.researchium/environment.json`
|
|
389
|
+
|
|
390
|
+
Environment metadata; this directory is the environment marker:
|
|
391
|
+
|
|
392
|
+
```json
|
|
393
|
+
{
|
|
394
|
+
"name": "Climate Genomics",
|
|
395
|
+
"created": "2026-09-22",
|
|
396
|
+
"researchium": "0.1.0"
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## Error handling
|
|
403
|
+
|
|
404
|
+
All domain errors subclass `researchium.ResearchiumError`:
|
|
405
|
+
|
|
406
|
+
| Error | Raised when |
|
|
407
|
+
| ---------------------------- | ----------------------------------------- |
|
|
408
|
+
| `EnvironmentExistsError` | `init` on an already-initialized directory |
|
|
409
|
+
| `EnvironmentNotFoundError` | no environment at/above the given path |
|
|
410
|
+
| `ExperimentExistsError` | creating a duplicate experiment name |
|
|
411
|
+
| `ExperimentNotFoundError` | no experiment matches the ID/name |
|
|
412
|
+
| `InvalidExperimentNameError` | empty or path-traversal experiment name |
|
|
413
|
+
| `RunScriptNotFoundError` | experiment has no `run.py` |
|
|
414
|
+
| `RunFailedError` | the run script raised |
|
|
415
|
+
|
|
416
|
+
The CLI catches these, prints `error: <message>` to stderr, and exits `1`.
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## Development
|
|
421
|
+
|
|
422
|
+
```bash
|
|
423
|
+
uv sync --extra dev # install the package + dev tools
|
|
424
|
+
uv run pytest # 44 tests
|
|
425
|
+
uv run pytest -q # keep it quiet
|
|
426
|
+
uv run ruff check . # lint (E, F, W, I, UP on src + tests)
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Test coverage is configured in `pyproject.toml` (`--cov=researchium
|
|
430
|
+
--cov-report=term-missing`) and is kept at 100%. The GitHub Actions workflow in
|
|
431
|
+
`.github/workflows/ci.yml` runs tests and lint on Python 3.10 / 3.12 / 3.14.
|
|
432
|
+
|
|
433
|
+
### Adding a feature
|
|
434
|
+
|
|
435
|
+
1. Add the function to the relevant module under `src/researchium/`.
|
|
436
|
+
2. Export it from `researchium/__init__.py` and add it to `__all__`.
|
|
437
|
+
3. Add tests under `tests/`; keep coverage at 100% (`uv run pytest` will show
|
|
438
|
+
any missed lines).
|
|
439
|
+
4. Run `uv run ruff check . && uv run pytest` and reinstall the global tool if
|
|
440
|
+
you use it: `uv tool install --force /path/to/researchium`.
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## Project layout
|
|
445
|
+
|
|
446
|
+
```
|
|
447
|
+
researchium/
|
|
448
|
+
├── pyproject.toml # PEP 621 metadata, hatchling build, ruff/pytest config
|
|
449
|
+
├── uv.lock # locked development dependencies
|
|
450
|
+
├── README.md
|
|
451
|
+
├── .python-version
|
|
452
|
+
├── .github/workflows/ci.yml # lint + tests matrix
|
|
453
|
+
├── src/researchium/
|
|
454
|
+
│ ├── __init__.py # public API surface
|
|
455
|
+
│ ├── environment.py # init, discovery, environment metadata + registry
|
|
456
|
+
│ ├── experiments.py # experiment lifecycle, search, lookup
|
|
457
|
+
│ ├── runtime.py # Config/Results registries + runner + formatters
|
|
458
|
+
│ └── cli.py # argparse-based CLI (entry point `researchium`)
|
|
459
|
+
└── tests/
|
|
460
|
+
├── test_environment.py
|
|
461
|
+
├── test_experiments.py
|
|
462
|
+
├── test_runtime.py
|
|
463
|
+
└── test_cli.py
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## Roadmap
|
|
469
|
+
|
|
470
|
+
Planned directions, in rough order:
|
|
471
|
+
|
|
472
|
+
- `researchium scaffold <name>` to generate a starter `run.py`.
|
|
473
|
+
- Experiment status (pending / running / completed) tracked in the registry.
|
|
474
|
+
- Deleting and renaming experiments with safe migration of results.
|
|
475
|
+
- A smaller `researchium` output format flag (`--json`) for scripting.
|