Aller au contenu

Python

1. Généralités

Ce Makefile étend les fonctionnalités communes en ajoutant notamment :

Exemple de Makefile à la racine de votre projet :

# Configuration de l'interpréteur Python
#
# AUTO_3_8  => auto-downloade python 3.8
# AUTO_3_9  => auto-downloade python 3.9 (defaut)
# AUTO_3_10 => auto-downloade python 3.10
# python3   => utilise le binaire python3 du système (doit être présent dans le PATH système)
#
# [note: cette variable spécifique doit être définie AVANT les includes]
PYTHON=AUTO_3_9

# Toujours inclure le "common_makefile.mk"
include .common_makefiles/common_makefile.mk

# L'inclusion de ce Makefile "python_makefile.mk" apporte les
# éléments spécifiques décrits sur cette page
include .common_makefiles/python_makefile.mk

APP_DIRS=myapp
TEST_DIRS=tests

Pour une arborescence classique Python du type :

/myproject/
    .common_makefiles/
        common_makefile.mk
        python_makefile.mk
        extra.tar
        [...]
    myapp/
        __init__.py
        mymodule.py
    tests/
        __init__.py
        test_basic.py
    Makefile
    setup.py

2. Gestion des dépendances (venv)

2.1 Modèle

Ce Makefile introduit une gestion complète des dépendances et de leur cycle de vie sur le modèle suivant :

graph LR
  subgraph "Utilisés à l'exécution"
  VENV["venv (en mode runenv)"]
  end
  subgraph "Automatiquement générés"
  R[requirements.txt] -- "génère précisément et<br> de façon reproductible" --> VENV
  end
  subgraph "Renseignés par le développeur"
  RNOT[requirements-notfreezed.txt] -- "génère en interrogeant<br>les versions disponibles" --> R
  F[forced-requirements.txt] -. "peut forcer certaines lignes" .-> R
  end
  style F stroke-dasharray: 5 5

Des dépendances sont exprimées de façon relativement laches dans le fichier requirements-notfreezed.txt. Dans l'esprit, vous ne devez y lister que les dépendances explicites de votre projet sous une forme relativement lache et flexible. Par exemple : django >=3.2,<4.0.

A partir de ce fichier, les "common makefiles" vont fabriquer automatiquement le fichier requirements.txt en interrogeant PyPi (et/ou d'autres repositories configurables) pour déterminer les versions exactes actuellement applicables ainsi que les dépendances de niveau supérieur (les dépendances de vos dépendances et ... récursivement). La liste complète ainsi obtenue (nom du package et version exacte) est alors consignée dans le fichier requirements.txt.

C'est ce fichier (et uniquement celui là) qui fabriquera l'environnement d'exécution virtuel (venv).

Le format a utiliser pour exprimer vos dépendances dans le fichier requirements-notfreezed.txt est le format standard des "requirements pip" :

C'est quoi ce forced-requirements.txt optionnel ?

C'est une fonctionnalité avancée utile dans assez peu de cas.

Dans certains cas, on peut souhaiter influencer la génération du requirements.txt et notamment le rendre un peu moins strict en terme de numéro de version.

L'exemple type c'est quand parmi les dépendances, on a une dépendance qui est gérée par nous même et dont on ne veutcl pas préciser le numéro de version précis dans requirements.txt (pour, par exemple, toujours prendre la dernière). Dans ce cas, il suffit de créer un fichier forced-requirements.txt en utilisant le même format que requirements-notfreezed.txt contenant uniquements les lignes qu'on veut forcer. Par exemple : malib. De ce fait, le ligne concernant le package malib dans requirements.txt qui était probablement qqch du type malib==1.2.3 sera transformée en malib.

2.2 devenv

Le principe est exactement le même si le schéma est en fait un petit peu plus compliqué dans la mesure où il y a une distinction entre l'environnement d'exécution simple (runenv) et l'environnement de développement complet (devenv) qui ajoute des dépendances en plus requises uniquement pendant la phase de développement (des linters par exemple).

L'environnement de dev est conçu comme un simple override de l'environnement d'exécution. Donc les prérequis (noms et versions exactes) de l'environnement d'exécution forcent les dépendances de développements (qui viennent donc uniquement se rajouter).

On obtient le schéma suivant :

graph LR
  subgraph "Utilisés à l'exécution"
  VENV["venv (en mode devenv)"]
  end
  subgraph "Automatiquement générés"
  R[requirements.txt] -- "force" --> D
  D[devrequirements.txt] -- "génère précisément et<br> de façon reproductible" --> VENV
  end
  subgraph "Renseignés par le développeur"
  RNOT[requirements-notfreezed.txt] -- "génère en interrogeant<br>les versions disponibles" --> R
  DNOT[devrequirements-notfreezed.txt] -- "génère en interrogeant<br>les versions disponibles" --> D
  F[forced-requirements.txt] -. "peut forcer certaines lignes" .-> R
  F[forced-requirements.txt] -. "peut forcer certaines lignes" .-> D
  end
  style F stroke-dasharray: 5 5

Le format et la logique d'un fichier devrequirements-notfreezed.txt est donc tout à fait similaire au fichier requirements-notfreezed.txt mais pour les dépendances spécifiques au développement. Le fichier devrait également commencer par la ligne -r requirements.txt pour lui dire de prendre en dépendance les versions exactes retenues pour l'environnement d'exécution.

2.3 FAQ

Je dois rajouter une dépendance foo >= 1.0,<2.0 à mon projet, j'utilise quel fichier ?

Si c'est une dépendance qui n'est requise pour la phase de développement, ajoutez la ligne foo >=1.0,<2.0 dans le fichier devrequirements-notfreezed.txt.

Si c'est une dépendance qui doit être présente dans tous les cas (runtime ou devtime), ajoutez la ligne foo >=1.0,<2.0 dans requirementst-notfreezed.txt.

Comment je génère automatiquement les fichiers intermédiaires ou mon virtualenv ?

  • make runenv pour environnement d'exécution
  • make devenv pour un environnement de développement
  • make tout court suffit également si vous avez déjà fait votre choix une première fois (runenv ou devenv)

Comment les "common makefiles" savent s'il faut recréer les fichiers intermédiaires et/ou le virtualenv ?

C'est la date de modification des fichiers qui permet de déterminer ce qui est plus vieux et donc ce qui nécessite d'être reconstruit.

Comment forcer une mise à jour de tous les fichiers intermédiaires et de mon virtualenv ?

Un touch *requirements-notfreezed.txt suivi d'un make suffit. Si vous préférez, un alias make refresh_venv est également disponible.

Dois je commiter mes fichiers *requirements.txt ?

Si vous ne les commitez pas, ils seront reconstruits à chaque fois après chaque git clone mais ils seront reconstruits en tenant compte des versions disponibles (sur les repositories configurés) au moment de la reconstruction. Du coup, vous n'aurez pas un comportement parfaitement reproductible (votre application peut fonctionner à l'instant T1 et ne plus fonctionner car une dépendance incompatible aura été publiée sur internet à l'instant T2).

Donc, oui, nous vous conseillons très fortement de les commiter.

Comment rentrer dans le venv en interactif ou depuis mon Makefile ?

En interactif, et c'est simplement lié à la manière dont les virtualenv python fonctionnent, vous devez "charger" le venv pour en bénéficier (version python, dépendances installées...). Pour le faire, utilisez la commande suivante dans votre terminal :

` source venv/bin/activate (une fois chargé, deactivate permet d'en sortir ou alors relancez simplement votre terminal)

Depuis votre Makefile, vous avez un racccourci qui consiste à rajouter $(ENTER_VENV) && devant votre commande. Par ex:

Makefile macible: $(ENTERVENV) && ma_commande

Puis je continuer d'utiliser pip avec ce système ?

Une fois le venv chargé, vous pouvez bien sûr utiliser pip manuellement. Mais à chaque fois que le venv sera reconstruit automatiquement à partir des fichiers *requirements.txt, vous perdrez le bénéfice des opérations exécutées manuellement. Donc si vous utilisez pip manuellement pour tester quelque chose par exemple, n'oubliez pas à la fin de votre test de reporter vos ajouts/modifications/suppressions dans le fichier *requirements-notfreezed.txt correspondant.

3. Les linters / reformaters

Par défaut, si le fichier n'existe pas déjà, le Makefile va vous générer un fichier devrequirements-notfreezed.txt contenant des dépendances à :

De ce fait, un simple make lint va les faire passer sur votre code.

Qu'est ce qui définit mon code ?

Les linters sont exécutés sur les répertoires pointés par les 2 variables suivantes qui devraient être définis dans votre Makefile :

  • APP_DIRS : liste de répertoires (séparés par des espaces) contenant le code Python de votre projet
  • TEST_DIRS : liste de répertoires (séparés par des espaces) contenant vos tests unitaires (laissez cette variable vide si vous n'avez pas de tests)

Si vous souhaitez retirer des linters, effacez simplement leur nom dans le fichier devrequirements-notfreezed.txt.

Vous pouvez également en ajouter. La liste des linters supportés "out of the box" est la suivante : flake8, black, isort, pylint, mypy, import-linter, safety et bandit.

Ajouter un linter/reformater non pris en charge ?

Si vous voulez ajouter un linter/reformater non pris en charge, vous pouvez ajouter une cible custom_lint et/ou custom_reformat qui sera automatiquement exécutée lors du make lint et/ou make reformat.

Vous pouvez également agir sur plein de paramètres de configuration (pour tuner leur comportement). Consultez la page de référence python pour plus de détails et/ou la page de référence commune.