{"id":15017461,"url":"https://github.com/johnthagen/python-blueprint","last_synced_at":"2025-10-08T18:16:45.076Z","repository":{"id":37453860,"uuid":"157031916","full_name":"johnthagen/python-blueprint","owner":"johnthagen","description":"🐍 Example Python project using best practices 🥇","archived":false,"fork":false,"pushed_at":"2025-05-11T18:03:14.000Z","size":2244,"stargazers_count":637,"open_issues_count":3,"forks_count":93,"subscribers_count":15,"default_branch":"main","last_synced_at":"2025-05-11T18:33:06.799Z","etag":null,"topics":["containers","coverage","docker","linting","mkdocs","mkdocs-material","mypy","nox","pep-621","pep-735","pycharm","pytest","python","python-3","ruff","type-checking","type-hints","uv"],"latest_commit_sha":null,"homepage":"https://johnthagen.github.io/python-blueprint/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/johnthagen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-11-10T23:48:23.000Z","updated_at":"2025-05-11T18:02:59.000Z","dependencies_parsed_at":"2023-10-03T19:11:35.680Z","dependency_job_id":"822ff890-4524-45bc-805f-73179d5ac09c","html_url":"https://github.com/johnthagen/python-blueprint","commit_stats":{"total_commits":405,"total_committers":5,"mean_commits":81.0,"dds":0.4839506172839506,"last_synced_commit":"1f7f158c42f48d6c344a5b06cdd1b2720f3824ab"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnthagen%2Fpython-blueprint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnthagen%2Fpython-blueprint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnthagen%2Fpython-blueprint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnthagen%2Fpython-blueprint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/johnthagen","download_url":"https://codeload.github.com/johnthagen/python-blueprint/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254259384,"owners_count":22040820,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["containers","coverage","docker","linting","mkdocs","mkdocs-material","mypy","nox","pep-621","pep-735","pycharm","pytest","python","python-3","ruff","type-checking","type-hints","uv"],"created_at":"2024-09-24T19:50:30.087Z","updated_at":"2025-10-08T18:16:45.070Z","avatar_url":"https://github.com/johnthagen.png","language":"Python","readme":"# python-blueprint\n\n[![GitHub Actions][github-actions-badge]](https://github.com/johnthagen/python-blueprint/actions)\n[![uv][uv-badge]](https://github.com/astral-sh/uv)\n[![Nox][nox-badge]](https://github.com/wntrblm/nox)\n[![Ruff][ruff-badge]](https://github.com/astral-sh/ruff)\n[![Type checked with mypy][mypy-badge]](https://mypy-lang.org/)\n\n[github-actions-badge]: https://github.com/johnthagen/python-blueprint/actions/workflows/ci.yml/badge.svg\n[uv-badge]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json\n[nox-badge]: https://img.shields.io/badge/%F0%9F%A6%8A-Nox-D85E00.svg\n[ruff-badge]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json\n[mypy-badge]: https://www.mypy-lang.org/static/mypy_badge.svg\n\nExample Python project that demonstrates how to create a Python package using the latest\nPython testing, linting, and type checking tooling. The project contains a `fact` package that\nprovides a simple implementation of the\n[factorial algorithm](https://en.wikipedia.org/wiki/Factorial) (`fact.lib`) and a command line\ninterface (`fact.cli`).\n\n# Package Management\n\nThis package uses [uv](https://docs.astral.sh/uv/) to manage dependencies and\nisolated [Python virtual environments](https://docs.python.org/3/library/venv.html).\n\nTo proceed,\n[install uv globally](https://docs.astral.sh/uv/getting-started/installation/)\nonto your system.\n\nTo [install a specific version of Python](https://docs.astral.sh/uv/guides/install-python/):\n\n```shell\nuv python install 3.13\n```\n\nTo upgrade a specific version of Python to the latest patch release:\n\n```shell\nuv python install --reinstall 3.13\n```\n\n## Dependencies\n\nDependencies are defined in [`pyproject.toml`](./pyproject.toml) and specific versions are locked\ninto [`uv.lock`](./uv.lock). This allows for exact reproducible environments across\nall machines that use the project, both during development and in production.\n\nTo install all dependencies into an isolated virtual environment:\n\n```shell\nuv sync\n```\n\nTo upgrade all dependencies to their latest versions:\n\n```shell\nuv lock --upgrade\n```\n\n## Packaging\n\nThis project is designed as a Python package, meaning that it can be bundled up and redistributed\nas a single compressed file.\n\nPackaging is configured by:\n\n- [`pyproject.toml`](./pyproject.toml)\n\nTo package the project as both a\n[source distribution](https://packaging.python.org/en/latest/flow/#the-source-distribution-sdist)\nand a [wheel](https://packaging.python.org/en/latest/specifications/binary-distribution-format/):\n\n```shell\nuv build\n```\n\nThis will generate `dist/fact-1.0.0.tar.gz` and `dist/fact-1.0.0-py3-none-any.whl`.\n\n\u003e [!TIP]\n\u003e Read more about the [advantages of wheels](https://pythonwheels.com/) to understand why\n\u003e generating wheel distributions are important.\n\n## Publish Distributions to PyPI\n\nSource and wheel redistributable packages can\nbe [published to PyPI](https://docs.astral.sh/uv/guides/package/) or installed\ndirectly from the filesystem using `pip`.\n\n```shell\nuv publish\n```\n\n\u003e [!NOTE]\n\u003e To enable publishing, remove the `\"Private :: Do Not Upload\"`\n\u003e [trove classifier](https://pypi.org/classifiers/).\n\n# Enforcing Code Quality\n\nAutomated code quality checks are performed using [Nox](https://nox.thea.codes/en/stable/) and\n[`nox-uv`](https://github.com/dantebben/nox-uv). Nox will automatically create virtual environments\nand run commands based on [`noxfile.py`](./noxfile.py) for unit testing, PEP 8 style guide\nchecking, type checking and documentation generation.\n\n\u003e [!NOTE]\n\u003e `nox` is installed into the virtual environment automatically by the `uv sync` command\n\u003e above.\n\nTo run all default sessions:\n\n```shell\nuv run nox\n```\n\n## Unit Testing\n\nUnit testing is performed with [pytest](https://pytest.org/). pytest has become the de facto Python\nunit testing framework. Some key advantages over the built-in\n[unittest](https://docs.python.org/3/library/unittest.html) module are:\n\n1. Significantly less boilerplate needed for tests.\n2. PEP 8 compliant names (e.g. `pytest.raises()` instead of `self.assertRaises()`).\n3. Vibrant ecosystem of plugins.\n\npytest will automatically discover and run tests by recursively searching for folders and `.py`\nfiles prefixed with `test` for any functions prefixed by `test`.\n\nThe `tests` folder is created as a Python package (i.e. there is an `__init__.py` file within it)\nbecause this helps `pytest` uniquely namespace the test files. Without this, two test files cannot\nbe named the same, even if they are in different subdirectories.\n\nCode coverage is provided by the [pytest-cov](https://pytest-cov.readthedocs.io/en/latest/) plugin.\n\nWhen running a unit test Nox session (e.g. `nox -s test`), an HTML report is generated in\nthe `htmlcov` folder showing each source file and which lines were executed during unit testing.\nOpen `htmlcov/index.html` in a web browser to view the report. Code coverage reports help identify\nareas of the project that are currently not tested.\n\npytest and code coverage are configured in [`pyproject.toml`](./pyproject.toml).\n\nTo pass arguments to `pytest` through `nox`:\n\n```shell\nuv run nox -s test -- -k invalid_factorial\n```\n\n## Code Style Checking\n\n[PEP 8](https://peps.python.org/pep-0008/) is the universally accepted style guide for Python\ncode. PEP 8 code compliance is verified using [Ruff][Ruff]. Ruff is configured in the\n`[tool.ruff]` section of [`pyproject.toml`](./pyproject.toml).\n\n[Ruff]: https://github.com/astral-sh/ruff\n\nSome code style settings are included in [`.editorconfig`](./.editorconfig) and will be configured\nautomatically in editors such as PyCharm.\n\nTo lint code, run:\n\n```shell\nuv run nox -s lint\n```\n\nTo automatically fix fixable lint errors, run:\n\n```shell\nuv run nox -s lint_fix\n```\n\n## Automated Code Formatting\n\n[Ruff][Ruff] is used to automatically format code and group and sort imports.\n\nTo automatically format code, run:\n\n```shell\nuv run nox -s fmt\n```\n\n## Type Checking\n\n[Type annotations](https://docs.python.org/3/library/typing.html) allows developers to include\noptional static typing information to Python source code. This allows static analyzers such\nas [mypy](http://mypy-lang.org/), [PyCharm](https://www.jetbrains.com/pycharm/),\nor [Pyright](https://github.com/microsoft/pyright) to check that functions are used with the\ncorrect types before runtime.\n\nEditors such as [PyCharm](https://www.jetbrains.com/help/pycharm/type-hinting-in-product.html) and\nVS Code are able to provide much richer auto-completion, refactoring, and type checking while the\nuser types, resulting in increased productivity and correctness.\n\n```python\ndef factorial(n: int) -\u003e int:\n    ...\n```\n\nmypy is configured in [`pyproject.toml`](./pyproject.toml). To type check code, run:\n\n```shell\nuv run nox -s type_check\n```\n\nSee also [awesome-python-typing](https://github.com/typeddjango/awesome-python-typing).\n\n### Distributing Type Annotations\n\n[PEP 561](https://www.python.org/dev/peps/pep-0561/) defines how a Python package should\ncommunicate the presence of inline type annotations to static type\ncheckers. [mypy's documentation](https://mypy.readthedocs.io/en/stable/installed_packages.html)\nprovides further examples on how to do this.\n\nMypy looks for the existence of a file named [`py.typed`](./src/fact/py.typed) in the root of the\ninstalled package to indicate that inline type annotations should be checked.\n\n## Continuous Integration\n\nContinuous integration is provided by [GitHub Actions](https://github.com/features/actions). This\nruns all tests, lints, and type checking for every commit and pull request to the repository.\n\nGitHub Actions is configured in [`.github/workflows/ci.yml`](./.github/workflows/ci.yml).\n\n# Documentation\n\n## Generating a User Guide\n\n[Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) is a powerful static site\ngenerator that combines easy-to-write Markdown, with a number of Markdown extensions that increase\nthe power of Markdown. This makes it a great fit for user guides and other technical documentation.\n\nThe example MkDocs project included in this project is configured to allow the built documentation\nto be hosted at any URL or viewed offline from the file system.\n\nTo build the user guide, run,\n\n```shell\nuv run nox -s docs\n```\n\nand open `docs/user_guide/site/index.html` using a web browser.\n\nTo build the user guide, additionally validating external URLs, run:\n\n```shell\nuv run nox -s docs_check_urls\n```\n\nTo build the user guide in a format suitable for viewing directly from the file system, run:\n\n```shell\nuv run nox -s docs_offline\n```\n\nTo build and serve the user guide with automatic rebuilding as you change the contents,\nrun:\n\n```shell\nuv run nox -s docs_serve\n```\n\nand open \u003chttp://127.0.0.1:8000\u003e in a browser.\n\nEach time the `main` Git branch is updated, the\n[`.github/workflows/pages.yml`](.github/workflows/pages.yml) GitHub Action will\nautomatically build the user guide and publish it to [GitHub Pages](https://pages.github.com/).\nThis is configured in the `docs_github_pages` Nox session. This hosted user guide\ncan be viewed at \u003chttps://johnthagen.github.io/python-blueprint/\u003e.\n\n## Generating API Documentation\n\nThis project uses [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) plugin for\nMkDocs, which renders\n[Google-style docstrings](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html)\ninto an MkDocs project. Google-style docstrings provide a good mix of easy-to-read docstrings in\ncode as well as nicely-rendered output.\n\n```python\n\"\"\"Computes the factorial through a recursive algorithm.\n\nArgs:\n    n: A positive input value.\n\nRaises:\n    InvalidFactorialError: If n is less than 0.\n\nReturns:\n    Computed factorial.\n\"\"\"\n```\n\n# Project Structure\n\nTraditionally, Python projects place the source for their packages in the root of the project\nstructure, like:\n\n``` {.sourceCode .}\nfact\n├── fact\n│   ├── __init__.py\n│   ├── cli.py\n│   └── lib.py\n├── tests\n│   ├── __init__.py\n│   └── test_fact.py\n├── noxfile.py\n└── pyproject.toml\n```\n\nHowever, this structure\nis [known](https://docs.pytest.org/en/latest/goodpractices.html#tests-outside-application-code) to\nhave bad interactions with `pytest` and `nox`, two standard tools maintaining Python projects. The\nfundamental issue is that Nox creates an isolated virtual environment for testing. By installing\nthe distribution into the virtual environment, `nox` ensures that the tests pass even after the\ndistribution has been packaged and installed, thereby catching any errors in packaging and\ninstallation scripts, which are common. Having the Python packages in the project root subverts\nthis isolation for two reasons:\n\n1. Calling `python` in the project root (for example, `python -m pytest tests/`)\n   [causes Python to add the current working directory](https://docs.pytest.org/en/latest/pythonpath.html#invoking-pytest-versus-python-m-pytest)\n   (the project root) to `sys.path`, which Python uses to find modules. Because the source\n   package `fact` is in the project root, it shadows the `fact` package installed in the Nox\n   session.\n2. Calling `pytest` directly anywhere that it can find the tests will also add the project root\n   to `sys.path` if the `tests` folder is a Python package (that is, it contains a `__init__.py`\n   file).\n   [pytest adds all folders containing packages](https://docs.pytest.org/en/latest/goodpractices.html#conventions-for-python-test-discovery)\n   to `sys.path` because it imports the tests like regular Python modules.\n\nIn order to properly test the project, the source packages must not be on the Python path. To\nprevent this, there are three possible solutions:\n\n1. Remove the `__init__.py` file from `tests` and run `pytest` directly as a Nox session.\n2. Remove the `__init__.py` file from tests and change the working directory of `python -m pytest`\n   to `tests`.\n3. Move the source packages to a dedicated `src` folder.\n\nThe dedicated `src` directory is the\n[recommended solution](https://docs.pytest.org/en/latest/pythonpath.html#test-modules-conftest-py-files-inside-packages)\nby `pytest` when using Nox and the solution this blueprint promotes because it is the least brittle\neven though it deviates from the traditional Python project structure. It results is a directory\nstructure like:\n\n``` {.sourceCode .}\nfact\n├── src\n│   └── fact\n│       ├── __init__.py\n│       ├── cli.py\n│       └── lib.py\n├── tests\n│   ├── __init__.py\n│   └── test_fact.py\n├── noxfile.py\n└── pyproject.toml\n```\n\n# Licensing\n\nLicensing for the project is defined in:\n\n- [`LICENSE.txt`](./LICENSE.txt)\n- [`pyproject.toml`](./pyproject.toml)\n\nThis project uses a common permissive license, the MIT license.\n\nYou may also want to list the licenses of all the packages that your Python project depends on.\nTo automatically list the licenses for all dependencies in (and their transitive dependencies)\nusing [pip-licenses-cli](https://github.com/stefan6419846/pip-licenses-cli):\n\n```shell\n$ uv run nox -s licenses\n...\n Name               Version  License\n Pygments           2.19.1   BSD License\n click              8.1.8    BSD License\n markdown-it-py     3.0.0    MIT License\n```\n\n# Container\n\n[Docker](https://www.docker.com/) is a tool that allows for software to be packaged into isolated\ncontainers. It is not necessary to use Docker in a Python project, but for the purposes of\npresenting best practice examples, a Docker configuration is provided in this project. The Docker\nconfiguration in this repository is optimized for small size and increased security, rather than\nsimplicity.\n\nDocker is configured in:\n\n- [`Dockerfile`](./Dockerfile)\n- [`.dockerignore`](./.dockerignore)\n\nTo build the container image:\n\n```shell\ndocker build --tag fact .\n```\n\nTo run the image in a container:\n\n```shell\ndocker run --rm --interactive --tty fact 5\n```\n\n# Miscellaneous\n\n## Shebang Line\n\nThe proper [shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) line for Python scripts is:\n\n```py\n#!/usr/bin/env python3\n```\n\nIf your script has external dependencies hosted on PyPI, add\n[PEP 723 Inline script metadata](https://peps.python.org/pep-0723/) and a `uv run` shebang line\nto allow easy execution on macOS and Linux.\n\n```py\n#!/usr/bin/env -S uv run --script\n# /// script\n# requires-python = \"\u003e=3.13\"\n# dependencies = [\n#     \"httpx\u003e=0.28.1\",\n# ]\n# ///\n\nimport httpx\n\nprint(httpx.get(\"https://example.com\").text)\n```\n\nOn Windows, remove the `-S` from the shebang and execute the script using the `py` launcher.\n\n## Package Dependency Tree\n\n`uv tree` is a command for listing installed packages in the form of a dependency tree. For large\nprojects, it is often difficult to determine dependency relationships soley from manually\ninspecting `uv.lock`.\n\n```shell\n$ uv tree --no-default-groups\n\nfact v1.0.0\n├── rich v13.9.4\n│   ├── markdown-it-py v3.0.0\n│   │   └── mdurl v0.1.2\n│   └── pygments v2.19.1\n└── typer-slim[standard] v0.15.2\n    ├── click v8.1.8\n    ├── typing-extensions v4.12.2\n    ├── rich v13.9.4 (extra: standard) (*)\n    └── shellingham v1.5.4 (extra: standard)\n```\n\n# PyCharm Configuration\n\n\u003e [!TIP]\n\u003e Looking for a vivid dark color scheme for PyCharm?\n\u003e Try [One Dark theme](https://plugins.jetbrains.com/plugin/11938-one-dark-theme).\n\nTo configure [PyCharm](https://www.jetbrains.com/pycharm/) to align to the code style used in this\nproject:\n\n- Settings | Search \"Hard wrap at\" (Note, this will be automatically set by\n  [`.editorconfig`](./.editorconfig))\n    - Editor | Code Style | General | Hard wrap at: 99\n\n- Settings | Search \"Optimize Imports\"\n    - Editor | Code Style | Python | Imports\n        - ☑ Sort import statements\n            - ☑ Sort imported names in \"from\" imports\n            - ☐ Sort plain and \"from\" imports separately within a group\n            - ☐ Sort case-insensitively\n        - Structure of \"from\" imports\n            - ◎ Leave as is\n            - ◉ Join imports with the same source\n            - ◎ Always split imports\n\n- Settings | Search \"Docstrings\"\n    - Tools | Python Integrated Tools | Docstrings | Docstring Format: Google\n\n- Settings | Search \"pytest\"\n    - Tools | Python Integrated Tools | Testing | Default test runner: pytest\n\n- Settings | Search \"Force parentheses\"\n    - Editor | Code Style | Python | Wrapping and Braces | \"From\" Import Statements\n        - ☑ Force parentheses if multiline\n\n## Ruff Integration\n\nIntegrate [Ruff](https://docs.astral.sh/ruff/editors/setup/#pycharm) linting and\nformatting into PyCharm.\n\n### Linting and Formatting\n\n1. Install the [Ruff PyCharm Plugin](https://plugins.jetbrains.com/plugin/20574-ruff)\n2. Open Preferences or Settings | Tools | Ruff\n    - **Check**: Run Ruff when the python file is saved\n    - **Check**: Use Import Optimizer\n    - **Check**: Use ruff format\n\nNow, on \u003ckbd\u003ectrl+s\u003c/kbd\u003e, the current source file will be automatically formatted and imports\nsorted on save.\n\n\u003e [!TIP]\n\u003e These tools work best if you properly mark directories as excluded from the project that should\n\u003e be, such as `.nox`. See\n\u003e \u003chttps://www.jetbrains.com/help/pycharm/project-tool-window.html#content_pane_context_menu\u003e on\n\u003e how to Right-Click | Mark Directory as | Excluded.\n\n## Nox Support\n\n[PyCharm does not yet natively support Nox](https://youtrack.jetbrains.com/issue/PY-37302). The\nrecommended way to launch Nox from PyCharm is to create a **Python**\n[Run Configuration](https://www.jetbrains.com/help/pycharm/run-debug-configuration.html).\n\n- Beside **Script Path**, press `▼` and select **Module name**: `nox`\n- **Parameters**, enter a Nox session: `-s test`\n- **Working Directory**: Enter the path to the current project\n- Select **Modify Options** | Check **Emulate terminal in output console** to enable colors to be\n  rendered properly\n\n![](docs/static/images/nox_run_configuration.png)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnthagen%2Fpython-blueprint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohnthagen%2Fpython-blueprint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnthagen%2Fpython-blueprint/lists"}