Self-hosted service
johnthagen/python-blueprint avatar
johnthagen/python-blueprint

python-blueprint is a reference Python project built around uv, nox and ruff

🗺️ Example Python project using best practices 🐍 https://github.com/johnthagen/python-blueprint

745 stars105 forksPythonMIT

At a glance

What is it?
The python-blueprint repository is a worked example of a modern Python package, with uv for dependencies, nox for sessions, pytest for tests, ruff for style, mypy for types and a multi-stage Dockerfile. It is meant to be copied rather than depended on, and its own publish guardrail shows who it is for.
Who is it for?
Adopt python-blueprint as a starting point when you want a Python package skeleton that already wires uv, nox, pytest, ruff and mypy together, and when you are willing to inherit uv as a required tool. Do not adopt it as a library, since the only code in it is a factorial implementation and a CLI, and do not copy it into a project that must support Python below 3.11 without editing the classifier and requires-python fields.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A reference project, with a factorial and a CLI

The README describes the repository as an example Python project that demonstrates how to create a Python package using the latest Python testing, linting and type checking tooling. Read that carefully. It is a template with a small payload: a `fact` package providing a simple implementation of the factorial algorithm in `fact.lib`, and a command line interface in `fact.cli`.

That is the point. There is no library API to adopt and no performance claim to evaluate. What you evaluate is the arrangement of tools, and the code exists so that the tools have something real to chew on. The console entry point is declared in `pyproject.toml` as `fact = "fact.cli:app"`, and the runtime dependencies are `rich` and `typer`, so the CLI is a real command line program rather than a stub.

Documentation lives at johnthagen.github.io/python-blueprint, the repository has a `docs/` directory with an `mkdocs.yml` configuration, and the topic tags on the repository name the tooling the example demonstrates, including uv, nox, ruff, mypy, pytest, mkdocs, docker and github-actions. It is a checklist you can run.

uv owns the environment, the interpreter and the lock file

Package management is the first decision the blueprint makes, and it makes it firmly. Dependencies are managed with uv, which also creates isolated Python virtual environments. The README assumes uv is installed globally and points at the uv documentation for that.

From there, a specific interpreter version is installed with:

shell
uv python install 3.14

and every uv-managed Python version can be moved to its latest patch release with:

shell
uv python upgrade

Dependencies are declared in `pyproject.toml` and pinned in `uv.lock`, which the README says allows for exact reproducible environments across all machines that use the project, both during development and in production. The two commands that matter daily are:

shell
uv sync

to install all dependencies into an isolated virtual environment, and:

shell
uv lock --upgrade

to move every dependency to its latest version. Note the asymmetry: `uv sync` is what a contributor runs, while `uv lock --upgrade` is a deliberate act that changes the pin file for everybody. The README also notes that `nox` is installed into the virtual environment automatically by `uv sync`, so the test runner arrives without a separate install step.

One pyproject.toml carries metadata, tools and dependency groups

The project file is where the modern packaging story lives, and the blueprint keeps it in one place. The opening of the file:

toml
[project]
name = "fact"
version = "1.0.0"
description = "Example Python project using best practices"
authors = [
    { name = "John Hagen", email = "[email protected]" }]
license = "MIT"
license-files = ["LICENSE*"]
readme = "README.md"

The metadata is PEP 621 style, with the licence declared as a string and the licence files globbed. The classifiers list Python 3.11 through 3.15, includes `Typing :: Typed` and a free threading classifier, and `requires-python` is `>=3.11`.

Tooling dependencies live in dependency groups rather than in the runtime requirements, which is the part worth copying:

toml
[dependency-groups]
nox = [
    "nox-uv",
]
test = [
    "pytest",
    "pytest-cov",
    "pytest-randomly",
]
type_check = [
    "mypy",
    # Add "types-" stub packages as needed: https://github.com/python/typeshed/tree/main/stubs
]
lint = [
    "ruff",
    "rumdl",
]

With groups for docs and licences alongside. The result is that a contributor installs the toolchain as one set, while the published package depends on `rich` and `typer` and nothing else.

The publish guardrail blocks uv publish until you remove it

Packaging is configured entirely by `pyproject.toml`, and the build is a single command:

shell
uv build

which the README says generates `dist/fact-1.0.0.tar.gz` and `dist/fact-1.0.0-py3-none-any.whl`, that is a source distribution and a pure Python wheel. Publishing is likewise one command:

shell
uv publish

But the blueprint ships with the classifier `Private :: Do Not Upload` in its project metadata, and the README draws attention to it: to enable publishing, remove that trove classifier. The file even carries a comment explaining that the classifier is there to prevent accidentally publishing private code to PyPI.

This is the clearest statement of the project's purpose. An example repository that could be published by accident is a hazard to every fork of it, so the blueprint arrives locked and you remove it deliberately. Read the classifier list before you rename the project, and decide whether you want that guardrail in your own repository.

nox turns the checks into named sessions

Automated code quality checks run through Nox with the `nox-uv` plugin, and the README says Nox will automatically create virtual environments and run commands based on `noxfile.py` for unit testing, PEP 8 style guide checking, type checking and documentation generation. The runner is invoked through uv:

shell
uv run nox

which runs all default sessions. Sessions can also be targeted and given arguments. To pass arguments through to pytest:

shell
uv run nox -s test -- -k invalid_factorial

The value of the session layer is isolation. Each tool runs in its own environment, so a lint session and a type checking session cannot interfere, and adding a new check means adding a session rather than composing a longer command line. The cost is a layer of indirection. A contributor who wants to run pytest directly has to find the environment that Nox created, and the sessions, not the underlying tools, are the documented interface.

The repository root holds `noxfile.py`, and the dependency group for it pulls in `nox-uv`.

pytest discovery and a coverage report you can open

Unit testing uses pytest, and the README gives the reasons it prefers over the built-in unittest module: significantly less boilerplate, PEP 8 compliant names such as `pytest.raises()` instead of `self.assertRaises()`, and a large plugin ecosystem. Discovery works by recursively searching for folders and `.py` files prefixed with `test`, and functions prefixed by `test`.

One layout detail is explained rather than assumed. The `tests` folder is created as a Python package, with an `__init__.py` inside it, because that helps pytest namespace the test files uniquely, so two test files cannot have the same name in different subdirectories. If you rename test files carelessly in a flat layout, you will hit that collision.

Coverage comes from the pytest-cov plugin, and running a unit test Nox session produces an HTML report in the `htmlcov` folder showing each source file and which lines were executed, viewable at `htmlcov/index.html`. The point of the report is to find the parts of the project that are not tested. pytest and coverage configuration both live in `pyproject.toml`, which is where you would change the source paths or the fail-under threshold.

Ruff for style and format, rumdl for Markdown, mypy for types

Style checking follows PEP 8 and is verified with Ruff, configured in the `[tool.ruff]` section of `pyproject.toml`. Some style settings are also in `.editorconfig`, which editors such as PyCharm pick up automatically. Linting is a session:

shell
uv run nox -s lint

and the fixable errors are fixed by a second session:

shell
uv run nox -s lint_fix

Formatting is separate, because the tool that formats is not the tool that checks:

shell
uv run nox -s fmt

Ruff handles the Python source and the import grouping and sorting, while Markdown files are handled by rumdl.

Type checking is the third axis. The README explains that type annotations let static analysers such as mypy, PyCharm or Pyright check that functions are used with the correct types before runtime, and that editors such as PyCharm and VS Code use the same annotations to give better completion, refactoring and checking while you type. The type checking dependency group holds mypy, with a comment reminding you to add `types-` stub packages when you need them.

The container build is pinned and deliberately multi-stage

The Dockerfile is the part of the blueprint that has the most comments, because it is where the trade-offs live. The build starts from a slim base image:

dockerfile
FROM python:3.13-slim-trixie AS python_builder

# Pin uv to a specific version to make container builds reproducible.
ENV UV_VERSION=0.13.0
ENV UV_PYTHON_DOWNLOADS=never

# Set ENV variables that make Python more friendly to running inside a container.
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONBUFFERED=1

# By default, pip caches copies of downloaded packages from PyPI. These are not useful within
# a contain image, so disable this to reduce the size of images.
ENV PIP_NO_CACHE_DIR=1
ENV WORKDIR=/src

The header comment explains the reasoning: a multi-stage build reduces the size of the final image, and a slim image reduces it further. It also spells out the alpine caveat, that Alpine uses MUSL rather than GLIBC, that most Python packages requiring C code are tested against GLIBC, that these packages usually ship only binary wheels for GLIBC, and that going alpine therefore means recompiling packages inside the container and slower builds. Pinning uv to a version and disabling uv's own Python downloads are both about reproducibility.

The licence is MIT with the file at `LICENSE.txt`, the repository carries an `AGENTS.md` for coding agents, and there is no tagged release, so take the branch as it stands. The last push was on 2026-10-15.

Editorial conclusion

Adopt python-blueprint as a starting point when you want a Python package skeleton that already wires uv, nox, pytest, ruff and mypy together, and when you are willing to inherit uv as a required tool. Do not adopt it as a library, since the only code in it is a factorial implementation and a CLI, and do not copy it into a project that must support Python below 3.11 without editing the classifier and requires-python fields. Verify first that the trove classifier "Private :: Do Not Upload" is removed before you run `uv publish`, that `noxfile.py` matches the sessions your team expects to run, and that the copy you take is current, since the last push was on 2026-10-15 and the repository publishes no tagged releases.

Frequently asked questions

What does the python-blueprint project contain?

It is an example Python package named fact that implements the factorial algorithm in fact.lib and provides a command line interface in fact.cli, exposed as the `fact` script. The purpose is to demonstrate the tooling rather than to provide a library to depend on.

How do I install the dependencies in python-blueprint?

The project uses uv, which must be installed globally first. Run `uv python install 3.14` to install an interpreter, `uv sync` to install all dependencies into an isolated virtual environment, and `uv lock --upgrade` to move dependencies to their latest versions, with exact versions locked in uv.lock.

Why does uv publish fail in python-blueprint?

The project metadata ships the trove classifier `Private :: Do Not Upload`, which exists to prevent accidentally publishing private code to PyPI. The README says you must remove that classifier before publishing with `uv publish`.

How are the tests and checks run in python-blueprint?

Nox drives the checks from noxfile.py, creating environments for unit testing, PEP 8 style checking, type checking and documentation generation. Run all default sessions with `uv run nox`, or a single one such as `uv run nox -s test -- -k invalid_factorial`, with coverage reported in the htmlcov folder.

Official sources

  1. Issues
  2. johnthagen/python-blueprint on GitHub
  3. License: MIT
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/johnthagen-python-blueprint.svg)](https://hysenlabs.com/projects/johnthagen-python-blueprint)

Was this useful?