Updated first chapter slides. Added new questions in the new training section.
284 lines
12 KiB
Markdown
284 lines
12 KiB
Markdown
---
|
||
title: Exporter un environnement Python
|
||
author: Steve Kossouho
|
||
mainfont: Source Sans Pro
|
||
---
|
||
|
||
# Exporter un environnement virtuel entre deux machines
|
||
|
||
Ou comment recréer un environnement virtuel sans devoir réinstaller toutes les dépendances à la main…
|
||
|
||
----
|
||
|
||
## Exporter l'environnement virtuel
|
||
|
||
Les environnements virtuels sont contenus dans des dossiers, et sont généralement dépendants du système sur lequel ils sont créés :
|
||
|
||
- L'exécutable `bin/python` y est un lien symbolique vers l'interpréteur Python qui a servi à créer l'environnement (version, OS, architecture processeur)
|
||
- les paquets externes écrits en C, et qui sont donc compilés, ne sont utilisables que sur une configuration spécifique (par exemple. Linux sur x86/64 Python 3.11, ou Windows sur ARM64 Python 3.12, ou Linux x86/32 Python 3.9 etc.); une release d'un tel paquet va générer de nombreuses variantes, et une seule version de Numpy existe actuellement dans 44 variantes.
|
||
|
||
Il serait rare qu'une simple copie du répertoire d'environnement virtuel permette de le réutiliser ailleurs.
|
||
|
||
----
|
||
|
||
…Et d'ailleurs, il existe une bien meilleure idée, beaucoup plus économe en efforts :
|
||
|
||
`pip` propose justement un moyen d'échanger une liste des dépendances installées dans l'environnement virtuel, pour pouvoir très simplement les réinstaller sur un autre système. Cette liste est appelée `fichier de dépendances`, ou `requirements.txt`. Elle est très souvent fournie avec des projets Python, justement pour en simplifier le partage et la réutilisation.
|
||
|
||
----
|
||
|
||
```{.bash .numberLines}
|
||
pip freeze --local > requirements.txt
|
||
```
|
||
|
||
Dans le terminal de votre projet (si votre environnement virtuel est activé, ce qui est le cas dans PyCharm), cette commande génère un fichier texte, `requirements.txt` qui contiendra une liste des paquets installés dans votre environnement ainsi que leur version.
|
||
|
||
----
|
||
|
||
Sur votre système cible, vous pouvez ensuite réimporter ce fichier et installer toutes les dépendances qui y sont inscrites avec cette commande :
|
||
|
||
```{.bash .numberLines}
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
Elles seront installées dans l'environnement virtuel actuellement activé. (pensez bien à l'y créer et à l'activer si besoin !)
|
||
|
||
----
|
||
|
||
## Créer manuellement un environnement virtuel
|
||
|
||
Pour créer un environnement virtuel Python, dans un terminal, tapez :
|
||
|
||
```{.bash .numberLines}
|
||
python -m venv <nom du nouveau dossier venv>
|
||
```
|
||
|
||
Le dossier créé contiendra un répertoire `bin` (`scripts` sous Windows) et un répertoire `lib`.
|
||
|
||
----
|
||
|
||
## Comment activer/désactiver un environnement virtuel
|
||
|
||
Vos environnements virtuels possèdent un sous-répertoire `bin`.
|
||
Pour activer et utiliser automatiquement votre environnement virtuel, il faut se rendre dans le dossier `bin` et taper dans un terminal :
|
||
|
||
```{.bash .numberLines}
|
||
source ./activate # ou activate.csh ou activate.fish
|
||
```
|
||
|
||
Sous Windows, vous devez plutôt simplement exécuter une commande :
|
||
|
||
```sh {.numberLines}
|
||
./scripts/activate.ps1 # Dans le powershell si vous êtes dans le répertoire du venv
|
||
```
|
||
|
||
(ça peut être `activate.fish` si votre shell utilise fish, et ainsi de suite...)
|
||
Votre invite de commande devrait contenir un nouvel élément, au moins entre parenthèses, vous indiquant que l'environnement virtuel est bien activé et utilisé.
|
||
|
||
----
|
||
|
||
Pour ensuite désactiver cet environnement virtuel, il vous suffit, dans le terminal où l'environnement est activé, de saisir :
|
||
|
||
```{.bash .numberLines}
|
||
deactivate
|
||
```
|
||
|
||
L'invite de commande du shell ne contiendra plus l'ajout indiquant que votre environnement est actuellement utilisé.
|
||
(Quand PyCharm active automatiquement un venv, la commande `deactivate`{.bash} est indisponible)
|
||
|
||
----
|
||
|
||
## Comment uniquement télécharger des paquets externes pour les machines déconnectées ?
|
||
|
||
Effectivement, vous pourriez vouloir créer un environnement où des machines nécessitant des paquets Python n'ont
|
||
pas accès à internet. Dans ce cas, il est possible d'utiliser `pip` pour utiliser un répertoire comme dépôt en remplacement de
|
||
PyPI. Ce répertoire peut être un répertoire réseau, par exemple, si vous avez au moins accès au réseau local.
|
||
|
||
----
|
||
|
||
Dans l'ordre, pour appliquer une telle mesure, vous pouvez :
|
||
|
||
- Utiliser le fichier de dépendances pour télécharger les archives des paquets nécessaires
|
||
- Placer les archives dans un dossier accessible sur le réseau (ou localement)
|
||
- Utiliser pip sur la machine cible, en précisant de ne pas utiliser PyPI mais notre dossier
|
||
|
||
----
|
||
|
||
```{.bash .numberLines}
|
||
pip download -r requirements.txt
|
||
```
|
||
|
||
Attention, ça va tout télécharger dans le répertoire en cours !
|
||
|
||
----
|
||
|
||
Sur la machine cible, il ne vous reste plus qu'à utiliser `pip` et lui dire d'aller chercher les paquets depuis le répertoire
|
||
local ou réseau :
|
||
|
||
```{.bash .numberLines}
|
||
pip install -r requirements.txt --no-index --find-links=file://chemin/repertoire
|
||
```
|
||
|
||
----
|
||
|
||
Si vous souhaitez créer un dépôt compatible avec PyPI, il vous suffit de créer un projet Python et d'installer dans votre environnement virtuel le paquet `pypiserver`. Il est très simple à utiliser, et [cet article chez Linode sur les dépôts pour pip](https://www.linode.com/docs/guides/how-to-create-a-private-python-package-repository/) devrait vous aider à y voir plus clair.
|
||
|
||
----
|
||
|
||
## Bonus : PyCharm et la gestion de vos dépendances
|
||
|
||
PyCharm est capable de remplir automatiquement votre fichier `requirements.txt` (possiblement avec la version) en analysant le
|
||
code de votre projet.
|
||
|
||

|
||
|
||
----
|
||
|
||
Dans le menu `Tools` → `Sync python requirements` (vous pouvez aussi utiliser le raccourci `Shift + Shift` pour rechercher rapidement l'entrée de menu), PyCharm vous offre un raccourci pour mettre à jour votre fichier de dépendances, conformément aux imports que vous avez utilisés dans votre code.
|
||
|
||
Il vous faudra préciser le chemin du fichier `requirements.txt` à mettre à jour, puis configurer la façon dont vous voudrez que PyCharm remplisse les dépendances (intégrer le numéro de version, supprimer les dépendances non utilisées, etc.)
|
||
|
||
----
|
||
|
||
## Extra : Gérer des paquets selon l'environnement
|
||
|
||
Selon la topologie dans laquelle vous évoluez (contraintes de sécurité incluses, ex. VPN), vous pouvez être amené à organiser vos projets de plusieurs manières. Du plus simple au plus difficile :
|
||
|
||
1. Projets Python sur un _dépôt Git public_
|
||
2. Projets Python sur un _dépôt Git privé_ (SSH etc.)
|
||
3. Projets Python packagés dans un dépôt brut sur un _répertoire réseau monté_
|
||
4. Projets Python packagés dans un serveur `pip`.
|
||
|
||
----
|
||
|
||
### Dépendances
|
||
|
||
Lorsque votre projet nécessite d'utiliser des bibliothèques Python pour fonctionner, il est rappelé qu'il est une bonne pratique de fournir avec celui-ci un fichier de _dépendances_. Ce fichier s'appelle par convention `requirements.txt`. Il contient la référence de toutes les bibliothèques que vous utilisez, dans la version dont vous avez besoin.
|
||
|
||
```{.requirements.txt .numberLines}
|
||
# Exemples de bibliothèques
|
||
ipython==8.15.0 # console Python avancée
|
||
pyside6==6.5.2 # Qt Framework
|
||
```
|
||
|
||
Une fois ce genre de fichier décrit, il est possible d'installer les dépendances qu'il référence avec la commande `pip install -r requirements.txt`.
|
||
|
||
----
|
||
|
||
### Dépôt Git public
|
||
|
||
Le fichier de dépendances de votre projet, **si vous avez accès à celles-ci**, peut contenir des entrées vers des branches ou des commits de projets présents sur un dépôt Git. Par exemple, si vous souhaitez inclure `Django==3.2.0`, vous pouvez rédiger une entrée similaire au contenu suivant :
|
||
|
||
```requirements.txt
|
||
# L'entrée est utilisée comme option d'un pip install
|
||
git+https://github.com/django/django.git@3.2
|
||
```
|
||
|
||
----
|
||
|
||
### Dépôt Git privé
|
||
|
||
Si votre entrée provient d'une usine logicielle et que votre dépôt est privé, l'entrée de votre fichier de dépendances reste la même, mais vous devrez peut-être posséder des identifiants pour y accéder. En général, autoriser une clé SSH configurée sur votre machine de développement sera nécessaire, mais probablement la seule étape nécessaire.
|
||
|
||
----
|
||
|
||
### Dossier monté
|
||
|
||
Monter un dossier distant consiste à rendre disponible localement l'accès à un répertoire présent sur une machine à distance (généralement un NAS).
|
||
|
||
Il est possible de demander à l'outil `pip` de télécharger et d'installer des paquets disponibles sur le protocole `file://`, à condition d'utiliser l'option `pip install -r requirements.txt --find-links=file:///path/to/dir`.
|
||
|
||
Les paquets proposés par vos collègues doivent être installables par `pip` et les archives (aux formats `.tar.gz` et `.whl`) doivent être disponibles dans ce répertoire. [Voir le protocole officiel pour la création de paquets Python](https://packaging.python.org/en/latest/tutorials/packaging-projects/)
|
||
|
||
----
|
||
|
||
### Imports dynamiques
|
||
|
||
Il peut arriver que, lors de la création d'une bibliothèque, un développeur doive s'adapter à des situations où certains imports ne seront pas disponibles.
|
||
Un cas extrême serait d'introduire l'utilisation de `pathlib` (disponible uniquement dans Python 3.4+), alors que votre bibliothèque est censée pouvoir fonctionner dans un environnement Python 2.7.
|
||
|
||
Techniquement, c'est une mauvaise idée de s'y prendre ainsi, mais si le choix n'est pas permis, il faut passer par certaines stratégies.
|
||
|
||
----
|
||
|
||
#### Imports alternatifs
|
||
|
||
La façon la plus connue de venir à bout de la possibilité d'utiliser des imports différents selon l'environnement consiste à utiliser la gestion d'exceptions :
|
||
|
||
```python {.numberLines}
|
||
try:
|
||
import package1
|
||
print("Package1 importé !")
|
||
except ImportError:
|
||
print("Erreur lors de l'importation de package1.")
|
||
try:
|
||
import package2
|
||
print("Package2 importé !")
|
||
except ImportError:
|
||
print("Erreur lors de l'importation de package2.")
|
||
exit()
|
||
```
|
||
|
||
----
|
||
|
||
Une autre façon plus contrôlée, mais plus rarement vue en production, consiste à interroger la version de l'interpréteur pour choisir quel package ou module importer :
|
||
|
||
```python {.numberLines}
|
||
# Récupère un objet représentant l'interpréteur Python
|
||
from sys import version_info
|
||
|
||
if version_info.major == 3:
|
||
import pathlib
|
||
filename = pathlib.Path("/home/demo/readme.txt").parent
|
||
else:
|
||
from os import path
|
||
filename = path.dirname("/home/demo/readme.txt")
|
||
```
|
||
|
||
----
|
||
|
||
#### Imports dynamiques par nom
|
||
|
||
Vous pouvez, en Python, importer un module ou un package par son nom complet. La méthode fonctionne aussi bien dans Python 2 que Python 3 .
|
||
|
||
Elle est un peu compliquée à utiliser, et accepte plusieurs arguments :
|
||
|
||
|
||
```{.python .numberLines}
|
||
path = __import__("os.path", globals(), locals(), ["basename", "join"], 0)
|
||
basename = path.basename
|
||
join = path.join
|
||
```
|
||
|
||
----
|
||
|
||
#### Imports depuis des répertoires personnalisés
|
||
|
||
Lorsque vous avez des modules dans des répertoires personnalisés, vous pouvez ajouter lesdits répertoires au `PYTHONPATH` :
|
||
|
||
```{.python .numberLines}
|
||
import sys
|
||
|
||
# Ajouter le répertoire au PYTHONPATH
|
||
sys.path.append("/home/new_path/root")
|
||
import mon_module # dans le répertoire
|
||
```
|
||
|
||
----
|
||
|
||
### Configurations
|
||
|
||
Lire des informations sous la forme de fichiers .ini :
|
||
|
||
```{.python .numberLines}
|
||
import configparser
|
||
|
||
data = configparser.ConfigParser().read("/home/demo/file.ini")
|
||
```
|
||
|
||
----
|
||
|
||
### Variables d'environnement
|
||
|
||
Une technique utilisée dans certains projets pour configurer certaines options (mots de passe, clés d'API) consiste à créer un fichier de configuration, que certaines bibliothèques peuvent charger et appliquer aux variables d'environnement (variables stockées par le système d'exploitation, différentes de la base de registre Windows)
|