Skip to content

Contributing

We are always looking for contributions! You can find below some relevant information and standards for databooks.

Setup ⚙️

After cloning the repo, make sure to set up the environment.

uv 📦

We use uv for both managing environments and packaging. Install it following the installation guide; the project needs uv 0.13.1 or newer. From there uv creates the environment for you, downloading Python 3.11 if you don't have it.

uv sync --python 3.11  # installs runtime, test, and development groups

Usage

Remember that to use the environment you can use the uv run <COMMAND> command or activate it with source .venv/bin/activate. For example, if you want to create the coverage report you could run

uv run pytest --cov=databooks tests/

or alternatively

source .venv/bin/activate
pytest --cov=databooks tests/

Add runtime dependencies with uv add <package> and tooling with uv add --group dev <package> (or --group test); both update uv.lock, which is committed next to pyproject.toml. Install the git hooks once with uv run pre-commit install.

Development 🛠

We welcome new features, bugfixes or enhancements (whether on code or docs). There are a few standards we adhere to, that are required for new features.

Type checking

We use type hints! Not only that, they are enforced and checked: ty type checks the code from the locked environment (configured under [tool.ty] in pyproject.toml), and ruff's ANN rules keep every function annotated. This is actually the reason for supporting Python 3.10+. There are a couple of reasons for using type hints, mainly:

  1. Better code coverage (avoid errors during runtime)
  2. Improve code understanding
  3. As databooks uses both Typer and Pydantic, types are not only for developer hints, but they are also used to cast notebook (JSON) values to the correct types as well as user inputs in the CLI

If you are not familiar with type hints, a good starting point is watching the Type-checked Python in the real world - PyCon 2018 talk.

Linting

In regards to code quality, we use a couple of linting tools to maintain the same "style" and uphold to the same standards. For that, we use:

  1. ruff for linting and code formatting (it replaces Black, isort and Flake8)
  2. codespell for spelling

Docs 📚

As for documentation, the databooks documentation "lives" both on the code itself and supporting documentation (markdown) files.

Code

Code docs include annotating type hints as well as function docstrings. For that, we use the reStructuredText -like format.

Providing docstrings not only give a clear way to document the code, but it is also picked up by MkDocs.

MkDocs

MkDocs gives a simple way to write markdown files that get rendered as HTML (under a certain theme) and served as documentation. We use MkDocs with different extensions. We use mkdocstrings to link function docstrings with the existing documentation.

You can check the generation of documentation by running from the project root

uv run mkdocs serve

mike

We also use the mike plugin in MkDocs to publish and keep different versions of documentation.

Cog

We use cog to dynamically generate parts of the documentation. That way, code changes trigger markdown changes as well.

Pre-commit

Pre-commit is the tool that automates everything, eases the workflow and run checks in CI/CD. It's highly recommended installing pre-commit and the hooks during development.

Tests 🗳

We use unit tests to ensure that our package works as expected. We use pytest for testing and Pytest-cov for checking how much of the code is covered in our tests.

The tests should mimic the package directory structure. The tests are also written to serve as an example of how to use the classes and methods and expected outputs.

The coverage is also added to the documentation. For that we use MkDocs Coverage Plugin. For that we need a htmlcov/ directory that is generated by Pytest-cov by running from the project root

uv run pytest --cov-report html --cov=databooks tests/

Dependency upgrades and audits

Use uv (0.13.1 or newer, the version CI pins) and Python 3.11 for QA and documentation. The test group runs on Python 3.10 to 3.15; tools in the dev group have Python 3.11+ markers. Use uv sync --no-default-groups for runtime dependencies only, uv sync --no-default-groups --group test for tests, and uv sync for everything. Re-resolve the lock after changing constraints with uv lock --upgrade. The build backend is hatchling: consumers that build from source (pip, pre-commit hook users) get hatchling>=1.27,<2 from [build-system], while uv-driven builds (uv build, uv sync) use the single release pinned in build-constraint-dependencies under [tool.uv]. CI builds also use the hash-pinned .github/build-constraints.txt, which locks hatchling and its own dependencies. Bump hatchling deliberately (it is not tied to the uv version): change build-constraint-dependencies, regenerate .github/build-constraints.txt with the command in its header and the new version (add --upgrade to also refresh hatchling's own dependencies), then run uv lock.

The ruff, ty, codespell, and cog hooks run from the locked project environment, avoiding duplicate dependency pins and unscannable development versions from source checkouts. The remaining Python hook environment uses Python 3.11. Refresh those hooks with uv run pre-commit autoupdate, then check that every selected revision is stable (the updater can select prerelease tags).

CI audits frozen runtime and development environments on every supported Python, and the QA environment plus every initialized Python hook environment. Reports and frozen inputs are retained as artifacts even on failure. Collection errors, skipped packages, missing report entries, and advisories all block CI; no advisory suppressions are used. The local repository distribution is recorded in the inventory and excluded from third-party PyPI lookups.

To reproduce the QA audit locally:

uv run pre-commit install-hooks
uv run --no-project --with pip-audit==2.10.1 scripts/audit.py --python .venv/bin/python \
  --hooks "${PRE_COMMIT_HOME:-$HOME/.cache/pre-commit}" --output audit-reports/qa

Generate CLI documentation with uv run cog -r docs/CLI.md (using the Typer CLI bundled with Typer), create coverage with uv run pytest --cov=databooks --cov-report=html, then build with uv run mkdocs build --strict.

Publishing

Releases start with a new, plain stable version tag such as 1.3.11. The tag is the release version: CI dynamically writes it into pyproject.toml and databooks/version.py before release tests, package builds, and docs deployment. The repository keeps its development placeholder (0.0.0-dev); no version metadata commit is needed. Choose the next version separately; v prefixes, prereleases, build suffixes, and leading zeroes are rejected.

Required tools: Git with access to origin, Just, and uv. Run these commands from the repository root after merging the changes you want to release into main:

git switch main
git pull --ff-only origin main
just release-tag 1.3.11
just release-push 1.3.11

Tagging requires clean tracked files, fetches origin/main, checks that HEAD is in its history, then creates an annotated tag. Pushing repeats the ancestry check against the tagged commit, even if you have since changed branches, and pushes only that tag. Existing local tags cannot be recreated; existing remote tags cannot be pushed again or replaced. Untracked files are ignored. Do not delete or move published tags; choose a new version for another release. A push to main alone does not publish.

just release-prepare VERSION is available as an optional local preview of CI's version stamping. It updates both fields without committing, and any later plain uv run records the previewed version in uv.lock too. It is not required for a release; discard all three files before tagging so tracked files are clean, e.g. git checkout -- pyproject.toml databooks/version.py uv.lock.

Only new version tag pushes trigger publishing. Tag updates and deletions are skipped; CI validates the exact version and main history before calling the reusable test workflow from the tagged commit. The workflow stamps the tag version and runs every Python 3.10 to 3.15 matrix job, with the pinned QA hooks checked separately on Python 3.11. The QA job also builds the packages and checks them with twine on every push and pull request, so the build backend is exercised long before a release. All jobs must pass before CI stamps and builds the packages with uv build, publishes them to TestPyPI and PyPI with uv publish (using TEST_PIPY_TOKEN and PIPY_TOKEN), then creates a GitHub release with generated notes and deploys docs under the tag version, updating latest. Branch and pull request tests continue to use the committed development version.

Before changing release tooling, run:

uv run pytest tests/test_release.py
uv run pytest
uv run pre-commit run --all-files
actionlint
just --list
just --dry-run release-prepare 1.3.11
just --dry-run release-tag 1.3.11
just --dry-run release-push 1.3.11

The helper tests use temporary repositories and a local bare remote. Install actionlint to check workflow syntax.

Contributors 👨‍💻👩‍💻

databooks was created by Murilo Cunha, and is maintained by dataroots.

Acknowledgements

Special thanks to:

Bart, Nick and Freddy for feedback and support.