Development¶
Environment¶
The project uses Dev Containers for a consistent environment, and uv for package management.
- Open the project in VS Code (or any editor supporting Dev Containers).
- Reopen in Container — this builds an environment with Python, Ansible and the CLI tooling already installed.
- Sync dependencies and activate the virtual environment:
Outside a dev container, uv sync is still all you need, plus
just for the recipes below.
Recipes¶
just check # ruff lint + ty type-check (CI runs this)
just format # ruff format + safe fixes
just test # pytest
just build # check, then uv build
Ansible collections, when you need them locally:
Documentation¶
Part of the docs is generated from the code by docs/gen_docs.py:
docs/labops.schema.json— the config's JSON Schema, from the Pydantic modelsdocs/configuration/*.md— the config reference, from that same schemadocs/commands/*.md— the CLI reference, from Typer
None of it is committed. Those paths are git-ignored, and CI runs the
generator immediately before mkdocs build, so the reference cannot fall out of
date with the code it describes — there is no stored copy to go stale. Rename a
CLI flag and the next deploy simply describes the new one.
just docs-serve # generate, then live preview at http://127.0.0.1:8000
just docs-build # generate, then build the static site into site/
just docs-gen # just the generation step, rarely needed on its own
Both site recipes generate first, because mkdocs.yml's nav references those
pages and --strict fails when a page is missing.
Everything else under docs/ is hand-written and tracked as normal.
The generator lives in docs/
Beside the pages it produces, and deliberately not under src/ or
models/ — those ship in the wheel (see pyproject.toml), and a docs
generator has no business in a user's install. MkDocs copies it into the
built site, which is harmless: the repository is public anyway.
Where documentation lives¶
| Change | Where to write it |
|---|---|
| What a config key means | Field(description=...) on the model |
| What a block is for, or a rule spanning fields | The model's class docstring |
| What a command does | Its Typer docstring, with a \b examples block |
| How a subsystem fits together, and why | A guide under docs/guides/ |
The first three end up in the generated reference and in --help / the JSON
Schema, so writing them once covers the terminal, the site and the user's editor.
Writing command help¶
The bar is set by src/cli/update.py and src/cli/wake.py: a summary line, a
[dim] paragraph for real side effects and caveats, and a \b examples block on
anything that takes a target.
Rich markup is fine — [bold], [dim] — because the generator converts it to
markdown. Do not turn off rich_markup_mode to make the docs cleaner; that would
fix the site by degrading the terminal.
Releases¶
Releases are automated with release-please. Commit messages follow Conventional Commits and a PR title check enforces it.
- Merging to
mainupdates a release PR with the changelog and version bump. - Merging that PR tags the release.
- Publishing the release triggers the PyPI upload — the package is not published by hand.
To build the distribution files locally for testing:
uv build # -> dist/*.whl and dist/*.tar.gz
just local-install # pipx install the freshly built wheel
Tests¶
The suite is pure logic — no infrastructure access, no network — covering the config models, the selectors, the finders and the render/plan steps. Anything that would touch a real host is not tested here.