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
- 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. -
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' {} \; make install
this will install all the dependencies and initialize pre-commit hookmake test
- Make sure it’s working:
poetry run app-cli "Developer"
- Optionally, squeeze history into one commit:
git reset $(git commit-tree HEAD^{tree} -m "Initial commit")