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.
Files changed (25) hide show
  1. {experiment_server-0.3.6 → experiment_server-0.3.7}/PKG-INFO +63 -11
  2. {experiment_server-0.3.6 → experiment_server-0.3.7}/README.md +58 -6
  3. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/_api.py +73 -13
  4. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/_client.py +7 -4
  5. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/_participant_ordering.py +89 -85
  6. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/_process_config.py +84 -52
  7. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/_server.py +18 -4
  8. experiment_server-0.3.7/experiment_server/_ui.py +917 -0
  9. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/cli.py +18 -20
  10. experiment_server-0.3.7/experiment_server/static/css/app.tcss +254 -0
  11. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/utils.py +31 -0
  12. {experiment_server-0.3.6 → experiment_server-0.3.7}/pyproject.toml +6 -4
  13. {experiment_server-0.3.6 → experiment_server-0.3.7}/LICENSE.md +0 -0
  14. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/__init__.py +0 -0
  15. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/css/bootstrap-5.2.3.min.css +0 -0
  16. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/index.html +0 -0
  17. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/initconfig.html +0 -0
  18. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/js/alpinejs3.min.js +0 -0
  19. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/js/bootstrap-4.5.0.min.js +0 -0
  20. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/js/fontawesome-1e694dd391.js +0 -0
  21. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/js/htmx-1.9.10.js +0 -0
  22. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/js/jquery-3.5.1.min.js +0 -0
  23. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/js/popper-1.16.0.min.js +0 -0
  24. {experiment_server-0.3.6 → experiment_server-0.3.7}/experiment_server/static/js/sweetalert2-11.js +0 -0
  25. {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.6
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.8
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
- assert default_participant_index > 0, "Default participant index should be >0"
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
- if default_participant_index not in self.global_state:
99
- self.global_state[default_participant_index] = ParticipantState(
100
- process_config_file(config_file, default_participant_index),
101
- default_participant_index,
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
- for participantState in self.global_state.values():
109
- config = process_config_file(self.config_file, participantState.participant_index)
110
- participantState.config = config
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.config_file, participant_index),
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.config_file, participant_index)
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}"
@@ -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
- Parameters
33
- - config (List[Dict]):
34
- A list of block configuration dictionaries. Each dict must contain a unique "name" key whose
35
- value will be coerced to a string. The returned value is a list of these configuration dicts
36
- arranged according to the computed participant order.
37
-
38
- - participant_index (int):
39
- 1-based index of the participant used to select deterministic rotations when latin-square or
40
- per-participant dictionary-based indexing is used. Must be positive.
41
-
42
- - order (Union[dict, list]):
43
- Specification of the main experimental order. Two forms are supported:
44
- * list of groups: e.g. [["A","B"], ["C","D","E"]] where each inner list is a group of block names.
45
- If a flat list of strings is provided (["A","B","C"]) it will be treated as a single group.
46
- * dict keyed by participant index (string or int keys) mapping to a group-list for that participant.
47
- When a dict is provided, the `groups_strategy` must be "as_is" (per-participant assignment by key).
48
- All block names in the resolved order must be strings and must exist in `config`.
49
-
50
- - init_block_names (Union[dict, list]):
51
- Names of blocks to prepend for the participant. Accepts a list of block names (strings) or a
52
- dictionary keyed by participant indices (1-based) to select a list for the participant. When a
53
- dict is supplied, the strategy for init blocks must be "as_is" (per-participant selection).
54
-
55
- - final_block_names (Union[dict, list]):
56
- Names of blocks to append for the participant. Accepts same forms and constraints as init_block_names.
57
-
58
- - within_groups_strategy (Union[str, None]):
59
- Strategy applied to the ordering of elements inside each group. Allowed values:
60
- * "as_is" - leave element order as provided
61
- * "randomize" - shuffle elements inside each group (non-deterministic)
62
- * "latin_square" - apply a balanced Latin square to permute positions across participants
63
- If None, defaults to "as_is". If `order` was provided as a list-of-lists and `groups_strategy`
64
- was set to "randomize" or "latin_square" and not explicitly set for `within_groups_strategy`,
65
- the `groups_strategy` may be reused for within-groups behavior in some calling patterns.
66
-
67
- - groups_strategy (Union[str, None]):
68
- Strategy applied to the sequence of groups. Allowed values:
69
- * "as_is"
70
- * "randomize"
71
- * "latin_square"
72
- If None, defaults to "as_is". When `order` is a dict keyed by participant index, `groups_strategy`
73
- must be "as_is" because the dict already selects per-participant grouping.
74
-
75
- - init_blocks_strategy (Union[str, None]):
76
- Strategy for ordering initial blocks. Allowed values:
77
- * "as_is"
78
- * "randomize"
79
- If None, defaults to "as_is". When init_block_names is a dict keyed by participant index,
80
- the strategy must be "as_is" (per-participant selection).
81
-
82
- - final_blocks_strategy (Union[str, None]):
83
- Strategy for ordering final blocks. Same semantics and allowed values as init_blocks_strategy.
84
-
85
- Returns
86
- - List:
87
- A list of block configuration dictionaries (the original dicts from `config`) in the final
88
- order constructed for the participant: [init_blocks..., main_blocks..., final_blocks...].
89
- The main_blocks portion is produced by flattening the possibly nested group structure after
90
- applying the requested group- and within-group strategies.
91
-
92
- Behavior details and constraints
93
- - Names in `order`, `init_block_names`, and `final_block_names` are validated to be strings and must
94
- match unique names in `config`. Duplicate names in `config` cause an error.
95
- - When `groups_strategy` or `within_groups_strategy` is "randomize", Python's random.shuffle is used
96
- (non-deterministic unless the caller seeds the RNG).
97
- - When "latin_square" is requested for groups or within-group ordering, a balanced Latin square
98
- generator is used and `participant_index` selects the row; latin-square requires equal-sized
99
- values where appropriate (e.g., all groups must have the same size when using within-group
100
- latin-square).
101
- - When dictionary mappings are provided for order/init/final blocks, keys are expected to be
102
- 1-based consecutive integer indices (or strings coercible to those integers). For dicts, the
103
- corresponding strategy argument must be "as_is".
104
-
105
- Exceptions
106
- - Raises ExperimentServerConfigurationExcetion on invalid configuration, such as:
107
- * duplicate block names in config
108
- * non-string block names in orders
109
- * unsupported strategy names
110
- * inconsistent group sizes for latin-square within-group ordering
111
- * improper dict key sets or types when dict-based per-participant selection is used
112
-
113
- Notes
114
- - The function does not modify the input `config` objects beyond coercing the "name" field to str.
115
- - Deterministic rotation behavior for latin-square and dict-based selection is 1-based and uses
116
- (participant_index - 1) modulo the appropriate period.
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