..

Modern Python application boilerplate

TL;DR: jump straight to: https://github.com/b1r3k/python-poetry-boilerplate

There is a lot of tooling and nitty-gritty details when it comes to bootstrapping project. This post is going to be a collection of best practices and tools which can be used to make life easier if one want’s to use python. Let’s go through them one by one.

Tools

Pyenv

It’s like rbenv or nvm but for python. It allows to install multiple versions of python and switch between them easily. It’s also possible to install python versions which are not available in package manager because you do not use rolling release distro. Just follow the instructions on the pyenv github page and you should be good to go. You can define python version for your project by creating .python-version file in the root of your project. Pyenv will automatically switch to that version when you enter the directory. You can also use pyenv local command to set python version for the current directory.

Poetry

Poetry is a tool for dependency management and packaging in Python. Basically it replaces setup.py / requirements based approach from old days - I find it stable enough to use it in production. Everything can be defined in pyproject.toml file. There is also poetry.lock useful for fixing dependencies versions. Poetry also allows to create virtualenvs for your project automatically. Few useful commands:

poetry init # initialize new project
poetry add <package> # add new package to your project
poetry add <package> --dev # add new package to your project as dev dependency
poetry add <package>=<version> # add new package to your project with specific version
poetry install # install dependencies
poetry shell # spawn shell in virtualenv
poetry run <command> # run command in virtualenv

Pre-commit

pre-commit is useful for running linters, formatters, or anything really when one makes commit. It’s also possible to create custom hooks. There are even some hooks for poetry too, but I’m using pre-commit to run black, isort, flake8 - all of that is done on git commit. There is one file to manage all of that - .pre-commit-config.yaml. Setup is being done via make target:

install:
	poetry install
	test -d .git/hooks/pre-commit || poetry run pre-commit install

Formatters: Black & Isort

Black is a code formatter. It’s opinionated, but very easy to use - just run black . and it will format all the files, recursively. Isort is a tool for sorting imports, just run isort . and it will sort all the imports statements in all files recursively. You can run it manually via make target lint-fix but both run on pre-commit hook.

lint-fix:
	poetry run isort --profile black .
	poetry run black ${APP_DIR}

Flake8

Flake8 is a tool for linting. It’s first step of enforcing code style when used with pre-commit hook. You can customize it via .flake8 file. One greate feature I love is measurement of Cyclomatic complexity - Wikipedia of functions when max-complexity is set. You can run it manually via make target lint-check but it runs on pre-commit hook.

lint-check:
	poetry run flake8 ${APP_DIR}
	poetry run mypy .

Pytest

My choice for running tests. I don’t run it on commit, but I have defined make target for it:

test:
	poetry run pytest --failed-first -x

I don’t run it in pre-commit hook because I prefer to run it in loop mode using this make target for example:

testloop:
	watch -n 3 poetry run pytest ${PYTEST_FLAGS}

Mypy (optional)

Mypy is a static type checker. It’s optional, but you may find it useful. You can run it manually via make target lint-check.

How to use it

  1. Make sure you have required python interpreter installed in pyenv in this case you need python >= 3.11 or adjust it in the pyproject.toml file.
  2. Rename app folder to your app name if needed (mv app_name new_app_name) and then:

    find ./ -type f -not -path "./.git/*" -exec sed -i 's/app_name/new_app_name/g' {} \;
    find ./ -type f -not -path "./.git/*" -exec sed -i 's/app-name/new-app-name/g' {} \;
    
  3. make install this will install all the dependencies and initialize pre-commit hook
  4. make test
  5. Make sure it’s working: poetry run app-cli "Developer"
  6. Optionally, squeeze history into one commit: git reset $(git commit-tree HEAD^{tree} -m "Initial commit")

Changelog

adds: summary section from repo readme
fixes: writing style
adds: final version of Modern Python application boilerplate
adds: draft for modern python app