Mathieu Agopian : Le miroir PyPI du pauvre

Deux notes pour commencer cet article :

Bonjour ami lecteur. Je vais te conter mon histoire, en me disant que tu es peut-être toi aussi passé par là, et que cette expérience t'enrichira.

Tout d'abord, le personnage principal : moi. Je suis fainéant (revoir la présentation [5] que j'ai donnée à Djangocong 2010 [6] à ce sujet).

Les protagonistes

pip [7] et PyPI [8]. Si tu ne connais pas pip, l'outil à utiliser pour installer des paquets python, honte à toi, arrête de lire immédiatement et documente toi à ce sujet.

Oui, maintenant.

PyPI (le Python Package Index) est le site listant et hébergeant la plupart des paquets, applications et librairies python qui ne sont pas dans la libraire standard.

L'intrigue

Je travaille sur un projet Django [9], et ayant fait les choses proprement, j'ai un ficher requirements.pip listant les noms ou les urls des différents paquets que j'utilise.

Ce fichier est utilisé par pip pour installer automatiquement toutes les dépendances de mon projet, par exemple lors de l'installation d'un nouveau serveur ou du déploiement de mon projet :

pip install -r requirements.pip

Les péripéties

Elles te sont peut-être arrivées à toi aussi :

Une solution à tous ces problèmes est d'utiliser un miroir local de PyPI, qui contiendra tous les paquets nécessaires à notre projet, dans leur version utilisée.

Il y a plusieurs projets qui permettent de faire ça de manière plus ou moins automatique :

Mais ça nécessite d'installer et de gérer une application de plus, or, comme je l'ai précisé, je suis fainéant, et je n'ai pas envie de consacrer une machine (et une installation de serveur web) pour ça.

Le sauveur

pip à la rescousse, avec deux options très pratiques :

--find-links
URL ou chercher des paquets (notre miroir local !)
--no-index
ignorer les index de paquets (comme PyPI), et ne regarder que sur l'URL fournie à --find-links

Mais il y a mieux. Il est possible de mettre l'option --find-links en tout début du fichier requirements.pip pour ne pas avoir besoin de l'utiliser en ligne de commande (et donc ne pas changer nos habitudes, parfait pour un fainéant ;).

Pour la deuxième option, elle n'est pas reconnue dans le fichier, mais on peut utiliser l'option --index-url à la place. On est alors sûr que pip n'essaiera pas de trouver un meilleur paquet (meilleure note, version plus récente) sur PyPI.

Maintenant qu'on sait comment utiliser notre miroir local, il ne reste plus qu'à le créer :

  1. Générer la liste exhaustive de tous les paquets et dépendances
  2. Les faire télécharger par pip dans un répertoire
  3. Servir ce répertoire avec un serveur web
  4. Modifier le fichier requirements.pip

Générer la liste des paquets

Rien de plus simple :

pip freeze > freezed.pip

Télécharger les paquets

On le fait faire par pip, ce serait trop long et fastidieux de tout télécharger (les paquets et leurs dépendances) sur PyPI (ou git, svn, mercurial, …) manuellement :

mkdir pypi
pip install -r freezed.pip --upgrade --download=pypi --build=pypi

Servir le répertoire avec un serveur web

SimpleHTTPServer [17] à la rescousse :

cd pypi
python -m SimpleHTTPServer

Le miroir est maintenant accessible sur http://localhost:8000.

Il existe sinon une autre méthode qui consiste à fournir directement une URL de type file:///path/to/mirror/folder au paramètre find-links. Dans ce cas, pas besoin de serveur web !

Modifier le fichier requirements.pip

La dernière étape de notre périple, avant de rentrer voir sa princesse, de vivre heureux et d'avoir beaucoup beaucoup d'enfants.

Comme nous l'avons vu, il faut placer les deux lignes suivantes en tête du fichier requirements.pip :

--find-links http://localhost:8000
--index-url http://localhost:8000

Ayant maintenant notre propre miroir local, il ne faut plus utiliser les URLs de téléchargement sur git/svn/mercurial/… pour les paquets qu'on ne souhaite pas réinstaller à chaque fois :

  • les paquets devant être réinstallés à partir de leur dépôts VCS à chaque fois resteront avec leur URL complète
  • les autres paquets installés à l'origine à partir de dépôts n'ont plus besoin de leur url : ne conserver que leur nom (la partie après #egg= dans leur URL)
  • tous les autres peuvent être listés sans leurs dépendances

Par exemple, si vous avez installé django-notification de la sorte :

pip install -e git+ssh://git@github.com/jtauber/django-notification.git#egg=django_notification:nohlsearch

Il suffira de mettre la ligne suivante dans le fichier requirements.pip :

django-notification

À partir de maintenant, tout appel à la commande suivante ira automatiquement installer les paquets disponibles dans le répertoire du miroir local (si le SimpleHTTPServer est lancé bien entendu) :

pip install -Ur requirements.pip