Werkzeug: un oeuf de paques bien caché

21 avril 2011 1 commentaire

Aujourd’hui, je fouinais un peu dans le code source de Flask, de fil en aiguille, je me retrouve dans le code de Werkzeug, et là une ligne (werkzeug/__init__.py:95) attire mon attention:

'werkzeug._internal':   ['_easteregg']

Eh oui, trouver un easter egg en pleine période de Pâques, ça ne s’invente pas!!! :D

Alors allons voir à quoi ressemble cet oeuf, direction le fichier werkzeug/_internal.py, là on trouve bien, ligne 345, une fonction _easteregg. Cette fonction semble prendre une application en argument, et contient un bloc de texte encodé en base64 ainsi que 2 fonctions imbriquées ayant pour but de modifier le html renvoyé en fonction de l’argument passé dans l’url:

def easteregged(environ, start_response):
    def injecting_start_response(status, headers, exc_info=None):
        headers.append(('X-Powered-By', 'Werkzeug'))
        return start_response(status, headers, exc_info)
    if environ.get('QUERY_STRING') != 'macgybarchakku':
        return app(environ, injecting_start_response)
    injecting_start_response('200 OK', [('Content-Type', 'text/html')])
    return ['''<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<title>About Werkzeug</title>
<style type="text/css">
body { font: 15px Georgia, serif; text-align: center; }
a { color: #333; text-decoration: none; }
h1 { font-size: 30px; margin: 20px 0 10px 0; }
p { margin: 0 0 30px 0; }
pre { font: 11px 'Consolas', 'Monaco', monospace; line-height: 0.95; }
</style>
<h1><a href="http://werkzeug.pocoo.org/">Werkzeug</a></h1>
<p>the Swiss Army knife of Python web development.
<pre>%s\n\n\n</>''' % gyver]

Nous allons donc créer une application des plus simple (l’exemple donné sur le site de Werkzeug), pour voir ce qui se cache derrière cette étrange fonction:

from werkzeug import Request, Response, run_simple
from werkzeug import _easteregg

@Request.application
def application(request):
    return Response('Hello World!')

run_simple('localhost', 8080, _easteregg(application))

Maintenant ouvrons l’url http://localhost:8080/?macgybarchakku (l’argument magique attendu par la fonction citée précédemment). Là on voit bien que quelque chose se passe, mais ça n’a pas l’air de se passer comme prévu, en effet, la barre de titre contient du code, qui devrait être affiché sur la page. Eh oui, les malins, ils ont volontairement omis de fermer le tag title (comme si ça n’était pas déjà assez tordu…), donc on édite le fichier werkzeug/_internal.py, et on ferme le tag à la ligne 387.

On relance l’application, et là, ô magie du Web, notre oeuf de pâques apparait enfin! (et non, il n’y a pas de screenshot du résultat, je ne vais quand meme pas vous gâcher le plaisir de voir par vous même… ^^)

Joyeuse Pâques !

EDIT: Il semblerait que le tag <title> non fermé n’était pas intentionnel, dans les dernières versions de Werkzeug il est correctement fermé.

Catégories:Astuces, Code, Flask Mots-clés : , , ,

Rendre une fonction asynchrone

15 novembre 2010 1 commentaire

Depuis quelques temps je joue un peu avec Twisted, et j’avoue que j’aime beaucoup, seulement c’est quand meme un truc assez balèse, et il peut arriver qu’on ait envie de rendre asynchrones quelques fonctions d’un programme, sans pour autant avoir Twisted pour dépendance.

C’est là qu’intervient toute la puissance de Python (non, je ne suis pas du tout de parti-pris, qu’on se le dise).

Le plus sympa pour rendre une fonction facilement asynchrone, ça serait d’avoir un décorateur qui fasse le travail pour nous, comme qui dit asynchrone dit callbacks, on va donc devoir faire un décorateur auquel on puisse passer des arguments. Un tel décorateur ressemble habituellement à ça:

class my_decorator(object):
    def __init__(self, argument):
        self.arguments = arguments
    def __call__(self, func):
        def wrapper(*args, **kwargs):
            # faire des trucs avec la fonction ici
        return wrapper

# usage
@my_decorator(deco_arg)
def my_func(arg):
    print "youpi"

Mais il faut que le code exécuté par notre décorateur le soit en arrière plan,  pour ça le mieux serait de l’exécuter dans un thread. Et si on faisait hériter notre décorateur de Thread pour que ce qu’il s’y passe se fasse en arrière plan? Voilà ce que ça donne:

from threading import Thread

class async(Thread):
    def __init__(self, callback, errback):
        super(async, self).__init__()
        self.callback = callback
        self.errback = errback

    def __call__(self, func):
        # à l'appel de la fonction, on récupère juste la fonction
        # et ses arguments, et on lance notre thread
        def wrapper(*args, **kwargs):
            self.func = func
            self.args = args
            self.kwargs = kwargs
            self.start()
        return wrapper

    def run(self):
        try:
            retval = self.func(*self.args, **self.kwargs)
            self.callback(retval)
        except Exception as err:
            self.errback(err)

Et c’est tout, magique non? Rendre une fonction asynchrone est devenu simple comme kikoo, il suffit de définir 2 fonctions, une pour le callback (appelée si la fonction est exécutée avec succès, elle reçoit la valeur renvoyée par la fonction en argument), et une pour l’errback (appelée si une exception est lancée lors de l’exécution de la fonction, elle reçoit l’exception en argument), et de décorer la fonction voulue.

Démonstration:

def my_callback(retval):
    print "CALLBACK:", retval
    # ici on peut faire des opérations supplémentaires
    # sur la valeur renvoyée par la fonction.
    # ce code est appelé dans le thread, il ne peut donc
    # pas bloquer notre thread principal

def my_errback(err):
    print "ERRBACK:", err
    # ici on peut re-lancer l'exception, ou simplement
    # l'ignorer. Ce code est également appelé dans le thread (logique)

@async(my_callback, my_errback):
def ma_fonction_reussie(n):
    sleep(n)
    return n

@async(my_callback, my_errback):
def ma_fonction_foireuse(n):
    sleep(n)
    raise AttributeError("Got %s, expected 'foo'" % n)

ma_fonction_reussie(5)
ma_fonction_foireuse(7)

# histoire de passer le temps pendant ce temps là...
for i in range(10):
    print "MAIN:", i
    sleep(1)

Résultat:

MAIN: 0
MAIN: 1
MAIN: 2
MAIN: 3
MAIN: 4
MAIN: 5
CALLBACK: 5
MAIN: 6
MAIN: 7
ERRBACK: Got 7, expected 'foo'
MAIN: 8
MAIN: 9

Code complet avec l’exemple

Catégories:Code Mots-clés : , , ,

Mon environnement Django dans IPython

Il y a différentes façons d’avoir accès à son environnement Django depuis le shell Python, la plus simple étant d’utiliser le shell fourni par django avec la commande

python manage.py shell

Mais étant habitué à IPython, j’ai cherché comment charger l’environnement de mon projet Django et y avoir accès dans IPython, je suis donc tombé sur ce post, j’ai légèrement modifié le code proposé pour finir avec ceci:

import os
if 'settings.py' in os.listdir(os.getcwd()):
    try:
        print "\nLoading Django environment"
        from django.core.management import setup_environ
        import settings
        setup_environ(settings)
        try:
            for app in settings.INSTALLED_APPS:
                if not app.startswith('django.'):
                    try:
                        exec("from %s.models import *" % app)
                        print "Imported models from '%s'" % app
                    except:
                        print "Can not import models from '%s'" % app
        except:
            print "Can not import project models"
        print "Django context successfuly loaded\n"
    except:
        print "Errors occured while loading Django context\n"

Il suffit de placer ce code dans un fichier « ~/.pythonrc », et ajouter

export PYTHONSTARTUP=~/.pythonrc

pour que ce code s’exécute à chaque lancement du shell Python (fonctionne également dans bpython).

Mais voilà, IPython possède son propre fichier de démarrage, du coup notre code n’est pas exécuté… Pas de problème! Il suffit d’ajouter

execfile ~/.pythonrc

au fichier ~/.ipython/ipythonrc, et voilà, il est maintenant possible d’utiliser la puissance de IPython sans les contraintes du chargement manuel de l’environnement Django.

Catégories:Astuces, Code Mots-clés : , , ,

Vim comme IDE Python

19 mars 2010 1 commentaire

Je me suis récemment décidé à peaufiner ma configuration de Vim (qui était plutot basique jusqu’ici) pour que le développement y soit plus facile. Alors étant donné que la grande majorité du code que j’édite est en Python, j’ai cherché un maximum d’astuces pour en faire un IDE digne de ce nom.

Voici donc ma nouvelle configuration:

vimrc


"
" Vim configuration file
"

set nocompatible

if has("autocmd")
 filetype plugin on
 filetype plugin indent on
endif
if has("mouse")
 set mouse=a
endif

syntax on
set hlsearch
set incsearch
set ignorecase
set showmatch
set noswapfile
set tabstop=4
set shiftwidth=4
set softtabstop=4
set expandtab
set autoindent
set foldmethod=indent
set backspace=indent,eol,start
set history=100
set ruler
set showcmd
set number
set nospell
colorscheme delek
map <C-t> :tabnew<CR>

"
"python specific settings
"

autocmd FileType python set complete+=k~/.vim/syntax/python.vim isk+=.,(
set omnifunc=pythoncomplete#Complete
imap <C-Space> <C-x><C-o>

autocmd BufWrite *.py silent! %s/[\r \t]\+$//

map <buffer> <F5> :w<CR>:!/usr/bin/env python % <CR>

python << EOF
import os
import sys
import vim
for p in sys.path:
 if os.path.isdir(p):
 vim.command(r"set path+=%s" % (p.replace(" ", r"\ ")))
EOF

autocmd BufNewFile *.py,*.pyw 0read ~/.vim/templates/python.txt

la plupart des commandes sont facilement compréhensibles, pour les autres, une petite explication:

ligne 34: raccourci clavier pour pouvoir ouvrir un nouvel onglet avec Ctrl+t

ligne 40,41: code-completion améliorée grace à ce script

ligne 42: code-completion avec Ctrl+Space au lieu de Ctrl+x,Ctrl+o (uniquement en mode insertion)

ligne 44: suppression des espaces en fin de ligne lors de la sauvegarde du fichier

ligne 46: lancer le fichier actuel avec la touche F5

ligne 48-55: ajoute les modules python au path de vim, ‘gf’ sur un import ouvrira le module en question dans vim (Ctrl+o pour revenir à l’ancien fichier)

ligne 57: utilisation d’un template lors de la création d’un nouveau fichier .py, mon template est tres simple et contient uniquement


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

J’ai également installé le plugin pyflakes pour vim, qui permet d’afficher les erreurs de syntaxe en temps réel, les erreurs sont soulignées et des infos supplémentaires s’affichent si on place le curseur sur l’erreur en question.

En plus de tout ça, j’ai installé un script vim pour améliorer l’indentation auto pour Python, pour cela il suffit de placer dans ~/.vim/indent/ le fichier récupérable ici

Voilà c’est tout, mais ca améliore vraiment le confort, en tout cas pour moi.

Si vous avez d’autres astuces, n’hésitez pas à poser un commentaire.

Catégories:Astuces Mots-clés : , , ,

Premier post

Salut,

Me retrouvant face à une masse impréssionante de bookmarks, je me suis dit qu’il me faudrait un endroit pour centraliser tous les bouts de code que je trouve à droite à gauche.

Ici vous trouverez donc toutes mes découvertes concernant Python. Je ne prétends pas rivaliser avec certains blogs dont les auteurs sont de vrais tueurs du py, mais juste partager mes quelques connaissances avec vous.

J’espere que vous y trouverez des trucs intéressants.

Catégories:Non classé
Suivre

Recevez les nouvelles publications par mail.

%d blogueurs aiment cette page :