Because Martin wanted some starting point, here is how I set up my Python. There are a lot of other things one can do, but this is supposed to be just a starting point.

For a new project, make a project directory, usually not with a local git repository.

kk:Python kris$ mkdir project
kk:Python kris$ cd project
kk:project kris$ git init
Initialized empty Git repository in /Users/kris/Python/project/.git/

We need a virtual environment to keep our modules apart from the system python.

kk:project kris$ python3 -mvenv ~/.venv/project

In my case, I have a bash PROMPT_COMMAND installed that automatically activates the venv when I enter a directory. From the .bashrc, some highly insecure code:

autoenv() {
  if [ -f ./.venv ]; then
    nv=$(cat ./.venv)
    if [ -f "$nv/bin/activate" ]; then
      if [ "$VIRTUAL_ENV" != "$nv" ]; then
        source $nv/bin/activate
      fi
    fi
  fi
}

PROMPT_COMMAND=autoenv

And then:

kk:project kris$ echo /Users/kris/.venv/project > .venv
(project) kk:project kris$

As can be seen from the prompt, the venv already active. Usually, after install the pip is outdated.

(project) kk:project kris$ pip install --upgrade pip
...
Successfully installed pip-20.1.1

We also need wheel for C-Language Extensions, and black, mypy and pre-commit.

(project) kk:project kris$ pip install wheel
...
Successfully installed wheel-0.34.2
(project) kk:project kris$ pip install black mypy pre-commit
...
Installing collected packages: click, attrs, pathspec, regex, toml, appdirs, typed-ast, black, mypy-extensions, typing-extensions, mypy, cfgv, filelock, six, zipp, importlib-metadata, distlib, virtualenv, nodeenv, pyyaml, identify, pre-commit
Successfully installed appdirs-1.4.4 attrs-19.3.0 black-19.10b0 cfgv-3.1.0 click-7.1.2 distlib-0.3.1 filelock-3.0.12 identify-1.4.20 importlib-metadata-1.7.0 mypy-0.782 mypy-extensions-0.4.3 nodeenv-1.4.0 pathspec-0.8.0 pre-commit-2.5.1 pyyaml-5.3.1 regex-2020.6.8 six-1.15.0 toml-0.10.1 typed-ast-1.4.1 typing-extensions-3.7.4.2 virtualenv-20.0.25 zipp-3.1.0

The last three collect a large number of additional dev-dependencies.

We then need to set-up the pre-commit environment:

(project) kk:project kris$ cat .pre-commit-config.yaml
repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v2.3.0
    hooks:
    - id: check-yaml
    - id: end-of-file-fixer
    - id: trailing-whitespace
-   repo: https://github.com/psf/black
    rev: ''
    hooks:
    - id: black
-   repo: https://github.com/pre-commit/mirrors-mypy
    rev: ''
    hooks:
    - id: mypy
      additional_dependencies: [ ]
(project) kk:project kris$ pre-commit install
pre-commit installed at .git/hooks/pre-commit

Pre-commit is running mypy and black, but in a separate environment, so it will be installed again, elsewhere, and we will also need to declare our mypy dependencies here again, unfortunately.

On the other hand, all our Python will be type-checked and blacken’ed automatically on commit.

(project) kk:project kris$ cat probe.py
#! /usr/bin/env python

import sys

print(f"{sys.version}")

And here we go:

(project) kk:project kris$ git add probe.py
(project) kk:project kris$ git commit -m 'testing'
Check Yaml...........................................(no files to check)Skipped
Fix End of Files.........................................................Passed
Trim Trailing Whitespace.................................................Passed
black....................................................................Passed
mypy.....................................................................Passed
[master (root-commit) c7734e4] testing
 1 file changed, 5 insertions(+)
 create mode 100755 probe.py

Now, I usually collect additional project dependencies in requirements.txt and freeze them into requirements-frozen.txt for deployment into a Python Container.

For example, in one project:

(bridge) kk:bridge kris$ cat requirements.txt
paho-mqtt
influxdb
(bridge) kk:bridge kris$ cat requirements-frozen.txt
paho-mqtt==1.5.0
influxdb==5.3.0
## The following requirements were added by pip freeze:
certifi==2020.4.5.1
chardet==3.0.4
idna==2.9
msgpack==0.6.1
python-dateutil==2.8.1
pytz==2020.1
requests==2.23.0
six==1.15.0
urllib3==1.25.9
(bridge) kk:bridge kris$ cat Dockerfile
FROM python:3.8-alpine

LABEL maintainer="isotopp" \
      description="MQTT to InfluxDB Bridge"

COPY requirements-frozen.txt /tmp/requirements.txt
RUN pip install -r /tmp/requirements.txt

COPY . /app
WORKDIR /app

CMD [ "python3", "-u", "bridge.py" ]

This is part of a larger deployment in a docker-compose.yml then:

---
version: "3"

services:
...
  bridge:
    build: "./bridge"
    image: "isotopp/mqttbridge"
    container_name: "mqttbridge"
    hostname: "mqttbridge"
    user: "1000"
    depends_on:
      - "influxdb"
      - "mosquitto"
    restart: "always"
...