experiment_server 0.3.6__tar.gz → 0.3.7__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.
- {experiment_server-0.3.6 → experiment_server-0.3.7}/PKG-INFO +63 -11
- {experiment_server-0.3.6 → experiment_server-0.3.7}/README.md +58 -6
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/_api.py +73 -13
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/_client.py +7 -4
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/_participant_ordering.py +89 -85
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/_process_config.py +84 -52
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/_server.py +18 -4
- experiment_server-0.3.7/experiment_server/_ui.py +917 -0
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/cli.py +18 -20
- experiment_server-0.3.7/experiment_server/static/css/app.tcss +254 -0
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/utils.py +31 -0
- {experiment_server-0.3.6 → experiment_server-0.3.7}/pyproject.toml +6 -4
- {experiment_server-0.3.6 → experiment_server-0.3.7}/LICENSE.md +0 -0
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/__init__.py +0 -0
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/css/bootstrap-5.2.3.min.css +0 -0
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/index.html +0 -0
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/initconfig.html +0 -0
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/js/alpinejs3.min.js +0 -0
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/js/bootstrap-4.5.0.min.js +0 -0
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/js/fontawesome-1e694dd391.js +0 -0
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/js/htmx-1.9.10.js +0 -0
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/js/jquery-3.5.1.min.js +0 -0
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/js/popper-1.16.0.min.js +0 -0
- {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/js/sweetalert2-11.js +0 -0
- {experiment_server-0.3.6 → experiment_server-0.3.7}/sample_config.toml +0 -0
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: experiment_server
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
4
4
|
Summary: Server for experiments to get configuarations from
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: experiment,study-design
|
|
7
7
|
Author: Ahmed Shariff
|
|
8
8
|
Author-email: shariff.mfa@outlook.com
|
|
9
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
10
|
Classifier: Development Status :: 1 - Planning
|
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
12
|
Classifier: Natural Language :: English
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: Programming Language :: Python
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -26,11 +24,13 @@ Requires-Dist: click-aliases (>=1.0.4)
|
|
|
26
24
|
Requires-Dist: easydict (>=1)
|
|
27
25
|
Requires-Dist: importlib-metadata (>=6.8.0)
|
|
28
26
|
Requires-Dist: loguru (>=0.5,<0.6)
|
|
29
|
-
Requires-Dist: pandas (>=1)
|
|
30
27
|
Requires-Dist: requests (>=2.25.1)
|
|
31
28
|
Requires-Dist: tabulate (>=0.8.9)
|
|
29
|
+
Requires-Dist: textual (>=7.5.0,<8.0.0)
|
|
32
30
|
Requires-Dist: toml (>=0.10.2)
|
|
33
31
|
Requires-Dist: tornado (>=6.2)
|
|
32
|
+
Requires-Dist: tree-sitter (>=0.25.2,<0.26.0)
|
|
33
|
+
Requires-Dist: tree-sitter-toml (>=0.7.0,<0.8.0)
|
|
34
34
|
Requires-Dist: watchdog (>=2.1.9)
|
|
35
35
|
Project-URL: Documentation, https://shariff-faleel.com/experiment_server/documentation/
|
|
36
36
|
Project-URL: Homepage, https://shariff-faleel.com/experiment_server/
|
|
@@ -48,6 +48,7 @@ Documentation is available at [https://shariff-faleel.com/experiment_server/](ht
|
|
|
48
48
|
- [Overview](#overview)
|
|
49
49
|
- [Installation](#installation)
|
|
50
50
|
- [Usage](#usage)
|
|
51
|
+
- [Textual UI (TUI)](#textual-ui-tui)
|
|
51
52
|
- [Configuration of an experiment](#configuration-of-an-experiment)
|
|
52
53
|
- [Verify config](#verify-config)
|
|
53
54
|
- [Loading experiment through server](#loading-experiment-through-server)
|
|
@@ -72,6 +73,55 @@ $ poetry add experiment-server
|
|
|
72
73
|
```
|
|
73
74
|
|
|
74
75
|
# Usage
|
|
76
|
+
## Textual UI (TUI)
|
|
77
|
+
|
|
78
|
+
A terminal-based user interface is provided for interactive management and monitoring of an experiment. It is useful when you want a lightweight, keyboard-driven interface to load configs, inspect/generate per‑participant configs, edit files, and control participant flow without using the web UI.
|
|
79
|
+
|
|
80
|
+
Launch the TUI with:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
# Start TUI (optionally provide a config file)
|
|
84
|
+
$ experiment-server ui
|
|
85
|
+
$ experiment-server ui -c sample_config.toml
|
|
86
|
+
|
|
87
|
+
# Common options
|
|
88
|
+
$ experiment-server ui -c sample_config.toml -i 1 -h 127.0.0.1 -p 5000
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Options:
|
|
92
|
+
- -c, --config-file : path to a .toml config to load on start (optional).
|
|
93
|
+
- -i, --default-participant-index : default participant index to use when no explicit participant id is provided (default 1).
|
|
94
|
+
- -h, --host : host/interface for the server (default 127.0.0.1).
|
|
95
|
+
- -p, --port : port for the server (default 5000).
|
|
96
|
+
|
|
97
|
+
Behavior and features
|
|
98
|
+
- Can be started without a config file; load a config later from the built-in file browser (filters to directories and .toml files).
|
|
99
|
+
- When a config is loaded the package starts the server in the same event loop, exposing the same REST API described elsewhere in this README.
|
|
100
|
+
- Participant management:
|
|
101
|
+
- View all participants and their status, block id and block name.
|
|
102
|
+
- Create new participants (auto id or a specific id).
|
|
103
|
+
- Move the default or a specific participant to the next block or to a specific block id.
|
|
104
|
+
- Move all active participants to a block.
|
|
105
|
+
- Reset a participant.
|
|
106
|
+
- Choose a monitored participant to view its current config and status.
|
|
107
|
+
- Config management:
|
|
108
|
+
- View the currently loaded .toml contents in a code editor pane.
|
|
109
|
+
- Edit the config in place and save; saving writes the file and the app will attempt to reload the config.
|
|
110
|
+
- Generate new config files using simple or advanced helpers (write a minimal example or build from JSON-specified order/blocks/parameters).
|
|
111
|
+
- Generate expanded JSON configs for a range of participant indices (write output to a directory).
|
|
112
|
+
- Live logging pane shows application logs for actions and errors.
|
|
113
|
+
- A simple edit UI is available to tweak the in-memory per-participant config (submit or cancel edits).
|
|
114
|
+
|
|
115
|
+
Notes
|
|
116
|
+
- Editing and saving the config file from the TUI writes to the same config path the server is using; the TUI will attempt to reload the new configuration but will preserve participant state (see server behavior note in the [Loading experiment through server](#loading-experiment-through-server) section).
|
|
117
|
+
- The TUI requires the textual/Rich environment provided by the package (normally installed with the package). Use `Ctrl+q` to exit or the TUI's quit action.
|
|
118
|
+
|
|
119
|
+
This TUI is intended as a compact, interactive alternative to the web interface for quick testing, config edits, and participant control.
|
|
120
|
+
<p style="display:flex; gap:8px; align-items:flex-start;">
|
|
121
|
+
<img src="https://raw.githubusercontent.com/ahmed-shariff/experiment_server/master/media/screenshot_tui1.png" alt="web UI screenshot 1" style="width:49%;">
|
|
122
|
+
<img src="https://raw.githubusercontent.com/ahmed-shariff/experiment_server/master/media/screenshot_tui2.png" alt="web UI screenshot 2" style="width:49%;">
|
|
123
|
+
</p>
|
|
124
|
+
|
|
75
125
|
## Configuration of an experiment
|
|
76
126
|
The configuration is defined in a [toml](https://toml.io/en/) file.
|
|
77
127
|
|
|
@@ -311,6 +361,8 @@ See [toml spec](https://toml.io/en/v1.0.0) for more information on the format of
|
|
|
311
361
|
```
|
|
312
362
|
</details>
|
|
313
363
|
|
|
364
|
+
See [construct_participant_condition][experiment_server._participant_ordering.construct_participant_condition] and [process_config_file][experiment_server._process_config.process_config_file] for more information.
|
|
365
|
+
|
|
314
366
|
## Verify config
|
|
315
367
|
A config file can be validated by running:
|
|
316
368
|
```sh
|
|
@@ -318,6 +370,8 @@ $ experiment-server verify-config-file sample_config.toml
|
|
|
318
370
|
```
|
|
319
371
|
This will show how the expanded config looks like for the first 5 participants.
|
|
320
372
|
|
|
373
|
+
See also [verify_config][experiment_server._process_config.verify_config]
|
|
374
|
+
|
|
321
375
|
## Loading experiment through server
|
|
322
376
|
After installation, the server can used as:
|
|
323
377
|
|
|
@@ -359,7 +413,7 @@ The server exposes the following REST API:
|
|
|
359
413
|
|
|
360
414
|
- [PUT] `/api/add-participant/:participant-id` - Add a new participant with `participant-id`. If there is already a participant with the `participant-id`, this will fail.
|
|
361
415
|
|
|
362
|
-
For a Python application, `experiment_server.Client` can be used to access configs from the server. Also, the server can be launched programmatically using `experiment_server.server_process` which returns a [`Process`](https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Process) object.
|
|
416
|
+
For a Python application, [`experiment_server.Client`][experiment_server.Client] can be used to access configs from the server. Also, the server can be launched programmatically using `experiment_server.server_process` which returns a [`Process`](https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Process) object.
|
|
363
417
|
|
|
364
418
|
**NOTE**: If the config file served is changed, the new config will be loaded, but the state of the participants will be maintained. i.e., the added participants and the block id they are at will not change. To move the block ids for all active participants, you would have to call the `move-all-to-block` endpoint.
|
|
365
419
|
|
|
@@ -369,7 +423,7 @@ The server also provides a simple web interface, which can be accessed at `/` or
|
|
|
369
423
|
|
|
370
424
|
|
|
371
425
|
## Loading experiment through API
|
|
372
|
-
A configuration can be loaded and managed by importing `experiment_server.Experiment
|
|
426
|
+
A configuration can be loaded and managed by importing [`experiment_server.Experiment`][experiment_server.Experiment].
|
|
373
427
|
|
|
374
428
|
## Generate expanded configs
|
|
375
429
|
A config file (i.e. `.toml` file), can be expanded to JSON with the following command
|
|
@@ -380,6 +434,8 @@ $ experiment-server generate-config-json sample_config.toml --participant-range
|
|
|
380
434
|
|
|
381
435
|
The above will generate the expanded configs for participant indices 1 to 5 as JSON output on stdout. This result can be written out to individual JSON files by setting the `--out-dir`/`-d` to a directory. See more options with `--help`
|
|
382
436
|
|
|
437
|
+
See also [_generate_config_json][experiment_server._api._generate_config_json]
|
|
438
|
+
|
|
383
439
|
## Function calls in config
|
|
384
440
|
A function call in the config is represented by a table, with the following keys
|
|
385
441
|
- `function_name`: This should be one of the names in the supported functions list below.
|
|
@@ -402,7 +458,3 @@ param = { foo = "test", bar = { function_name = "choices", args = { population =
|
|
|
402
458
|
|
|
403
459
|
For more on the `experiemnt-server` and how it can be used see the [wiki](https://github.com/ahmed-shariff/experiment_server/wiki)
|
|
404
460
|
|
|
405
|
-
# Wishlist (todo list?)
|
|
406
|
-
- Improved docs
|
|
407
|
-
- Add the option of using dict values in order
|
|
408
|
-
|
|
@@ -9,6 +9,7 @@ Documentation is available at [https://shariff-faleel.com/experiment_server/](ht
|
|
|
9
9
|
- [Overview](#overview)
|
|
10
10
|
- [Installation](#installation)
|
|
11
11
|
- [Usage](#usage)
|
|
12
|
+
- [Textual UI (TUI)](#textual-ui-tui)
|
|
12
13
|
- [Configuration of an experiment](#configuration-of-an-experiment)
|
|
13
14
|
- [Verify config](#verify-config)
|
|
14
15
|
- [Loading experiment through server](#loading-experiment-through-server)
|
|
@@ -33,6 +34,55 @@ $ poetry add experiment-server
|
|
|
33
34
|
```
|
|
34
35
|
|
|
35
36
|
# Usage
|
|
37
|
+
## Textual UI (TUI)
|
|
38
|
+
|
|
39
|
+
A terminal-based user interface is provided for interactive management and monitoring of an experiment. It is useful when you want a lightweight, keyboard-driven interface to load configs, inspect/generate per‑participant configs, edit files, and control participant flow without using the web UI.
|
|
40
|
+
|
|
41
|
+
Launch the TUI with:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
# Start TUI (optionally provide a config file)
|
|
45
|
+
$ experiment-server ui
|
|
46
|
+
$ experiment-server ui -c sample_config.toml
|
|
47
|
+
|
|
48
|
+
# Common options
|
|
49
|
+
$ experiment-server ui -c sample_config.toml -i 1 -h 127.0.0.1 -p 5000
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Options:
|
|
53
|
+
- -c, --config-file : path to a .toml config to load on start (optional).
|
|
54
|
+
- -i, --default-participant-index : default participant index to use when no explicit participant id is provided (default 1).
|
|
55
|
+
- -h, --host : host/interface for the server (default 127.0.0.1).
|
|
56
|
+
- -p, --port : port for the server (default 5000).
|
|
57
|
+
|
|
58
|
+
Behavior and features
|
|
59
|
+
- Can be started without a config file; load a config later from the built-in file browser (filters to directories and .toml files).
|
|
60
|
+
- When a config is loaded the package starts the server in the same event loop, exposing the same REST API described elsewhere in this README.
|
|
61
|
+
- Participant management:
|
|
62
|
+
- View all participants and their status, block id and block name.
|
|
63
|
+
- Create new participants (auto id or a specific id).
|
|
64
|
+
- Move the default or a specific participant to the next block or to a specific block id.
|
|
65
|
+
- Move all active participants to a block.
|
|
66
|
+
- Reset a participant.
|
|
67
|
+
- Choose a monitored participant to view its current config and status.
|
|
68
|
+
- Config management:
|
|
69
|
+
- View the currently loaded .toml contents in a code editor pane.
|
|
70
|
+
- Edit the config in place and save; saving writes the file and the app will attempt to reload the config.
|
|
71
|
+
- Generate new config files using simple or advanced helpers (write a minimal example or build from JSON-specified order/blocks/parameters).
|
|
72
|
+
- Generate expanded JSON configs for a range of participant indices (write output to a directory).
|
|
73
|
+
- Live logging pane shows application logs for actions and errors.
|
|
74
|
+
- A simple edit UI is available to tweak the in-memory per-participant config (submit or cancel edits).
|
|
75
|
+
|
|
76
|
+
Notes
|
|
77
|
+
- Editing and saving the config file from the TUI writes to the same config path the server is using; the TUI will attempt to reload the new configuration but will preserve participant state (see server behavior note in the [Loading experiment through server](#loading-experiment-through-server) section).
|
|
78
|
+
- The TUI requires the textual/Rich environment provided by the package (normally installed with the package). Use `Ctrl+q` to exit or the TUI's quit action.
|
|
79
|
+
|
|
80
|
+
This TUI is intended as a compact, interactive alternative to the web interface for quick testing, config edits, and participant control.
|
|
81
|
+
<p style="display:flex; gap:8px; align-items:flex-start;">
|
|
82
|
+
<img src="https://raw.githubusercontent.com/ahmed-shariff/experiment_server/master/media/screenshot_tui1.png" alt="web UI screenshot 1" style="width:49%;">
|
|
83
|
+
<img src="https://raw.githubusercontent.com/ahmed-shariff/experiment_server/master/media/screenshot_tui2.png" alt="web UI screenshot 2" style="width:49%;">
|
|
84
|
+
</p>
|
|
85
|
+
|
|
36
86
|
## Configuration of an experiment
|
|
37
87
|
The configuration is defined in a [toml](https://toml.io/en/) file.
|
|
38
88
|
|
|
@@ -272,6 +322,8 @@ See [toml spec](https://toml.io/en/v1.0.0) for more information on the format of
|
|
|
272
322
|
```
|
|
273
323
|
</details>
|
|
274
324
|
|
|
325
|
+
See [construct_participant_condition][experiment_server._participant_ordering.construct_participant_condition] and [process_config_file][experiment_server._process_config.process_config_file] for more information.
|
|
326
|
+
|
|
275
327
|
## Verify config
|
|
276
328
|
A config file can be validated by running:
|
|
277
329
|
```sh
|
|
@@ -279,6 +331,8 @@ $ experiment-server verify-config-file sample_config.toml
|
|
|
279
331
|
```
|
|
280
332
|
This will show how the expanded config looks like for the first 5 participants.
|
|
281
333
|
|
|
334
|
+
See also [verify_config][experiment_server._process_config.verify_config]
|
|
335
|
+
|
|
282
336
|
## Loading experiment through server
|
|
283
337
|
After installation, the server can used as:
|
|
284
338
|
|
|
@@ -320,7 +374,7 @@ The server exposes the following REST API:
|
|
|
320
374
|
|
|
321
375
|
- [PUT] `/api/add-participant/:participant-id` - Add a new participant with `participant-id`. If there is already a participant with the `participant-id`, this will fail.
|
|
322
376
|
|
|
323
|
-
For a Python application, `experiment_server.Client` can be used to access configs from the server. Also, the server can be launched programmatically using `experiment_server.server_process` which returns a [`Process`](https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Process) object.
|
|
377
|
+
For a Python application, [`experiment_server.Client`][experiment_server.Client] can be used to access configs from the server. Also, the server can be launched programmatically using `experiment_server.server_process` which returns a [`Process`](https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Process) object.
|
|
324
378
|
|
|
325
379
|
**NOTE**: If the config file served is changed, the new config will be loaded, but the state of the participants will be maintained. i.e., the added participants and the block id they are at will not change. To move the block ids for all active participants, you would have to call the `move-all-to-block` endpoint.
|
|
326
380
|
|
|
@@ -330,7 +384,7 @@ The server also provides a simple web interface, which can be accessed at `/` or
|
|
|
330
384
|
|
|
331
385
|
|
|
332
386
|
## Loading experiment through API
|
|
333
|
-
A configuration can be loaded and managed by importing `experiment_server.Experiment
|
|
387
|
+
A configuration can be loaded and managed by importing [`experiment_server.Experiment`][experiment_server.Experiment].
|
|
334
388
|
|
|
335
389
|
## Generate expanded configs
|
|
336
390
|
A config file (i.e. `.toml` file), can be expanded to JSON with the following command
|
|
@@ -341,6 +395,8 @@ $ experiment-server generate-config-json sample_config.toml --participant-range
|
|
|
341
395
|
|
|
342
396
|
The above will generate the expanded configs for participant indices 1 to 5 as JSON output on stdout. This result can be written out to individual JSON files by setting the `--out-dir`/`-d` to a directory. See more options with `--help`
|
|
343
397
|
|
|
398
|
+
See also [_generate_config_json][experiment_server._api._generate_config_json]
|
|
399
|
+
|
|
344
400
|
## Function calls in config
|
|
345
401
|
A function call in the config is represented by a table, with the following keys
|
|
346
402
|
- `function_name`: This should be one of the names in the supported functions list below.
|
|
@@ -362,7 +418,3 @@ param = { foo = "test", bar = { function_name = "choices", args = { population =
|
|
|
362
418
|
```
|
|
363
419
|
|
|
364
420
|
For more on the `experiemnt-server` and how it can be used see the [wiki](https://github.com/ahmed-shariff/experiment_server/wiki)
|
|
365
|
-
|
|
366
|
-
# Wishlist (todo list?)
|
|
367
|
-
- Improved docs
|
|
368
|
-
- Add the option of using dict values in order
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from sys import stdout
|
|
2
|
-
from typing import Any, Dict, Iterable, List, Union
|
|
2
|
+
from typing import Any, Callable, Dict, Iterable, List, Union
|
|
3
3
|
|
|
4
4
|
from loguru import logger
|
|
5
5
|
from experiment_server._process_config import process_config_file
|
|
@@ -89,25 +89,85 @@ class Experiment:
|
|
|
89
89
|
config_file (str): Path to the TOML configuration file.
|
|
90
90
|
default_participant_index (int): Default 1-based index used when none is provided.
|
|
91
91
|
"""
|
|
92
|
-
|
|
92
|
+
self.on_file_change_callback:list[Callable] = []
|
|
93
|
+
self.on_config_change_callback:list[Callable] = []
|
|
94
|
+
|
|
95
|
+
self.watchdog = None
|
|
93
96
|
self.global_state: Dict[int, ParticipantState] = {}
|
|
97
|
+
self.config_file = Path(config_file)
|
|
94
98
|
self.default_participant_index = default_participant_index
|
|
95
|
-
self.watchdog = FileModifiedWatcher(config_file, self._config_file_modified_callback)
|
|
96
|
-
self.config_file = config_file
|
|
97
99
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
@property
|
|
101
|
+
def config_file(self) -> Path:
|
|
102
|
+
return self._config_file
|
|
103
|
+
|
|
104
|
+
@config_file.setter
|
|
105
|
+
def config_file(self, value):
|
|
106
|
+
"""Load a new config file. All participants states will be reset."""
|
|
107
|
+
try:
|
|
108
|
+
for ppid in self.global_state.keys():
|
|
109
|
+
self.global_state[ppid] = ParticipantState(
|
|
110
|
+
process_config_file(value, ppid), ppid, False)
|
|
111
|
+
|
|
112
|
+
if self.watchdog is not None:
|
|
113
|
+
self.watchdog.end_watch()
|
|
114
|
+
self.watchdog = FileModifiedWatcher(value, self._config_file_modified_callback)
|
|
115
|
+
|
|
116
|
+
self._config_file = value
|
|
117
|
+
|
|
118
|
+
for _callback in self.on_config_change_callback:
|
|
119
|
+
try:
|
|
120
|
+
_callback(True)
|
|
121
|
+
except Exception as _e:
|
|
122
|
+
logger.exception(f"Failed to call callback {_callback}: {_e}")
|
|
123
|
+
except Exception as e:
|
|
124
|
+
if self.watchdog is not None:
|
|
125
|
+
self.watchdog.end_watch()
|
|
126
|
+
self.watchdog = FileModifiedWatcher(self._config_file, self._config_file_modified_callback)
|
|
127
|
+
|
|
128
|
+
for _callback in self.on_config_change_callback:
|
|
129
|
+
try:
|
|
130
|
+
_callback(False)
|
|
131
|
+
except Exception as _e:
|
|
132
|
+
logger.exception(f"Failed to call callback {_callback}: {_e}")
|
|
133
|
+
logger.exception(f"Failed to set new config {e}")
|
|
134
|
+
raise
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def default_participant_index(self) -> int:
|
|
138
|
+
return self._default_participant_index
|
|
139
|
+
|
|
140
|
+
@default_participant_index.setter
|
|
141
|
+
def default_participant_index(self, value):
|
|
142
|
+
assert value > 0, "Default participant index should be >0"
|
|
143
|
+
self._default_participant_index = value
|
|
144
|
+
|
|
145
|
+
if self._default_participant_index not in self.global_state:
|
|
146
|
+
self.global_state[self._default_participant_index] = ParticipantState(
|
|
147
|
+
process_config_file(self._config_file, self._default_participant_index),
|
|
148
|
+
self._default_participant_index,
|
|
102
149
|
False,
|
|
103
150
|
)
|
|
104
151
|
|
|
105
152
|
def _config_file_modified_callback(self):
|
|
106
153
|
"""Reload configurations for all known participants when the file changes."""
|
|
107
154
|
logger.info("Reloading config")
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
155
|
+
try:
|
|
156
|
+
for participantState in self.global_state.values():
|
|
157
|
+
config = process_config_file(self._config_file, participantState.participant_index)
|
|
158
|
+
participantState.config = config
|
|
159
|
+
for _callback in self.on_file_change_callback:
|
|
160
|
+
try:
|
|
161
|
+
_callback(True)
|
|
162
|
+
except Exception as _e:
|
|
163
|
+
logger.exception(f"Failed to call callback {_callback}: {_e}")
|
|
164
|
+
except Exception as e:
|
|
165
|
+
for _callback in self.on_file_change_callback:
|
|
166
|
+
try:
|
|
167
|
+
_callback(False)
|
|
168
|
+
except Exception as _e:
|
|
169
|
+
logger.exception(f"Failed to call callback {_callback}: {_e}")
|
|
170
|
+
logger.exception(f"Failed to load config {e}")
|
|
111
171
|
|
|
112
172
|
def get_next_participant(self) -> int:
|
|
113
173
|
"""Allocate and return the next participant index (max existing + 1)."""
|
|
@@ -124,7 +184,7 @@ class Experiment:
|
|
|
124
184
|
if participant_index in self.global_state:
|
|
125
185
|
return False
|
|
126
186
|
self.global_state[participant_index] = ParticipantState(
|
|
127
|
-
process_config_file(self.
|
|
187
|
+
process_config_file(self._config_file, participant_index),
|
|
128
188
|
participant_index,
|
|
129
189
|
False,
|
|
130
190
|
)
|
|
@@ -169,7 +229,7 @@ class Experiment:
|
|
|
169
229
|
"""Reload the participant's configuration from file and replace their stored config."""
|
|
170
230
|
if participant_index is None:
|
|
171
231
|
participant_index = self.default_participant_index
|
|
172
|
-
self.global_state[participant_index].config = process_config_file(self.
|
|
232
|
+
self.global_state[participant_index].config = process_config_file(self._config_file, participant_index)
|
|
173
233
|
return True
|
|
174
234
|
|
|
175
235
|
def get_blocks_count(self, participant_index:int|None=None) -> int:
|
|
@@ -14,16 +14,14 @@ class Client:
|
|
|
14
14
|
- Methods operate on a per-participant basis when applicable (provide
|
|
15
15
|
participant_index or omit to use the server's default participant).
|
|
16
16
|
|
|
17
|
-
Parameters
|
|
18
|
-
- server_host (str): Hostname or IP of the server (default "127.0.0.1").
|
|
19
|
-
- server_port (str|int): Port of the server (default "5000").
|
|
20
|
-
|
|
21
17
|
Return value for API methods
|
|
18
|
+
|
|
22
19
|
- All public API methods return a tuple (success: bool, data: dict|str).
|
|
23
20
|
On success data is the parsed JSON response (or "" if empty). On failure
|
|
24
21
|
success is False and data contains an error message.
|
|
25
22
|
|
|
26
23
|
Main methods
|
|
24
|
+
|
|
27
25
|
- move_to_next(participant_index=None)
|
|
28
26
|
- get_config(participant_index=None)
|
|
29
27
|
- server_is_active(participant_index=None)
|
|
@@ -33,6 +31,11 @@ class Client:
|
|
|
33
31
|
- new_participant()
|
|
34
32
|
- add_participant(participant_index)
|
|
35
33
|
- shutdown()
|
|
34
|
+
|
|
35
|
+
Parameters:
|
|
36
|
+
server_host (str): Hostname or IP of the server (default "127.0.0.1").
|
|
37
|
+
server_port (str|int): Port of the server (default "5000").
|
|
38
|
+
|
|
36
39
|
"""
|
|
37
40
|
def __init__(self, server_host:str ="127.0.0.1", server_port:Union[str, int]="5000") -> None:
|
|
38
41
|
self._server_url = f"http://{server_host}:{server_port}"
|
{experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/_participant_ordering.py
RENAMED
|
@@ -29,91 +29,95 @@ def construct_participant_condition(config: List[Dict],
|
|
|
29
29
|
- within_groups (ordering of elements inside each group)
|
|
30
30
|
- init_blocks and final_blocks (ordering of initial and final blocks appended before/after main groups)
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
32
|
+
Behavior details and constraints:
|
|
33
|
+
- Names in `order`, `init_block_names`, and `final_block_names` are validated to be strings and must
|
|
34
|
+
match unique names in `config`. Duplicate names in `config` cause an error.
|
|
35
|
+
- When `groups_strategy` or `within_groups_strategy` is "randomize", Python's random.shuffle is used
|
|
36
|
+
(non-deterministic unless the caller seeds the RNG).
|
|
37
|
+
- When "latin_square" is requested for groups or within-group ordering, a balanced Latin square
|
|
38
|
+
generator is used and `participant_index` selects the row; latin-square requires equal-sized
|
|
39
|
+
values where appropriate (e.g., all groups must have the same size when using within-group
|
|
40
|
+
latin-square).
|
|
41
|
+
- When dictionary mappings are provided for order/init/final blocks, keys are expected to be
|
|
42
|
+
1-based consecutive integer indices (or strings coercible to those integers). For dicts, the
|
|
43
|
+
corresponding strategy argument must be "as_is".
|
|
44
|
+
|
|
45
|
+
Notes:
|
|
46
|
+
The function does not modify the input `config` objects beyond coercing the "name" field to str.
|
|
47
|
+
Deterministic rotation behavior for latin-square and dict-based selection is 1-based and uses
|
|
48
|
+
(participant_index - 1) modulo the appropriate period.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
config (List[Dict]): A list of block configuration dictionaries. Each dict must contain a
|
|
52
|
+
unique "name" key whose value will be coerced to a string. The returned value is a list of
|
|
53
|
+
these configuration dicts arranged according to the computed participant order.
|
|
54
|
+
|
|
55
|
+
participant_index (int): 1-based index of the participant used to select deterministic
|
|
56
|
+
rotations when latin-square or per-participant dictionary-based indexing is used. Must be
|
|
57
|
+
positive.
|
|
58
|
+
|
|
59
|
+
order (Union[dict, list]):
|
|
60
|
+
Specification of the main experimental order. Two forms are supported:
|
|
61
|
+
|
|
62
|
+
* list of groups: e.g. [["A","B"], ["C","D","E"]] where each inner list is a group of block names.
|
|
63
|
+
If a flat list of strings is provided (["A","B","C"]) it will be treated as a single group.
|
|
64
|
+
* dict keyed by participant index (string or int keys) mapping to a group-list for that participant.
|
|
65
|
+
When a dict is provided, the `groups_strategy` must be "as_is" (per-participant assignment by key).
|
|
66
|
+
|
|
67
|
+
All block names in the resolved order must be strings and must exist in `config`.
|
|
68
|
+
|
|
69
|
+
init_block_names (Union[dict, list]): Names of blocks to prepend for the
|
|
70
|
+
participant. Accepts a list of block names (strings) or a dictionary keyed by participant
|
|
71
|
+
indices (1-based) to select a list for the participant. When a dict is supplied, the
|
|
72
|
+
strategy for init blocks must be "as_is" (per-participant selection).
|
|
73
|
+
|
|
74
|
+
final_block_names (Union[dict, list]): Names of blocks to append for the
|
|
75
|
+
participant. Accepts same forms and constraints as init_block_names.
|
|
76
|
+
|
|
77
|
+
within_groups_strategy (Union[str, None]):
|
|
78
|
+
Strategy applied to the ordering of elements inside each group. Allowed values:
|
|
79
|
+
|
|
80
|
+
* "as_is" - leave element order as provided
|
|
81
|
+
* "randomize" - shuffle elements inside each group (non-deterministic)
|
|
82
|
+
* "latin_square" - apply a balanced Latin square to permute positions across participants
|
|
83
|
+
|
|
84
|
+
If None, defaults to "as_is". If `order` was provided as a list-of-lists and `groups_strategy`
|
|
85
|
+
was set to "randomize" or "latin_square" and not explicitly set for `within_groups_strategy`,
|
|
86
|
+
the `groups_strategy` may be reused for within-groups behavior in some calling patterns.
|
|
87
|
+
|
|
88
|
+
groups_strategy (Union[str, None]): Strategy applied to the sequence of groups. Allowed values:
|
|
89
|
+
|
|
90
|
+
* "as_is"
|
|
91
|
+
* "randomize"
|
|
92
|
+
* "latin_square"
|
|
93
|
+
|
|
94
|
+
If None, defaults to "as_is". When `order` is a dict keyed by participant index, `groups_strategy`
|
|
95
|
+
must be "as_is" because the dict already selects per-participant grouping.
|
|
96
|
+
|
|
97
|
+
init_blocks_strategy (Union[str, None]): Strategy for ordering initial blocks. Allowed values:
|
|
98
|
+
|
|
99
|
+
* "as_is"
|
|
100
|
+
* "randomize"
|
|
101
|
+
|
|
102
|
+
If None, defaults to "as_is". When init_block_names is a dict keyed by participant index,
|
|
103
|
+
the strategy must be "as_is" (per-participant selection).
|
|
104
|
+
|
|
105
|
+
final_blocks_strategy (Union[str, None]): Strategy for ordering final blocks. Same semantics and allowed values as init_blocks_strategy.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
List: A list of block configuration dictionaries (the original dicts from `config`) in the final
|
|
109
|
+
order constructed for the participant: [init_blocks..., main_blocks..., final_blocks...].
|
|
110
|
+
The main_blocks portion is produced by flattening the possibly nested group structure after
|
|
111
|
+
applying the requested group- and within-group strategies.
|
|
112
|
+
|
|
113
|
+
Exceptions:
|
|
114
|
+
ExperimentServerConfigurationExcetion: on invalid configuration, such as:
|
|
115
|
+
|
|
116
|
+
* duplicate block names in config
|
|
117
|
+
* non-string block names in orders
|
|
118
|
+
* unsupported strategy names
|
|
119
|
+
* inconsistent group sizes for latin-square within-group ordering
|
|
120
|
+
* improper dict key sets or types when dict-based per-participant selection is used
|
|
117
121
|
"""
|
|
118
122
|
if within_groups_strategy is None:
|
|
119
123
|
within_groups_strategy = ORDERING_STRATEGY.as_is
|