|
| 1 | +# Contributing to respyra |
| 2 | + |
| 3 | +Thanks for your interest in contributing to respyra! This guide covers everything |
| 4 | +you need to get started. |
| 5 | + |
| 6 | +## Development setup |
| 7 | + |
| 8 | +respyra requires **Python 3.10** (PsychoPy does not yet support 3.11+). |
| 9 | + |
| 10 | +```bash |
| 11 | +git clone https://github.com/embodied-computation-group/respyra.git |
| 12 | +cd respyra |
| 13 | + |
| 14 | +# Create a Python 3.10 virtual environment |
| 15 | +# Windows (Python Launcher) |
| 16 | +py -3.10 -m venv .venv |
| 17 | +.venv\Scripts\activate |
| 18 | + |
| 19 | +# macOS / Linux |
| 20 | +python3.10 -m venv .venv |
| 21 | +source .venv/bin/activate |
| 22 | + |
| 23 | +# Install in editable mode with dev and vis extras |
| 24 | +pip install -e ".[dev,vis]" |
| 25 | +``` |
| 26 | + |
| 27 | +This installs the runtime dependencies plus **ruff** (linter/formatter), |
| 28 | +**pytest** and **pytest-cov** (testing), **pandas** and **matplotlib** (visualization). |
| 29 | + |
| 30 | +## Running tests |
| 31 | + |
| 32 | +The test suite lives in `tests/` and covers every module in `respyra/core/`. |
| 33 | + |
| 34 | +```bash |
| 35 | +# Run all tests |
| 36 | +pytest tests/ -v |
| 37 | + |
| 38 | +# Run with coverage report |
| 39 | +pytest tests/ -v --cov=respyra --cov-report=term-missing |
| 40 | +``` |
| 41 | + |
| 42 | +### Mock strategy |
| 43 | + |
| 44 | +PsychoPy and godirect cannot be installed in most CI environments, so |
| 45 | +`tests/conftest.py` patches them out at the `sys.modules` level before any |
| 46 | +test module imports `respyra.core.*`. The mock list includes: |
| 47 | + |
| 48 | +- `psychopy` and its subpackages (`psychopy.visual`, `psychopy.core`, etc.) |
| 49 | +- `godirect` |
| 50 | + |
| 51 | +This happens at **module level** in `conftest.py` (not inside fixtures) because |
| 52 | +Python caches imports — by the time a fixture runs, the import has already |
| 53 | +happened. Tests then create focused mocks for the specific objects they need. |
| 54 | + |
| 55 | +## Linting and formatting |
| 56 | + |
| 57 | +respyra uses [ruff](https://docs.astral.sh/ruff/) for both linting and formatting. |
| 58 | +Configuration lives in `pyproject.toml` under `[tool.ruff]`. |
| 59 | + |
| 60 | +```bash |
| 61 | +# Check for lint errors |
| 62 | +ruff check . |
| 63 | + |
| 64 | +# Auto-fix what can be fixed |
| 65 | +ruff check . --fix |
| 66 | + |
| 67 | +# Check formatting |
| 68 | +ruff format --check . |
| 69 | + |
| 70 | +# Apply formatting |
| 71 | +ruff format . |
| 72 | +``` |
| 73 | + |
| 74 | +**Enabled rule sets:** `E` `F` `W` (pyflakes/pycodestyle), `I` (isort import |
| 75 | +ordering), `UP` (pyupgrade), `B` (flake8-bugbear), `SIM` (flake8-simplify). |
| 76 | + |
| 77 | +CI runs `ruff check` and `ruff format --check` on every push and pull request — |
| 78 | +both must pass. |
| 79 | + |
| 80 | +## Code style |
| 81 | + |
| 82 | +- **Line length:** 99 characters |
| 83 | +- **Quote style:** ruff default (double quotes) |
| 84 | +- **Import ordering:** handled by `ruff check` with the `I` (isort) rule |
| 85 | +- **Target version:** Python 3.10 |
| 86 | + |
| 87 | +## Adding tests |
| 88 | + |
| 89 | +Test files go in `tests/` and follow the naming convention `test_<module>.py`. |
| 90 | +Each file tests the corresponding module in `respyra/core/`. |
| 91 | + |
| 92 | +| Test file | Module under test | |
| 93 | +|---|---| |
| 94 | +| `test_breath_belt.py` | `respyra.core.breath_belt` | |
| 95 | +| `test_data_logger.py` | `respyra.core.data_logger` | |
| 96 | +| `test_display.py` | `respyra.core.display` | |
| 97 | +| `test_events.py` | `respyra.core.events` | |
| 98 | +| `test_target_generator.py` | `respyra.core.target_generator` | |
| 99 | + |
| 100 | +### How conftest mocking works |
| 101 | + |
| 102 | +The `conftest.py` at the root of `tests/` inserts `MagicMock` objects into |
| 103 | +`sys.modules` for PsychoPy and godirect. This means: |
| 104 | + |
| 105 | +- You can `from respyra.core import <module>` freely in tests — the imports |
| 106 | + won't fail even without PsychoPy installed. |
| 107 | +- PsychoPy objects (e.g., `visual.Window`, `visual.TextStim`) are `MagicMock` |
| 108 | + instances. If your test needs specific return values, mock them explicitly |
| 109 | + in the test function. |
| 110 | +- Shared fixtures like `simple_segment` and `simple_condition` are defined in |
| 111 | + `conftest.py` and available to all test files. |
| 112 | + |
| 113 | +### What to mock |
| 114 | + |
| 115 | +- **PsychoPy objects** — already handled by conftest; add focused patches if |
| 116 | + you need specific behavior (e.g., `window.size` returning `(1920, 1080)`). |
| 117 | +- **File I/O** — use `tmp_path` (pytest built-in) for tests that write files. |
| 118 | +- **gdx / breath belt** — mock the `GdxDevice` or belt reader; never connect |
| 119 | + to real hardware in tests. |
| 120 | + |
| 121 | +## Project structure |
| 122 | + |
| 123 | +See the [README](https://github.com/embodied-computation-group/respyra#project-structure) for a full directory tree and the |
| 124 | +[documentation](https://embodied-computation-group.github.io/respyra/) for |
| 125 | +detailed API reference and user guides. |
| 126 | + |
| 127 | +## Submitting changes |
| 128 | + |
| 129 | +1. **Fork** the repository and create a feature branch from `main`. |
| 130 | +2. Make your changes, adding tests for new functionality. |
| 131 | +3. Ensure **lint** and **tests** pass locally: |
| 132 | + ```bash |
| 133 | + ruff check . |
| 134 | + ruff format --check . |
| 135 | + pytest tests/ -v |
| 136 | + ``` |
| 137 | +4. Commit with a clear, descriptive message. |
| 138 | +5. Open a **pull request** against `main`. CI will run lint and test checks |
| 139 | + automatically — both must pass before merging. |
| 140 | + |
| 141 | +## The `gdx/` directory |
| 142 | + |
| 143 | +The `respyra/core/gdx/` directory contains third-party code from |
| 144 | +[VernierST/godirect-examples](https://github.com/VernierST/godirect-examples), |
| 145 | +licensed under the **BSD 3-Clause License** (see `respyra/core/gdx/LICENSE`). |
| 146 | + |
| 147 | +This directory is: |
| 148 | +- **Excluded from ruff linting** (configured in `pyproject.toml`) |
| 149 | +- **Not covered by the test suite** |
| 150 | + |
| 151 | +Please do not modify files in `gdx/` unless there is a specific upstream |
| 152 | +compatibility issue that requires a patch. |
0 commit comments