Accès clients

Le Blogpython

Je travaille actuellement sur une application Django que je compte publier sous licence libre, et je suis confronté au problème classique de l'exposition de la configuration au développeur via les settings de son propre projet.

Classiquement, on a tendance à proposer les settings "à plat", dans le module settings.py du projet :

# settings.py
MY_APP_NAME_FOO = 42
MY_APP_NAME_ENABLE_CHUCK_NORRIZ_MODE = True

Et donc depuis votre appli, vous pouvez récupérer les settings utilisateur de cette façon, en leur assignant une valeur par défaut s'ils ne sont pas déclarés :

# apps/myapp/foo.py
from django.conf import settings

FOO = getattr(settings, 'MY_APP_NAME_FOO', 42)
ENABLE_CHUCK_NORRIZ_MODE = getattr(settings, 'MY_APP_NAME_ENABLE_CHUCK_NORRIZ_MODE', False)

Simple, pratique, suffisant me direz vous. Oui, mais bon, c'est un petit peu verbeux à mon sens, et pas toujours souple pour gérer un catalogue de settings ainsi que leur surcharge. Et puis j'ai l'impression en préfixant systématiquement ces noms de variables de faire insulte à cette merveilleuse fonctionnalité qu'on appelle la gestion des espaces de noms (voire de refaire du PHP < 5.3, ce qui provoque chez moi des bouffées d'angoisse et entame un processus de décapilation douloureux, mais je m'égare).

Qui plus est, personnellement en temps que développeur, j'aurai tendance à préférer gérer les settings correspondant à une application dans un dictionnaire dédié, un peu comme ce que propose la django-debug-toolbar.

Par exemple, en reprenant l'exemple de code initial ou seul le setting ENABLE_CHUCK_NORRIZ_MODE est finalement surchargé :

# settings.py
MY_APP_CONFIG = {
    'ENABLE_CHUCK_NORRIZ_MODE': True,
}

J'ai donc trouvé un moyen assez simple de proposer cette fonctionnalité. Dans le fichier __init__.py de votre module d'application, ajoutez le code suivant :

# apps/my_app/__init__.py
from django.conf import settings

app_settings = dict({
    'FOO': 42,
    'ENABLE_CHUCK_NORRIZ_MODE': False,
}, **getattr(settings, 'MY_APP_CONFIG', {}))

Vous constaterez qu'on fusionne bêtement les settings par défaut et ceux de l'utilisateur qui auront la priorité de surcharge. Ainsi, partout depuis votre application, vous pourrez accéder à ce dictionnaire de settings de cette façon :

# apps/my_app/utils.py
from . import app_settings

if app_settings.get('ENABLE_CHUCK_NORRIZ_MODE'):
    print 'Chuck Norriz is watching you'
else:
    print 'Dance dance, little lamb'

Et bien entendu, pour importer les settings de l'application depuis n'importe où (sous réserve que le module de l'application soit dans votre PYTHON_PATH) :

# foo/bar.py
from my_app import app_settings

print app_settings.get('FOO') # 42

Merci de votre attention, et à bientôt pour de nouvelle aventures.

Sometimes you work on stuff you don't really control, eg. when interacting with some mysterious SOAP server accross the Internets, and you'd appreciate a little help from the Django ecosystem to ease debugging.

That's — you guessed it — my case currently, and I really appreciated being able to create my own custom panel for adding specific debugging capabilities to the awesome Django Debug Toolbar.

Here's how I did, learning mainly from the code of the panels shipping with the DJT. I'm supposing you have installed and configured the DJT in your project already.

First of all, create a panels.py module within one of your app (or wherever you want as it's in your python path) and create a DebugPanel derivated class, like this:

from debug_toolbar.panels import DebugPanel
from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _

class MyUsefulDebugPanel(DebugPanel):
    name = 'MyUseful'
    has_content = True

    def nav_title(self):
        return _('Useful Infos')

    def title(self):
        return _('My Useful Debug Panel')

    def url(self):
        return ''

    def content(self):
        context = self.context.copy()
        context.update({
            'infos': [
                {'plop': 'plip'},
                {'plop': 'plup'},
            ],
        })
        return render_to_string('panels/my_useful_panel.html', context)

The debug panel class methods and code should be self-explanatory enough. Just note you have to create a template, here panels/my_useful_panel.html to be stored in your project templates directory, with this kind of contents:

<p>Hey, these are useful informations, I swear:</p>
<ul>
{% for info in infos %}
    <li>Plop is {{ info.plop}}</li>
{% endfor %}
</ul>

Now you have to register the new panel by adding its path to the DEBUG_TOOLBAR_PANELS tuple, in your settings.py (create it if it's not there):

DEBUG_TOOLBAR_PANELS = (
    'debug_toolbar.panels.version.VersionDebugPanel',
    'debug_toolbar.panels.timer.TimerDebugPanel',
    'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel',
    'debug_toolbar.panels.headers.HeaderDebugPanel',
    'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
    'debug_toolbar.panels.template.TemplateDebugPanel',
    'debug_toolbar.panels.sql.SQLDebugPanel',
    'debug_toolbar.panels.signals.SignalDebugPanel',
    'debug_toolbar.panels.logger.LoggingPanel',
    'my_app_name.panels.MyUsefulDebugPanel',
)

Of course it's up to you to put really useful informations there, but here's the result, tadaa:

result screenshot

Thanks for reading, happy ponying.

Python is awesome, and so is its native interactive interpreter. I discovered today that it can even provide autocompletion using a very simple trick:

# in your ~/.profile
export PYTHONSTARTUP=$HOME/.pythonrc.py

# in a new ~/.pythonrc.py file
try:
    import readline
except ImportError:
    print("Module readline not available.")
else:
    import rlcompleter
    readline.parse_and_bind("tab: complete")

Note: Don't forget to source your .profile with $ source ~/.profile.

Magic? Well if like me you're running Mac OS X, it won't work, no autocompletion, nada. OS X seems to ship with a very poor (and obsolete) python, and no readline implementation — which is mandatory to achieve our purpose. I even tried to install readline by its own, but it won't solve the problem.

So while being at tweaking up my python setup, let me get rid of the Apple stuff and install a fresh version of python using Homebrew, a great package manager for OSX:

$ brew install readline python

Tadaa! Now you get autocompletion, plus a shiny python 2.7.1 (you could also install latest python3 running brew install python3 by the way).

As a side note, if you work with virtualenvs like me, creating a new env will now involve specifying which python you want to use:

$ mkvirtualenv -p /usr/local/Cellar/python/2.7.1/bin/python \
    --no-site-packages `pwd`/env

That's all folks.

As a personal reminder, here's how to install PIL with jpeg and freetype support in a Python virtualenv with a little help from Homebrew:

$ brew install jpeg
$ wget http://mirrors.fe.up.pt/pub/nongnu/freetype/freetype-2.4.4.tar.gz
$ tar xvzf freetype-2.4.4.tar.gz && cd freetype-2.4.4
$ ./configure && make && sudo make install
$ cd .. && rm -rf freetype-2.4.4*
$ mkvirtualenv fubar --no-site-packages
(fubar)$ pip install PIL

You should obtain something like this at the end of the installation process:

--------------------------------------------------------------------
PIL 1.1.7 SETUP SUMMARY
--------------------------------------------------------------------
version       1.1.7
platform      darwin 2.6.1 (r261:67515, Jun 24 2010, 21:47:49)
              [GCC 4.2.1 (Apple Inc. build 5646)]
--------------------------------------------------------------------
--- TKINTER support available
--- JPEG support available
--- ZLIB (PNG/ZIP) support available
--- FREETYPE2 support available
*** LITTLECMS support not available
--------------------------------------------------------------------

That's all for today folks, thanks for your attention.

EDIT: If you want little-cms support, just run:

$ brew install little-cms
(fubar)$ pip install PIL

Ce billet résume les étapes nécessaires pour installer un ou plusieurs environnements de développement Django fonctionnels, portables et faciles à maintenir sous Mac OS X.

Même si Django est un framework relativement simple à installer, lorsqu'il s'agit de développer plusieurs projets mettant en œuvre différentes versions de ce dernier ou de librairies tierces nécessaires pour assurer son bon fonctionnement, le casse-tête peut rapidement devenir ingérable si l'on ne prend pas garde à bien isoler le contexte applicatif dans un environnement dédié, isolé du reste du système.

Concrètement, imaginons que j'ai deux projets Django :

  • Le projet A utilise Django 1.2-DEV, django_toolbar en version 0.8.3 et Python 2.6
  • Le projet B utilise Django 1.1 et Pinax en version 0.6 tournant avec Python 2.5

Impossible dans ces conditions d'utiliser une version unique de chacune des librairies installées sur le système (et non, l'utilisation de liens symboliques et de préfixes n'est pas une solution acceptable sur le moyen/long terme).

Aussi, découvrant progressivement la richesse de l'écosystème Python, je n'ai pas manqué de m'extasier devant la puissance et la simplicité d'outils tels que pip, virtualenv et virtualenvwrapper pour répondre à ces questions.

pip, un installeur de paquet simple et efficace

D'aucuns de ceux qui utilisent une distribution Linux connaissent le bonheur d'utiliser un gestionnaire de paquets. L'installation de programmes et de librairies s'effectuent la plupart du temps en ligne de commande, et la résolution des dépendances est totalement prise en charge de façon transparente.

pip est un gestionnaire de paquets Python, écrit lui-même en Python, qui tient ce rôle à merveille. L'installation de pip est fort simple, pour peu de disposer de setup_tools :

$ sudo easy_install pip

Pour installer un paquet, par exemple la dernière version stable de Django (la 1.1.1), il vous suffit de taper en ligne de commande :

$ sudo pip install Django

Pour chercher un paquet, c'est aussi simple que :

$ pip search django-debug-toolbar

virtualenv, un environnement Python virtuel étanche et cloisonné

virtualenv vous propose ni plus ni moins de créer à la demande des environnements de travail virtuels pour tout ce qui touche à Python : chaque environnement possède ses propres paquets et librairies, voire dispose de sa propre version de l'interpréteur !

virtualenvwrapper, quand à lui, est un jeu de scripts utilitaires permettant de créer, modifier, supprimer et - d'une façon plus générale - de travailler efficacement avec virtualenv.

L'installation de virtualenv et de virtualenvwrapper, ça tombe bien, peuvent se faire directement via pip :

$ sudo pip install virtualenv virtualenvwrapper

Pour finir de configurer l'installation de virtualenv, il nous reste cependant quelques étapes supplémentaires.

Tout d'abord, il nous faut créer le répertoire qui contiendra nos environnements virtuels sur notre machine 1 :

$ mkdir ~/.virtualenvs

Il faut également renseigner ce chemin dans la variable d'environnement $WORKON_HOME et instancier la gestion des environnements virtuels, le plus simple étant alors de placer les déclarations ad-hoc dans le fichier ~/.profile de votre compte utilisateur 2 :

$ echo "export WORKON_HOME=$HOME/.virtualenvs" >> ~/.profile
$ echo "export PIP_VIRTUALENV_BASE=$WORKON_HOME" >> ~/.profile
$ echo "export PIP_RESPECT_VIRTUALENV=true" >> ~/.profile
$ echo "source /usr/local/bin/virtualenvwrapper.sh" >> ~/.profile

Note : il se peut que selon le mode d'installation utilisé, le chemin vers le fichier /usr/local/bin/virtualenvwrapper.sh soit à adapter spécifiquement.

Comme nous venons d'ajouter des directives à notre fichier ~/.profile, il faut le recharger :

$ source ~/.profile

Créer son premier environnement virtuel

Reprenons le cas de nos deux projets A et B ; nous avons besoin de créer deux environnements virtuels distincts pour travailler sereinement avec les paquets adéquats pour chacun d'eux :

Nous allons nous concentrer sur la création du premier environnement, pour l'occasion destiné à travailler sur le projet A :

$ mkvirtualenv DjangoEnvX --no-site-packages
New python executable in DjangoEnvX/bin/python
Installing setuptools............done.

L'environnement a été créé. Notez trois choses importantes :

  • Les setuptools ont été installés dans l'environnement, ainsi que pip même s'il n'en est pas fait mention ; cela nous permettra de disposer de moyens d'installation depuis l'environnement en question ;
  • L'option --no-site-packages a été passée, ce qui permet de constituer un environnement intégralement vierge de tout paquet Python. Ne pas passer l'option aurait lié l'ensemble des paquets installés sur le système dans notre environnement de développement, ce que nous ne voulons justement pas !
  • Je ne nomme pas l'environnement de travail du nom du « Projet A », dans la mesure où cet environnement pourrait éventuellement être réutilisé pour d'autres projets ayant des besoins et contraintes similaires.

Mais examinons de plus près ce que la commande mkvirtualenv a créé pour nous :

~$ workon DjangoEnvX
(DjangoEnvX)~ $ cdvirtualenv
(DjangoEnvX)~/.virtualenvs/DjangoEnvX $ ls -l
total 8
drwxr-xr-x   6 niko  staff  204 May  5 16:08 .
drwxr-xr-x  14 niko  staff  476 May  5 16:08 ..
lrwxr-xr-x   1 niko  staff   63 May  5 16:08 .Python -> /System/Library/Frameworks/Python.framework/Versions/2.6/Python
drwxr-xr-x   9 niko  staff  306 May  5 16:08 bin
drwxr-xr-x   3 niko  staff  102 May  5 16:08 include
drwxr-xr-x   3 niko  staff  102 May  5 16:08 lib

Notez les éléments suivants :

  • L'utilisation de la commande workon, fournie par virtualenvwrapper, qui permet d'activer un environnement virtuel de travail ; l'autocomplétion du nom de l'environnement virtuel est d'ailleurs disponible !
  • La commande cdvirtualenv nous place directement à la racine du répertoire de l'environnement virtuel ;
  • Les répertoires bin, include et lib ont été créés, ainsi qu'un lien symbolique vers la version de l'intérpréteur Python du système.
  • Un préfixe (ici (DjangoEnvX)) est ajouté devant le prompt lorqu'on travaille dans un environnement spécifique : cela permet de toujours savoir dans quel environnement on travaille, afin d'éviter les mauvaises surprises ;)

Je peux maintenant installer sereinement les paquets dont j'ai besoin dans le cadre de mon projet A, où que je sois sur le système de fichiers. Par exemple, pour installer la version de dev de Django 1.2 depuis son mirroir git :

(DjangoEnvX)~ $ cd ~
(DjangoEnvX)~ $ pip install -e git+http://github.com/django/django.git#egg=django

Vérifions que la version de développement de Django a bien été installée dans notre environnement DjangoEnvX :

(DjangoEnvX)~ $ cdvirtualenv
(DjangoEnvX)~/.virtualenvs/DjangoEnvX $ ll src
total 0
drwxr-xr-x   3 niko  staff  102 May  5 17:07 .
drwxr-xr-x   7 niko  staff  238 May  5 17:08 ..
drwxr-xr-x  16 niko  staff  544 May  5 17:08 django
(DjangoEnvX)~/.virtualenvs/DjangoEnvX $ echo -e "import django\nprint django.get_version()"|python
1.2 beta 1

Installons maintenant de la même façon le paquet django-debug-toolbar en version 0.8.3 :

(DjangoEnvX)~ $ pip install -e git+git://github.com/robhudson/django-debug-toolbar@0.8.3#egg=django-debug-toolbar

Nous avons maintenant nos paquets installés, créons un nouveau projet Django. On peut créer un répertoire n'importe où sur le système de fichiers, cela n'a aucune importance : les environnements virtuels et les projets ne sont pas directement liés.

(DjangoEnvX)~ $ cd ~/Sites/
(DjangoEnvX)~/Sites $ django-admin.py startproject my_django_project
(DjangoEnvX)~/Sites $ cd my_django_project/
(DjangoEnvX)~/Sites/my_django_project $ ll
total 24
drwxr-xr-x   6 niko  staff   204 May  5 17:36 .
drwxr-xr-x  94 niko  staff  3196 May  5 17:36 ..
-rw-r--r--   1 niko  staff     0 May  5 17:36 __init__.py
-rwxr-xr-x   1 niko  staff   546 May  5 17:36 manage.py
-rw-r--r--   1 niko  staff  3313 May  5 17:36 settings.py
-rw-r--r--   1 niko  staff   564 May  5 17:36 urls.py
(DjangoEnvX)~/Sites/my_django_project $ ./manage.py runserver
Validating models...
0 errors found

Django version 1.2 beta 1, using settings 'my_django_project.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Voila, nous pouvons travailler sur notre projet dans l'environnement DjangoEnvX l'esprit serein. On pourra éventuellement ajouter d'autre paquets, ceux-ci ne seront toujours installés que pour cet environnement. Si d'aventure nous voulions versionner la liste des dépendances installées va pip, c'est aussi simple que :

(DjangoEnvX)~/Sites/my_django_project $ pip freeze > requirements.txt

Le fichier requirements.txt ainsi créé contiendra la liste de tous les paquets installés dans l'environnement DjangoEnvX :

(DjangoEnvX)~/Sites/my_django_project $ cat requirements.txt
-e git+http://github.com/django/django.git@25a45619fe5d7ff3d4f2dbf8f8879a3a00c3625d#egg=Django-1.2_beta_1-py2.6-dev
-e git://github.com/robhudson/django-debug-toolbar@ee1811238e91ae0ad33413b0d40d2f8482101951#egg=django_debug_toolbar-0.8.3-py2.6-dev
wsgiref==0.1.2

Libre à vous alors de versionner ce fichier, ce qui permettra à vos collaborateurs d'instancier un nouvel envronnement de travail et d'installer les dépendances requises d'une simple ligne de commande sur son poste de travail :

(WtfDevEnv)$ pip install -r /path/to/requirements.txt

En espérant vous avoir été utile avec ce billet, je m'en retourne vaquer à mes occupations, et vous rappelle à toutes fins utiles qu'Akei serait quand même super contente d'avoir un coup de fil de votre part pour travailler sur vos chouettes projets en devenir, pourquoi pas en créant plein de virtualenv Python sympas comme tout ;)

Edit : Prise en compte de la variable d'environnement PIP_RESPECT_VIRTUALENV pour que pip detecte automatiquement la présence d'un environnement virtuel lors de son utilisation (merci Mathieu !)


  1. Vous pouvez bien entendu créer ce répertoire où bon vous semble sur votre système, à partir du moment où votre utilisateur a les droits de lecture et d'écriture dessus.  

  2. le fichier ~/.profile est chargé à chaque démarrage de session Max OS X. Utilisateurs de GNU/Linux, l'équivalent est le fichier ~/.bashrc.  

Derniers commentaires

Tweets