expdeploy 0.1.0__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.
- expdeploy-0.1.0/.dockerignore +20 -0
- expdeploy-0.1.0/.github/workflows/ci.yml +47 -0
- expdeploy-0.1.0/.github/workflows/container.yml +41 -0
- expdeploy-0.1.0/.github/workflows/docs.yml +39 -0
- expdeploy-0.1.0/.github/workflows/release.yml +40 -0
- expdeploy-0.1.0/.gitignore +43 -0
- expdeploy-0.1.0/.pre-commit-config.yaml +20 -0
- expdeploy-0.1.0/CONTRIBUTING.md +58 -0
- expdeploy-0.1.0/Dockerfile +32 -0
- expdeploy-0.1.0/LICENSE +21 -0
- expdeploy-0.1.0/PKG-INFO +65 -0
- expdeploy-0.1.0/README.md +24 -0
- expdeploy-0.1.0/docs/cli-reference.md +94 -0
- expdeploy-0.1.0/docs/container.md +37 -0
- expdeploy-0.1.0/docs/getting-started.md +46 -0
- expdeploy-0.1.0/docs/index.md +23 -0
- expdeploy-0.1.0/docs/manifest.md +63 -0
- expdeploy-0.1.0/docs/storage.md +68 -0
- expdeploy-0.1.0/docs/superpowers/plans/2026-05-14-expdeploy-bootstrap.md +2824 -0
- expdeploy-0.1.0/docs/superpowers/plans/2026-05-15-expdeploy-plan-2-storage-battery.md +3113 -0
- expdeploy-0.1.0/docs/superpowers/plans/2026-05-17-expdeploy-plan-3-supabase-container-docs.md +2165 -0
- expdeploy-0.1.0/docs/superpowers/specs/2026-05-14-expdeploy-design.md +782 -0
- expdeploy-0.1.0/examples/hello_world/index.js +47 -0
- expdeploy-0.1.0/examples/hello_world/manifest.toml +16 -0
- expdeploy-0.1.0/examples/hello_world/style.css +15 -0
- expdeploy-0.1.0/examples/mini_battery/battery.toml +11 -0
- expdeploy-0.1.0/examples/mini_battery/flanker/index.js +24 -0
- expdeploy-0.1.0/examples/mini_battery/flanker/manifest.toml +11 -0
- expdeploy-0.1.0/examples/mini_battery/stroop/index.js +24 -0
- expdeploy-0.1.0/examples/mini_battery/stroop/manifest.toml +11 -0
- expdeploy-0.1.0/mkdocs.yml +50 -0
- expdeploy-0.1.0/pyproject.toml +99 -0
- expdeploy-0.1.0/scripts/fetch_jspsych_assets.py +174 -0
- expdeploy-0.1.0/src/expdeploy/__init__.py +3 -0
- expdeploy-0.1.0/src/expdeploy/__main__.py +6 -0
- expdeploy-0.1.0/src/expdeploy/app.py +256 -0
- expdeploy-0.1.0/src/expdeploy/battery/__init__.py +0 -0
- expdeploy-0.1.0/src/expdeploy/battery/counterbalance.py +86 -0
- expdeploy-0.1.0/src/expdeploy/battery/orchestrator.py +48 -0
- expdeploy-0.1.0/src/expdeploy/cli.py +580 -0
- expdeploy-0.1.0/src/expdeploy/importmap.py +76 -0
- expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/jspsych.css +524 -0
- expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/jspsych.js +5535 -0
- expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/manifest.json +53 -0
- expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/call-function.js +69 -0
- expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/fullscreen.js +164 -0
- expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/html-button-response.js +223 -0
- expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/html-keyboard-response.js +183 -0
- expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/image-button-response.js +326 -0
- expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/image-keyboard-response.js +270 -0
- expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/instructions.js +341 -0
- expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/preload.js +384 -0
- expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/survey-text.js +244 -0
- expdeploy-0.1.0/src/expdeploy/jspsych_assets/__init__.py +1 -0
- expdeploy-0.1.0/src/expdeploy/loader.py +44 -0
- expdeploy-0.1.0/src/expdeploy/manifest.py +127 -0
- expdeploy-0.1.0/src/expdeploy/renderer.py +37 -0
- expdeploy-0.1.0/src/expdeploy/session.py +96 -0
- expdeploy-0.1.0/src/expdeploy/storage/__init__.py +1 -0
- expdeploy-0.1.0/src/expdeploy/storage/base.py +57 -0
- expdeploy-0.1.0/src/expdeploy/storage/fs.py +193 -0
- expdeploy-0.1.0/src/expdeploy/storage/sqlite.py +171 -0
- expdeploy-0.1.0/src/expdeploy/storage/supabase.py +104 -0
- expdeploy-0.1.0/src/expdeploy/storage/supabase_schema.sql +26 -0
- expdeploy-0.1.0/src/expdeploy/templates/deploy.html.j2 +46 -0
- expdeploy-0.1.0/tests/__init__.py +0 -0
- expdeploy-0.1.0/tests/conftest.py +1 -0
- expdeploy-0.1.0/tests/e2e/__init__.py +0 -0
- expdeploy-0.1.0/tests/e2e/test_hello_world.py +91 -0
- expdeploy-0.1.0/tests/e2e/test_mini_battery.py +94 -0
- expdeploy-0.1.0/tests/integration/__init__.py +0 -0
- expdeploy-0.1.0/tests/integration/test_app.py +304 -0
- expdeploy-0.1.0/tests/unit/__init__.py +0 -0
- expdeploy-0.1.0/tests/unit/test_battery_manifest.py +57 -0
- expdeploy-0.1.0/tests/unit/test_battery_orchestrator.py +68 -0
- expdeploy-0.1.0/tests/unit/test_cli.py +349 -0
- expdeploy-0.1.0/tests/unit/test_counterbalance.py +93 -0
- expdeploy-0.1.0/tests/unit/test_fs.py +209 -0
- expdeploy-0.1.0/tests/unit/test_importmap.py +98 -0
- expdeploy-0.1.0/tests/unit/test_loader.py +52 -0
- expdeploy-0.1.0/tests/unit/test_manifest.py +102 -0
- expdeploy-0.1.0/tests/unit/test_renderer.py +82 -0
- expdeploy-0.1.0/tests/unit/test_sanity.py +9 -0
- expdeploy-0.1.0/tests/unit/test_session.py +70 -0
- expdeploy-0.1.0/tests/unit/test_sqlite.py +105 -0
- expdeploy-0.1.0/tests/unit/test_supabase.py +106 -0
- expdeploy-0.1.0/tests/unit/test_sync.py +91 -0
- expdeploy-0.1.0/uv.lock +2413 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.git/
|
|
2
|
+
.venv/
|
|
3
|
+
.uv/
|
|
4
|
+
.mypy_cache/
|
|
5
|
+
.ruff_cache/
|
|
6
|
+
.pytest_cache/
|
|
7
|
+
**/__pycache__/
|
|
8
|
+
**/*.pyc
|
|
9
|
+
data/
|
|
10
|
+
state/
|
|
11
|
+
tests/
|
|
12
|
+
docs/
|
|
13
|
+
examples/
|
|
14
|
+
.github/
|
|
15
|
+
**/*.md
|
|
16
|
+
!README.md
|
|
17
|
+
.gitignore
|
|
18
|
+
.pre-commit-config.yaml
|
|
19
|
+
playwright-report/
|
|
20
|
+
test-results/
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ${{ matrix.os }}
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
os: [ubuntu-latest, macos-latest]
|
|
16
|
+
python: ["3.11", "3.12"]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v3
|
|
23
|
+
with:
|
|
24
|
+
version: "0.5.x"
|
|
25
|
+
|
|
26
|
+
- name: Set up Python ${{ matrix.python }}
|
|
27
|
+
run: uv python install ${{ matrix.python }}
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: uv sync --extra dev
|
|
31
|
+
|
|
32
|
+
- name: Install Playwright browsers
|
|
33
|
+
run: uv run playwright install --with-deps chromium
|
|
34
|
+
|
|
35
|
+
- name: Lint (ruff)
|
|
36
|
+
run: |
|
|
37
|
+
uv run ruff check .
|
|
38
|
+
uv run ruff format --check .
|
|
39
|
+
|
|
40
|
+
- name: Type check (mypy)
|
|
41
|
+
run: uv run mypy src/expdeploy
|
|
42
|
+
|
|
43
|
+
- name: Unit + integration tests
|
|
44
|
+
run: uv run pytest tests/unit tests/integration -v
|
|
45
|
+
|
|
46
|
+
- name: End-to-end (Playwright)
|
|
47
|
+
run: uv run pytest tests/e2e -v -m e2e
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Container
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ['v*']
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
packages: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: docker/setup-qemu-action@v3
|
|
18
|
+
- uses: docker/setup-buildx-action@v3
|
|
19
|
+
- uses: docker/login-action@v3
|
|
20
|
+
with:
|
|
21
|
+
registry: ghcr.io
|
|
22
|
+
username: ${{ github.actor }}
|
|
23
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
24
|
+
- id: meta
|
|
25
|
+
uses: docker/metadata-action@v5
|
|
26
|
+
with:
|
|
27
|
+
images: ghcr.io/${{ github.repository_owner }}/expdeploy
|
|
28
|
+
tags: |
|
|
29
|
+
type=ref,event=tag
|
|
30
|
+
type=semver,pattern={{version}}
|
|
31
|
+
type=semver,pattern={{major}}.{{minor}}
|
|
32
|
+
type=raw,value=latest
|
|
33
|
+
- uses: docker/build-push-action@v6
|
|
34
|
+
with:
|
|
35
|
+
context: .
|
|
36
|
+
platforms: linux/amd64,linux/arm64
|
|
37
|
+
push: true
|
|
38
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
39
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
40
|
+
cache-from: type=gha
|
|
41
|
+
cache-to: type=gha,mode=max
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
pages: write
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: "pages"
|
|
15
|
+
cancel-in-progress: false
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- uses: astral-sh/setup-uv@v3
|
|
23
|
+
with:
|
|
24
|
+
version: "0.5.x"
|
|
25
|
+
- run: uv python install 3.12
|
|
26
|
+
- run: uv sync --extra docs
|
|
27
|
+
- run: uv run mkdocs build --strict
|
|
28
|
+
- uses: actions/upload-pages-artifact@v3
|
|
29
|
+
with:
|
|
30
|
+
path: site
|
|
31
|
+
deploy:
|
|
32
|
+
needs: build
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
environment:
|
|
35
|
+
name: github-pages
|
|
36
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
37
|
+
steps:
|
|
38
|
+
- id: deployment
|
|
39
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ['v*']
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: astral-sh/setup-uv@v3
|
|
18
|
+
with:
|
|
19
|
+
version: "0.5.x"
|
|
20
|
+
- run: uv python install 3.12
|
|
21
|
+
- run: uv sync --extra dev
|
|
22
|
+
- run: uv build
|
|
23
|
+
- uses: actions/upload-artifact@v4
|
|
24
|
+
with:
|
|
25
|
+
name: dist
|
|
26
|
+
path: dist/
|
|
27
|
+
publish:
|
|
28
|
+
needs: build
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
environment:
|
|
31
|
+
name: pypi
|
|
32
|
+
url: https://pypi.org/p/expdeploy
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/download-artifact@v4
|
|
35
|
+
with:
|
|
36
|
+
name: dist
|
|
37
|
+
path: dist/
|
|
38
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
39
|
+
with:
|
|
40
|
+
packages-dir: dist/
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
.eggs/
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
.venv/
|
|
10
|
+
.python-version
|
|
11
|
+
|
|
12
|
+
# uv
|
|
13
|
+
.uv/
|
|
14
|
+
|
|
15
|
+
# pytest / coverage
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
.coverage
|
|
18
|
+
htmlcov/
|
|
19
|
+
.tox/
|
|
20
|
+
|
|
21
|
+
# mypy / ruff
|
|
22
|
+
.mypy_cache/
|
|
23
|
+
.ruff_cache/
|
|
24
|
+
|
|
25
|
+
# Playwright
|
|
26
|
+
test-results/
|
|
27
|
+
playwright-report/
|
|
28
|
+
playwright/.cache/
|
|
29
|
+
|
|
30
|
+
# data dirs from local runs
|
|
31
|
+
data/
|
|
32
|
+
state/
|
|
33
|
+
|
|
34
|
+
# editor / OS
|
|
35
|
+
.DS_Store
|
|
36
|
+
.idea/
|
|
37
|
+
.vscode/
|
|
38
|
+
|
|
39
|
+
# expdeploy-specific
|
|
40
|
+
sessions_*/
|
|
41
|
+
|
|
42
|
+
# mkdocs build output
|
|
43
|
+
site/
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.3.4
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
|
|
9
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
10
|
+
rev: v1.9.0
|
|
11
|
+
hooks:
|
|
12
|
+
- id: mypy
|
|
13
|
+
files: ^src/
|
|
14
|
+
additional_dependencies:
|
|
15
|
+
- fastapi>=0.110
|
|
16
|
+
- pydantic>=2.6
|
|
17
|
+
- typer>=0.12
|
|
18
|
+
- jinja2>=3.1
|
|
19
|
+
- filelock>=3.13
|
|
20
|
+
- types-toml
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Contributing to expdeploy
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in contributing.
|
|
4
|
+
|
|
5
|
+
## Development setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/lobennett/expdeploy.git
|
|
9
|
+
cd expdeploy
|
|
10
|
+
uv sync --extra dev
|
|
11
|
+
uv run pre-commit install
|
|
12
|
+
uv run playwright install --with-deps chromium
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Branches
|
|
16
|
+
|
|
17
|
+
- `main` is the integration branch; releases are tagged from `main` (`v0.X.Y`).
|
|
18
|
+
- Feature work lands on `feat/<short-name>` branches and merges via PR.
|
|
19
|
+
- Each PR runs the full CI matrix (Ubuntu+macOS × Python 3.11+3.12).
|
|
20
|
+
|
|
21
|
+
## Commit messages
|
|
22
|
+
|
|
23
|
+
Short imperative subject lines, ≤72 characters. Examples:
|
|
24
|
+
- `Add SupabaseAdapter.save (Postgres upsert + storage bucket upload)`
|
|
25
|
+
- `Drop dead tomli conditional dep (we require Python 3.11+)`
|
|
26
|
+
|
|
27
|
+
## Tests
|
|
28
|
+
|
|
29
|
+
- TDD is the default workflow. Write a failing test before the implementation.
|
|
30
|
+
- Three tiers: `tests/unit/`, `tests/integration/`, `tests/e2e/` (Playwright).
|
|
31
|
+
- New storage adapters must satisfy the parametrized adapter contract suite (TBD).
|
|
32
|
+
- E2E tests are marked `@pytest.mark.e2e`. Run with `uv run pytest -m e2e`.
|
|
33
|
+
|
|
34
|
+
## Lint + format + types
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uv run ruff check .
|
|
38
|
+
uv run ruff format --check .
|
|
39
|
+
uv run mypy src/expdeploy
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`pre-commit` runs all three on every commit. If a hook reformats your code, re-stage and commit again.
|
|
43
|
+
|
|
44
|
+
## jsPsych asset re-vendoring
|
|
45
|
+
|
|
46
|
+
If you bump the jsPsych version in `scripts/fetch_jspsych_assets.py`, re-run the script:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uv run python scripts/fetch_jspsych_assets.py
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Commit the regenerated `src/expdeploy/jspsych_assets/<version>/` directory.
|
|
53
|
+
|
|
54
|
+
## Releasing
|
|
55
|
+
|
|
56
|
+
1. Bump `version` in `pyproject.toml` and `__version__` in `src/expdeploy/__init__.py`.
|
|
57
|
+
2. Open a PR titled `Release v0.X.Y`.
|
|
58
|
+
3. After merge, tag `v0.X.Y`. CI publishes the multi-arch container to GHCR and the wheel to PyPI.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1
|
|
2
|
+
FROM python:3.12-slim AS runtime
|
|
3
|
+
|
|
4
|
+
# Install uv from its official image
|
|
5
|
+
COPY --from=ghcr.io/astral-sh/uv:0.5 /uv /usr/local/bin/uv
|
|
6
|
+
|
|
7
|
+
WORKDIR /opt/expdeploy
|
|
8
|
+
|
|
9
|
+
# Install Python deps first (cache layer)
|
|
10
|
+
COPY pyproject.toml uv.lock README.md ./
|
|
11
|
+
COPY src/ ./src/
|
|
12
|
+
|
|
13
|
+
RUN uv sync --frozen --no-dev --extra supabase \
|
|
14
|
+
&& rm -rf /root/.cache/uv
|
|
15
|
+
|
|
16
|
+
# Non-root for HPC / Apptainer compatibility
|
|
17
|
+
RUN useradd -m -u 1000 expdeploy
|
|
18
|
+
USER expdeploy
|
|
19
|
+
|
|
20
|
+
ENV PYTHONUNBUFFERED=1 \
|
|
21
|
+
EXPDEPLOY_DATA_DIR=/data \
|
|
22
|
+
EXPDEPLOY_HOST=0.0.0.0
|
|
23
|
+
|
|
24
|
+
EXPOSE 8080
|
|
25
|
+
VOLUME ["/data", "/experiments"]
|
|
26
|
+
|
|
27
|
+
ENTRYPOINT ["uv", "run", "--no-sync", "expdeploy"]
|
|
28
|
+
CMD ["--help"]
|
|
29
|
+
|
|
30
|
+
LABEL org.opencontainers.image.title="expdeploy" \
|
|
31
|
+
org.opencontainers.image.source="https://github.com/lobennett/expdeploy" \
|
|
32
|
+
org.opencontainers.image.licenses="MIT"
|
expdeploy-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Logan Bennett
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
expdeploy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: expdeploy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Modern Python deploy tool for jsPsych v8 experiments
|
|
5
|
+
Author-email: Logan Bennett <logben@stanford.edu>
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: bids,cognitive-science,experiment,fmri,jspsych,psychology
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Requires-Dist: fastapi>=0.110
|
|
17
|
+
Requires-Dist: filelock>=3.13
|
|
18
|
+
Requires-Dist: jinja2>=3.1
|
|
19
|
+
Requires-Dist: pydantic>=2.6
|
|
20
|
+
Requires-Dist: python-ulid>=2.7
|
|
21
|
+
Requires-Dist: rich>=13.7
|
|
22
|
+
Requires-Dist: typer>=0.12
|
|
23
|
+
Requires-Dist: uvicorn[standard]>=0.27
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
26
|
+
Requires-Dist: hypothesis>=6.98; extra == 'dev'
|
|
27
|
+
Requires-Dist: mypy>=1.9; extra == 'dev'
|
|
28
|
+
Requires-Dist: playwright>=1.42; extra == 'dev'
|
|
29
|
+
Requires-Dist: pre-commit>=3.6; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-cov>=4.1; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff>=0.3; extra == 'dev'
|
|
34
|
+
Provides-Extra: docs
|
|
35
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
36
|
+
Requires-Dist: mkdocs-typer>=0.0.3; extra == 'docs'
|
|
37
|
+
Requires-Dist: mkdocs>=1.6; extra == 'docs'
|
|
38
|
+
Provides-Extra: supabase
|
|
39
|
+
Requires-Dist: supabase>=2.4; extra == 'supabase'
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# expdeploy
|
|
43
|
+
|
|
44
|
+
A modern Python deploy tool for [jsPsych v8](https://www.jspsych.org/) experiments.
|
|
45
|
+
Pays homage to [expfactory](https://github.com/expfactory) and expands its scope:
|
|
46
|
+
canonical jsPsych ESM authoring, BIDS-compliant data layout, batteries with
|
|
47
|
+
counterbalancing, and reproducibility via OCI containers.
|
|
48
|
+
|
|
49
|
+
**Status:** v0.1-alpha. Under active development; not yet stable.
|
|
50
|
+
|
|
51
|
+
## Quick start
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
uv tool install expdeploy
|
|
55
|
+
expdeploy run ./examples/hello_world --subject 01 --port 8080
|
|
56
|
+
# opens http://localhost:8080
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Documentation
|
|
60
|
+
|
|
61
|
+
See `docs/superpowers/specs/2026-05-14-expdeploy-design.md` for the v0.1 design spec.
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# expdeploy
|
|
2
|
+
|
|
3
|
+
A modern Python deploy tool for [jsPsych v8](https://www.jspsych.org/) experiments.
|
|
4
|
+
Pays homage to [expfactory](https://github.com/expfactory) and expands its scope:
|
|
5
|
+
canonical jsPsych ESM authoring, BIDS-compliant data layout, batteries with
|
|
6
|
+
counterbalancing, and reproducibility via OCI containers.
|
|
7
|
+
|
|
8
|
+
**Status:** v0.1-alpha. Under active development; not yet stable.
|
|
9
|
+
|
|
10
|
+
## Quick start
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
uv tool install expdeploy
|
|
14
|
+
expdeploy run ./examples/hello_world --subject 01 --port 8080
|
|
15
|
+
# opens http://localhost:8080
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Documentation
|
|
19
|
+
|
|
20
|
+
See `docs/superpowers/specs/2026-05-14-expdeploy-design.md` for the v0.1 design spec.
|
|
21
|
+
|
|
22
|
+
## License
|
|
23
|
+
|
|
24
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# CLI reference
|
|
2
|
+
|
|
3
|
+
!!! note "Auto-generation not available"
|
|
4
|
+
`mkdocs-typer` is installed but did not expand the directive with the current Typer version.
|
|
5
|
+
Run `expdeploy --help` (or `expdeploy <cmd> --help`) to see full command documentation.
|
|
6
|
+
|
|
7
|
+
## Global options
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
expdeploy [OPTIONS] COMMAND [ARGS]...
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
### `run`
|
|
16
|
+
|
|
17
|
+
Serve a single experiment or battery.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
expdeploy run PATH [OPTIONS]
|
|
21
|
+
--subject TEXT Subject ID (required)
|
|
22
|
+
--session TEXT Session number
|
|
23
|
+
--run TEXT Run number
|
|
24
|
+
--port INTEGER Port to listen on [default: 8080]
|
|
25
|
+
--data-dir PATH Data output directory [default: ./data]
|
|
26
|
+
--no-browser Don't open browser automatically
|
|
27
|
+
--remote TEXT Remote adapter names to mirror writes to (e.g. supabase)
|
|
28
|
+
--vars TEXT JSON string of extra variables injected into window.expdeploy.vars
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### `validate`
|
|
32
|
+
|
|
33
|
+
Validate a manifest.toml or battery.toml without running.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
expdeploy validate PATH
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### `status`
|
|
40
|
+
|
|
41
|
+
List runs recorded in the local catalog.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
expdeploy status [OPTIONS]
|
|
45
|
+
--data-dir PATH Data directory [default: ./data]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### `sync`
|
|
49
|
+
|
|
50
|
+
Replay failed remote-storage writes against the configured adapter.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
expdeploy sync [OPTIONS]
|
|
54
|
+
--adapter TEXT Remote adapter name [default: supabase]
|
|
55
|
+
--dry-run List pending writes without executing
|
|
56
|
+
--data-dir PATH Data directory [default: ./data]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### `build`
|
|
60
|
+
|
|
61
|
+
Build a study-specific OCI image with experiments baked in.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
expdeploy build TARGET [OPTIONS]
|
|
65
|
+
--tag TEXT OCI image tag (required)
|
|
66
|
+
--base-tag TEXT Base image tag to FROM [default: ghcr.io/lobennett/expdeploy:latest]
|
|
67
|
+
--engine TEXT docker | podman [default: docker]
|
|
68
|
+
--push / --no-push Push after build [default: no-push]
|
|
69
|
+
--output PATH Where to write study.Dockerfile
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### `supabase migrate`
|
|
73
|
+
|
|
74
|
+
Apply idempotent DDL to the configured Supabase Postgres.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
expdeploy supabase migrate
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `supabase test-connection`
|
|
81
|
+
|
|
82
|
+
Verify the configured Supabase credentials and bucket access.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
expdeploy supabase test-connection
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### `supabase drop`
|
|
89
|
+
|
|
90
|
+
DROP the expdeploy schema. Test environments only.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
expdeploy supabase drop --confirm
|
|
94
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Container
|
|
2
|
+
|
|
3
|
+
The OCI image lives at `ghcr.io/lobennett/expdeploy:<version>` (multi-arch: linux/amd64 + linux/arm64).
|
|
4
|
+
|
|
5
|
+
## Day-to-day dev (bind-mount)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
podman run --rm -p 8080:8080 \
|
|
9
|
+
-v $PWD/experiments:/experiments:ro \
|
|
10
|
+
-v $PWD/data:/data \
|
|
11
|
+
ghcr.io/lobennett/expdeploy:latest \
|
|
12
|
+
run /experiments/flanker --subject 01 --data-dir /data
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Study image (reproducible scientific artifact)
|
|
16
|
+
|
|
17
|
+
`expdeploy build` produces an image with experiments baked in. This is the image you cite in your paper.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
expdeploy build ./battery.toml \
|
|
21
|
+
--tag ghcr.io/your-lab/study-2026:2026-05-17 \
|
|
22
|
+
--engine podman \
|
|
23
|
+
--push
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The generated `study.Dockerfile` is written next to the battery file and is gitable for transparency. The image carries OCI labels:
|
|
27
|
+
|
|
28
|
+
- `org.expdeploy.manifest_hash` — content hash of every experiment file + battery.toml
|
|
29
|
+
- `org.expdeploy.deploy_version` — `expdeploy` version that built the image
|
|
30
|
+
|
|
31
|
+
## Apptainer / Singularity
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
apptainer pull docker://ghcr.io/lobennett/expdeploy:latest
|
|
35
|
+
apptainer run --bind ./experiments:/experiments --bind ./data:/data \
|
|
36
|
+
expdeploy.sif run /experiments/battery.toml --subject 01
|
|
37
|
+
```
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
The package will be available on PyPI after the v0.1.0 release:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv tool install expdeploy
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
For development:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
git clone https://github.com/lobennett/expdeploy.git
|
|
15
|
+
cd expdeploy
|
|
16
|
+
uv sync --extra dev
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Run the hello-world
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
expdeploy run ./examples/hello_world --subject 01 --port 8080
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This opens `http://localhost:8080` in your browser. Press any key on the stimulus and you'll see a `Saved.` confirmation. The raw JSON lands at `./data/raw/sub-01/sub-01_task-hello_beh.json`; an entry appears in `./data/catalog.sqlite`.
|
|
26
|
+
|
|
27
|
+
## Inspect runs
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
expdeploy status --data-dir ./data
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Run a battery
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
expdeploy run ./examples/mini_battery/battery.toml --subject 0
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or inline:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
expdeploy run \
|
|
43
|
+
--exps ./flanker,./stroop,./nback \
|
|
44
|
+
--counterbalance latin_square \
|
|
45
|
+
--subject 01
|
|
46
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# expdeploy
|
|
2
|
+
|
|
3
|
+
A modern Python deploy tool for [jsPsych v8](https://www.jspsych.org/) experiments.
|
|
4
|
+
|
|
5
|
+
**Status: v0.1.0** — lab-ready.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
- Serves jsPsych v8 experiments locally with **zero Node.js dependency** for experimenters.
|
|
10
|
+
- Authoring in canonical ESM (`import { initJsPsych } from 'jspsych'`) with local imports working out of the box.
|
|
11
|
+
- **BIDS-compatible** filesystem layout for fMRI and behavioral data.
|
|
12
|
+
- **Batteries** of experiments with four counterbalance schemes (fixed, Latin square, seeded random, user-supplied).
|
|
13
|
+
- **Local-first** storage with optional Supabase mirror, replayable via `expdeploy sync`.
|
|
14
|
+
- **Reproducibility**: layered OCI images via `expdeploy build` — a study image freezes deploy version, jsPsych version, every experiment file, and every Python dep.
|
|
15
|
+
|
|
16
|
+
## Quick install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv tool install expdeploy
|
|
20
|
+
expdeploy run ./examples/hello_world --subject 01
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
→ [Getting started](getting-started.md)
|