Development¶
Repository layout¶
src/nuboard/ Python package (the name lives here and in pyproject.toml)
__init__.py init, load, Run, Artifact
_meta.py NAME, FORMAT_VERSION, defaults
run.py _writer.py logging API and background writer (stdlib only)
_format.py event-log frames (stdlib only)
_env.py _media.py environment capture, image/figure encoding (stdlib only)
artifacts.py versioned artifacts (stdlib only)
reader.py series.py NumPy reader, downsampling, aggregation
compact.py alerts.py Parquet compaction (PyArrow), health checks (NumPy)
catalog.py query.py run discovery, SQLite cache, query language
collection.py collections of runs (stdlib only)
tensorboard.py TensorBoard event-file import (stdlib only)
merge.py remove.py nuboard merge and nuboard rm
analysis.py nb.load(...) → Arrow tables
server/ Starlette app and data access
static/ built frontend (generated, not in git)
cli.py demo.py command line and demo data
frontend/ Svelte 5 + TypeScript + Vite + Tailwind v4 + uPlot
tests/ pytest (tests/data: TensorBoard logs from real writers)
benchmarks/bench.py performance checks
scripts/blank_lines.py blank-line-around-blocks formatter (see Code style)
docs/ this site (mkdocs)
examples/ runnable examples
hatch_build.py wheel build hook that builds and bundles the frontend
Setup¶
You need uv or pip, and for the frontend Node
20+ with pnpm. The commands below use uv; with pip, run
pip install -e . --group dev (pip 25.1 or newer) in a virtual environment
instead of uv sync, and drop uv run from the other commands.
uv sync # Python deps + dev tools into .venv
cd frontend && pnpm install && pnpm build && cd .. # builds into src/nuboard/static/
uv run nuboard demo demo-runs --up
For frontend work, run the API and Vite's dev server side by side; Vite
proxies /api to port 6070:
uv run nuboard up demo-runs # terminal 1 (port 6070)
cd frontend && pnpm dev # terminal 2, open http://localhost:5173
Code style¶
- PEP 8, formatted by
ruff formatwith 100-character lines. - Google-style docstrings (
Args:,Returns:,Raises:,Yields:) on every module, class and function, checked by ruff'spydocstylerules. Constructor arguments are documented on the class. Inline code uses single backticks. -
A blank line separates every
if,for,while,with,tryandmatchblock from the statements before and after it:if config is None: return {} if isinstance(config, Mapping): return dict(config) to_dict = getattr(config, "to_dict", None)
python scripts/blank_lines.py src tests adds the missing ones, and
tests/test_style.py fails if any are missing.
Checks¶
uv run pytest # Python tests (crash safety, resume, format, server, …)
uv run ruff check . && uv run ruff format --check .
python scripts/blank_lines.py --check src tests scripts examples benchmarks
uv run pyright
uv run python benchmarks/bench.py
cd frontend
pnpm test # Vitest unit tests
pnpm check # svelte-check (TypeScript)
pnpm lint # ESLint
pnpm exec playwright install chromium # once
pnpm e2e # Playwright: starts `nuboard demo --up` on port 6071
Building a wheel¶
uv build # runs hatch_build.py: pnpm install + pnpm build if static/ is missing
The wheel contains the built frontend, so users never need Node. Set
SKIP_FRONTEND_BUILD=1 to build without it (the viewer then shows a "frontend
not built" page).
Releasing¶
- Set the version in
pyproject.tomlandfrontend/package.json, thenuboard X.Y.Zlines ofdocs/installation.mdand a## X.Y.Zsection indocs/changelog.md;tests/test_release.pychecks that they agree. Then runuv lock. -
Merge into
main, then build and upload from a clean checkout of it:rm -rf dist src/nuboard/static # no stale viewer build in the package uv build # builds the viewer too (Node 20+ and pnpm) uv publish # token from UV_PUBLISH_TOKEN or --tokenrm -rf dist src/nuboard/static python -m build twine upload dist/*Use a PyPI API token scoped to the
nuboardproject, and keep it out of the repository. To try a release first, upload to TestPyPI withuv publish --publish-url https://test.pypi.org/legacy/(ortwine upload -r testpypi dist/*). 3. Optionally, publish a GitHub release taggedvX.Y.Zwith the changelog section as its notes.
Docs¶
uv run mkdocs serve # http://localhost:8000
uv run mkdocs build # static site in site/
The Docs workflow builds the site on every push to main and publishes it
to https://eagomez2.github.io/nuboard/. It needs Settings → Pages →
Source: GitHub Actions set once in the repository.
Keep docs/format.md in sync with every change to what is
written to disk.
Rules for the logging side¶
- Standard library only. Anything that needs NumPy or PyArrow belongs to the
reader, compaction, analysis or server modules and is imported lazily from
run.py. - Never raise from the writer thread into training: failures are warned once.
- Never add a lock to the write path or a database next to the runs.
Flag any change that adds a logging-side dependency or a server to deployment.
Renaming¶
The name exists in exactly three places:
pyproject.toml:name, the[project.scripts]entry, andpackages = ["src/nuboard"];- the package folder
src/nuboard/; frontend/src/lib/brand.ts(andfrontend/package.json'sname, which is cosmetic).
Everything else derives from NAME (nuboard._meta.NAME = __name__.split(".")[0]):
the environment variable prefix (NUBOARD_DIR), the cache folder, CLI help,
the page title. Docs and README mention the name in prose and need a
search-and-replace. The on-disk format does not contain the name (the log
magic is RUNLOG), so existing runs stay readable after a rename.