Local Development¶
The dev stack runs the full StreamWeave service suite in Docker with hot reload, a local HTTPS hostname, pre-seeded test data, and Mailpit for email testing.
Prerequisites¶
- Docker Desktop (or Docker Engine + Compose plugin)
uv— Python package manager (install)mkcert— local TLS certificate tool (install)
One-Time Host Setup¶
1. Add the dev hostname to /etc/hosts¶
The dev stack is served at https://streamweave.local. Because .local mDNS only resolves real LAN devices (not loopback), you need a manual hosts entry:
# check to see if there's already a line for streamweave in the
# hosts file, and add one if not
grep -qF '127.0.0.1 streamweave.local' /etc/hosts || echo '127.0.0.1 streamweave.local' | sudo tee -a /etc/hosts
macOS: add an IPv6 entry too
On macOS, .local domains are handled by mDNS (Bonjour). Without an
IPv6 entry, macOS issues a parallel AAAA mDNS query that times out
after ~5 seconds on every first connection, making the app feel slow.
Add this line to avoid it:
# check to see if there's already a line for streamweave in the
# hosts file, and add one if not
if (-not (Select-String -Path "$env:SystemRoot\System32\drivers\etc\hosts" -Pattern '127\.0\.0\.1 streamweave\.local' -Quiet)) { Add-Content -Path "$env:SystemRoot\System32\drivers\etc\hosts" -Value "127.0.0.1 streamweave.local" }
2. Generate a local TLS certificate with mkcert¶
mkcert creates a locally-trusted dev CA and signs a certificate for streamweave.local. It installs the CA into your OS and browser trust stores automatically, so no manual certificate import is needed.
Install mkcert, then run the helper script:
# macOS
brew install mkcert
# Linux — see https://github.com/FiloSottile/mkcert#linux for your distro
The script runs mkcert -install (trusts the CA), writes
caddy/certs/streamweave.local.crt / .key for Caddy to use, and
copies the mkcert root CA to caddy/certs/rootCA.pem for use by
Python httpx clients.
Install mkcert via Chocolatey or Scoop, then run the commands manually:
Restart your browser once after the first mkcert -install.
Starting the Dev Stack¶
On first boot, the dev-seed container automatically seeds the database with sample instruments, storage locations, schedules, and hooks. Re-running is safe — existing records are skipped.
Services¶
| Service | URL | Description |
|---|---|---|
| StreamWeave UI | https://streamweave.local |
React frontend (Vite hot reload via Caddy) |
| Prefect UI | https://streamweave.local/prefect/ |
Orchestration dashboard (admin-only) |
| Mailpit | https://streamweave.local/mail/ |
SMTP catch-all — inspect all outgoing emails |
| S3-dev | https://streamweave.local/s3/ |
S3-compatible storage (access key: devkey, secret: devsecret) |
| API docs | https://streamweave.local/docs |
Swagger UI |
| API ReDoc | https://streamweave.local/redoc |
ReDoc API reference |
The CIFS instrument simulators (NMR, HPLC, MS, TEM) are available internally on the Docker network for the worker to harvest from.
Architecture¶
graph LR
Browser(["🌐 Browser"])
subgraph proxy["Reverse proxy"]
Caddy["<b>caddy</b><br/>Caddy<br/>:443 / :80<br/><i>streamweave.local</i>"]
end
subgraph app["Application tier"]
API["<b>api</b><br/>FastAPI<br/>:8000"]
Worker["<b>worker</b><br/>Prefect worker"]
Frontend["<b>frontend</b><br/>Vite dev server<br/>:3000"]
Prefect["<b>prefect-server</b><br/>Prefect 3<br/>:4200"]
end
subgraph data["Data tier"]
PG[("postgres<br/>PostgreSQL 16<br/>:5432")]
PrefectPG[("prefect-postgres<br/>PostgreSQL 16")]
Redis[("redis<br/>Redis 7<br/>:6379")]
end
subgraph sources["Instrument sources (CIFS)"]
Init["<b>instruments-init</b><br/>busybox<br/><i>copies sample data</i>"]
Samba["<b>samba-instruments</b><br/>Samba<br/>shares: nmr, hplc, ms, tem"]
end
subgraph destination["Archive destinations"]
Archive["<b>samba-archive</b><br/>Samba<br/>share: archive"]
S3["<b>s3-dev</b><br/>rclone S3<br/>:9000"]
end
subgraph initc["Init containers (run once)"]
Seed["<b>dev-seed</b><br/>seeds DB + storage"]
end
Browser -->|"HTTPS :443"| Caddy
Caddy -->|"/api/*"| API
Caddy -->|"/prefect/*"| Prefect
Caddy -->|"everything else"| Frontend
Frontend -->|"API calls"| API
API --> PG
Worker --> PG
API --> Prefect
Worker --> Prefect
Prefect --> PrefectPG
Prefect --> Redis
Samba -->|"CIFS harvest"| Worker
Worker -->|"CIFS transfer"| Archive
Worker -->|"S3 transfer"| S3
Init -->|"copies sample data"| Samba
Seed --> API
style app fill:#e0e7ff,stroke:#6366f1,color:#1e1b4b
style sources fill:#fef3c7,stroke:#d97706,color:#451a03
style destination fill:#fef3c7,stroke:#d97706,color:#451a03
Logging In¶
Demo mode shortcuts¶
The dev stack runs with VITE_DEMO_MODE=true, which adds one-click login buttons on the login page for the admin account, and a few example user
accounts in different groups.
| Role | Password | |
|---|---|---|
| Admin | admin@example.com |
adminpassword |
| Regular user | chemist@example.com |
devpass123! |
| Regular user | proteomics@example.com |
devpass123! |
| Regular user | em-operator@example.com |
devpass123! |
You can override the default admin credentials with environment variables before starting:
export ADMIN_EMAIL=me@example.com
export ADMIN_PASSWORD=mypassword
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
TORTURE_MODE: Stress-Testing with Large Datasets¶
TORTURE_MODE pre-loads the database with a large, realistic dataset on startup. Use it when you need to stress-test pagination, UI performance, or query latency with hundreds of instruments and hundreds of thousands of files.
Activation¶
Add TORTURE_MODE=true to your .env file (or to the backend service's environment block in docker-compose.dev.yml):
Then start (or restart) the backend. The seeder runs automatically during the FastAPI lifespan startup hook.
What gets seeded¶
| Entity | Count (defaults) |
|---|---|
| Storage location | 1 — "TORTURE Archive" |
| Service accounts | 4 — one per instrument type |
| Instruments | 200 — 50 each of NMR, HPLC, MS, TEM |
| Harvest schedules | 200 — one per instrument |
| File records | ~200 000 — ~1 000 per instrument |
All instruments are named TORTURE-{TYPE}-{NNN} (e.g. TORTURE-NMR-001). File paths follow realistic directory trees for each instrument type:
| Type | Path pattern |
|---|---|
| NMR (Bruker) | /{year}/{mm}/{dd}/{expno:04d}/{fname} |
| HPLC (Waters) | /{year}/{mm}/{set_name}/{fname} |
| MS (Thermo) | /{year}/batch_{batch:04d}/{fname} |
| TEM (FEI) | /{year}/{n:04d}_{session_date}/{fname} |
Idempotency¶
The seeder is safe to run repeatedly. If any instrument whose name starts with TORTURE- already exists, the seeder prints a skip message and returns without inserting anything.
Removing torture data¶
The seeder has no built-in teardown. To remove torture data, either drop and recreate the database, or run a targeted delete:
# Connect to the running backend container and open a Python shell
docker compose exec backend uv run python - <<'EOF'
import asyncio
from sqlalchemy import delete, select
from app.database import async_session_factory
from app.models.instrument import Instrument
async def main():
async with async_session_factory() as session:
result = await session.execute(
select(Instrument.id).where(Instrument.name.like("TORTURE-%"))
)
ids = result.scalars().all()
print(f"Deleting {len(ids)} TORTURE instruments…")
await session.execute(
delete(Instrument).where(Instrument.name.like("TORTURE-%"))
)
await session.commit()
print("Done.")
asyncio.run(main())
EOF
File records are not cascade-deleted
The FileRecord rows reference instruments via a foreign key but have no ON DELETE CASCADE. If you need a clean slate, it is simpler to drop and recreate the dev database than to manually delete in dependency order.
Hot Reload¶
- Frontend: Vite serves
frontend/src/directly; changes reload the browser instantly. - Backend:
uvicorn --reloadwatchesbackend/app/; changes restart the API server.
Running Tests¶
Both the frotend and backend test suites use in-process mocks and do not require the Docker stack to be running. These scripts will also report coverage. It is expected that test coverage will always be at 100%.
Backend:
Frontend:
Linting¶
The following scripts will check the codebase for code style issues and report any errors. Running these prior to commits will help prevent CI issues:
Pre-commit config¶
StreamWeave uses prek (a Rust-based pre-commit runner) with a standard .pre-commit-config.yaml file. The hooks run automatically on git commit, or you can invoke them manually:
# Run all hooks against staged files
prek run
# Run a specific hook
prek run ruff
prek run eslint
# Install the git hook (one-time, per clone)
prek install
The configured hooks are:
| Hook | Scope | What it does |
|---|---|---|
ruff |
backend/ |
Lint Python with auto-fix |
ruff-format |
backend/ |
Format Python |
ty |
backend/ |
Type-check Python with uv run ty check |
prettier |
frontend/src/ |
Format TypeScript/TSX |
eslint |
frontend/src/ |
Lint TypeScript/TSX |
tsc |
frontend/ |
Type-check TypeScript (--noEmit) |