Developer Guide
Git Workflow
TVBO uses a standard GitHub Flow with a two-branch model:
dev— active development branchmain— stable release branch, protected by CI
Contributing
This follows standard open-source contribution practice:
- Fork or branch — external contributors fork the repo; team members create feature branches off
dev. - Create a feature branch — name it descriptively (e.g.,
feat/jax-backend,fix/import-error,docs/add-tutorial). - Develop and commit — make focused commits with clear messages.
- Open a Pull Request — PR your feature branch into
dev(for ongoing work) ordev→main(for releases). - CI runs automatically — all checks must pass.
- Code review — at least one approving review before merge.
- Merge — use squash-merge or merge commit, then delete the feature branch.
Branch naming conventions
| Prefix | Purpose | Example |
|---|---|---|
feat/ |
New feature | feat/symbolic-export |
fix/ |
Bug fix | fix/optional-imports |
docs/ |
Documentation only | docs/developer-guide |
refactor/ |
Code restructure, no behavior change | refactor/template-utils |
ci/ |
CI/CD changes | ci/update-actions-v6 |
test/ |
Adding or fixing tests | test/coupling-functions |
Typical workflow
# 1. Start from dev
git checkout dev
git pull origin dev
# 2. Create feature branch
git checkout -b feat/my-feature
# 3. Work, commit
git add -A
git commit -m "Add new feature X"
# 4. Push and open PR
git push -u origin feat/my-feature
# → Open PR on GitHub: feat/my-feature → devFor release merges (dev → main), the same PR process applies — CI gates protect main.
CI/CD Workflows
TVBO has 5 GitHub Actions workflows. Here is what each one does, when it runs, and what it produces.
1. CI (ci.yml)
Runs on: push to
mainordev, pull requests targetingmain
The core quality gate. Ensures code is correct before it reaches main.
┌─────────────────────────────────────────────────┐
│ CI Pipeline │
│ │
│ ┌──────┐ │
│ │ Lint │ flake8 syntax check (blocking) │
│ └──┬───┘ │
│ │ │
│ ├──────────────┐ │
│ ▼ ▼ │
│ ┌──────┐ ┌───────────┐ │
│ │ Test │ │ Test Docs │ │
│ └──┬───┘ └─────┬─────┘ │
│ │ pytest on │ Execute all │
│ │ Python │ .qmd notebooks │
│ │ 3.10–3.13 │ │
│ └──────┬───────┘ │
│ ▼ │
│ ┌───────────┐ │
│ │ Package │ (main branch only) │
│ └───────────┘ │
│ Build sdist + wheel → upload artifact │
│ │
│ ❌ Any failure in Lint/Test/Test Docs │
│ → PR cannot be merged │
└─────────────────────────────────────────────────┘
| Job | What it does | Blocks merge? |
|---|---|---|
| Lint | flake8 syntax errors (E9,F63,F7,F82) + style warnings (non-blocking) |
✅ Yes (syntax errors) |
| Test | pytest -q on Python 3.10, 3.11, 3.12, 3.13 (matrix) |
✅ Yes |
| Test Docs | Converts all .qmd doc pages to notebooks and executes them end-to-end |
✅ Yes |
| Package | Builds sdist + wheel, uploads as artifact (only on main push) |
N/A |
All three gate jobs must pass before a PR can be merged.
2. Docs (docs.yml)
Runs on: push to
main, manual dispatch
Publishes the Quarto documentation site to GitHub Pages.
| Step | Details |
|---|---|
| Checkout code | actions/checkout@v6 |
| Install Python + deps | pip install ".[all,docs]" + quartodoc, jupyter |
| Install Quarto | quarto-dev/quarto-actions/setup@v2 |
| Publish | quarto-dev/quarto-actions/publish@v2 → pushes to gh-pages branch |
- Uses a concurrency group (
pages) to cancel in-progress deploys if a new push arrives. - Requires
contents: writepermission to push to thegh-pagesbranch. - Can be triggered manually via the Actions tab (workflow_dispatch).
3. Docker (docker.yml)
Runs on: push to
mainordev, version tags (v*), manual dispatch
Builds multi-platform Docker images and pushes to two registries.
| Detail | Value |
|---|---|
| Platforms | linux/amd64, linux/arm64 |
| Registries | Docker Hub (leonmartin2/tvbo) + GHCR (ghcr.io/virtual-twin/tvbo) |
| Cache | GitHub Actions cache (type=gha) |
Image tags depend on the trigger:
| Trigger | Tags produced |
|---|---|
Push to main |
main, latest, <commit-sha> |
Push to dev |
dev, <commit-sha> |
Version tag (e.g., v0.5.0) |
0.5.0, <commit-sha> |
4. Publish to PyPI (publish-pypi.yml)
Runs on: GitHub Release published, manual dispatch
The full release-to-PyPI pipeline with its own test gate.
┌───────────────────────────────────────┐
│ PyPI Publish Pipeline │
│ │
│ 1. Test (Python 3.12, 3.13) │
│ └─► pytest -q │
│ │
│ 2. Build │
│ └─► python -m build │
│ └─► twine check dist/* │
│ └─► Upload artifact │
│ │
│ 3. Publish │
│ └─► Download artifact │
│ └─► pypa/gh-action-pypi-publish │
│ (OIDC Trusted Publishing) │
└───────────────────────────────────────┘
- Uses Trusted Publishing (OIDC) — no API token needed, GitHub identity is verified by PyPI directly.
- The
id-token: writepermission is required for OIDC. - Tests run again independently (not reusing CI results) to ensure the release commit is clean.
5. Release Ontology (release-ontology.yml)
Runs on: push to
main(only whentvbo/data/ontology/tvb-o.owlortvbo/__init__.pychanged)
Automatically creates a GitHub Release for the OWL ontology file when it changes.
| Step | Details |
|---|---|
| Detect change | git diff checks if tvb-o.owl was modified in the latest commit |
| Extract version | Reads __version__ from tvbo/__init__.py |
| Check existing release | Skips if ontology-v<version> release already exists |
| Generate checksum | sha256sum tvb-o.owl |
| Create release | gh release create ontology-v<version> with OWL file + checksum attached |
- Tag format:
ontology-v<version>(e.g.,ontology-v0.5.0) - Provides a persistent URL for referencing the ontology at a specific version.
- Uses
actions/checkout@v4andactions/setup-python@v5(not yet updated to v6).
What Happens When…
…you open a Pull Request to main
The CI workflow triggers. All three gate jobs (Lint, Test, Test Docs) must pass. The Package job does not run on PRs — only on merge.
…a PR is merged to main
Five things happen simultaneously:
- CI runs again (lint + test + test-docs + package build)
- Docker builds and pushes images tagged
main,latest,<sha> - Docs publishes the site to GitHub Pages
- Release Ontology creates a GitHub Release if the OWL file changed
- Nothing happens on PyPI (that requires a manual GitHub Release)
…you push to dev
Only CI (lint + test + test-docs, no package) and Docker (tagged dev, <sha>) run. Docs are not published.
…you create a GitHub Release
Publish to PyPI runs: tests → builds → publishes the package to PyPI via Trusted Publishing.
…you trigger a workflow manually
CI, Docs, Docker, and PyPI workflows all support workflow_dispatch — you can run them from the GitHub Actions tab at any time.
Release Process
- Develop on
dev(or feature branches →dev) - Open PR:
dev→main - CI validates everything (lint, tests, doc notebooks)
- Get code review approval, then merge
- Merge triggers: Docker build, docs publish, package artifact, ontology release (if OWL changed)
- To publish to PyPI: create a GitHub Release with a version tag (e.g.,
v0.5.0)- First update
__version__intvbo/__init__.py - Merge that change to
main - Create a release on GitHub → PyPI publish workflow runs automatically
- First update
Local Development
Setup
git clone https://github.com/virtual-twin/tvbo.git
cd tvbo
git checkout dev
python -m venv .venv
source .venv/bin/activate
pip install -e ".[all]"Running Tests
# All tests (excluding docs)
pytest -q
# Specific test file
pytest tests/test_model_loading.py -v
# Doc notebook tests (requires Quarto)
pytest tests/test_docs.py -v -m docs
# Functional tests only
pytest tests/functional/ -vLinting
# Blocking errors (must pass before merge)
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# Full style check (non-blocking)
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statisticsBuilding Docs Locally
cd docs
quarto preview # Live preview at localhost:4000
quarto render # Full build to docs/_site/Building the Package
pip install build
python -m build # Creates dist/tvbo-*.tar.gz and dist/tvbo-*.whl
python -m twine check dist/* # Verify package metadataProject Structure
.github/workflows/
├── ci.yml # Lint + Test + Test Docs + Package
├── docker.yml # Docker multi-platform build & push
├── docs.yml # Quarto publish to GitHub Pages
├── publish-pypi.yml # PyPI release (on GitHub Release)
└── release-ontology.yml # OWL ontology GitHub Release
Schema Changes
If you modify schema/tvbo_datamodel.yaml:
make gen-linkml # Regenerate tvbo/datamodel/Always commit the regenerated files alongside schema changes.
Validation Checklist
Before opening a PR: