Concept de Python eggs

Note

Ce chapitre utilise un dépôt subversion créé dans le chapitre La gestion des sources avec subversion

Définition

Les Python eggs sont des packages distribuables. La notion de egg doit être bien comprise pour comprendre la suite. C’est la base des outils d’aujourd’hui comme zc.buildout qui sert au déploiement d’un site Plone et paster pour la génération de squelette de projet.

Savoir

  • distribute/setuptools
  • environnement isolé avec virtualenv
  • création d’un egg avec paster
  • metadata (description au format ReST, dépendances, extras)
  • installation via easy_install
  • entry points et plugins (paster utilise ce mécanisme de plugins)

Installation de Python et Distribute

Dans ce qui suit, je considère que vous êtes sous Ubuntu ou Debian et que vous souhaitez travailler sur Plone 4 qui requiert Python 2.6. Si vous voulez installer un Plone 3, il vous faudra Python 2.4, dans ce cas, remplacez 2.6 par 2.4 dans les instructions suivantes.

Installez les packages suivants :

$ sudo apt-get install build-essential python2.6 python2.6-dev

Le meta-paquet build-essential vous installera tout ce qu’il faut (compilateur gcc, make, etc.) pour compiler certains modules Python. Si par la suite vous avez une erreur disant que le fichier Python.h ne peut être trouvé, c’est très probablement que vous n’avez pas le paquet python2.6-dev d’installé.

Sous Ubuntu, installez le gestionnaire de paquets distribute comme ceci :

$ wget http://python-distribute.org/distribute_setup.py
$ sudo python distribute_setup.py

Si vous voulez être sûr de la version de Python que vous utilisez, vous pouvez vérifier la version comme ceci :

$ python --version
Python 2.7.3

Et savoir exactement où il se trouve comme ceci :

$ which python
/usr/bin/python

Ce python est en fait un lien symbolique ici vers python2.7, comme on peut le voir :

$ ls -l /usr/bin/python
lrwxrwxrwx 1 root root 9 juil.  9 16:41 /usr/bin/python -> python2.7

À tout moment, vous pouvez exécuter ces commandes pour savoir quel Python vous utilisez réellement.

Après l’installation de distribute, la commande easy_install est disponible pour installer de nouveaux eggs pour Python 2.7.

Packaging Python

Un package Python peut être distribué sous la forme d’une simple archive (zip ou tar.gz).

Python inclut la librairie distutils afin de réaliser ces distributions source, mais celle-ci ne gère pas les dépendances entre packages. distribute est un fork de setuptools, une extension à distutils qui ajoute de nombreuses fonctionnalités :

  • dépendances (metadata install_requires)
  • distribution binaire, egg
  • entry points

Le package distribute fournit la commande easy_intall qui permet d’installer un package donné :

$ easy_install zope.interface

Il y a également pip qui lui propose un moyen alternatif à easy_install pour installer un package.

Tarek Ziadé et d’autres personnes travaillent sur l’amélioration de la gestion des packages Python avec le nouveau package distutils2 qui remplacera distutils et distribute.

  • implémentation d’une fonctionnalité de désinstallation d’un package
  • ajout du metadata install_requires (entre autres) pour décrire les dépendances, mais la gestion des dépendances se fera toujours avec une commande tierce.

Vous pouvez lire le billet de Tarek et les PEPs associés si vous êtes intéressé sur le sujet.

Installation d’un egg

La communauté Python possède un dépôt central où sont stockés tous les packages Python, c’est le Pypi (Python Package Index), connu autrefois sous le nom de Cheese Shop. Son adresse : http://pypi.python.org/pypi

Lorsque vous installez un package Python via easy_install, c’est sur cet index que le package est recherché.

En effet l’index par défaut est l’URL suivante : http://pypi.python.org/simple

Exécutez easy_install Fabric, voici ce qui est exactement fait :

  • connexion à l’index http://pypi.python.org/simple
  • recherche de Fabric dans la liste des liens, si un lien est trouvé, il est suivi
  • nous arrivons donc sur http://pypi.python.org/simple/Fabric/ Cette page contient une liste d’urls où l’on peut télécharger directement l’egg, mais également toutes les urls contenues dans la description longue du egg.
  • la liste fournie sur cette page est ensuite filtrée de la manière suivante :
    • on donne priorité au egg binaire (bdist) qu’à la distribution source (sdist)
    • on garde les packages liés à l’OS, donc si on est sous Windows, les eggs ayant win32 sont gardés
    • seul les eggs utilisant la version de Python qu’on est en train d’utiliser sont gardés
    • si aucun egg binaire n’est trouvé, alors on recherche une distribution source (tar.gz ou zip)
    • il se peut qu’il n’y ait aucun lien direct vers un egg sur cette page, mais un lien vers une ou plusieurs urls où l’on peut les télécharger (liens contenus dans long_description ou via l’option download_url précisé lors de la création du egg). Dans ce cas-là, les liens sont suivis pour aller chercher une liste des versions.

Si le package n’a pas été trouvé sur l’index, alors on entame une nouvelle recherche, cette fois parmi tous les find-links (éventuellement filtré par l’option -H/--allow-hosts) :

$ easy_install --find-links http://pkg.example.com/packages/ monpackage

Vous pouvez par exemple autoriser seulement les connexions vers votre intranet et pypi :

$ easy_install -H *.myintranet.example.com,*.python.org zope.interface

L’option -H/--allow-hosts permet aussi par exemple d’installer un package sans le réseau, en interdissant toutes les URLs et en spécifiant un dossier somedir où aller chercher le package SomePackage :

$ easy_install -H None -f somedir SomePackage

Au lieu de préciser l’option en ligne de commande, vous pouvez le mettre dans le fichier de configuration ~/.pydistutils.cfg :

[easy_install]
allow_hosts = *.myintranet.example.com

Dans ce cas, seul les packages téléchargeables sur myintranet.example.com pourront être installés.

Il est possible de changer l’index par défaut par lequel les eggs sont recherchés via l’option -i/--index-url. Pypi possède des miroirs

Voir aussi le projet pour créer et/ou utiliser des miroirs de pypi.

Si l’on veut utiliser un miroir ou un index privé par exemple. Nous traiterons le cas d’un index privé avec le produit PloneSoftwareCenter par la suite.

Télécharger un package sans l’installer

Il est possible de télécharger le code source (sdist) d’un package sans pour autant l’installer :

$ easy_install -b . -e zope.interface

Exécutez easy_install -h pour connaitre la signification des options.

En savoir plus : Documentation EasyInstall

Création d’un environnement isolé avec virtualenv

Il est fréquent de vouloir tester plusieurs versions d’un framework. Admettons que vous ayez zope 3.4 installé globalement, comment pouvez-vous tester zope 3.5 sans que votre installation de zope 3.4 interfère ? La solution est de créer un environnement isolé avec virtualenv.

Lisez le tutoriel virtualenv sur grok.zope.org pour savoir comment l’installer et l’utiliser. Revenez ici lorsque c’est fait.

Si ce n’est déjà fait, installez virtualenv avec Python 2.4 :

$ easy_install-2.4 virtualenv

Bien, vous êtes revenu. Maintenant expliquons comment la magie opère.

Dans Python, vous avez dans sys.path la liste des chemins dans lesquels on peut trouver des packages Python :

$ which python2.4
/usr/bin/python2.4
$ python2.4
>>> import sys
>>> sys.path
['', '/usr/lib/python2.4', '/usr/lib/python2.4/plat-linux2',
 '/usr/lib/python2.4/lib-tk', '/usr/lib/python2.4/lib-dynload',
 '/usr/local/lib/python2.4/site-packages',
 '/usr/lib/python2.4/site-packages',
 '/usr/lib/python2.4/site-packages/Numeric',
 '/usr/lib/python2.4/site-packages/PIL',
 '/usr/lib/python2.4/site-packages/gst-0.10',
 '/var/lib/python-support/python2.4',
 '/usr/lib/python2.4/site-packages/gtk-2.0',
 '/var/lib/python-support/python2.4/gtk-2.0']

Créons un environnement nommé myenv :

$ virtualenv myenv --distribute

Ce que fait cette commande peut se résumer plus ou moins à ces commandes :

$ mkdir -p myenv/bin myenv/lib/python2.4/site-packages
$ cp /usr/bin/python2.4 myenv/bin/python
$ cp /usr/bin/python2.4 myenv/bin/python2.4

création de liens symboliques vers les modules de la librairies standard installation de distribute (ou setuptools à défaut du paramètre --distribute) dans cet environnement, ce qui génère les commandes command:bin/easy_install et bin/easy_install-2.4 (c’est le même exécutable) et la création d’un script bin/activate.

Notez que python (sans suffixe) est la version 2.5 sous Ubuntu 8.04 et 8.10 :

$ which python
/usr/bin/python
$ python -V
Python 2.5.2

Entrons dans le dossier et activons l’environnement :

$ cd myenv/
$ source bin/activate

Le prompt indique que votre environnement est actif. Jetez un œil au source du fichier bin/activate, il n’y a rien de magique là dedans, il change seulement la variable d’environnement PATH pour y inclure au début le dossier myenv/bin. La partie essentielle de ce script est :

$ export PATH="/home/vincentfretin/myenv/bin:$PATH"

Cela a son importance, précédement python était le binaire /usr/bin/python qui est la version 2.5 de Python sous Ubuntu 8.04 et 8.10. Maintenant c’est le python de l’environnement, qui est un Python 2.4 :

(myenv)$ which python
.../myenv/bin/python
(myenv)$ python -V
Python 2.4.5

Maintenant regardons le sys.path :

(myenv)$ python
>>> import sys
>>> sys.path
['',
'/home/vincentfretin/myenv/lib/python2.4/site-packages/setuptools-0.6c11-py2.4.egg',
'/home/vincentfretin/myenv/lib/python2.4',
'/home/vincentfretin/myenv/lib/python2.4/plat-linux2',
'/home/vincentfretin/myenv/lib/python2.4/lib-tk',
'/home/vincentfretin/myenv/lib/python2.4/lib-dynload', '/usr/lib/python2.4',
'/usr/lib64/python2.4', '/usr/lib/python2.4/plat-linux2',
'/usr/lib/python2.4/lib-tk', '/usr/lib64/python2.4/lib-tk',
'/home/vincentfretin/myenv/lib/python2.4/site-packages',
'/usr/local/lib/python2.4/site-packages', '/usr/lib/python2.4/site-packages',
'/usr/lib/python2.4/site-packages/Numeric',
'/usr/lib/python2.4/site-packages/PIL',
'/usr/lib/python2.4/site-packages/gst-0.10',
'/var/lib/python-support/python2.4',
'/usr/lib/python2.4/site-packages/gtk-2.0',
'/var/lib/python-support/python2.4/gtk-2.0']

Vous voyez que les chemins vers les dossiers globaux sont toujours inclus mais que les premiers sont ceux de notre environnement. En effet vous pouvez utiliser la bibliothèque PIL qui est installé globalement :

>>> import PIL

Sous Ubuntu 9.04, PIL n’est pas disponible sous Python 2.4. Ici import PIL est seulement utilisé comme exemple d’import d’un package installé globalement. Le package virtualenv a aussi été installé globalement, donc vous pouvez utiliser import virtualenv à la place pour tester.

En général vous voulez un environnement isolé des packages extérieurs, c’est le rôle de l’option --no-site-packages de virtualenv. Nous allons recréer l’environnement avec cette option, tout d’abord désactivez l’environnement :

(myenv)$ deactivate

deactivate est juste une fonction bash créée lorsque vous avez sourcé bin/activate.

Supprimez votre environnement et recréez le avec l’option --no-site-packages :

$ cd ..
$ rm -rf myenv
$ virtualenv --no-site-packages myenv

Maintenant voyez par vous même la différence :

$ cd myenv/
$ . bin/activate
(myenv)$ python
>>> import sys
>>> sys.path
['',
'/home/vincentfretin/myenv/lib/python2.4/site-packages/setuptools-0.6c9-py2.4.egg',
'/home/vincentfretin/myenv/lib/python2.4',
'/home/vincentfretin/myenv/lib/python2.4/plat-linux2',
'/home/vincentfretin/myenv/lib/python2.4/lib-tk',
'/home/vincentfretin/myenv/lib/python2.4/lib-dynload', '/usr/lib/python2.4',
'/usr/lib64/python2.4', '/usr/lib/python2.4/plat-linux2',
'/usr/lib/python2.4/lib-tk', '/usr/lib64/python2.4/lib-tk',
'/home/vincentfretin/myenv/lib/python2.4/site-packages']

Le dossier PIL n’est plus là, comme l’atteste l’exception ImportError :

>>> import PIL
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ImportError: No module named PIL

Vous pouvez installer PIL dans cet environnement comme ceci :

easy_install --find-links http://dist.plone.org/thirdparty/ PIL

(L’archive PIL de pypi n’est pas easy installable.)

Ici, nous avons installé virtualenv avec easy_install-2.4, comment créer un environnement avec une autre version de Python ? virtualenv possède une option -p pour préciser un exécutable python alternatif :

$ virtualenv -p /usr/bin/python --no-site-packages --distribute myenv25
$ cd myenv25
$ . bin/activate

Nous allons utiliser ce nouvel environnement pour installer Fabric qui nécessite Python >= 2.5. Vérifiez que vous avez la package Ubuntu python2.5-dev ou python2.6-dev d’installé, il est nécessaire pour compiler pycrypto, une dépendance de Fabric. Fabric est un outil pour scripter les deploiements. Nous n’allons pas utiliser easy_install Fabric ici, mais récupérer l’archive pour l’installer.

Nous téléchargons l’archive avec wget et exécutons ensuite easy_install avec l’archive en paramètre pour installer le package :

(myenv25)$ wget http://git.fabfile.org/cgit.cgi/fabric/snapshot/fabric-0.9a3.tar.gz
(myenv25)$ easy_install fabric-0.9a3.tar.gz

Nous aurions très bien pu faire directement easy_install http://git.fabfile.org/cgit.cgi/fabric/snapshot/fabric-0.9a3.tar.gz.

Vous pouvez remarquer que Fabric et ses dépendances ont été installées en eggs zippés :

(myenv25)$ ls -l lib/python2.5/site-packages/
total 1064
-rw-r--r-- 1 vincentfretin vincentfretin    306 2009-05-25 11:35 easy-install.pth
-rw-r--r-- 1 vincentfretin vincentfretin  71581 2009-05-25 11:35 Fabric-0.9a3-py2.5.egg
-rw-r--r-- 1 vincentfretin vincentfretin 296831 2009-05-25 11:35 paramiko-1.7.4-py2.5.egg
-rw-r--r-- 1 vincentfretin vincentfretin 358122 2009-05-25 11:35 pycrypto-2.0.1-py2.5-linux-x86_64.egg
-rw-r--r-- 1 vincentfretin vincentfretin 328025 2009-05-25 11:34 distribute-0.6.8-py2.5.egg
-rw-r--r-- 1 vincentfretin vincentfretin     29 2009-05-25 11:34 setuptools.pth

Tous les eggs ne sont pas installés zippés. C’est le mainteneur du package qui décide si son egg est “zip safe” ou non. Un package n’est par exemple pas zip safe s’il utilise la variable spéciale __file__ dans son code.

Vous vous demandez à quoi servent ces fichiers setuptools.pth et easy-install.pth n’est-ce pas ? Un petit rappel Python va vous faire du bien alors.

Que contient ces fichiers xyz.pth (pour path) ? Comme son extension le suggère, ces fichiers contiennent une liste de chemins où l’on peut trouver des packages :

(myenv25)$ cat lib/python2.5/site-packages/setuptools.pth
./distribute-0.6.8-py2.5.egg
(myenv25)$ cat lib/python2.5/site-packages/easy-install.pth
import sys; sys.__plen = len(sys.path)
./distribute-0.6.8-py2.5.egg
./Fabric-0.9a3-py2.5.egg
./paramiko-1.7.4-py2.5.egg
./pycrypto-2.0.1-py2.5-linux-x86_64.egg
import sys; new=sys.path[sys.__plen:]; del sys.path[sys.__plen:]; p=getattr(sys,'__egginsert',0); sys.path[p:p]=new; sys.__egginsert = p+len(new)

Comme vous le voyez, la commande easy_install maintient dans le fichier easy-install.pth une liste des eggs qu’elle a installés.

Au démarrage de Python, tous les packages python (dans le sens d’un dossier contenant un fichier __init__.py) se trouvant dans lib/python2.5/site-packages/ sont ajoutés au sys.path. Ça c’est la première étape, et dans notre cas, il n’y a aucun package. La deuxième étape recherche des fichiers xyz.pth, les lit et inclut les chemins inclus si un package s’y trouve.

La première et dernière ligne du fichier easy-install.pth sont utilisés pour ajouter les eggs au début de sys.path pour prendre la précédence aux packages éventuellement installés.

Suppression d’un egg

Il n’y a pas de commande uninstall pour désinstaller un egg. Une implémentation est en cours dans distutils2.

Pour le moment, il faut donc désinstaller manuellement et là il faut savoir ce que l’on fait.

La première chose qui vient à l’esprit est de supprimer le egg du site-packages. C’est très bien mais cela ne suffit pas comme nous allons le voir.

Nous allons désinstaller Fabric pour l’installer d’une autre manière. Nous allons profiter de cette désintallation pour revenir sur le fichier xyz.pth.

Notez bien que nous avons dans le sys.path setuptools, Fabric et paramiko, dans le même ordre que listé dans easy-install.pth :

(myenv25)vincentfretin@lelouch:~/myenv25$ python
Python 2.5.2 (r252:60911, Oct  5 2008, 19:29:17)
[GCC 4.3.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['',
'/home/vincentfretin/myenv25/lib/python2.5/site-packages/setuptools-0.6c11-py2.5.egg',
'/home/vincentfretin/myenv25/lib/python2.5/site-packages/Fabric-0.9a3-py2.5.egg',
'/home/vincentfretin/myenv25/lib/python2.5/site-packages/paramiko-1.7.4-py2.5.egg',
'/home/vincentfretin/myenv25/lib/python2.5/site-packages/pycrypto-2.0.1-py2.5-linux-x86_64.egg',
'/home/vincentfretin/myenv25/lib/python2.5', ...]

Maintenant supprimons le egg de Fabric :

(myenv25)vincentfretin@lelouch:~/myenv25$ rm lib/python2.5/site-packages/Fabric-0.9a3-py2.5.egg

Mais nous n’avons pas supprimé l’entrée dans easy-install.pth. Allons nous encore avoir /home/vincentfretin/myenv25/lib/python2.5/site-packages/Fabric-0.9a3-py2.5.egg dans le sys.path ? Voyons voir :

(myenv25)vincentfretin@lelouch:~/myenv25$ python
Python 2.5.2 (r252:60911, Oct  5 2008, 19:29:17)
[GCC 4.3.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path

['',
'/home/vincentfretin/myenv25/lib/python2.5/site-packages/setuptools-0.6c9-py2.5.egg',
'/home/vincentfretin/myenv25/lib/python2.5/site-packages/paramiko-1.7.4-py2.5.egg',
'/home/vincentfretin/myenv25/lib/python2.5/site-packages/pycrypto-2.0.1-py2.5-linux-x86_64.egg',
'/home/vincentfretin/myenv25/lib/python2.5', ...]

Et bien non, Python n’a trouvé aucun package Python ./Fabric-0.9a3-py2.5.egg qui n’existe plus, il ne l’a donc pas ajouté dans le sys.path.

Pour faire une désintallation propre d’un egg, il faut :

  • supprimer le egg
  • supprimer l’entrée dans easy-install.pth
  • supprimer les éventuels scripts qui ont été générés à l’installation, ici bin/fab.

Methode “originelle” pour installer un package

easy_install fait partie du package distribute / setuptools. Si distribute ou setuptools n’est pas disponible dans votre environnement, on peut très bien installer un package en l’extrayant et exécutant la commande python setup.py install :

(myenv25)$ tar xvf fabric-0.9a3.tar.gz
(myenv25)$ cd fabric-0.9a3/
(myenv25)$ python setup.py install

En fait, c’est exactement ce que fait la commande easy_install.

Installation de virtualenvwrapper

virtualenvwrapper est un ensemble de fonctions bash pour gérer vos environnements.

Pour l’installer :

$ sudo easy_install virtualenvwrapper

Éditez ensuite votre ~/.bashrc pour sourcer le fichier /usr/local/bin/virtualenvwrapper.sh.

Sur Ubuntu, j’ai l’habitude de décommenter dans ~/.bashrc les 3 lignes concernant l’inclusion de ~/.bash_aliases. Je met ensuite dans ce fichier tous les alias et autres variables d’environnement que je veux. Ici nous voulons ces deux lignes :

TMPDIR=/tmp
source /usr/local/bin/virtualenvwrapper.sh

virtualenvwrapper utilise le dossier ~/.virtualenvs par défaut pour créer et chercher les environnements :

$ mkdir ~/.virtualenvs

Démarrez un nouveau terminal, vous avez maintenant à disposition les commandes suivantes :

  • workon : affiche la liste des environnements contenu dans ~/.virtualenvs
  • workon myenv : active l’environnement myenv
  • mkvirtualenv myenv : crée l’environnement myenv avec la commande virtualenv et l’active Tous les paramètres données à mkvirtualenv serons donnés à la commande command:virtualenv.
  • rmvirtualenv myenv : supprime l’environnement myenv
  • cdvirtualenv : va dans le dossier de l’environnement actif
  • cdsitepackages : va dans le dossier site-packages de l’environnement actif
  • lssitepackages : liste les eggs installés de l’environnement actif
  • cpvirtualenv : copie un environnement existant

Donc avant pour activer un environnement, vous faisiez :

$ cd myenv
$ . bin/activate

Maintenant vous n’avez qu’à taper workon myenv où que vous soyez.

Passons au développement

Installation de la commande paster

Dans ce qui suit je travaille dans mon environnement myenv, je n’indiquerai plus le “(myenv)” dans le prompt.

Installez le egg PasteScript.

Le egg PasteScript fournit la commande paster avec laquelle on peut créer des squelettes de code.

Pour lister les templates disponibles :

$ paster create --list-templates
Available templates:
  basic_package:  A basic setuptools-enabled package
  paste_deploy:   A web application deployed through paste.deploy

Il n’y a pas beaucoup de templates par défaut.

Installez le egg ZopeSkel qui fournit divers templates et reexécutez la commande :

$ paster create --list-templates
Available templates:
Available templates:
  archetype:         A Plone project that uses Archetypes content types
  basic_buildout:    A basic buildout skeleton
  basic_namespace:   A basic Python project with a namespace package
  basic_package:     A basic setuptools-enabled package
  nested_namespace:  A basic Python project with a nested namespace (2 dots in name)
  paste_deploy:      A web application deployed through paste.deploy
  plone_basic:       A package for Plone add-ons
  plone_nested:      A package for Plone add-ons with a nested namespace
  recipe:            A recipe project for zc.buildout
  zope2_basic:       A Zope project
  zope2_nested:      A nested-namespace Zope package

Ah il y a déjà plus de choix !

Ceux que nous utiliserons par la suite sont basic_namespace, plone_basic, plone_nested.

En fait, vous auriez très bien pu installer uniquement ZopeSkel car PasteScript en est une dépendance.

Création de votre premier egg

Pour créer un squelette, vous choisissez votre template et exécutez :

$ paster create -t nom_de_la_template

Créez votre premier egg :

$ paster create -t basic_namespace
Selected and implied templates:
  templer.core#basic_namespace  A basic Python project with a namespace package

Enter project name: foo.bar
Variables:
  egg:      foo.bar
  package:  foobar
  project:  foo.bar
Expert Mode? (What question mode would you like? (easy/expert/all)?) ['easy']:
Version (Version number for project) ['1.0']:
Description (One-line description of the project) ['']:
Creating template basic_namespace
Creating directory ./foo.bar
  Copying setup.py_tmpl to ./foo.bar/setup.py
  Recursing into src
    Creating ./foo.bar/src/
    Recursing into +namespace_package+
      Creating ./foo.bar/src/foo/
      Recursing into +package+
        Creating ./foo.bar/src/foo/bar/
        Copying __init__.py_tmpl to ./foo.bar/src/foo/bar/__init__.py
      Copying __init__.py_tmpl to ./foo.bar/src/foo/__init__.py
Running /home/cedric/.virtualenvs/myenv/bin/python setup.py egg_info

Voyons ce qu’il a généré :

$ tree foo.bar
foo.bar/
|-- CHANGES.txt
|-- CONTRIBUTORS.txt
|-- docs
|   |-- LICENSE.GPL
|   `-- LICENSE.txt
|-- README.txt
|-- setup.py
`-- src
    |-- foo
    |   |-- bar
    |   |   `-- __init__.py
    |   `-- __init__.py
    `-- foo.bar.egg-info
        |-- dependency_links.txt
        |-- entry_points.txt
        |-- namespace_packages.txt
        |-- not-zip-safe
        |-- PKG-INFO
        |-- requires.txt
        |-- SOURCES.txt
        `-- top_level.txt

Le dossier foo.bar.egg-info est généré automatiquement avec la commande python setup.py egg_info (dernière commande exécutée par paster).

Ce dossier ne sera donc pas ajouté au gestionnaire de version comme nous le verrons plus loin.

Le fichier setup.py contient les données que vous avez entrées.

Déclaration des dépendances

L’option install_requires dans setup.py permet d’indiquer des dépendances, ici notre egg dépend de setuptools.

Les dépendances sont vérifiées à l’installation de l’egg. install_requires est une liste de Requirement.

requirement ::=  nom_egg
                 [(">=" | ">" | "<" | "<=" | "==" | "!=") version]

En savoir plus : Declaring Dependencies (concepts d’extras)

L’option entry_points sera expliquée plus loin.

Egg en mode développement

Installons tout de suite ce nouvel egg pour pouvoir l’importer.

La première chose a laquelle vous pensez est de faire python setup.py install et vous avez raison !

Mais l’inconvénient dans ce cas-là est qu’à chaque fois que vous allez changer quelque chose à votre package, vous devrez reexécuter cette commande.

Nous avons une commande develop, qui est bien mieux pour installer un egg tout en le développant. Faites donc ceci :

$ cd foo.bar
$ python setup.py develop

Cette commande, au lieu de copier le dossier dans site-packages, crée un fichier foo.bar.egg-link qui n’est autre finalement qu’un lien symbolique multi-plateforme qui pointe vers le dossier de votre egg en développement.

En savoir plus : “Development Mode”

Le hello world que tout le monde attend

Allez-y maintenant, ouvrez un python et importez votre package :

$ python
>>> import foo.bar

Et le “hello world” me direz vous ? Bien je vois que vous avez l’habitude.

Éditez le fichier foo/__init__.py pour y ajouter :

print "Hello"

Réimportez votre module :

$ python
>>> import foo.bar
Hello

Éditez le fichier foo/bar/__init__.py et ajoutez-y :

print "world!"

Réimportez le module :

$ python
>>> import foo.bar
Hello
world!

Et voilà !

En plus l’exemple sert pour faire un petit rappel Python : Face à import foo.bar que fait l’interpréteur Python ?

Eh bien il regarde dans le sys.path un package foo (dossier foo avec un fichier __init__.py dedans) ou un module foo (fichier foo.py), dans cet ordre.

Ici un package foo est trouvé, et le contenu du fichier __init__.py est exécuté.

On passe ensuite à bar, un package ou un module est recherché à l’intérieur du package : mod:foo.

Ici un package bar est trouvé, le contenu de son fichier __init__.py est exécuté.

Les espaces de nom ou namespaces

À quoi sert le code dans foo/__init__.py ? Très bonne question !

Créez un egg comme précédemment nommé : file:foo.rab (pas très inspiré), et installez le en mode développé.

Vous l’avez fait sans regarder le texte au dessus, c’est très bien !

Vous avez usé de la flêche haute, avouez le. C’est encore mieux !

Éditez foo.rab/foo/__init__.py :

print "Bonjour"

Éditez foo.rab/foo/rab/__init__.py :

print "le monde"

On y est. Vérifions que foo.bar et foo.rab sont dans notre sys.path et sont bien dans cette ordre :

$ python
>>> import sys
>>> sys.path
[..., '/home/vincentfretin/src/foo.bar', '/home/vincentfretin/src/foo.rab',
...]

Comme dit plus haut, Python recherche un package nommé foo, il en trouve un, exécute le contenu de __init__.py et normalement devrait s’arrêter là.

Donc cela devrait donner ceci :

>>> import foo
Hello

car foo.bar étant en premier dans le sys.path.

Au lieu de ça, qu’avons-nous ?

>>> import foo
Bonjour
Hello

Il se peut que vous ayez “Hello Bonjour” comme ordre, c’est assez mystérieux. L’essentiel est que vous ayez les deux.

Ensuite :

>>> import foo.rab
le monde
>>> import foo.bar
world!

Le code de foo.bar/foo/__init__.py indique que : mod:foo est un espace de nom. Et cela change le comportement de l’import.

Au lieu de s’arrêter au premier package foo trouvé, la recherche continue et tous les packages foo trouvés sont exécutés.

Si foo n’était pas déclaré comme espace de nom dans l’egg foo.bar, alors vous auriez eu ceci :

>>> import foo.rab
Traceback (most recent call last)
 File "<stdin>", line 1, in ?
 ImportError: No module named rab

Vous pouvez faire le test en commentant namespace_packages=['foo'] du setup.py de foo.bar.

Il faut réexécuter python setup.py egg_info (la commande egg_info est également exécutée lors d’un install ou d’un develop)

pour mettre à jour les metadonnées du egg situées dans le dossier foo.bar.egg-info. Commentez également les lignes dans foo.bar/foo/__init__.py.

En temps normal, ne mettez pas de code dans les fichiers __init__.py des packages servant d’espace de nom comme le dit la documentation de setuptools.

En savoir plus : Namespace Packages

L’API pkg_resources

setuptools fournit un module pkg_resources avec lequel on peut par exemple récupérer la version d’un egg.

Cet API sert à lire les différents fichiers du dossier .egg-info.

Exemple pour récupérer la version du egg foo.bar installé :

$ python
>>> import pkg_resources
>>> d = pkg_resources.get_distribution("foo.bar")
>>> d.version
'1.0dev'
>>> d.location
'/home/vincentfretin/src/foo.bar'

En savoir plus : Documentation PkgResources

Les entry points

Revenons sur l’option entry_points dans setup.py.

Cette option sert à définir des points d’entrées pour le egg. On peut utiliser cette notion pour réaliser des plugins.

Reprenons les eggs PasteScript et ZopeSkel. Comment PasteScript a fait pour découvrir les nouveaux templates installés par : mod:ZopeSkel ?

ZopeSkel utilise le système templer pour générer ses templates. Templer définit des entry points pour le groupe paste.paster_create_template :

$ cdsitepackages
$ cat templer.plone-1.0b1-py2.7.egg-info/entry_points.txt
# -*- Entry points: -*-
[paste.paster_create_template]
plone_basic = templer.plone:Plone
plone_nested = templer.plone:NestedPlone
archetype = templer.plone:Archetype

plone_basic est un nom, templer.plone est un module et Plone un callable, ici une classe.

et PasteScript lui fait une recherche des eggs déclarant des entry points pour paste.paster_create_template avec l’API pkg_resources :

$ python
>>> import pkg_resources
>>> list(pkg_resources.iter_entry_points('paste.paster_create_template'))
[...,
EntryPoint.parse('plone_basic = templer.plone:Plone'),
EntryPoint.parse('plone_nested = templer.plone:NestedPlone'),
EntryPoint.parse('archetype = templer.plone:Archetype'),
...]

On peut charger le callable d’un entry point, souvent une classe :

>>> entry_points = list(pkg_resources.iter_entry_points('paste.paster_create_template'))
>>> ep = entry_points[4]
>>> ep
EntryPoint.parse('plone_basic = templer.plone:Plone')
>>> PloneBasic = ep.load()
>>> PloneBasic
<class 'templer.plone.plone.Plone'>

En savoir plus : Dynamic Discovery of Services and Plugins

groupe console_scripts

Le groupe console_scripts est spécial. Il est utilisé lors de l’installation du egg pour générer les scripts dans le dossier bin.

Pour générer un script bin/fab, le egg Fabric définit dans son setup.py :

entry_points={
    'console_scripts': [
        'fab = fabric.main:main',
    ]
},

On peut également l’écrire de la manière suivante directement :

entry_points="""
[console_scripts]
fab = fabric.main:main
""",

Dans les deux cas, le fichier entry_points.txt généré sera normalisé comme ceci :

[console_scripts]
fab = fabric.main:main

Concrétement, exécuter la commande fab revient à faire :

$ python
>>> from fabric.main import main
>>> main()

En savoir plus : Automatic Script Creation

Mise en place d’un Pypi privé avec PloneSoftwareCenter

Créez une instance Plone avec l’id “site” sur une machine servant de serveur (Installation via l’Unified Installer), nous allons l’appeler devagile, avec la résolution DNS dans /etc/hosts :

10.56.8.47      devagile

Il est très facile de transformer une instance Plone en un Pypi pour votre entreprise en installant le produit Products.PloneSoftwareCenter.

Créez un site Plone avec comme id site, installez le module et ajoutez un élément Software Center nommé products à la racine du site.

L’URL de ce Pypi sera donc http://devagile:8080/site/products

Installation de collective.dist pour Python 2.4 et 2.5

Si vous utilisez Python 2.4 ou 2.5, il vous faut installer collective.dist qui introduit deux nouvelles commandes mregister et mupload pour pouvoir enregister votre egg sur plusieurs serveurs.

Si vous utilisez Python 2.6, remplacez mregister par register, et mupload par upload dans ce qui suit.

En effet le support de serveurs multiples n’a été introduit qu’à partir de la version 2.6 de Python.

Configuration des serveurs

Il faut tout d’abord configurer votre fichier ~/.pypirc :

[distutils]
index-servers =
    pypi
    mycompany

[pypi]
username:user
password:password

[mycompany]
repository:http://devagile:8080/site/products
username:ploneuser
password:password

Sous Windows vous ne pouvez pas créer ce fichier .pypirc avec le gestionnaire de fichiers, mais dans un shell, vous pouvez.

Dans un shell dos, allez dans C:\Profiles\User, et créez le fichier avec la commande :

edit .pypirc

Enregistrement et upload

Exécutez ensuite :

Avec Python < 2.6

$ python setup.py mregister -r mycompany sdist --formats=zip mupload -r mycompany

Avec Python >= 2.6 :

$ python setup.py register -r mycompany sdist --formats=zip upload -r mycompany
  • mregister permet d’enregistrer le egg sur le serveur
  • sdist permet de créer une distribution source
  • mupload transfère sur la distribution source vers le serveur
  • -r mycompany précise d’enregistrer et de transfèrer sur le serveur mycompany (r pour repository dans doute). Si cette option n’est pas précisée, c’est le serveur Pypi d’origine.
  • --formats=zip, génère une archive au format zip. Par défaut sous Linux, une archive tar.gz est générée, le module tarfile dans Python < 2.6 semble avoir certains problèmes de lecture de ces archives.

La commande mregister exécute implicitement la commande egg_info. Cette commande génère entre autres le numéro de version.

Le fichier setup.cfg est lu par cette commande, il configure quelques options liées à la génération du numéro de version. Créez un fichier setup.cfg qui contient :

$ cat setup.cfg
[egg_info]
tag_build = dev
tag_svn_revision = true

La version générée sera donc de la forme “1.0dev”.

Pour une release stable, on supprime généralement ce fichier pour que la version soit simplement “1.0”.

On peut également laisser le fichier en place et écraser la configuration en ligne de commande comme ceci :

$ python setup.py egg_info -RDb "" register -r mycompany sdist --formats=zip upload -r mycompany

L’option --formats=zip permet de générer une archive zip au lieu d’une archive tar.gz par défaut sous Linux.

Avec python setup.py sdist --help-formats, vous pouvez voir la liste des formats possibles d’archives.

Si vous voulez par exemple créer une archive zip et tar.gz, vous pouvez spécifier l’option --formats=zip,gztar.

Regardez la signification des options avec :

$ python setup.py egg_info -h
--tag-build (-b)         Specify explicit tag to add to version number
--no-svn-revision (-R)   Don't add subversion revision ID [default]
--no-date (-D)           Don't include date stamp [default]

Nous verrons par la suite comment faire une release en bonne et due forme avec le gestionnaire de version subversion.

On peut remplacer sdist par bdist_egg pour générer un egg, une distribution binaire.

La convention est de générer un bdist_egg pour chaque version de Python pour la plateforme Windows si le egg contient des librairies C à compiler.

Pour les autres OS, la distribution source sera récupérée et les librairies C seront compilées à l’installation.

Broken release

Créez un nouvel environnement testenv :

$ mkvirtualenv testenv

Essayez maintenant d’installer foo.bar 1.0 à partir de votre pypi :

$ easy_install -i http://devagile:8080/site/products/simple foo.bar

Il y a une erreur à l’installation disant qu’il ne trouve pas le fichier CONTRIBUTORS.txt.

La release est cassée car elle ne contient pas le fichier CONTRIBUTORS.txt. Et nous avons de ce fichier pour la long_description.

Le dossier docs est également manquant car dans setup.py nous avons package=find_packages, ça recherche seulement les dossiers contenant un fichier __init__.py. docs n’étant pas un package, il n’a pas été inclu dans l’archive.

Pour régler le problème, il faut mettre le code source dans un dépôt subversion et grâce à l’option include_package_data=True dans setup.py, tous les fichiers subversionnés seront ajoutés à l’archive.

Note

Vous pouvez créer facilement un dépôt subversion comme ceci :

svnadmin create Formation

Et pour faire un checkout :

svn co http://devagile/Formation

Donc on va importer notre code dans le dépôt Formation :

$ svn import foo.bar/ http://devagile/Formation/foo.bar -m "First import of foo.bar"

Attention, le dossier .egg-info a été commité ! Nous allons le supprimer de subversion :

$ svn co http://devagile/Formation
$ cd Formation/foo.bar
$ svn rm foo.bar.egg-info dist
$ svn ci -m "Delete egg-info and dist directories"

Nous allons donc maintenant faire une nouvelle release de foo.bar, pour cela incrémentez la version dans setup.py, mettez 1.1, éditez le fichier docs/HISTORY.txt pour ajouter une information au changelog, commitez et refaites la release.

Nous allons faire pareil pour foo.rab, mais nous allons tout d’abord configurer l’option global-ignores dans ~/.subversion/config pour ignorer le dossier .egg-info lors de l’import.

Ouvrez le fichier ~/.subversion/config et configurez global-ignores comme suit :

global-ignores = *.o *.lo *.la #*# .*.rej *.rej .*~ *~ .#* .DS_Store *.pyc *.pyo .installed.cfg bin var parts downloads *.swp develop-eggs fake-eggs eggs archgenxml.log *.egg-info *.mo build dist .mr.developer.cfg

Vous pouvez maintenant importer le code source dans subversion et faire la release.


blog comments powered by Disqus