Note
Ce chapitre utilise un dépôt subversion créé dans le chapitre La gestion des sources avec subversion
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.
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.
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 :
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.
Vous pouvez lire le billet de Tarek et les PEPs associés si vous êtes intéressé sur le sujet.
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 :
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.
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
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.
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 :
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.
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 :
Donc avant pour activer un environnement, vous faisiez :
$ cd myenv
$ . bin/activate
Maintenant vous n’avez qu’à taper workon myenv où que vous soyez.
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.
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.
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.
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”
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é.
À 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
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
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
où 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
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
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
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.
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
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
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.
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.