---
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.

![Remplissage automatique des dépendances](assets/images/x-requirements-auto-pycharm.jpg)

----

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)
