Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
602eb64
refactor: consume psyexp-core for shared experiment infrastructure
ericwang401 Jun 23, 2026
3607815
build: lock psyexp-core to git tag v0.5.0
ericwang401 Jun 23, 2026
739ef28
fix(ci): keep trial hot-loop reads PsychoPy-free; derive psyexp-core …
ericwang401 Jun 23, 2026
7974c03
test(parity): run the MATLAB/Octave reference in a single engine launch
ericwang401 Jun 23, 2026
d4e4c3b
ci: bump astral-sh/setup-uv to v7 to fix Node 20 deprecation
ericwang401 Jun 23, 2026
220c297
ci: bump checkout to v5 and action-gh-release to v3 off Node 20
ericwang401 Jun 23, 2026
fcaf7d9
build: consume psyexp-core from PyPI (0.7.0) instead of git tag
ericwang401 Jun 26, 2026
abce876
feat: prompt operator for display, recording the chosen monitor
ericwang401 Jun 26, 2026
a9e0b3a
chore: cap Python compatibility to match PsychoPy
ericwang401 Jun 26, 2026
fd2c8c9
docs: correct psyexp-core editable-overlay guidance, add justfile
ericwang401 Jun 29, 2026
301a677
refactor(manifest): rename `session_time` to `session_started_at`
ericwang401 Jun 29, 2026
5e1a410
refactor!: rename `session_time` to `session_started_at`
ericwang401 Jun 29, 2026
b79a5d4
refactor!: rename `session_time` to `session_started_at`
ericwang401 Jun 29, 2026
71ff9fb
refactor: standardize keymaps, fix ratings todos, consume core keyboa…
ericwang401 Jun 30, 2026
9623715
refactor: guaranteed teardown via ExitStack in __main__
ericwang401 Jun 30, 2026
7dd0c9a
docs: mark local ExitStack teardown as implemented in both tasks (§6b)
ericwang401 Jun 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

# Guard the release: the tag, pyproject.toml version, and CHANGELOG.md must
# agree. Fail loudly rather than publish a mislabeled release.
Expand Down Expand Up @@ -48,7 +48,7 @@ jobs:
# `uv lock --check` fails if the lock is out of date (e.g. the version was
# bumped in pyproject.toml but uv.lock was never re-locked to match).
- name: Verify uv.lock is in sync with pyproject.toml
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v7
- name: uv lock --check
run: uv lock --check

Expand Down Expand Up @@ -79,7 +79,7 @@ jobs:
echo "file=$notes_file" >> "$GITHUB_OUTPUT"

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
with:
name: ${{ github.ref_name }}
body_path: ${{ steps.notes.outputs.file }}
Expand Down
23 changes: 17 additions & 6 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
pytest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

# Octave is the reference engine for the calibration MATLAB-parity test
# (tests/test_calibration_matlab_parity.py). Without it that test skips
Expand All @@ -21,18 +21,29 @@ jobs:
octave --version | head -1

- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v7

# PsychoPy is deliberately NOT installed: it's a heavy GUI/audio dep whose
# transitive build (ffpyplayer → SDL headers) is fragile on headless CI.
# The source modules guard their psychopy imports (try/except), so the
# whole test suite imports and runs without it; only pandas is needed at
# runtime (sequences.py). We install just that + pytest and put `src` on
# PYTHONPATH rather than resolving the project's full dependency graph.
# whole test suite imports and runs without it; pandas (sequences.py) and
# pydantic (psyexp-core's manifest models, since core 0.7.0) are needed at
# runtime. We install just those + pytest and put `src` on PYTHONPATH
# rather than resolving the project's full dependency graph.
#
# psyexp-core is installed --no-deps for the same reason: the light modules
# the tests pull in (recording / manifest / rundir / diagnostics / keyboard)
# are import-clean without PsychoPy, so --no-deps avoids dragging the GUI
# stack back in (pydantic, installed above, is its one non-GUI runtime dep
# the tests touch). Its pinned version is read from uv.lock via `uv export`
# rather than hardcoding it here — the lockfile stays the single source of
# truth.
- name: Create venv and install minimal test deps
run: |
uv venv
uv pip install pytest pandas
uv pip install pytest pandas pydantic
uv export --frozen --no-hashes --no-emit-project | grep '^psyexp-core==' > psyexp-core-req.txt
uv pip install --no-deps -r psyexp-core-req.txt

# tests/test_overlay.py is excluded: it's not an automated test (no test
# functions) but a manual `visual.Window` script that needs a live display
Expand Down
42 changes: 41 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,44 @@ engine the test skips with a loud warning rather than silently passing.
and its PyPI dependencies from `pyproject.toml` (`pip install -e .`). The UV-only sections
(`[tool.uv.*]`) and `uv.lock` are silently ignored by pip/conda, so no `pyproject.toml` changes are
needed to support conda — conda just resolves dependencies fresh from PyPI instead of from the
lockfile.
lockfile.

## Shared harness: psyexp-core

The generic experiment plumbing — fullscreen window + VSYNC/frame-timing setup, timestamped run
directories, the `CsvWriter` base, run-manifest writing, setup-wizard primitives, the instruction
pager, and the keyboard abstraction — lives in the shared [`psyexp-core`](https://github.com/HAPNlab/psyexp-core)
package. This repo keeps only MID-specific logic (trial loop, reward rule, adaptive staircase,
sequences, ratings survey, legacy MATLAB CSV) and consumes the harness as a dependency.

`psyexp-core` is a **published PyPI package**, declared in `[project].dependencies` as
`psyexp-core>=X.Y`; the exact version is pinned in `uv.lock` so both uv and pip/conda resolve the
same core, and every run's `manifest.json` records the resolved `psyexp_core_version`.

To **co-develop** the core against this task, overlay an editable sibling checkout. The catch:
`uv.lock` is authoritative, so *any* `uv sync` reverts the overlay back to the locked PyPI version —
`--inexact` does **not** help (it only spares packages absent from the lock, and the core is in it).
The one thing that preserves the overlay is skipping the sync:

```sh
uv pip install -e ../psyexp-core
uv run --no-sync pytest # or: export UV_NO_SYNC=1 for the shell
```

The `just` recipes wrap this: `just core-dev` overlays the editable checkout, then `just core-run` /
`just core-test` run with `--no-sync`; `just core-release` drops it (plain `uv sync`). Don't run a
bare `uv sync`/`uv run` while overlaying — it silently reverts the editable core. For a setup that
survives sync, declare the path source in `pyproject.toml`
(`[tool.uv.sources] psyexp-core = { path = "../psyexp-core", editable = true }`) and keep that edit
local with `git update-index --skip-worktree pyproject.toml uv.lock` so it never lands in a commit.

**Updating to a new `psyexp-core` release:** a bare `uv sync` won't move it — it installs exactly
what `uv.lock` pins. Re-resolve the lock, then apply it (or run `just core-upgrade`):

```sh
uv lock --upgrade-package psyexp-core # rewrite uv.lock to the newest version the constraint allows
uv sync
```

Commit the updated `uv.lock` (and bump the `>=` floor in `pyproject.toml` first if you want to
require a new minimum).
Loading