PsyCloud

Python SDK (psycloudpy)

psycloudpy lets you author a complete study in Python with a fluent, chainable builder. It compiles to the same bundle format Studio produces, so a code-authored study runs on the same runtime and can be opened in Studio for further editing.

Pre-release install

The SDKs ship inside the PsyCloud monorepo and are under active development (psycloudpy is 0.0.1, not yet on PyPI). Clone the repo and install in editable mode:

pip install -e packages/psycloudpy

A complete example

This builds a minimal Stroop study end-to-end — factors, a screen, a keypress response — and writes a runnable bundle to disk:

stroop.py
from psycloudpy import experiment, text, keypress
from psycloudpy.expr import col
 
bundle = (
    experiment(id="hello.stroop", name="Hello Stroop")
    .phase("main", label="Trials")
    .factors(word=["red", "blue"], ink=["red", "blue"])
    .cross()
    .shuffle()
    .screen("trial")
    .present(text("+"), 250)
    .ask(
        text(col.word, fill=col.ink, font_size=72),
        keypress(keys=["r", "b"]),
    )
    .bind_self("word", "ink")
    .done()
    .bundle(with_auto_ids=True)
)
 
from psycloudpy import pc_bundle_write
 
pc_bundle_write(bundle, "stroop_bundle", overwrite=True)

That stroop_bundle/ directory can be served, compiled, or imported into Studio — see the CLI reference and Interoperability.

The fluent chain

Authoring flows through three chained builders. Each .method() returns the next builder, so a study reads top to bottom:

  1. experiment(…) → project

    Start a study. experiment(id, name) returns the project builder. Project-level helpers include .hypothesis(), .within_subjects(), .between_subjects(), .measures(), .instructions_page(), and .end_page(), plus flow control (.run(), .repeat_block(), .cycle_block(), .branch_block(), .loop_until()).

  2. .phase(…) → trial generation

    .phase(id, label) opens a phase. Build its trial table with .factors(), .cross(), .shuffle(), .repeat(), .sample(), .per_block(), .derive(), .filter(), .constrain(), .assign_between(), or load rows directly with .trials(data).

  3. .screen(…) → what participants see

    .screen(id) opens the trial template. Sequence it with .present(stimulus, duration_ms), .ask(stimulus, response), .feedback(...), and .pause(...). Choose which columns to record with .bind_self(...) / .record(...).

  4. .done().bundle(…) → compile

    .done() closes the screen back to the project; .bundle(with_auto_ids=True) validates and produces the bundle. (.bundle() on the screen is shorthand for .done().bundle().)

Stimulus builders include text, image, fixation_cross, blank, markdown, group, and slider; response builders include keypress, slider, button_response, prompt, likert_scale, multichoice, image_choice, and anykey.

Dynamic values: the expression DSL

Static values are fine for fixed stimuli, but most studies need values that depend on the trial, the response, or running state. Import these from psycloudpy.expr:

from psycloudpy.expr import col, row, state, outcome, const
from psycloudpy.expr import expr_if, case_when, uppercase
ReferenceMeans
col.wordthe current trial's word value at run time
row.wordthe design-time row value (resolved before run time)
outcome.correct, outcome.rt, outcome.timedOutresponse results (after a response)
state.streaka phase-scoped counter you maintain
const.max_rta study-level constant set at participant entry

Operators work naturally (==, <, +, &, |, ~), and helpers like expr_if, case_when, uppercase, lowercase, coalesce, and one_of cover common logic. For example, conditional feedback:

.feedback(
    text(case_when(
        (outcome.correct, "Correct!"),
        (outcome.timedOut, "Too slow"),
        default="Incorrect",
    )),
    timeout(1000),
)
col vs row

Use col.x for values resolved at run time (the participant's actual trial) and row.x for values computed at design time while the trial table is being built. They look similar but resolve at different moments.

From bundle to running study

from psycloudpy import write_bundle, check, simulate_tables
 
# Write the bundle directory (validates as it writes)
write_bundle(bundle, dir="./stroop_bundle", overwrite=True)
 
# Compile + lint without leaving Python
report = check("./stroop_bundle")
 
# Dry-run participant sessions to sanity-check the design
tables = simulate_tables("./stroop_bundle", n_participants=10)

A bundle directory contains bundle.json, designProgram.json, screenProgram.json, bindings.json, and (when used) counterbalancePolicy.json. From there you can serve or compile it with the CLI, or import it into Studio.

Publishing needs the backend

write_bundle, check, and simulate_tables run entirely offline. Pushing a study to a hosted server (pc_push / pc_push_from_path) requires server credentials (PSYCLOUD_SERVER_URL, PSYCLOUD_API_KEY).

Worked examples

The package ships runnable example studies under packages/psycloudpy/src/psycloudpy/examples/high_level/ — including color_stroop, simple_rt, recognition_memory, continuous_recognition, panas_survey, matrix_reasoning, adaptive_practice, luck_vogel_1997, and a design-first memory study. The docs/tutorials/ folder maps nine jsPsych paradigms to psycloudpy.

Next