Skip to content

A cookiecutter template for a Python package, with heaps of engineering goodness baked into it.

License

Notifications You must be signed in to change notification settings

pragmaticindustries/python-package-template

 
 

Repository files navigation

license pre-commit conventional-commits black mypy pylint pytest coverage

Python Package Template

This repository is intended to be a base template, a cookiecutter for a new Python package project while keeping PEP518 in mind. Because it’s hosted on Github it already utilizes a few Github Actions that enforce repository-side checks for continuous integration and that implement a semantic release setup. And while this package is a starting point for a Python project with good engineering practices, it’s intended to be improved and added to in various ways — see the Wiki for more suggestions.

Table of Contents

Features
Typing
Quality assurance
Unit testing
Documentation
Versioning and publishing
Dependency analysis
Security analysis
Standalone
How to use this repository
Git hooks
Testing
Generating documentation
Versioning, publishing and changelog
Frequently asked questions

Features

The badges above give you an idea of what this project template provides. It’s work in progress, and I try to enable as much engineering goodness as is possible and is sensibly bearable using git hooks (see below) and Github Actions.

Typing

The package requires a minimum of Python 3.9 and supports Python 3.10. All code requires comprehensive typing. The mypy static type checker is invoked by a git hook and through a Github Action to enforce continuous type checks. Make sure to add type hints to your code or to use stub files for types, to ensure that users of your package can import and type-check your code (see also PEP 561).

Quality assurance

A number of git hooks are invoked before and after a commit, and before push. These hooks are all managed by the pre-commit tool and enforce a number of software quality assurance measures (see below).

Unit testing

Comprehensive unit testing is enabled using pytest combined with Hypothesis (to generate test payloads and strategies), and test code coverage is measured using coverage (see below).

Documentation

Documentation is important, and Sphinx is set up already to produce standard documentation for the package, assuming that code contains docstrings with reStructuredText (see below).

Versioning and publishing

Automatic package versioning and tagging, publishing to PyPI, and Changelog generation are enabled using Github Actions (see below).

Dependency analysis

Dependabot is enabled to scan the dependencies and automatically create pull requests when an updated version is available.

Security analysis

CodeQL is enabled to scan the Python code for security vulnerabilities. You can adjust the GitHub Actions workflow at .github/workflows/codeql-analysis.yaml and the configuration file at .github/codeql/codeql-config.yaml to add more languages, change the default paths, scan schedule, and queries.

Additionally, the bandit tool is being installed as part of a development environment (i.e. the [dev] package extra); however, bandit does not run automatically! Instead, you can invoke it manually:

bandit --recursive src  # Add '--skip B101' when checking the tests, Bandit issue #457.

Standalone

In addition to being an importable standard Python package, the package is also set up to be used as a runnable and standalone package using Python’s -m command-line option, or by simply calling its console script wrapper something which is automatically generated and installed into the hosting Python environment.

How to use this repository

If you’d like to contribute to the project template, please open an issue for discussion or submit a pull request.

If you’d like to start your own Python project from scratch, you can either copy the content of this repository into your new project folder or fork this repository. Either way, consider making the following adjustments to your copy:

  • Change the LICENSE.md file and the license badge according to your needs, replace the symbolic link README.md with an actual README file, and similarly replace the symbolic link CHANGELOG.md with an actual CHANGELOG file which contains a single line:

    <!--next-version-placeholder-->
  • Install pre-commit and set it up for your new package repository to ensure that all git hooks are active:

    pre-commit install
    pre-commit install --hook-type commit-msg
    pre-commit install --hook-type pre-push
  • Rename the src/package/ folder to whatever your own package’s name will be, and adjust the Github Actions in .github/workflows/, setup.py, pyproject.toml, pre-commit-config.yaml and the unit tests accordingly.

  • Adjust the content of the setup.py file according to your needs, and make sure to fill in the project URL, maintainer and author information too. Don’t forget to reset the package’s version number in src/package/__init__.py.

  • If you import packages that do not provide type hints into your new repository, then mypy needs to be configured accordingly: add these packages to the mypy.ini file using the ignore-missing-imports option.

  • If you’d like to publish your package to PyPI then set the upload_to_pypi variable in the pyproject.toml file to true.

  • Adjust the Dependabot settings in .github/dependabot.yaml to your desired target branch that you’d like to have monitored by Dependabot.

To develop your new package, create a virtual environment and install its dev, test and docs dependencies:

python3.10 -m venv .
source ./bin/activate
pip install --upgrade pip
pip install --editable .[dev,test,docs]

With that in place, you’re ready to build your own package.

Git hooks

Using the pre-commit tool and its .pre-commit-config.yaml configuration, the following git hooks are active in this repository:

  • When committing code, a number of pre-commit hooks ensure that your code is formatted according to PEP 8 using the black tool, and they’ll invoke flake8 (and various plugins), pylint and mypy to check for lint and correct types. There are more checks, but those two are the important ones. You can adjust the settings for these tools in one of the pyproject.toml or pylintrc or mypy.ini or .flake8 configuration files.
  • The commit message hook enforces conventional commit messages and that, in turn, enables a semantic release of this package on the Github side: upon merging changes into the main branch, the semantic release action produces a changelog and computes the next version of this package and publishes a release — all based on the commit messages.
  • Using a pre-push hook this package is also set up to run pytest; in addition, the coverage plugin makes sure that all of your package’s code is covered by tests and Hypothesis is already installed to help with generating test payloads.

Testing

As mentioned above, this repository is set up to use pytest either standalone or as a pre-push git hook. Tests are stored in the tests/ folder, and you can run them manually like so:

pytext

which runs all tests in both your local Python virtual environment. For more options, see the pytest command-line flags. Also note that pytest includes doctest, which means that module and function docstrings may contain test code that executes as part of the unit tests.

Test code coverage is already tracked using coverage and the pytest-cov plugin for pytest. Code coverage is tracked automatically when running pytest; in addition, the plugin can be explicitly invoked with the following command line:

pytest --cov package tests

and measures how much code in the src/package/ folder is covered by tests:

============================= test session starts =============================
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- ...
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/.../.hypothesis/examples')
rootdir: /.../python-package-template, configfile: pyproject.toml, testpaths: tests
plugins: hypothesis-6.24.2, cov-3.0.0
collected 1 item  

tests/test_something.py::test_something PASSED                           [100%]

---------- coverage: platform darwin, python 3.10.0-final-0 -----------
Name                   Stmts   Miss  Cover   Missing
----------------------------------------------------
package/__init__.py        1      0   100%
package/something.py       4      0   100%
----------------------------------------------------
TOTAL                      5      0   100%

Required test coverage of 100.0% reached. Total coverage: 100.00%

============================== 1 passed in 0.07s ==============================

Note that code that’s not covered by tests is listed under the Missing column.

Hypothesis is a package that implements property based testing and that provides payload generation for your tests based on strategy descriptions (more). Using its pytest plugin Hypothesis is ready to be used for this package.

Generating documentation

As mentioned above, all package code should make use of Python docstrings in reStructured text format. Using these docstrings and the documentation template in the docs/source/ folder, you can then generate proper documentation in different formats using the Sphinx tool:

cd docs
make html

This example generates documentation in HTML, which can then be found here:

open _build/html/index.html

Versioning, publishing and changelog

To enable automation for versioning, package publishing, and changelog generation it is important to use meaningful conventional commit messages! This package template already has a semantic release Github Action enabled which is set up to take care of all three of these aspects — every time changes are merged into the main branch.

For more configuration options, please refer to the tool.semantic_release section in the pyproject.toml file, and read the semantic release documentation.

You can also install and run the tool manually, for example:

pip install python-semantic-release
semantic-release changelog
semantic-release version

Use the --verbosity=DEBUG command-line argument for more details.

Frequently asked questions

  • Question: Why don’t you use tools like tox or nox to orchestrate testing?
    Answer: We’ve removed tox based on a discussion in issue #100 and PR #102. In short: we want to run tests inside the development venv using pytest, and run more tests using an extensive test matrix using Github Actions.

About

A cookiecutter template for a Python package, with heaps of engineering goodness baked into it.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Python 95.8%
  • JavaScript 4.2%