Development Guide¤
Prerequisites¤
Python¤
This project is written in Python and requires a Python interpreter to be installed for development.
We recommend using uv to install and manage Python versions on your development system. uv is also used for the rest of the project's development workflow (see the uv section below).
First, follow the Installing uv documentation and make sure that the uv command is available in your shell.
Next, use uv to install Python 3.10 or later. We'll install the latest version of Python provided by uv (currently 3.13 at the time of writing).
Note
As long as you have Python 3.10 or later installed, you should be able to follow the remainder of this guide.
The simplest way to install Python is to download an official installer. Check out this article for an overview of some alternative installation options.
uv¤
This project uses uv for project management. This includes managing the project's dependencies, running project tasks (via poe), running tests against multiple versions of Python, building the docs, creating new releases, etc.
If you installed Python with uv in the previous section, you already have uv installed. Otherwise, follow the Installing uv documentation now.
The important thing is that at this point, you should be able to run the uv command.
Node.js¤
Some of the project's development tools (e.g. the cspell spell checker that runs as part of uv run poe check) are distributed as npm packages and require Node.js to run. Install Node.js and make sure the node and npm commands are available in your shell.
Next, install the project's Node.js development dependencies (declared in package.json).
The npm ci command installs the exact versions of the tools recorded in package-lock.json into a node_modules directory at the project root. Some of the project's tasks invoke these tools via npx — for example, uv run poe check runs the cspell spell checker with npx cspell.
Pull Requests¤
Installation and setup¤
Fork the tinydantic repository, clone it, and change directory to the project root.
Install the project along with all of its development dependencies.
Behind the scenes, uv creates a Python virtual environment in a .venv directory at the project root, installs the tinydantic package in development mode by performing an editable installation, and installs all of the project's dependencies. The --all-groups flag ensures that every dependency group is installed (including the dev group, which pulls in everything needed for testing, linting, type-checking, and building the docs).
You can run any command inside this environment by prefixing it with uv run. For example, you should be able to start the python interpreter and import the tinydantic package.
Install pre-commit hooks¤
tinydantic uses pre-commit to automatically run a series of quality checks against your code locally before it is committed to the repository. (The pre-commit command was installed into the virtual environment when you ran uv sync --all-groups above.)
Install the pre-commit hook scripts into your local git repository.
When you commit your changes (git commit ...), the git hook scripts will run automatically and check for issues in your code.
Normally, pre-commit only checks the files that were modified as part of the commit. A poe task is provided to run pre-commit manually on all the files if needed.
Make your changes¤
Create a new branch for your changes.
Run tests¤
While you are making changes in your new branch, you can run the test suite to make sure you didn't break anything.
You can also run the test suite together with a Coverage report showing which parts of the code were executed by the tests.
Note
The test and test-cov tasks run against the single Python version installed in your .venv. The full test matrix — every supported Python version (3.10–3.14) across Linux, macOS, and Windows — runs automatically in CI when you open a pull request, which can catch issues that only show up with a specific version of Python or a specific operating system.
To run the test suite against every supported Python version locally, use the test-matrix task. It uses uv to build a temporary environment from uv.lock for each Python version (downloading any missing interpreters automatically) and runs test-cov in each one, without touching your .venv. After the per-version runs, it combines their coverage data and prints a single report spanning all versions, so version-specific branches aren't flagged as missed — you can also follow up with uv run coverage html for a combined HTML report. This covers the Python-version axis of the CI matrix; the operating-system axis still only runs in CI.
After running uv run poe test-cov, you can also generate an interactive HTML coverage report.
Run formatters and linters¤
Before committing your changes, you should format your code and run the project's checks. First, format your code and apply autofixes with the Ruff formatter and linter.
Then run the full suite of checks (linting, license/SBOM checks, spell-checking, and type-checking).
Build Documentation¤
If you have made any changes to the documentation (including changes to function signatures, class definitions, or docstrings that will appear in the API documentation), make sure it builds successfully.
The following command will build and serve the documentation locally on your machine. While the server is running, it will watch for any changes to the documentation files, rebuild the site, and refresh your browser automatically.
Before committing your changes, it's good to build and run some validation checks on the built documentation.
Note
The documentation build downloads Sphinx object inventories so that cross-references to external documentation (Python, Pydantic, TinyDB) resolve to links.
However, Read the Docs has put the site behind a Cloudflare bot challenge which blocks mkdocstrings' mkdocstrings/0.15.0 user agent, rejecting the TinyDB inventory download (this shows up as mkdocstrings: Couldn't load inventory ... HTTP Error 429: Too Many Requests).
If that happens (to any of the inventories), set the corresponding environment variable in your shell to point the build at a downloaded copy of the objects.inv inventory instead, then run the docs task as usual (from the repo root). The file: URL is resolved against the current working directory, so build from the repo root (the poe docs... tasks already require this).
| Environment variable | Default inventory URL |
|---|---|
PYTHON_OBJECTS_INV |
https://docs.python.org/3/objects.inv |
PYDANTIC_OBJECTS_INV |
https://docs.pydantic.dev/latest/objects.inv |
TINYDB_OBJECTS_INV |
https://tinydb.readthedocs.io/en/latest/objects.inv |
For example, to override the TinyDB inventory:
Commit your changes¤
When you are done making changes, commit them to your new branch.
tinydantic follows the Conventional Commits specification to enforce some consistency across commit messages.
This cheat sheet lists the allowed options for the <type> field.
Here's an example of a commit message that follows the spec.
Tip
The development dependencies automatically install the Commitizen CLI tool (cz) which walks you through creating a conventional commit message via a questionnaire-style interface.
Instead of git commit ..., use the cz command to commit your changes.
Create a PR on GitHub¤
When your changes are ready for review, push your branch to GitHub and create a pull request. Link to any relevant issues and include a description of your changes.
Creating a pull request will kick off a series of automated checks that run as part of workflows in GitHub Actions. If any of the checks fail, fix the issues locally in your PR branch, create a new commit (or amend your existing commits), and push the changes to the remote PR branch. GitHub will automatically run the checks again when changes are pushed to the PR branch. Repeat this process until all checks have passed.
Release Process¤
The release process is automated using workflows in GitHub Actions. New releases are created and build artifacts are published automatically when a compliant SemVer release tag of the form v<MAJOR>.<MINOR>.<PATCH> is pushed to GitHub.
Because the main branch is protected (no direct pushes), the version bump travels in a pull request like any other change, and the release tag is pushed afterward — git tags are not governed by branch protection.
-
Update
CHANGELOG.mdwith a section for the new release. The changelog is curated by hand;cz bumpdoes not generate it. -
On a release branch, let Commitizen determine the next SemVer-compliant version from the commit history and write it to
pyproject.tomlwithout committing or tagging, then refresh the lockfile to match: -
Commit the result (for example
bump: version 0.1.19 → 0.2.0), push, open a pull request, and merge it once CI passes. -
Tag the merged commit on
mainand push the tag:
When the tag is pushed to GitHub, a GitHub Actions workflow verifies that the tag matches the version in pyproject.toml, builds and publishes the Python package, creates a release on GitHub, and updates the documentation site.
Note
Do not create the GitHub release manually in the web UI — the release workflow creates it from the tag and will fail if one already exists.
Editor Setup¤
Todo
Add VS Code setup instructions