Mon cookbook : pip, virtualenv

Je présente dans cet article mon workflow concernant l'utilisation de virtualenv et pip

Je tiens à préciser qu'il y a d'autres workflows possibles, par exemple l'utilisation de buildout mais ce n'est pas le sujet de cet article.

Méthode « classique »

Voici la méthode que je nomme "classique" car je pense qu'elle est beaucoup utilisée. J'utilisais cette méthode il y a encore quelques mois.

Prérequis sur la machine (je ne précise pas leur installation, ce n'est pas mon sujet) :

Je prépare un dossier de travail et je clone mon projet :

$ mkdir ~/my_project/
$ cd ~/my_project/
$ git clone http://stephane@repos.stephane-klein.info/example1 .
$ ls -1
devel-requirements.txt
example1
requirements.txt
setup.py
tests
unittest.cfg

Je crée un environnement virtuel python dans le dossier courant :

$ virtualenv .

Comme mon projet est un package python, mon dossier contient un fichier setup.py.

Je lance son installation mais avec l'option -e pour qu'il soit éditable.

$ bin/pip install -e .

Je peux maintenant utiliser mon package :

$ bin/python
>>> import example1
>>> example1.hello_world()
Hello world
Voila, mon installation en mode développement est finie… La seule difficulté pour certaines personnes c'est l'installation des dépendances de bases (distribute/setuptools, pip, virtualenv).
La suite de l'article présente une solution plus simple, avec moins de dépendances.

Installation en mode « édition » ?

Voici deux méthodes pour installer un package python à partir du fichier setup.py :

$ python setup.py install

ou

$ pip install .

Ces deux commandes installent le package dans le dossier site-packages de votre environnement python.

Si vous modifiez le code source de votre package, vos modifications ne seront pas "visibles" lors de l'exécution du package dans votre environnement, car une copie des fichiers sources a été faite lors de l'installation.
Par conséquent, à chaque modification, vous allez devoir installer de nouveau votre package.

Ceci est très pénible si vous êtes en train de développer votre application.

C'est là que l'installation en mode editable entre en jeu.
Lors de l'installation en mode édition, le code source n'est pas copié vers le dossier site-packages mais un lien symbolique est utilisé.
Vos modifications seront tout le temps pris en compte, sans passer par l'étape d'installation.

Voici deux commandes pour effectuer une installation en mode « édition » :

$ python setup.py develop

ou

$ pip install -e .

Par le passé, python setup.py develop ne permettait pas la désinstalation du package. Maintenant ce n'est plus le cas, par conséquent ces deux commandes semblent être synonymes.

Utilisation des fichiers requirements.txt

L'option --requirement de pip est intéressante, elle permet d'installer des dépendances depuis un fichier texte.

Bon, je pense que je n'apprends rien à personne, cette option est très connue.

Par contre, sa syntaxe est moins bien connue, exemples :

  • -e package permet d'installer un package en mode édition
  • -e . est équivalent à pip install -e . ou python setup.py develop
  • -e https://github.com/pypa/pip.git#egg=pip fait un clone du dépôt dans src/pip/ et installe le package en mode édition
  • -r requirements.txt il est possible d'installer un autre fichier de requirements

Il est souvent utile d'avoir des dépendances de packages pour la version de production et des dépendances supplémentaires pour le mode développement.

Par conséquent on trouve souvent deux fichiers requirements :

  • requirements.txt
  • devel-requirements.txt

Exemple de fichier devel-requirements.txt que j'utilise :

-e .
git+https://github.com/nose-devs/nose2.git#egg=nose2
sphinx
Sphinx-PyPI-upload

Explications :

  • j'utilise -e . pour installer le package sur lequel je suis en train de travailler
  • j'installe une version qui n'est pas encore releasé de nose2
  • j'installe des outils de documentation dont j'ai besoin en mode développement

Donc pour installer la version de développement de mon projet, je fais :

$ pip install -r devel-requirements.txt

Installation de requirements.txt depuis setup.py

Il est possible d'avoir un setup.py qui utilise la liste des dépendances de requirements.txt.

Exemple :

from setuptools import setup, find_packages


def parse_requirements(file_name):
    requirements = []
    for line in open(file_name, 'r').read().split('\n'):
        if re.match(r'(\s*#)|(\s*$)', line):
            continue
        if re.match(r'\s*-e\s+', line):
            requirements.append(re.sub(r'\s*-e\s+.*#egg=(.*)$', r'\1', line))
        elif re.match(r'\s*-f\s+', line):
            pass
        else:
            requirements.append(line)

    return requirements

setup(
    name='my_project',
    version='0.1.0',
    packages=find_packages(),
    include_package_data=True,
    zip_safe=False,
    install_requires=parse_requirements("requirements.txt")
)

Ici, parse_requirements lit le fichier requirements.txt et utilise sont contenu avec l'argument install_requires de la fonction setup.

Mais attention ! La fonction setup ne sait pas traiter la syntax spécifique à pip. Par exemple, les lignes du style git+http... ne fonctionnneront pas.

Méthode avec bootstrap de virtualenv

Depuis quelques mois, j'utilise la fonctionnalité « Creating Your Own Bootstrap Scripts » de virtualenv.

Cette fonctionnalité permet de générer un fichier python, qui installera automatiquement un environnement virtuel comme la commande virtualenv . mais sans aucune dépendance à installer. pip sera bien présent dans bin même si vous ne l'avez pas installé avant.

Utilisation de bootstrap.py

Avant de voir comment générer un fichier bootstrap.py, nous allons voir comment l'utiliser :

$ mkdir ~/my_project/
$ cd ~/my_project/
$ git clone http://stephane@repos.stephane-klein.info/example1.1 .
$ python bootstrap.py
$ bin/pip install -r devel-requirements.txt

Voila, rien de plus… on va voir par la suite qu'il est même possible d'installer automatiquement les devel-requirements.txt.

Génération d'un bootstrap.py

La documentation de virtualenv, donne une exemple de génération d'un fichier bootstrap.py.

Personnellement, je place un fichier nommé create-bootstrap.py dans le dossier où je souhaite créer mon fichier bootstrap.py. Ce fichier create-bootstrap.py contient le code suivant :

import virtualenv, textwrap

output = virtualenv.create_bootstrap_script(textwrap.dedent("""
import os, subprocess

def adjust_options(options, args):
    if len(args) == 0:
        args.append('.')

def after_install(options, home_dir):
    subprocess.call([
        os.path.join('bin', 'pip'),
        'install', '-r', 'devel-requirements.txt'
    ])
"""))
f = open('bootstrap.py', 'w').write(output)

La ligne args.append('.') indique qu'au lancement de bootstrap.py l'environnement python sera installé dans le dossier courant.

Un peu plus bas, je lance l'installation de devel-requirements.txt.

J'ai juste à lancer (une fois) python create-bootstrap.py pour générer bootstrap.py :

$ ls -1
create-bootstrap.py
setup.py
devel-requirements.txt
requirements.txt
$ python create-bootstrap.py
$ ls -1
bootstrap.py
create-bootstrap.py
devel-requirements.txt
requirements.txt
setup.py

Maintenant, j'ajoute create-bootstrap.py et bootstrap.py dans mon dépôt.

$ git add create-bootstrap.py bootstrap.py

Par la suite, il n'y a plus besoin d'utiliser create-bootstrap.py à moins de vouloir modifier bootstrap.py pour ajouter/supprimer des actions automatiques.

Pour finir, mon workflow complet

Prérequis:

  • python

Simple niveau prérequis, non ? Un peu comme buildout pour ceux qui connaissent.

Je prépare un dossier avec mon projet :

$ mkdir ~/my_project/
$ cd ~/my_project/
$ git clone http://my-project.org/ .

En une seule commande, j'installe mon environnement python et mon projet en mode développement (éditable).

$ python bootstrap.py
Read and Post Comments

Explication à propos de l'initialisation / configuration de SQLAlchemy et Elixir dans Pylons

Voici dans ce billet quelques notes à propos de la configuration de Elixir dans Pylons "0.9.7". J'y ai aussi ajouté une "explication" personnelle de l'initialisation/configuration de SQLAlchemy dans Pylons.

1.   SQLAlchemy

1.1.   Généralités

L'initialisation de SQLAlchemy consiste par exemple à :

  • créer un objet "metadata"
from sqlalchemy import MetaData

metadata = MetaData()
  • créer un objet "session"
from sqlalchemy.orm import sessionmaker
session = sessionmaker()
  • créer un objet "engine"
from sqlalchemy import create_engine
engine = create_engine('sqlite://') # in memory
  • connecter la session à un "engine"
session.configure(bind = engine)

En résumé, il nous faut 3 éléments :

  • un objet MetaData
  • un objet Session
  • un objet Engine

Nous retrouverons chacun de ces trois éléments dans les deux méthodes de configurations présentées ci-dessous.

1.2.   Par défaut, comment Pylons initialise SQLAlchemy ?

Pylons défini quelques conventions quant à l'initialisation de SQLAlchemy.

Les fichiers en questions :

config/environment.py
model/__init__.py
model/meta.py
1.2.1.   model/meta.py

Le module "model/meta.py" permet un accès standardisé aux variables "engine", "Session" et "metadata".

Extrait de quelques lignes du module "model/meta.py" :

from sqlalchemy import MetaData

engine = None
Session = None
metadata = MetaData()
1.2.2.   model/__init__.py

Le module "model/__init__.py" se charge de l'initialisation de la couche modèle. Dans notre contexte (utilisation de SQLAlchemy) ce module se charge d'initialiser SQLAlchemy.

Extrait de quelques lignes du module "model/__init__.py" :

from sqlalchemy import orm

from pylonsapp.model import meta

def init_model(engine):
    sm = orm.sessionmaker(autoflush=True, autocommit=False, bind=engine)

    meta.engine = engine
    meta.Session = orm.scoped_session(sm)

Ce module défini une fonction nommée "init_model" avec un seul paramètre nommé "engine". Cette fonction se charge de créer un objet Session et de connecter ce dernier avec l'objet engine passé en paramètre. Cette fonction est appelé par le module "config/environment.py".

1.2.3.   config/environment.py

Pour finir, le module config/environment.py est chargé de la configuration globale du framework Pylons. Celle-ci s'effectue en fonction de paramètres définies dans un fichier ".ini". Ce fichier ".ini" est passé en paramètre lors du lancement de Pylons (par exemple : paster serve development.ini).

Extrait de quelques lignes du module "config/environment.py" :

from sqlalchemy import engine_from_config

...

from pylonsapp.model import init_model

def load_environment(global_conf, app_conf):

    ...

    engine = engine_from_config(config, 'sqlalchemy.')
    init_model(engine)

La fonction "engine_from_config" crée un objet Engine en fonction des paramètres présents dans le fichier ".ini".

1.2.4.   Résumé

Pour résumer :

  • model/meta.py : création de l'objet MetaData
  • config/environment.py : création de l'objet Engine en fonction des paramètres définis dans un fichier ".ini"
  • model/__init__.py : création de l'objet Session et connection de ce dernier avec l'objet engine créé dans environment.py

Note : par convention, model/__init__.py doit aussi se charger de l'initialisation des entités de la couche modèle.

2.   Elixir

2.1.   Configuration standard de Elixir

Voici la méthode standard d'initialisation de Elixir :

import elixir

elixir.metadata.bind = "sqlite:///movies.sqlite"
elixir.metadata.bind.echo = True

# ... déclaration des entités

elixir.setup_all()

Elixir met directement à disposition les objets suivants :

  • Metadata : elixir.metadata
  • Session : elixir.session

L'objet fournit par "elixir.metadata" est identique à un objet créé via "sqlalchemy.MetaData()".

Il est tout à fait possible de remplacer l'objet Session accessible via "elixir.session"... c'est ce qui va être fait dans la section suivante.

2.2.   Initialiser Elixir dans Pylons

Voici maintenant comment configurer Elixir dans Pylons.

2.2.1.   model/meta.py

Extrait de quelques lignes du module "model/meta.py" :

import elixir

engine = None
Session = None
metadata = elixir.metadata

Cette fois metadata contient l'objet Metadata défini par Elixir.

2.2.2.   model/__init__.py

Extrait de quelques lignes du module "model/__init__.py" :

from sqlalchemy import orm
import elixir

from pylonsapp.model import meta

meta.Session = orm.scoped_session(orm.sessionmaker(autoflush=False, expire_on_commit=False))
elixir.options_defaults.update(dict(shortnames=True, inheritance='multi', polymorphic=True))
elixir.session = meta.Session
from pylonsapp.model.entities import *

def init_model(engine):
    meta.metadata.bind = engine
    meta.engine = engine
    elixir.setup_all()

Explications du contenu/changement de ce module :

  • cette fois, l'objet Session n'est pas créé dans la fonction "init_model" mais avant le chargement des classes "Entities"
  • "elixir.options_defaults.update" permet de configurer certains "comportements" de Elixir
  • le module "pylonsapp.model.entities" contient la déclaration des classes entities
  • la fonction "init_model" permet de connecter l'objet Engine à l'objet Metadata créé par Elixir. Ensuite "elixir.setup_all()" permet l'initialisation des entities.

3.   Modification de "lib/base.py"

Le billet Using Elixir with Pylons précise qu'il faut modifier le module "lib/base.py" afin de s'assurer du "nettoyage" de la Session de SQLAlchemy.

from pylonsapp.meta import Session

class BaseController(WSGIController):

    def __call__(self, environ, start_response):
        try:
            return WSGIController.__call__(self, environ, start_response)
        finally:
            Session.remove()

4.   Notes

  • Cette configuration de Elixir permet toujours de créer des tables, classes... via les méthodes classiques de SQLAlchemy
  • En cas du problème suivant : rien n'est enregistré lors de l'exécution de session.commit() c'est sans doute parce que les entities ont été sauvegardé dans une autre session.
Read and Post Comments

Quelques bonnes pratiques qui simplifient la maintenance de la documentation technique d'un projet

1   Introduction

Au quotidien, j'essaie autant que possible d'être un développeur agile. Agile dans ma façon de "coder", agile dans ma façon de concevoir la documentation technique de mes projets.

Dans cet article, je vais dresser une liste de quelques bonnes pratiques qui permettent de réaliser et de maintenir efficacement la documentation technique d'un projet. J'essaie personnellement de les suivre dans mes projets bien que cela ne soit pas toujours le cas... et c'est mal :).
L'origine de la plupart de ces bonnes pratiques proviennent de lectures diverses ou de l'observation de projets existants.
Quelques-unes d'entre elles viennent de réflexions personnelles.

2   Problématique

En génie logiciel, la mise à jour des documentations techniques est un problème récurrent.
Il est très difficile d'avoir assez de rigueur pour tenir à jour les documentations tout au long de la vie d'un projet.

Au cours de la vie d'un projet, de nombreux événements viennent perturber les plans initiaux des développeurs : cahier des charges incomplets ou erronés, évolution du besoin initial, erreurs de conceptions ou d'analyse... il faut ajouter à cela le refactoring nécessaire à l'amélioration constante de la qualité et la consistance du code source.

Ces événements ont des conséquences directes sur le code source : modification des API, création de nouvelles classes, suppression d'autres classes, création de nouvelles librairies, changement d'algorithme... Si l'on ajoute à ces changements, le manque de temps, la mise à jour de la documentation passe à la trappe.

Le problème de mise à jour de la documentation a un autre effet pervers : une personne qui consulte la documentation n'a pas connaissance du niveau de mise à jour de celle-ci, un doute persiste concernant l'exactitude des informations qui s'y trouvent. Il se peut que suite à de nombreuses expériences négatives (documentations obsolètes), une personne puisse prendre l'habitude de lire directement le code source car c'est la seule source d'information dont l'exactitude soit certaine.

3   Solutions existantes

3.1   Quelques bonnes pratiques

Quelques bonnes pratiques peuvent aider fortement à la bonne tenue de la documentation :

  • la documentation doit être le plus près possible du code source

    En pratique, il est plus facile de tenir à jour la documentation quand celle-ci est intégrée dans le code source.
    En Python, les docstrings permettent d'intégrer la documentation à l'intérieur du code source. La documentation est au plus près du code, lorsque le code est modifié le développeur est mieux à même à mettre tout de suite à jour la documentation.
    Lorsqu'une documentation n'est pas sujette à être intégrée dans le code source, une bonne pratique est de l'intégrer dans un fichier texte enregistré dans le répertoire qui correspond le mieux avec la partie du projet qu'elle documente.
    Par exemple, si le fichier documente l'utilisation d'une librairie, il est judicieux d'enregistrer la documentation à la racine du code source de la librairie.
    Si la documentation traite du projet dans son ensemble, il est judicieux d'enregistrer le fichier à la racine du projet...
    L'idée est toujours la même : garder la documentation le plus près possible du code qu'elle documente.
  • la documentation doit être facilement modifiable

    Généralement, les développeurs utilisent un éditeur texte (plain text) pour éditer du code source. La documentation doit être éditable aussi facilement que le code source du programme. C'est pour cela qu'il est conseillé d'écrire la documentation au format texte (plain text) plutôt que dans un format spécifique comme Writer d'OpenOffice.org...
    En Python, le format communément utilisé est le reSTructuredText, un format texte enrichi mais de manière non intrusif.
    Là encore, le but est de favoriser la mise à jour de la documentation en rendant sa modification le plus simple possible.
  • éviter au maximum la redondance

    Certaines informations clés qui sont au coeur de la documentation technique sont déjà présentes dans le code source du projet.
    Partant de ce constat, il peut être judicieux d'automatiser l'extraction de ces informations vers la documentation afin d'éviter la redondance et par conséquent éviter les besoins de mises à jour et les risques d'erreurs.
    La documentation peut être en partie générée à partir du code source du projet.

    Exemples pratiques :

    • génération automatique de l'API d'une librairie à partir du code source ( exemples d'outils python : Epydoc [1], Sphinx [2]);
    • génération d'un diagramme de classe à partir du code source (voir futur billet à propos de code2uml);
    • génération d'un diagramme de base de données à partir du code sql qui permet la création de la structure de la base de données (voir futur billet à propos de code2uml);
    • génération d'une liste de tâches à réaliser (todo list) à partir de marques à l'intérieur du code source (voir exemple dans Zope3 [3]). Ceci evite d'avoir deux fois la même information : une fois dans le code source à l'endroit précis où la tâche doit être réalisée et une fois dans la liste des tâches à réaliser. De plus, cette pratique a l'avantage de situer la marque d'indication de la tâche au plus près du code source.
    • Les scénarios de tests automatisés d'interfaces utilisateurs peuvent être utilisés pour générer automatiquement des screncasts des applications. Ils pourront alors être utilisés comme documentation à destination des utilisateurs finals. De plus, ils auront l'avantage d'être toujours à jour et valides (si les tests sont réalisés avec succès).
  • donner à la documentation un intérêt fonctionnel

    Une autre bonne pratique est de donner un intérêt fonctionnel aux documentations. C'est-à-dire que cette documentation doit être utile pour générer ou réaliser d'autres choses. Si la documentation automatise certaines tâches et simplifie la vie du développeur, ce dernier aura plus de motivation pour réaliser et mettre à jour sa documentation.

    Exemples :

    • Les doctests sont de bons exemples: cette documentation sert à la fois de tutoriels et de scripts de tests de bon fonctionnement du code source;
    • Un document de spécification (cahier des charges) bien formalisé peut être utilisé pour communiquer à propos de l'état d'avancement d'un projet.
[1]http://epydoc.sourceforge.net/
[2]http://sphinx.pocoo.org/
[3]http://amy.gocept.com/~ctheune/XXXreport.html

4   Est-ce que la documentation doit générer du code source ou alors est-ce que le code source doit générer de la documentation ?

Je pense personnellement que la meilleure solution est de générer de la documentation à partir du code source. Pourquoi ?
Parce que la première solution, celle qui consiste à générer du code source à partir de la documentation peut fonctionner correctement seulement lors de la phase initiale du projet. Lorsqu'il existe déjà du code source, il est difficile de modifier ce code à partir de la documentation sans prendre le risque de casser beaucoup de chose.
D'autre part, lorsqu'un développeur est pressé, correction de bug... il va toujours modifier le code source, il ne va pas modifier la documentation, exécuter une regénération... Le coeur d'un projet est toujours son code source, c'est la matière première du projet et l'on doit utiliser cette matière pour générer d'autres informations / documents... et non l'inverse.

5   Pourquoi ce billet ?

J'ai écrit ce billet car il me sert d'introduction pour un prochain billet qui traitera de mon application qui permet de générer un graphe UML à partir du code source d'un programme.

6   Outils et références

Livres :

Outils Python :

  • Epydoc : pour générer automatiquement la documentation d'une API, librairie...
  • Sphinx : outil qui permet entre autres de générer une documentation cohérente à partir de différents fichiers reSTructuredText ainsi qu'à partir du code source d'un projet
  • doctest : outil qui permet de tester un programme à partir d'un jeu de tests écrit sous la forme reSTructuredText
Read and Post Comments

Mes flux :