experiment_server 0.3.4__tar.gz → 0.3.5__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 (24) hide show
  1. {experiment_server-0.3.4 → experiment_server-0.3.5}/PKG-INFO +71 -20
  2. {experiment_server-0.3.4 → experiment_server-0.3.5}/README.md +67 -17
  3. {experiment_server-0.3.4 → experiment_server-0.3.5}/experiment_server/_api.py +86 -42
  4. {experiment_server-0.3.4 → experiment_server-0.3.5}/experiment_server/_client.py +28 -0
  5. experiment_server-0.3.5/experiment_server/_participant_ordering.py +229 -0
  6. {experiment_server-0.3.4 → experiment_server-0.3.5}/experiment_server/_process_config.py +134 -118
  7. {experiment_server-0.3.4 → experiment_server-0.3.5}/pyproject.toml +2 -4
  8. {experiment_server-0.3.4 → experiment_server-0.3.5}/sample_config.toml +50 -8
  9. experiment_server-0.3.4/experiment_server/_participant_ordering.py +0 -126
  10. {experiment_server-0.3.4 → experiment_server-0.3.5}/LICENSE.md +0 -0
  11. {experiment_server-0.3.4 → experiment_server-0.3.5}/experiment_server/__init__.py +0 -0
  12. {experiment_server-0.3.4 → experiment_server-0.3.5}/experiment_server/_server.py +0 -0
  13. {experiment_server-0.3.4 → experiment_server-0.3.5}/experiment_server/cli.py +0 -0
  14. {experiment_server-0.3.4 → experiment_server-0.3.5}/experiment_server/static/css/bootstrap-5.2.3.min.css +0 -0
  15. {experiment_server-0.3.4 → experiment_server-0.3.5}/experiment_server/static/index.html +0 -0
  16. {experiment_server-0.3.4 → experiment_server-0.3.5}/experiment_server/static/initconfig.html +0 -0
  17. {experiment_server-0.3.4 → experiment_server-0.3.5}/experiment_server/static/js/alpinejs3.min.js +0 -0
  18. {experiment_server-0.3.4 → experiment_server-0.3.5}/experiment_server/static/js/bootstrap-4.5.0.min.js +0 -0
  19. {experiment_server-0.3.4 → experiment_server-0.3.5}/experiment_server/static/js/fontawesome-1e694dd391.js +0 -0
  20. {experiment_server-0.3.4 → experiment_server-0.3.5}/experiment_server/static/js/htmx-1.9.10.js +0 -0
  21. {experiment_server-0.3.4 → experiment_server-0.3.5}/experiment_server/static/js/jquery-3.5.1.min.js +0 -0
  22. {experiment_server-0.3.4 → experiment_server-0.3.5}/experiment_server/static/js/popper-1.16.0.min.js +0 -0
  23. {experiment_server-0.3.4 → experiment_server-0.3.5}/experiment_server/static/js/sweetalert2-11.js +0 -0
  24. {experiment_server-0.3.4 → experiment_server-0.3.5}/experiment_server/utils.py +0 -0
@@ -1,8 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: experiment_server
3
- Version: 0.3.4
3
+ Version: 0.3.5
4
4
  Summary: Server for experiments to get configuarations from
5
- Home-page: https://shariff-faleel.com/experiment_server/
6
5
  License: MIT
7
6
  Keywords: experiment,study-design
8
7
  Author: Ahmed Shariff
@@ -19,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.9
19
18
  Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
22
  Requires-Dist: Jinja2 (>=3.1.2)
23
23
  Requires-Dist: asyncio (>=3.4.3)
24
24
  Requires-Dist: click (>=6.0)
@@ -33,6 +33,7 @@ Requires-Dist: toml (>=0.10.2)
33
33
  Requires-Dist: tornado (>=6.2)
34
34
  Requires-Dist: watchdog (>=2.1.9)
35
35
  Project-URL: Documentation, https://shariff-faleel.com/experiment_server/documentation/
36
+ Project-URL: Homepage, https://shariff-faleel.com/experiment_server/
36
37
  Project-URL: Repository, https://github.com/ahmed-shariff/experiment_server
37
38
  Description-Content-Type: text/markdown
38
39
 
@@ -85,18 +86,41 @@ See example `.toml` below for how the configuration can be defined.
85
86
  # The `configuration` table contains the settings of the study/experiment itself
86
87
  [configuration]
87
88
  # The `order` is an array of block names or an array of array of block names.
89
+ # The `order` also can be a dictionary/table. If it is a dictionary/table, the keys should
90
+ # be participant indices starting from 1. The values should be a list of block names.
91
+ # The keys are the participant indices - when experiment manager is queries with a given
92
+ # participant index, the dictionary/table will be used as a lookup table and the
93
+ # corresponding list of blocks will be used. When a participant index larger than the
94
+ # highest participant index is queries through experiment manager, the modulus of the
95
+ # index will be used to determine which list of conditions should be used.
96
+ # In the following example order with a table/dictionary, participant index 4 would get
97
+ # the blocks assigned to participant index 1:
98
+ # order = {1= ["conditionA", "conditionB", "conditionA", "conditionB"], 2= ["conditionB", "conditionA", "conditionB", "conditionA"], 3= ["conditionB", "conditionB", "conditionA", "conditionA"]}
88
99
  order = [["conditionA", "conditionB", "conditionA", "conditionB"]]
89
- # The `groups` and `within_groups` are optional keys that allows you to define how the
90
- # conditions specified in `order` will be managed. `groups` would dictate how the top
91
- # level array of `order` will be handled. `within_groups` would dictate how the conditions
92
- # in the nested arrays (if specified) would be managed. These keys can have one
93
- # of the following values.
100
+
101
+
102
+ # The `groups_strategy` and `within_groups_strategy` are optional keys that allows you to
103
+ # define how the conditions specified in `order` will be managed. `groups_strategy` would
104
+ # dictate how the top level array of `order` will be handled. `within_groups_strategy`
105
+ # would dictate how the conditions in the nested arrays (if specified) would be
106
+ # managed. These keys can have one of the following values.
94
107
  # - "latin_square": Apply latin square to balance the values.
95
108
  # - "randomize": For each participant randomize the order of the values in the array.
96
109
  # - "as_is": Use the order of the values as specified.
97
110
  # When not specified, the default value is "as_is" for both keys.
98
- groups = "latin_square"
99
- within_groups= "randomize"
111
+ groups_strategy = "latin_square"
112
+ within_groups_strategy = "randomize"
113
+
114
+ # The following are optional keys. `init_blocks` is a list of blocks which will be
115
+ # appended to the start and `final_blocks` are a list of blocks that would be appended to
116
+ # the end. The respective strategies dictate the ordering withing the list of init or
117
+ # final blocks. For init and final blocks only the "randomize" and "as_is" are
118
+ # supported. By default, their values are "as_is".
119
+ init_blocks = ["initialBlock"]
120
+ final_blocks = ["finalBlockA", "finalBlockB"]
121
+ init_blocks_strategy = "as_is"
122
+ final_blocks_strategy = "randomize"
123
+
100
124
  # The random seed to use for any randomization. Default seed is 0. The seed will be
101
125
  # the value of random_seed + participant_index
102
126
  random_seed = 0
@@ -146,6 +170,15 @@ See [toml spec](https://toml.io/en/v1.0.0) for more information on the format of
146
170
  The above config file, after being processed, would result in the following list of blocks for participant number 1:
147
171
  ```json
148
172
  [
173
+ {
174
+ "name": "initialBlock",
175
+ "config": {
176
+ "param1": 1,
177
+ "participant_index": 1,
178
+ "name": "initialBlock",
179
+ "block_id": 0
180
+ }
181
+ },
149
182
  {
150
183
  "name": "conditionB",
151
184
  "extends": "conditionA",
@@ -157,11 +190,11 @@ The above config file, after being processed, would result in the following list
157
190
  "param1": 1,
158
191
  "param2": [
159
192
  1,
160
- 2
193
+ 3
161
194
  ],
162
195
  "participant_index": 1,
163
196
  "name": "conditionB",
164
- "block_id": 0
197
+ "block_id": 1
165
198
  }
166
199
  },
167
200
  {
@@ -174,11 +207,11 @@ The above config file, after being processed, would result in the following list
174
207
  2
175
208
  ],
176
209
  "param3": [
177
- 3
210
+ 1
178
211
  ],
179
212
  "participant_index": 1,
180
213
  "name": "conditionA",
181
- "block_id": 1
214
+ "block_id": 2
182
215
  }
183
216
  },
184
217
  {
@@ -187,15 +220,15 @@ The above config file, after being processed, would result in the following list
187
220
  "trialsPerItem": 3,
188
221
  "param1": 1,
189
222
  "param2": [
190
- 1,
191
- 1
223
+ 2,
224
+ 3
192
225
  ],
193
226
  "param3": [
194
227
  2
195
228
  ],
196
229
  "participant_index": 1,
197
230
  "name": "conditionA",
198
- "block_id": 2
231
+ "block_id": 3
199
232
  }
200
233
  },
201
234
  {
@@ -208,12 +241,30 @@ The above config file, after being processed, would result in the following list
208
241
  "trialsPerItem": 3,
209
242
  "param1": 1,
210
243
  "param2": [
211
- 3,
212
- 1
244
+ 2,
245
+ 3
213
246
  ],
214
247
  "participant_index": 1,
215
248
  "name": "conditionB",
216
- "block_id": 3
249
+ "block_id": 4
250
+ }
251
+ },
252
+ {
253
+ "name": "finalBlockA",
254
+ "config": {
255
+ "param1": 1,
256
+ "participant_index": 1,
257
+ "name": "finalBlockA",
258
+ "block_id": 5
259
+ }
260
+ },
261
+ {
262
+ "name": "finalBlockB",
263
+ "config": {
264
+ "param1": 1,
265
+ "participant_index": 1,
266
+ "name": "finalBlockB",
267
+ "block_id": 6
217
268
  }
218
269
  }
219
270
  ]
@@ -47,18 +47,41 @@ See example `.toml` below for how the configuration can be defined.
47
47
  # The `configuration` table contains the settings of the study/experiment itself
48
48
  [configuration]
49
49
  # The `order` is an array of block names or an array of array of block names.
50
+ # The `order` also can be a dictionary/table. If it is a dictionary/table, the keys should
51
+ # be participant indices starting from 1. The values should be a list of block names.
52
+ # The keys are the participant indices - when experiment manager is queries with a given
53
+ # participant index, the dictionary/table will be used as a lookup table and the
54
+ # corresponding list of blocks will be used. When a participant index larger than the
55
+ # highest participant index is queries through experiment manager, the modulus of the
56
+ # index will be used to determine which list of conditions should be used.
57
+ # In the following example order with a table/dictionary, participant index 4 would get
58
+ # the blocks assigned to participant index 1:
59
+ # order = {1= ["conditionA", "conditionB", "conditionA", "conditionB"], 2= ["conditionB", "conditionA", "conditionB", "conditionA"], 3= ["conditionB", "conditionB", "conditionA", "conditionA"]}
50
60
  order = [["conditionA", "conditionB", "conditionA", "conditionB"]]
51
- # The `groups` and `within_groups` are optional keys that allows you to define how the
52
- # conditions specified in `order` will be managed. `groups` would dictate how the top
53
- # level array of `order` will be handled. `within_groups` would dictate how the conditions
54
- # in the nested arrays (if specified) would be managed. These keys can have one
55
- # of the following values.
61
+
62
+
63
+ # The `groups_strategy` and `within_groups_strategy` are optional keys that allows you to
64
+ # define how the conditions specified in `order` will be managed. `groups_strategy` would
65
+ # dictate how the top level array of `order` will be handled. `within_groups_strategy`
66
+ # would dictate how the conditions in the nested arrays (if specified) would be
67
+ # managed. These keys can have one of the following values.
56
68
  # - "latin_square": Apply latin square to balance the values.
57
69
  # - "randomize": For each participant randomize the order of the values in the array.
58
70
  # - "as_is": Use the order of the values as specified.
59
71
  # When not specified, the default value is "as_is" for both keys.
60
- groups = "latin_square"
61
- within_groups= "randomize"
72
+ groups_strategy = "latin_square"
73
+ within_groups_strategy = "randomize"
74
+
75
+ # The following are optional keys. `init_blocks` is a list of blocks which will be
76
+ # appended to the start and `final_blocks` are a list of blocks that would be appended to
77
+ # the end. The respective strategies dictate the ordering withing the list of init or
78
+ # final blocks. For init and final blocks only the "randomize" and "as_is" are
79
+ # supported. By default, their values are "as_is".
80
+ init_blocks = ["initialBlock"]
81
+ final_blocks = ["finalBlockA", "finalBlockB"]
82
+ init_blocks_strategy = "as_is"
83
+ final_blocks_strategy = "randomize"
84
+
62
85
  # The random seed to use for any randomization. Default seed is 0. The seed will be
63
86
  # the value of random_seed + participant_index
64
87
  random_seed = 0
@@ -108,6 +131,15 @@ See [toml spec](https://toml.io/en/v1.0.0) for more information on the format of
108
131
  The above config file, after being processed, would result in the following list of blocks for participant number 1:
109
132
  ```json
110
133
  [
134
+ {
135
+ "name": "initialBlock",
136
+ "config": {
137
+ "param1": 1,
138
+ "participant_index": 1,
139
+ "name": "initialBlock",
140
+ "block_id": 0
141
+ }
142
+ },
111
143
  {
112
144
  "name": "conditionB",
113
145
  "extends": "conditionA",
@@ -119,11 +151,11 @@ The above config file, after being processed, would result in the following list
119
151
  "param1": 1,
120
152
  "param2": [
121
153
  1,
122
- 2
154
+ 3
123
155
  ],
124
156
  "participant_index": 1,
125
157
  "name": "conditionB",
126
- "block_id": 0
158
+ "block_id": 1
127
159
  }
128
160
  },
129
161
  {
@@ -136,11 +168,11 @@ The above config file, after being processed, would result in the following list
136
168
  2
137
169
  ],
138
170
  "param3": [
139
- 3
171
+ 1
140
172
  ],
141
173
  "participant_index": 1,
142
174
  "name": "conditionA",
143
- "block_id": 1
175
+ "block_id": 2
144
176
  }
145
177
  },
146
178
  {
@@ -149,15 +181,15 @@ The above config file, after being processed, would result in the following list
149
181
  "trialsPerItem": 3,
150
182
  "param1": 1,
151
183
  "param2": [
152
- 1,
153
- 1
184
+ 2,
185
+ 3
154
186
  ],
155
187
  "param3": [
156
188
  2
157
189
  ],
158
190
  "participant_index": 1,
159
191
  "name": "conditionA",
160
- "block_id": 2
192
+ "block_id": 3
161
193
  }
162
194
  },
163
195
  {
@@ -170,12 +202,30 @@ The above config file, after being processed, would result in the following list
170
202
  "trialsPerItem": 3,
171
203
  "param1": 1,
172
204
  "param2": [
173
- 3,
174
- 1
205
+ 2,
206
+ 3
175
207
  ],
176
208
  "participant_index": 1,
177
209
  "name": "conditionB",
178
- "block_id": 3
210
+ "block_id": 4
211
+ }
212
+ },
213
+ {
214
+ "name": "finalBlockA",
215
+ "config": {
216
+ "param1": 1,
217
+ "participant_index": 1,
218
+ "name": "finalBlockA",
219
+ "block_id": 5
220
+ }
221
+ },
222
+ {
223
+ "name": "finalBlockB",
224
+ "config": {
225
+ "param1": 1,
226
+ "participant_index": 1,
227
+ "name": "finalBlockB",
228
+ "block_id": 6
179
229
  }
180
230
  }
181
231
  ]
@@ -11,6 +11,14 @@ from experiment_server.utils import ExperimentServerExcetion, FileModifiedWatche
11
11
 
12
12
  class ParticipantState:
13
13
  def __init__(self, config, participant_index, active):
14
+ """
15
+ Track a single participant's progress through an experiment configuration.
16
+
17
+ Args:
18
+ config (List[Dict[str, Any]]): Ordered list of block dictionaries for this participant.
19
+ participant_index (int): 1-based participant index.
20
+ active (bool): True if participant is currently between START and END (inclusive of blocks).
21
+ """
14
22
  self.participant_index = participant_index
15
23
  self._block_id: int = -1
16
24
  self.config = config
@@ -18,10 +26,19 @@ class ParticipantState:
18
26
 
19
27
  @property
20
28
  def block_id(self) -> int:
29
+ """Current zero-based index of the active block, -1 if before START, len(config) if after END."""
21
30
  return self._block_id
22
31
 
23
32
  @block_id.setter
24
33
  def block_id(self, block_id: int):
34
+ """
35
+ Set the current block index and update the active flag.
36
+
37
+ If block_id < 0 -> mark as not active (before start).
38
+ If block_id >= len(config) -> mark as not active (after end).
39
+ Otherwise active = True.
40
+ An assertion ensures consistency between block_id and the block's own 'config.block_id'.
41
+ """
25
42
  if block_id < 0:
26
43
  self._block_id = -1
27
44
  self.active = False
@@ -36,30 +53,42 @@ class ParticipantState:
36
53
 
37
54
  @property
38
55
  def block(self) -> Dict[str, Any]|None:
56
+ """Return the current block dict or None if before START or after END."""
39
57
  if self.block_id >= len(self.config) or self.block_id < 0:
40
58
  return None
41
- else:
42
- return self.config[self.block_id]
59
+ return self.config[self.block_id]
43
60
 
44
61
  @property
45
62
  def block_name(self) -> str:
63
+ """Human-readable block position: 'START', 'END' or the current block name."""
46
64
  if self.block_id >= len(self.config):
47
65
  return "END"
48
- elif self.block_id < 0:
66
+ if self.block_id < 0:
49
67
  return "START"
50
68
  return self.config[self.block_id]["name"]
51
69
 
52
70
  def move_to_next_block(self) -> str:
71
+ """Advance to the next block and return its block_name."""
53
72
  self.block_id += 1
54
73
  return self.block_name
55
74
 
56
- def status_string(self):
57
- return f'Participant index: {self.participant_index} \nBlock: {self._block_id} / {len(self.config)} \n Name: {self.block["name"] if self.block is not None else "N/A"}'
75
+ def status_string(self) -> str:
76
+ """Single-line status summary suitable for logs or simple UIs."""
77
+ name = self.block["name"] if self.block is not None else "N/A"
78
+ return f'Participant index: {self.participant_index} \nBlock: {self._block_id} / {len(self.config)} \n Name: {name}'
58
79
 
59
80
 
60
81
  class Experiment:
61
- """Load and manage experiemnt from a local file."""
62
- def __init__(self, config_file:str, default_participant_index:int=1) -> None:
82
+ """Load and manage an experiment configuration file and participant states."""
83
+
84
+ def __init__(self, config_file: str, default_participant_index: int = 1) -> None:
85
+ """
86
+ Initialize experiment state and watch the configuration file for changes.
87
+
88
+ Args:
89
+ config_file (str): Path to the TOML configuration file.
90
+ default_participant_index (int): Default 1-based index used when none is provided.
91
+ """
63
92
  assert default_participant_index > 0, "Default participant index should be >0"
64
93
  self.global_state: Dict[int, ParticipantState] = {}
65
94
  self.default_participant_index = default_participant_index
@@ -67,90 +96,99 @@ class Experiment:
67
96
  self.config_file = config_file
68
97
 
69
98
  if default_participant_index not in self.global_state:
70
- self.global_state[default_participant_index] = ParticipantState(process_config_file(config_file, default_participant_index),
71
- default_participant_index,
72
- False)
99
+ self.global_state[default_participant_index] = ParticipantState(
100
+ process_config_file(config_file, default_participant_index),
101
+ default_participant_index,
102
+ False,
103
+ )
73
104
 
74
105
  def _config_file_modified_callback(self):
106
+ """Reload configurations for all known participants when the file changes."""
75
107
  logger.info("Reloading config")
76
108
  for participantState in self.global_state.values():
77
109
  config = process_config_file(self.config_file, participantState.participant_index)
78
110
  participantState.config = config
79
111
 
80
112
  def get_next_participant(self) -> int:
81
- """Return the index of the next participant."""
113
+ """Allocate and return the next participant index (max existing + 1)."""
82
114
  new_participant_index = max(self.global_state.keys()) + 1
83
115
  self.add_participant_index(new_participant_index)
84
116
  return new_participant_index
85
117
 
86
118
  def add_participant_index(self, participant_index) -> bool:
87
- """Add a participant with `participant_index`.
88
- If `participant_index` already exists, returns False, else return True"""
119
+ """
120
+ Add a participant by index.
121
+
122
+ Returns True if added, False if the index already exists.
123
+ """
89
124
  if participant_index in self.global_state:
90
125
  return False
91
- self.global_state[participant_index] = ParticipantState(process_config_file(self.config_file, participant_index),
92
- participant_index,
93
- False)
126
+ self.global_state[participant_index] = ParticipantState(
127
+ process_config_file(self.config_file, participant_index),
128
+ participant_index,
129
+ False,
130
+ )
94
131
  return True
95
132
 
96
133
  def get_participant_state(self, participant_index) -> ParticipantState:
134
+ """
135
+ Return the ParticipantState for the given index.
136
+
137
+ If participant_index is None, uses the default. Raises ExperimentServerExcetion if not present.
138
+ """
97
139
  if participant_index is None:
98
140
  participant_index = self.default_participant_index
99
141
  if participant_index not in self.global_state:
100
142
  raise ExperimentServerExcetion(f"participant with index {participant_index} is not set. Consider using `add_participant_index`")
101
143
  return self.global_state[participant_index]
102
144
 
103
- def get_state(self, participant_index:int|None=None):
145
+ def get_state(self, participant_index:int|None=None) -> bool:
146
+ """Return whether the participant is active."""
104
147
  if participant_index is None:
105
148
  participant_index = self.default_participant_index
106
149
  return self.global_state[participant_index].active
107
150
 
108
151
  def move_to_next(self, participant_index:int|None=None) -> str:
109
- """Moves the pointer to the current block to the next block for `participant_index`.
110
- if `participant_index` is None, seld.default_participant_index is used.
111
- """
152
+ """Advance the participant to the next block and return the new block_name."""
112
153
  if participant_index is None:
113
154
  participant_index = self.default_participant_index
114
155
  return self.global_state[participant_index].move_to_next_block()
115
156
 
116
157
  def get_config(self, participant_index:int|None=None) -> Union[Dict[str, Any], None]:
117
- """Return the config of the current block for `participant_index`.
118
- if `participant_index` is None, seld.default_participant_index is used.
119
- If the experiment has not started (`move_to_next` has not
120
- been called atleast once), this will return `None`."""
158
+ """
159
+ Return the current block's config for the participant, or None if experiment not started or finished.
160
+ """
121
161
  if participant_index is None:
122
162
  participant_index = self.default_participant_index
123
163
  block = self.global_state[participant_index].block
124
164
  if block is None:
125
165
  return None
126
- else:
127
- return block["config"]
166
+ return block["config"]
128
167
 
129
168
  def reset_participant(self, participant_index:int|None=None) -> bool:
130
- """Reset the participant's config to that was loaded from the file."""
169
+ """Reload the participant's configuration from file and replace their stored config."""
131
170
  if participant_index is None:
132
171
  participant_index = self.default_participant_index
133
172
  self.global_state[participant_index].config = process_config_file(self.config_file, participant_index)
134
173
  return True
135
174
 
136
175
  def get_blocks_count(self, participant_index:int|None=None) -> int:
137
- """Return the total number of blocks."""
176
+ """Return the number of blocks for the participant."""
138
177
  if participant_index is None:
139
178
  participant_index = self.default_participant_index
140
179
  return len(self.global_state[participant_index].config)
141
180
 
142
181
  def get_all_configs(self, participant_index:int|None=None) -> List[dict]:
143
- """Return all configs in order for `participant_index`.
144
- if `participant_index` is None, seld.default_participant_index is used.
145
- """
182
+ """Return the list of all block 'config' dicts for the participant in order."""
146
183
  if participant_index is None:
147
184
  participant_index = self.default_participant_index
148
185
  return [c["config"] for c in self.global_state[participant_index].config]
149
186
 
150
187
  def move_to_block(self, block_id: int, participant_index:int|None=None) -> str:
151
- """For `participant_index` move the pointer of the current
152
- block to the block in index a `block_id` in the list of
153
- blocks. If `participant_index` is None, seld.default_participant_index is used.
188
+ """
189
+ Move the participant pointer to a specific block index and return its block_name.
190
+
191
+ block_id must be an integer.
154
192
  """
155
193
  assert isinstance(block_id, int), "`block` should be an int"
156
194
  if participant_index is None:
@@ -159,9 +197,10 @@ class Experiment:
159
197
  return self.global_state[participant_index].block_name
160
198
 
161
199
  def move_all_to_block(self, block_id: int) -> str:
162
- """For active participants move the pointer of the current
163
- block to the block in index a `block_id` in the list of
164
- blocks.
200
+ """
201
+ Move every participant's pointer to `block_id`.
202
+
203
+ Returns the block_name of the first participant after the move.
165
204
  """
166
205
  assert isinstance(block_id, int), "`block` should be an int"
167
206
  for participantState in self.global_state.values():
@@ -169,12 +208,17 @@ class Experiment:
169
208
  return list(self.global_state.values())[0].block_name
170
209
 
171
210
 
172
- def _generate_config_json(config_file: Union[str, Path], participant_indices:Iterable[int], out_dir: Union[str, Path, None] = None) -> None:
211
+ def _generate_config_json(config_file: Union[str, Path], participant_indices: Iterable[int], out_dir: Union[str, Path, None]=None) -> None:
173
212
  """
174
- Write out the json config from the `config_file` for participants in `participant_indices`
175
- in the `out_dir`. The `out_dir` should be a directory or should be None.
176
- If it is None, it will write the config's, one line per participant. If the passed directory
177
- does not exist, it will be created.
213
+ Emit the resolved JSON configuration for the given participant indices.
214
+
215
+ If out_dir is provided, writes one file per participant named "<config_stem>-participant_<i>.json".
216
+ If out_dir is None, writes each participant's JSON to stdout (one line per participant).
217
+
218
+ Args:
219
+ config_file: Path to the TOML configuration file.
220
+ participant_indices: Iterable of 1-based participant indices to generate.
221
+ out_dir: Optional directory to write files into. Created if missing.
178
222
  """
179
223
  if out_dir is not None:
180
224
  out_dir = Path(out_dir)
@@ -6,6 +6,34 @@ from experiment_server.utils import ExperimentServerExcetion
6
6
 
7
7
 
8
8
  class Client:
9
+ """
10
+ HTTP client for the Experiment Server API.
11
+
12
+ Usage:
13
+ - Instantiate with optional server_host and server_port.
14
+ - Methods operate on a per-participant basis when applicable (provide
15
+ participant_index or omit to use the server's default participant).
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
+ Return value for API methods
22
+ - All public API methods return a tuple (success: bool, data: dict|str).
23
+ On success data is the parsed JSON response (or "" if empty). On failure
24
+ success is False and data contains an error message.
25
+
26
+ Main methods
27
+ - move_to_next(participant_index=None)
28
+ - get_config(participant_index=None)
29
+ - server_is_active(participant_index=None)
30
+ - get_blocks_count(participant_index=None)
31
+ - get_all_configs(participant_index=None)
32
+ - move_to_block(block_id, participant_index=None)
33
+ - new_participant()
34
+ - add_participant(participant_index)
35
+ - shutdown()
36
+ """
9
37
  def __init__(self, server_host:str ="127.0.0.1", server_port:Union[str, int]="5000") -> None:
10
38
  self._server_url = f"http://{server_host}:{server_port}"
11
39