Accès clients

Le Blog — Billets taggués python

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.

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

Derniers commentaires

Tweets