Werkzeug: un oeuf de paques bien caché
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!!!
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é.
Rendre une fonction asynchrone
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
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.
Vim comme IDE Python
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.
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.