My Python Setup

[…] because the language is easy to learn and put to use, many practicing Python programmers leverage only a fraction of its powerful features.\
Luciano Ramalho1

Is the thought of not fully leveraging the Python environment daunting you?2 We are not that different then. Thus my constant search for new tools and ways of improving my Python experience.

This is an opinionated summary-guide of my current setup for the future me. Feel free to follow along with me!

My current operating system is Windows. I am using WSL2 inside Windows Terminal. My IDE of choice is PyCharm.

Note

PyCharm integration with WSL and Docker is a Professional feature. Think about using Visual Studio Code instead.

Installing Python

The first step in setting up our dev environment is installing Python in our WSL. We use sudo apt-get update && sudo apt-get dist-upgrade to re-synchronize the package index files, upgrade, and handles dependencies conflicts.

If you are using multiple versions of Python, think about using pyenv. Otherwise, we install our preferred version of Python using the following commands:

sudo apt install software-properties-common
sudo add-apt-repository -y ppa:deadsnakes/ppa
sudo apt install python3.8

Then we check everything went as expected:

$ python3 --version
Python 3.8.5

I prefer having Python 3 as my default version – using python instead of python3. On Ubuntu 20.04+ you can do the following to achieve that:

sudo apt install python-is-python3

Then:

$ python --version
Python 3.8.5

Installing Poetry

Poetry is a tool for dependency management and packaging in Python.

Poetry is my chosen solution for dependencies management. I switched from Pyenv after some stability issues.

To install Poetry:

curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -

A quick health check:

$ poetry --version
Poetry version 1.1.4

My playground is laboratory on my C drive. To create a new project:

$ cd /mnt/c/laboratory
$ poetry new python-boilerplate
Created package python_boilerplate in python-boilerplate

Then inside python-boilerplate

$ cd python-boilerplate
$ poetry install
Creating virtualenv python-boilerplate-zUD6aEZx-py3.8 in /home/ayoub/.cache/pypoetry/virtualenvs
Updating dependencies
Resolving dependencies... (1.7s)

Writing lock file

Package operations: 8 installs, 0 updates, 0 removals

  • Installing pyparsing (2.4.7)
  • Installing attrs (20.3.0)
  • Installing more-itertools (8.6.0)
  • Installing packaging (20.7)
  • Installing pluggy (0.13.1)
  • Installing py (1.9.0)
  • Installing wcwidth (0.2.5)
  • Installing pytest (5.4.3)

Installing the current project: python-boilerplate (0.1.0)

Notice that Pytest is installed by default. The highlighted line contains the path to your virtual environments; /home/ayoub/.cache/pypoetry/virtualenvs. We will use this path to set up PyCharm. Keep it!

Setting up PyCharm

We first open the project using your PyCharm. My project directory is /mnt/c/laboratory inside WSL or C:\laboratory\python-boilerplate in Windows.

To add a new Python interpreter to PyCharm: CTRL + ALT + S > Python Interpreter > > Add.

Adding Python Interpreter to PyCharm

Then from the side menu: WSL > ... > /home/ayoub/.cache/pypoetry/virtualenvs/python-boilerplate-zUD6aEZx-py3.8/bin/python3.8. The path is to your virtual enviroment generated by Poetry. Use poetry env info to print it again.

PyCharm Python Environment Path

With Python interpreter set up. Let’s change the Terminal to WSL instead of CMD: CTRL + ALT + S > Terminal > Shell path > wsl.exe

PyCharm Terminal to WSL

Installing development dependencies

Testing

Programmers! Cast out your guilt! Spend half your time in joyous testing and debugging! Stalk bugs with care, methodology, and reason. Build traps for them. Be more artful than those devious bugs and taste the joys of guiltless programming! 3
— Boris Beizer

Pytest, the framework of choice for testing in Python, is installed by default with Poetry. To keep an eye on coverage we use Coverage.py:

poetry add --dev coverage[toml] pytest-cov

The [toml] extension allows us to configure coverage through pyproject.toml file. While pytest-cov is the plugin to use Pytest to generate coverage reports like so:

pytest --cov=myproj tests/
-------------------- coverage: ... ---------------------
Name                 Stmts   Miss  Cover
----------------------------------------
myproj/__init__          2      0   100%
myproj/myproj          257     13    94%
myproj/feature4286      94      7    92%
----------------------------------------
TOTAL                  353     20    94%

If you feel optimistic add the following configuration to your pyproject.toml file:

# pyproject.toml
[tool.coverage.report]
fail_under = 100

For some “Mocking”, explore pytest-mock.

Code linting

Flake8 is a wrapper around these tools:

Flake8 runs all those tools by launching a single flake8 command. It displays the warnings in a per-file, merged output. Flake8 could be reinforced with additional extensions. We will use a few of those here.

poetry add --dev flake8

To configure your Flake8 create .flake8 in the root of your project and add these lines:

# .flake8
[flake8]
extend-ignore = E203, E266, E501
max-line-length = 88
max-complexity = 18

For Flake8 to play nice with Black we will be ignoring some rules:

We ignored the E501 while setting the max line length to 88; the default in Black.

Code formatting

Speaking of which, Black is the uncompromising Python code formatter. Black just works. No need for configuration. We use Flake8’s extension flake8-black to run Black --check ... from within the Flake8 plugin ecosystem.

poetry add --dev black flake8-black

The basic configuration for Black:

# Borrowed from: https://github.com/psf/black/blob/master/pyproject.toml
# pyproject.toml
[tool.black]
include = '\.pyi?$'
exclude = '''
/(
    \.git
  | \.hg
  | \.mypy_cache
  | \.tox
  | \.venv
  | _build
  | buck-out
  | build
  | dist
)/
'''

We add isort a Python utility to sort imports alphabetically, and automatically separated into sections and by type.

poetry add --dev isort flake8-isort

Then again:

# pyproject.toml
[tool.isort]
multi_line_output = 3
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
ensure_newline_before_comments = true
line_length = 88

Defining tasks

Every development pipeline has tasks, such as test, lint or publish. With taskipy, you can define those tasks in one file and run them with a simple command.

poetry add --dev taskipy
# myproject.toml
[tool.taskipy.tasks]
format = "black . && isort ."

Pre-commit hooks

I think we should use Git for this project, even though python-boilerplate-v3 is a much more elegant solution than Git syntax.

Note

Don't forget to set up your .gitignore.

To make the pipeline more robust we use pre-commit. A framework for managing and maintaining multi-language pre-commit hooks.

Git hook scripts are useful for identifying simple issues before submission to code review. We run our hooks on every commit to automatically point out issues in code such as missing semicolons, trailing whitespace, and debug statements.

poetry add --dev pre-commit

Place the configuration in the root directory inside .pre-commit-config.yaml:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/psf/black
    rev: 20.8b1
    hooks:
    -   id: black
  - repo: https://github.com/PyCQA/isort
    rev: '5.6.4'
    hooks:
    -   id: isort
  - repo: https://gitlab.com/pycqa/flake8
    rev: 3.8.4
    hooks:
      - id: flake8

We run with this configuration Black, Isort, and Flake8 before each commit.

Documentation

I always document my functions. To do the same, and once you are done writing your function in PyCharm, right after your function definition, start typing a multiline string starting with """. PyCharm will generate the boilerplate for your function docstring documentation automatically.

To check for these docstrings we will use flake8-docstrings.

poetry add --dev flake8-docstrings

We use Sphinx to extract these definitions into standalone documentation. And sphinx-autodoc-typehints brings type hints supports to Sphinx.

poetry add --dev sphinx sphinx-autodoc-typehints

Create a docs directory to store your documentation:

mkdir docs && cd docs
poetry run sphinx-quickstart

Then change the list of extensions inside of docs/cong.py

# docs/cong.py
extensions = [
    "sphinx.ext.autodoc",
    "sphinx_autodoc_typehints",
]

After that let’s create the following file:

.. docs/api.rst
API!
===============================================
.. automodule:: src.python_boilerplate.main
   :members:

And reference it inside the index.rst:

.. docs/index.rst
.. toctree::
   :maxdepth: 2
   :caption: Contents:

   api
poetry run make html

Check the generated HTML files for the docs!

Conclusion

The combinations of Python libraries are truly endless. And that’s the power of Python — laugh in Elm. We didn’t get into CI/CD pipelines and containerization, I opted to keep those for future explorations. And with that, I wish you a Happy new year!


  1. Ramalho, Luciano. Fluent Python. O’Reilly Media, 2015. ↩︎

  2. Fear of missing out ↩︎

  3. Beizer, Boris. Software Testing Techniques, 2nd Edition. Itp - Media, 1990. ↩︎