Derniers billets de blog

Expert Python/Django/Plone et chef de projet agile, j'aime coder et découvrir de nouvelles technologies.

Bien configurer son environnement de développement Python : Standardiser l'exécution des tests avec tox. Partie 4

tox permet d'automatiser l'exécution de la suite de tests d'un projet dans plusieurs environnements virtuels (différentes versions de Python ou d'interpréteurs) en une seule commande. Cela s'avère vite indispensable lorsque l'on développe un module open source.

Cet article fait partie fait partie d'une série d'articles présentant quelques bonnes pratiques qui vous permettront d'avoir un environnement de développement Python aux petits oignons :

Standardiser l'exécution des tests avec tox

Intégrons le à un projet existant, au hasard Django :

# Créons un projet dédié
mkproject tox-django
(tox-django) cdproject
# Installation de tox
(tox-django) pip install tox
# Clone du code du projet Django
(tox-django) git clone git@github.com:django/django.git

Ajoutons à présent un fichier tox.ini permettant d'installer le projet et de lancer la suite de tests du projet dans deux environnements : Python 2.7 et Python 3.4. On en profite pour générer un rapport de couverture de test HTML qui s'ouvrira automatiquement.

(tox-django) cd django
(tox-django) vi tox.ini
# tox.ini, à mettre dans le dossier que setup.py

[tox]
envlis = py27,py34

[testenv:py27]
changedir = tests
# Installe coverage dans l'environnement virtuel
deps =
    coverage
# Commandes externes à l'environnement virtuel, pour éviter les warnings
whitelist_externals = open
# La documentation expliquant comment lancer la suite de tests Django est ici :
# https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/unit-tests/
commands =
    pip install -r requirements/py2.txt
    coverage erase
    coverage run --rcfile=.coveragerc --include=*site-packages/django/* runtests.py --settings=test_sqlite --parallel=1
    coverage html
    open coverage_html/index.html

[testenv:py34]
changedir = tests
deps =
    coverage
whitelist_externals = open
commands =
    pip install -r requirements/py3.txt
    coverage erase
    coverage run --rcfile=.coveragerc --include=*site-packages/django/* runtests.py --settings=test_sqlite --parallel=1
    coverage html
    open coverage_html/index.html

Pour lancer les tests, une commande suffit à présent :

(tox-django) tox

Il est également possible de préciser un environnement particulier :

(tox-django) tox -e py34

Aller plus loin avec tox

Une collection de fontes libres, prêtes à l'emploi et optimisées pour le web

Alsacreations met à disposition sur l'un de ses dépôts Github une collection de fontes libres, optimisées pour le web, et facilement intégrable dans un nouveau projet. Voyons à quoi ressemblent ces fonts. Un grand merci à Alsacreations !
Dépôt Github : https://github.com/alsacreations/webfonts

alexbrush-regular

Beautiful is better than ugly.

calendas-plus-regular

Explicit is better than implicit.

droidsans-bold

Simple is better than complex.

droidsans-regular

Complex is better than complicated.

grandhotel-regular

Flat is better than nested.

lato-bold

Sparse is better than dense.

lato-italic

Readability counts.

lato-regular

Special cases aren't special enough to break the rules.

leaguegothic-regular

Although practicality beats purity.

merriweather-bold

Errors should never pass silently.

merriweather-regular

Unless explicitly silenced.

opensans-bold

In the face of ambiguity, refuse the temptation to guess.

opensans-italic

There should be one-- and preferably only one --obvious way to do it.

opensans-light

Although that way may not be obvious at first unless you're Dutch.

opensans-regular

Now is better than never.


Et en attendant de nouvelles polices, la fin du Zen de Python :
  • Although never is often better than *right* now.
  • If the implementation is hard to explain, it's a bad idea.
  • If the implementation is easy to explain, it may be a good idea.
  • Namespaces are one honking great idea -- let's do more of those!

Bien configurer son environnement de développement Python : Valider son code Python avec flake8. Partie 3

La PEP 8 (Style guide for Python Code) de Python est un ensemble de conventions permettant d'écrire du beau code Python. flake8 est un des outils mis à disposition par la communauté pour aider à valider son code Python au regard de la PEP 8.

Cet article fait partie fait partie d'une série d'articles présentant quelques bonnes pratiques qui vous permettront d'avoir un environnement de développement Python aux petits oignons :

Installation de flake8

flake8 permet de valider son code Python au regard des conventions de codage PEP 8 (Style guide for Python Code) et de pyflakes (détection d'erreurs).

# Créons un projet dédié
mkproject test-flake8
(test-flake8) cdproject

# Installation de flake8
(test-flake8) pip install flake8

Valider son code Python avec flake8

Une fois installé, flake8 peut être invoqué sur un module Python ou un dossier complet.

Créons un petit module Python (mytest.py) comportant quelques erreurs :

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import mylib  # noqa (i.e ignore cette erreur)

def foo( bar):
    print 'foo {}'.format(bar)

et invoquons flake8 :

(test-flake8) flake8 mytest.py
mytest.py:4:1: F401 'os' imported but unused
mytest.py:7:1: E302 expected 2 blank lines, found 1
mytest.py:7:9: E201 whitespace after '('
mytest.py:8:31: W292 no newline at end of file

Ceci dit, tous les éditeurs de code Python dignes de ce nom intègrent ou permettent d'intégrer via un plugin le support de flake8... donc aucune excuse pour ne pas produire du beau code Python !

Jetez également un coup d'oeil à isort qui permet de trier dans l'ordre alphabétique les imports d'un module Python et de les organiser par section (les imports Future, puis ceux de la librairie standard, ...).

Bien configurer son environnement de développement Python : Personnaliser le script de démarrage du shell Python. Partie 2

Après avoir vu comment gérer plusieurs projets Python dans des environnements virtuels séparés grâce à virtualenvwrapper, nous allons voir à présent comment personnaliser le script de démarrage qui est exécuté automatiquement lorsque l'on démarre un shell Python.

Cet article fait partie fait partie d'une série d'articles présentant quelques bonnes pratiques qui vous permettront d'avoir un environnement de développement Python aux petits oignons :

Personnaliser le script de démarrage du shell Python

Python met à disposition la variable d'environnement PYTHONSTARTUP qui permet de choisir un script de démarrage qui sera exécuté automatiquement lorsque l'on démarre un shell Python. Cette variable attend un chemin absolu vers un fichier qui peut contenir n'importe quel code Python :

# Config à ajouter dans ~/.bashrc ou ~/.profile
export PYTHONSTARTUP=~/python_startup_script.py

Voici un exemple de script de démarrage qui :

  • Importe quelques fonctions de compatibilité Python 3
  • Importe des modules de la librairie standard que j'utilise fréquemment
  • Importe des modules additionnels potentiellement présents
  • Et si on est au sein d'un environnement virtuel :
    • Affiche le nom de l'environnement virtuel dans le prompt
    • Affiche la liste des modules qui ont été installés avec pip
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Compatibilité Python 3
from __future__ import unicode_literals, print_function, absolute_import

# Quelques modules de la lib standard
import sys
import os
import json
import re
import subprocess
from pprint import pprint
from datetime import datetime, timedelta

# Modules additionnels potentiellement présents
try:
    import requests
except ImportError:
    pass

# Environnement virtuel ?
env = os.environ.get('VIRTUAL_ENV')
if env:

    # Affiche le nom de l'environnement virtuel dans le prompt
    env_name = os.path.basename(env)
    sys.ps1 = '({0}) {1} '.format(env_name, getattr(sys, 'ps1', '>>>'))

    # Affiche la liste des modules qui ont été installés avec pip
    print("\nVirtualenv '{}' contains:".format(env_name))
    cmd = subprocess.check_output(
        [env + "/bin/pip", "freeze"],
        stderr=subprocess.STDOUT
    )
    try:
        cmd = cmd.decode('utf8')
    except:
        pass

    modules = [
        "'{}'".format(m.split('==')[0])  # exemple: u'Django==1.8.4' => u'Django'
        for m in cmd.strip().split("\n")
    ]
    print(', '.join(sorted(modules)) + '\n')


print('Use Python startup script : {}\n'.format(os.environ.get('PYTHONSTARTUP')))

Et à présent lorsque l'on lance un shell au sein d'un environnement virtuel :

mkvirtualenv project
(project) pip install requests Django
(project) python
[...]
Virtualenv 'project' contains:
'Django', 'requests'

Use Python startup script : ~/python_startup_script.py

(project) >>> pprint({i: 'c' + 'o'*i + 'l' for i in range(2, 10)})
{2: u'cool',
 3: u'coool',
 4: u'cooool',
 5: u'coooool',
 6: u'cooooool',
 7: u'coooooool',
 8: u'cooooooool',
 9: u'coooooooool'}

Bien configurer son environnement de développement Python : Les environnements virtuels. Partie 1

C'est quand même plus sympa de développer quand notre environnement de développement est bien configuré ! Ce didacticiel est une introduction en plusieurs parties à quelques bonnes pratiques qui vous permettront d'avoir un environnement de développement Python aux petits oignons. Partie 1 : Travailler avec des environnements virtuels et les associer aux projets.

Cet article fait partie fait partie d'une série d'articles présentant quelques bonnes pratiques qui vous permettront d'avoir un environnement de développement Python aux petits oignons :

Pré-requis

Je considère dans ce tutorial que vous avez déjà installé Python > 2.7.9 et > 3.4.

Ces versions de Python fournissent par défaut pip qui est un outil permettant de télécharger et d'installer des modules Python depuis le Python Package Index (PyPI).

1. Principe de base : Travailler avec des environnements virtuels Python

Les environnements virtuels Python permettent d'avoir des installations de Python isolées du système et séparées les unes des autres. Cela permet de gérer plusieurs projets sur sa machine de développements, certains utilisant des modules de versions différentes, voir même des versions différentes de Python.

Installation de Virtualenv

pip install virtualenv

Exemple d'utilisation

mkdir -p ~/virtualenvs
virtualenv ~/virtualenvs/project1

Virtualenv créait ainsi un environnement Python complètement isolé, dans lequel on va pouvoir installer, mettre à jour, supprimer des modules Python après avoir activé l'environnement :

# Activation de l'environnement
source ~/virtualenvs/project1/bin/activate

# Le path de notre interpréteur Python
(project1) which python
~/virtualenvs/project1/bin/python

# Installer un module, le mettre à jour
(project1) pip install gunicorn
(project1) pip install gunicorn --upgrade
(project1) pip install requests

# Installer une version spécifique d'un module
# ou downgrader vers une version antérieure
(project1) pip install Django==1.8.6
(project1) pip install Django==1.8.5

# Supprimer un module
(project1) pip uninstall requests

# Lister le contenu de notre environnement
(project1) pip freeze
Django==1.8.5
gunicorn==19.4.1
# En enregistrer le contenu
(project1) pip freeze > requirements.txt
# Et pour installer l'ensemble en une commande sur une autre machine
(project1) pip install -r requirements.txt

Tip : Pensez à mettre à jour pip lui-même de temps en temps :

(project1) pip install --upgrade pip

Et pour sortir de l'environnement :

(project1) deactivate

À la création de l'environnement virtuel, vous pouvez choisir si ce dernier hérite ou non des modules du Python système. Par défaut, les modules du Python système ne sont pas hérités. Pour forcer l'héritage :

virtualenv --system-site-packages ~/virtualenvs/project2

Si vous souhaitez utiliser une version spécifique de Python, ou avoir pour un même projet un environnement Python 2 et un autre en Python 3 :

virtualenv ~/virtualenvs/project3-py2 -p /usr/bin/python2.7
virtualenv ~/virtualenvs/project3-py3 -p /usr/bin/python3.4

Aller plus loin avec pip

2. J'ai 99 environnements virtuels, comment me faciliter la vie ?

virtualenvwrapper est la solution, il va nous nous offrir :

  • Une centralisation des environnements virtuels et des projets associés,
  • Une gestion simple des environnements virtuels (création, suppression, copie),
  • Une commande pour passer facilement d'un environnement à l'autre,
  • De l'autocomplétion sur les noms des environnements virtuels,
  • Pour les utilisateurs avancés la possibilité de configurer des hooks par environnement.

Installons et configurons virtualenvwrapper

# Installation
pip install virtualenvwrapper

# Config à ajouter dans ~/.bashrc ou ~/.profile
# - dossier contenant les environnements virtuels
export WORKON_HOME=~/.virtualenvs
mkdir -p $WORKON_HOME
# - dossier contenant les projets associés
export PROJECT_HOME=~/pyprojects
mkdir -p $PROJECT_HOME
# - s'assurer que virtualenvwrapper est toujours disponible
source /usr/local/bin/virtualenvwrapper.sh

Pensez à relancer le terminal pour activer les modifications et permettre à virtualenvwrapper de s'installer (les messages d'installation ne s'afficheront plus par la suite).

Gestion des environnements

# Création d'un premier environnement
mkvirtualenv project1
# L'environnement est créé et automatiquement activé
# Installons Django :
(project1) pip install Django==1.8

# Créons un deuxième environnement
(project1) mkvirtualenv project2
(project2) pip install Django==1.7
(project2) cdvirtualenv
(project2) pwd
~/.virtualenvs/project2
(project2) cdsitepackages
(project2) pwd
~/.virtualenvs/project2/lib/python2.7/site-packages

# Lister les environnements
(project2) lsvirtualenv -b
project1
project2

# Passer d'un environnement à l'autre
(project2) workon project1
(project1) workon project2
(project2)

# Supprimer un environnement
(project2) deactivate
rmvirtualenv project2

Gestion des projets associés aux environnements

# Créer un projet vide et son environnement virtuel associé
mkproject project3
(project3) cdvirtualenv
(project3) pwd
~/.virtualenvs/project3
(project3) cdproject
(project3) pwd
~/pyprojects/project3

Ok, mais comment faire si on a déjà un projet existant depuis longtemps et que l'on ne souhaite pas le redéployer ou en modifier les chemins d'installation ? La commande setvirtualenvproject arrive à la rescousse en permettant d'associer un environnement virtuel existant à un projet existant :

setvirtualenvproject virtualenv_path project_path
# Une fois l'environnement virtuel activé,
# les commandes cdvirtualenv et cdproject sont opérationnelles.

Attention, si l'environnement virtuel n'est pas situé dans le dossier $WORKON_HOME (où les environnements sont créés par les commandes mkvirtualenv et mkproject), il ne sera pas utilisable avec la commande workon. Pour y remédier, il suffit d'y créer un lien symbolique :

cd $WORKON_HOME
ln -s virtualenv_path

Aller plus loin avec virtualenvwrapper

Dans la prochaine partie, nous verrons comment configurer le script de démarrage du shell Python.

Plone 5 : Inclure du code avec la coloration synthaxique (restructuredText)

Pygments permet de rendre du code informatique plus lisible en y ajoutant des informations de style et des couleurs. Après une introduction au module Pygments, nous verrons comment l'intégrer dans Plone 5 afin de pouvoir utiliser la coloration syntaxique dans nos articles rédigés en "text/restructured".

Coloration syntaxique avec Pygments

Introduction à Pygments

La coloration syntaxique permet de donner une couleur et un style particulier à chaque élément constituant un code source informatique, l'objectif étant de rendre ce code plus lisible et agréable à lire.

Pygments est une library Python qui permet la coloration syntaxique d'un très grand nombre de langages informatiques tel que Python, HTML, CSS, Javascript, PHP, Ruby, etc. Cet outil peut être utilisé directement avec la ligne de commande ou en tant que librairie intégrée dans un projet, permettant ainsi l'affichage de codes sources informatiques dans un site web, un wiki, un forum, etc.

Installer Pygments

Rien de plus simple, dans votre environnement virtuel Python :

pip install pygments

Générer la version HTML d'un "bout de code"

Soit le fichier code.py contenant le code Python suivant :

# Fibonacci numbers
def fib(n):    # write Fibonacci series up to n
    a, b = 0, 1
    while b < n:
        print(b, end=' ')
        a, b = b, a+b
    print()

La version HTML de ce code s'obtient avec la commande suivante :

pygmentize -f html -l python -o code.html code.py

Où :

  • -f est le format de sortie souhaité (ici html)
  • -o est le nom du fichier de sortie
  • -l est le module utilisé pour parser le code, appelé lexer (ici python)

Le code HTML ainsi généré est le suivant :

<div class="highlight"><pre><span class="c1"># Fibonacci numbers</span>
<span class="k">def</span> <span class="nf">fib</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>    <span class="c1"># write Fibonacci series up to n</span>
    <span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span>
    <span class="k">while</span> <span class="n">b</span> <span class="o">&lt;</span> <span class="n">n</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s1">&#39; &#39;</span><span class="p">)</span>
        <span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="n">b</span><span class="p">,</span> <span class="n">a</span><span class="o">+</span><span class="n">b</span>
    <span class="k">print</span><span class="p">()</span>
</pre></div>

Pygments offres d'autres options pour améliorer le résultat, tel que l'ajout des numéros de ligne ou encore la génération d'un ficher HTML complet directement utilisable dans un navigateur.

Générer la feuille de style CSS : pygments.css

Le code HTML généré par Pygments n'inclu pas les informations de style CSS mais uniquement des balises <span> avec les classes nécessaires (ex: <span class="c1"># Fibonacci numbers</span>). Il nous faut par conséquent générer la feuille de styles CSS nécessaire au bon affichage, à inclure dans notre page web :

Pour générer cette feuille de style, utilisez la commande suivante :

pygmentize -f html -S colorful > pygments.css

Le fichier pygments.css contient alors (extrait) :

.hll { background-color: #ffffcc }
.c { color: #888888 } /* Comment */
.err { color: #FF0000; background-color: #FFAAAA } /* Error */
.k { color: #008800; font-weight: bold } /* Keyword */
.o { color: #333333 } /* Operator */
.ch { color: #888888 } /* Comment.Hashbang */
.cm { color: #888888 } /* Comment.Multiline */
.cp { color: #557799 } /* Comment.Preproc */
...

Intégrer et utiliser Pygments dans Plone

Écrire ses articles dans le format "text/restructured"

Pour pouvoir utiliser le format "text/restructured" dans l'éditeur WYSIWYG de Plone, il est nécessaire de l'activer dans la page de configuration "Configuration du site > Contenu > Formatage du texte": cochez "text/restructured".

Le format apparaît alors dans la liste de choix se situant juste en dessous de l'éditeur WYSIWYG.

Ajouter Pygments dans le buildout du projet

# buildout.cfg file

[buildout]
eggs =
    Plone
    Pillow
    Pygments
...

[versions]
Pygments = 2.0.2
...
bin/buildout

Saisir du code source en "text/restructured" dans un article

Exemple pour du code Python :

.. sourcecode:: python

    # Fibonacci numbers
    def fib(n):    # write Fibonacci series up to n
        a, b = 0, 1
        while b < n:
            print(b, end=' ')
            a, b = b, a+b
        print()

En savoir plus sur la directive sourcecode.

Le code HTML généré par Plone est le suivant :

<pre class="code python literal-block"><span class="comment"># Fibonacci numbers</span>
<span class="keyword">def</span> <span class="name function">fib</span><span class="punctuation">(</span><span class="name">n</span><span class="punctuation">):</span>       <span class="comment"># write Fibonacci series up to n</span>
    <span class="name">a</span><span class="punctuation">,</span> <span class="name">b</span> <span class="operator">=</span> <span class="literal number integer">0</span><span class="punctuation">,</span> <span class="literal number integer">1</span>
    <span class="keyword">while</span> <span class="name">b</span> <span class="operator">&lt;</span> <span class="name">n</span><span class="punctuation">:</span>
        <span class="keyword">print</span><span class="punctuation">(</span><span class="name">b</span><span class="punctuation">,</span> <span class="name">end</span><span class="operator">=</span><span class="literal string">' '</span><span class="punctuation">)</span>
        <span class="name">a</span><span class="punctuation">,</span> <span class="name">b</span> <span class="operator">=</span> <span class="name">b</span><span class="punctuation">,</span> <span class="name">a</span><span class="operator">+</span><span class="name">b</span>
    <span class="keyword">print</span><span class="punctuation">()</span>
</pre>

Générer la feuille de style CSS : pygments.css

Le code HTML généré par Pygments n'inclu pas les informations de style CSS mais uniquement des balises <span> avec les classes nécessaires (ex: <span class="c1"># Fibonacci numbers</span>). Il nous faut par conséquent générer la feuille de styles CSS nécessaire au bon affichage :

cd <zeocluster-directory>
bin/pygmentize -f html -S colorful -a pre.code > pygments.css

Cette commande va générer un fichier pygments.css utilisant le thème "colorful" pour le sélecteur "pre.code" afin de ne pas styler d'autres éléments de notre page.

Malheureusement l'intégration de cette feuille de style dans Plone ne suffira pas pour que la coloration soit effective sur notre code :(

En effet, le code HTML généré par Plone contient des noms de classe css longs, comme par exemple <span class="comment"># Fibonacci numbers</span> alors que la feuille de style fournit par Pygments permet de traiter des noms de classe css courts comme <span class="c1"># Fibonacci numbers</span>.

Un contributeur sur stackoverflow propose une solution à ce problème
en utilisant un script Python qui permet de générer une feuille de style Pygments contenant à la fois les noms de classe css courts et les noms de classe css long, solutionnant ainsi notre problème.

Intégrer pygments.css dans Plone

Pour finaliser le tout il ne reste plus qu'à intégrer la feuille de style Pygments dans Plone.

En ce qui me concerne je l'ai ajouté dans mon thème et je l'ai inclu dans mon fichier less principal avec la directive "(css)" qui permet d'inclure tel quel du code CSS dans un fichier less :

// Pygments
@import (css) "pygments.css";

Et voilà !

Le présent article utilise cette technique !