Академический Документы
Профессиональный Документы
Культура Документы
Nicolas Paris
September 20, 2011
http://www.nicosphere.net
nicolas.caen@gmail.com
Table des matires
1 Utilisation de Modules 3
1.1 Consulter la documentation en local . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Piles et Files avec Deque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Tenir un fichier de log . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.4 Script concret pour Urllib . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.5 Parser un fichier de configuration . . . . . . . . . . . . . . . . . . . . . . . . 9
2 Python et Ncurses 15
2.1 Transparence avec Ncurses . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.2 Dplacement de fentre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.3 Menu navigable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.4 Visualiser et scroller un fichier . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.5 Les scripts de dmo fournis par Python . . . . . . . . . . . . . . . . . . . . . 28
3 Utilisation dAPI 31
3.1 Gnrateur de documentation Sphinx . . . . . . . . . . . . . . . . . . . . . . 31
3.2 Client Twitter et Identi.ca . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.3 Api goo.gl et Oauth . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.4 Request, HTTP pour humains . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.5 Scrapy, Crawler web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
i
ii
Prface
Ce document est issue principalement de billets pris sur mon blog, revu pour loccasion. Aillant
depuis quelques temps cumul des suites de petits tutoriels un peu sous forme de recettes, je
pense quil pouvait tre intressant de les runir sous un format PDF. Ce format sy prtait
plutt bien, du fait davoir trait quelques thmes sous diffrents aspects, notamment avec la
partie Ncurses et Tests Unitaires. Le style est certainement plus celui utilis pour un blog, bien
que jen ai apport quelques modifications pour garder une cohrence.
Le document est sous licence libre Crative Commun CC-BY-SA 1 et puisquun PDF ne se-
rait pas vraiment considr comme libre sans les sources, elles peuvent tre tlcharges sur
Github 2 . Vous avez le droit de le tlcharger, le redistribuer, le modifier et mme de limpri-
mer pour le lire dans les toilettes. Merci de copier ce document ! Je demande juste dtre cit,
Nicolas Paris, et de faire un lien vers mon blog, http ://www.nicosphere.net.
Vous pouvez me contacter via email : nicolas.caen@gmail.com
La version de Python utilis est gnralement la 2.7, parfois la 3.2. Jai gnralement fait mon
possible pour que ce soit clair. Une prcision tout de mme, tant sous Arch Linux, Python
par dfaut est le 3.2.x, mais jai essay autant que possible de garder dans le texte Python 2.7
comme celui de dfaut, pour viter les confusions de ceux ne connaissant pas cette spcificit
dArch Linux.
Jespre que ce document trouvera lecteurs, bien que ce soit mon premier ebook.
Nicolas Paris
1. http ://creativecommons.org/licenses/by-sa/2.0/fr/
2. https ://github.com/Nic0/python_en_action
Utilisation de Modules
Lide est de lire la documentation de tout les modules dans un navigateur, quils soient natif
Python ou installe implicitement, le tout en local. Plusieurs avantages a :
Avoir toujours la documentation correspondante la version installe
Naviguer rapidement entre les modules tiers, install par lutilisateur
Cest une faon assez classique de procder, quon retrouve dans certains langages. Jen crivais
un billet pour ruby 1 de ce mme procd. On retrouve galement pour Perl, Perldoc-server 2 .
Cependant, je le trouve vraiment moins bien fait que celui de Ruby, qui a pour avantage dtre
bien plus facilement navigable, la possibilit dafficher le code correspondant (vraiment utile).
Sans parler de lesthtisme global bien meilleur. Il est dommage quil ne soit pas un peu plus
aboutis que a.
Le fonctionnement est trs simple, on lance le serveur comme suit :
$ pydoc -p 1337
Server ready at http://localhost:1337/
Server commands: [b]rowser, [q]uit
server>
Il vous suffit prsent de se rendre sur http ://localhost :1337 et de naviguer entre les diffrents
modules affichs.
Le choix du port est arbitraire.
1. http ://www.nicosphere.net/lire-la-documentation-rdoc-des-gem-avec-un-navigateur-2239/
2. http ://search.cpan.org/dist/Perldoc-Server/
3
Python en Action
1.2.1 Introduction
En programmation, les piles 3 et files 4 , aka LIFO (last in, first out) et FIFO (first in, first out)
sont des incontournables. Il sagit de stocker des donnes, et surtout de lordre dans lequel on
rcupre celle-ci. Pour information, javais crit Exemple de file avec deux pointeurs en langage
C 5 qui est un exemple de liste chane utilise comme une file en C.
Comme Python est un langage de haut niveau, il offre une faon simple de mettre en place lun
ou lautre. On va voir quil correspond une classe du module collections.
Pourquoi ne pas utiliser simplement une liste ?
Il est possible dobtenir le mme comportement avec une liste, surtout quelle offre un peu prs
les mmes mthodes (pop), cependant, deque est spcialement prvu pour cette effet, et est
donc optimis dans ce sens. Il est donc prfrable dutiliser deque si lon sait quon lutilise
comme fifo ou lifo.
1.2.2 Utilisation
Rien de bien compliqu, regardons cette suite de commande effectue directement avec linter-
prteur.
Fonctionnement comme pile (lifo)
>>> from collections import deque
>>> pile = deque([1, 2, 3, 4, 5])
>>> from collections import deque
>>> pile = deque([1, 2, 3, 4, 5])
>>> pile.pop()
5
>>> pile.pop()
4
>>> pile.append(6)
>>> pile
deque([1, 2, 3, 6])
3. http ://fr.wikipedia.org/wiki/Pile_(informatique)
4. http ://fr.wikipedia.org/wiki/File_(structure_de_donnes)
5. http ://www.nicosphere.net/exemple-de-file-avec-deux-pointeurs-en-langage-c-1752/
Et aprs ?
Le module vient avec dautres mthodes pouvant tre utile dans ce genre de cas tel que par
exemple :
appendleft()
clear()
extend()
reverse()
rotate(n)
Pour plus de dtails sur les possibilits, lisez la documentation officiel sur ce module 6 .
Ce module est assez simple prendre en main, mais toujours utile si on sait quon veut mettre
en place rapidement une fifo ou lifo. Ce nest quune mthode parmi dautre pour obtenir ce
que lon souhaite, mai lavantage de deque est dtre trs simple lemploi, tout en tant plus
performant. ventuellement, pour les plus curieux, lutilisation de queue.LifoQueue pourrait
intresser, mais lide ici tait de garder une utilisation simplifi au maximum.
Il est souvent utile de tenir un fichier de log dans plusieurs situations, comme archiver diff-
rentes erreurs, ou mme pour les dbugs. Python fournit nativement un module 7 trs bien fait
et complet pour faire cela, avec une redirection de linformation soit en console ou dans un
fichier, et permettant plusieurs niveaux dimportance de logs, du message de dbug aux erreurs
critiques.
Ce module permet daller bien plus loin de ce que jen aurai besoin, comme un serveur de
log pour centraliser les logs de clients, des logs tournantes ou encore lenvoi de journaux par
SMTP (mail). Il nest pas question ici de tout voir. Une petite introduction qui permet de tenir
simplement un fichier de log, pour une prise main rapide.
Le module dfini plusieurs niveau dimportance dans les messages, qui sont dans lordre crois-
sant :
Debug
Info
Warning
Error
Critical
Note : Dans cet exemple, je me suis servis de Python3, je nai pas vrifi la compatibilit avec
Python2, mais son usage doit tre trs similaire.
6. http ://docs.python.org/py3k/library/collections.html#collections.deque
7. http ://docs.python.org/library/logging.html
Essayons un script rapide, qui na pas vraiment dintrt si ce nest de voir ce quil sy passe.
import logging
logging.warning(coin)
logging.debug(pan!)
$ ./logger.py
WARNING:root:coin
Plusieurs remarques :
Seul le warning sest affich, sexpliquant par la configuration de dfaut naffichant unique-
ment les messages partir du seuil warning. On verra comment le modifier par la suite.
La sortie contient le niveau (WARNING), un root un peu obscure, et le message, la sortie
nest pas trs jolie ni trs verbeuse, mais fait ce quon lui demande.
Le message est envoy en console et non pas dans un fichier comme nous le souhaitons.
Regardons pour enregistrer ce message dans un fichier maintenant. Cette redirection peut tre
utile par exemple si le programme est crit en ncurses ou quon ne possde pas vraiment de
retour visuel. Mais galement pour avoir des informations sur une plus longue priode.
Dans le script prcdent, on rajoute avant les messages de warning et debug la ligne suivante :
logging.basicConfig(filename="prog.log",level=logging.DEBUG)
On excute et regarde le fichier prog.log, on obtient exactement quoi lon sattendait, les deux
messages. Par simplicit, le fichier est renseigner par un chemin relatif, et se retrouve donc
lendroit de lexecution du programme. On pourrait mettre un chemin absolu, en concatnant
des variables denvironnement.
Comme il peut tre utile de rajouter quelques variables, afin davoir un aperu au moment du
log, on peut le faire en les rajoutant comme argument, comme on le ferait pour une string,
comme dans lexemple suivant :
import logging
a = coin
$ ./logger.py
WARNING:root:a:coin b:pan!
Dans un fichier de log, on souhaite gnralement avoir chaque message, une indication de
lheure, et ventuellement de la date, il est possible de le faire, une fois pour toute, de la faon
suivante :
import logging
import time
logging.basicConfig(
filename=prog.log,
level=logging.INFO,
format=%(asctime)s %(levelname)s - %(message)s,
datefmt=%d/%m/%Y %H:%M:%S,
)
Pour avoir la possibilit de sparer la configuration du logger et le code en gnral, il est pos-
sible de tenir un fichier spcifique la configuration, il supporte deux syntaxes diffrentes, bas
sur ConfigParser ou YAML.
Comme ce module permet un niveau de personnalisation assez avanc, il est normal que cela
se ressente sur la verbosit du fichier de configuration. Pour ma part, je ne pense pas en avoir
besoin dans limmdiat, donc je vous renvoi sur la documentation officiel, et notamment ce
how-to 8 qui est un bon endroit pour commencer, et dont ce billet sen inspire fortement.
8. http ://docs.python.org/py3k/howto/logging.html
Je laccorde que ce script t fait un peu La Rache 9 un samedi soir et sur un coup de tte,
le titre original tait dailleurs Le script idiot du dimanche, titre honteusement repomp de PC
INpact (les liens idiots du dimanche) mais qui tait de circonstance. Je pense quil peut faire
une petite introduction Urllib, tout en restant amusant, chose importante lorsquon apprends,
et surtout de faon auto-didacte.
Mais la question quon peut se poser est :
Mais que fait ce script ? !
Vous connaissez peut tre le site downforeveryoneorjustme.com ? Il permet de sassurer que
pour un site qui vous est inaccessible, quil ne sagisse pas dun problme venant de chez vous.
Lintrt est de passer par un autre site, afin de sassurer que le problme rencontrer nest pas
entre le premier site et soi. Ce script est tout simplement un front-end de ce site, dont lusage
en console est on ne peu plus simple :
$ ./downforeveryoneorjustme http://www.nicosphere.net
Its just you. http://www.nicosphere.net is up.
De plus, laffichage se fait en couleur, rouge si cest down, et vert si le site est fonctionnel (jai
hsit longuement sur le choix des couleurs).
Vous pouvez cloner le dpt git fait cette occasion. Si si, jai fais un dpt rien que pour a. . .
Note : Je nai test le script quavec Python 2.7, et il faudra certainement adapter la partie
avec urllib, comme il y a quelques changement entre les deux versions majeurs de Python.
import re
import sys
import urllib
class DownForEveryone(object):
base_url = http://www.downforeveryoneorjustme.com/
def __init__(self):
self.parse_url()
self.is_url()
self.get_answer(
urllib.urlopen(self.base_url+self.url).read()
)
def parse_url(self):
9. http ://www.nicosphere.net/utilisez-la-methode-la-rache-pour-vos-projets-1363/
def is_url(self):
url_regex =
http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+
resp = re.findall(url_regex, self.url)
if len(resp) != 1:
print The argument does not seems to be an URL
self.usage()
def usage(self):
print Usage:
print {} http://www.exemple.org.format(sys.argv[0])
sys.exit(0)
if __name__ == __main__:
DownForEveryone()
pas recrer un module afin de prendre en compte un fichier de configuration, fonctionnalit trs
courante.
Utiliser un fichier de configuration permet dobtenir une application plus souple, si vous faites
un choix arbitraire dans votre programme, demandez vous sil ne serait pas plus efficace de
lextraire, et de le mettre en tant que paramtre, modifiable par lutilisateur. Dune part il est
plus agrable de les manipuler de faon spar et regrouper, et surtout cela vite quun utili-
sateur voulant changer une variable le fasse en fouillant dans le code, au risque de changer un
mauvais paramtre par mgarde dans votre code source.
Puisquun module existe pour Python, autant lutiliser. Un des avantages est davoir la possi-
bilit de commenter vos configuration en commencent par # (dise) ou ; (point-virgule) une
ligne, celle-ci sera ignor. Le fichier suit une structure assez simple, quon retrouve rgulire-
ment, bas sur un systme de section, et dargument/valeurs. Pour rsumer dans un schma :
[my_section]
coin = pan
answer = 42
On peut de la sorte avoir plusieurs noms de section et ce nest l que pour donner une ide
basique du module.
Premier exemple tout simple, nous allons lire dans un fichier quelques arguments, pour les
afficher en retour dans la console. Par commodit, on va placer le fichier de configuration
lendroit o sera excut le programme. Le nom du fichier serra ici conf.rc mais aucune
importance, du moment quon sy retrouve.
conf.rc :
[user]
nom = Dupond
age = 42
import ConfigParser
config = ConfigParser.RawConfigParser()
if __name__ == __main__:
main()
On le rend excutable :
chmod +x config.py
Puis on le lance :
$ ./config.py
Bonjour M. Dupond, vous avez 42 ans
import ConfigParser
config = ConfigParser.RawConfigParser()
config.read(conf.rc)
Nous indiquons ici quel est le fichier de configuration que nous utilisons. Pour simplifier laf-
faire, le fichier est en relatif (par rapport lendroit do est excut le programme), et surtout,
il ny a pas de vrification que le fichier existe, ni quil est lu correctement. Je suis sr que vous
trouverez comment le faire de vous mme.
Cest l quon lit vraiment les variables, un dtail not, tout comme on peut le deviner par la
suite, la variable age est considr comme une chaine de caractre, et non comme un nombre
part entire, dans ltat il ne serait pas possible deffectuer des calcules dessus, mais pouvant
tre convertie facilement de la sorte :
De la sorte, age est maintenant effectivement un entier, et nous pourrions en faire toute sorte
de calculs afin de trouver lge du capitaine.
Je ne pense pas que le reste ait besoin de plus dexplication, plusieurs remarques cependant.
Le plus important, on remarque quil est simple dinterprter de la sorte un fichier de confi-
guration, le tout en une poigne de lignes seulement.
Il ny a aucune gestion derreur ici, sil manque un argument, ou mme la section, le pro-
gramme va planter lamentablement... Mais nous allons voir a maintenant.
try/except block
import sys
import ConfigParser
config = ConfigParser.RawConfigParser()
config.read(conf.rc)
try:
nom = config.get(user, nom)
age = config.get(user, age)
except ConfigParser.Error, err:
print Oops, une erreur dans votre fichier de conf (%s) % err
sys.exit(1)
if __name__ == __main__:
main()
[user]
nom = Dupond
$ ./config.py
Oops, une erreur dans votre fichier de conf (No option age in section : user)
has_section, has_option
Le module viens avec deux mthodes permettant de vrifier la prsence de section ou doption,
on peut donc sen servir, avec quelques choses ressemblant a par :
On affecte galement des valeurs par dfaut si une option nest pas trouv, on peut not gale-
ment, que la gestion derreur sur la section nest pas faite ici, uniquement les options.
Jusquici, nous avons vu comment lire les donnes dun fichier de configuration. Pendant quon
y est, autant jeter un il sur la faon dcrire, et donc sauvegarder, une configuration. Dans la
ligne de ce qui dj tait fait, mme nom de fichier, mme section et options.
Dans un premier temps, on supprime le fichier de configuration conf.rc si vous laviez gard
depuis lexercice plus haut, et on crit dans config.py le code suivant :
import ConfigParser
config = ConfigParser.RawConfigParser()
config.add_section(user)
config.set(user, nom, Dupond)
config.set(user, age, 42)
if __name__ == __main__:
main()
On rend excutable avec chmod +x config.py, puis on excute le script, et on observe le rsultat
en ouvrant le fichier conf.rc, il contient exactement ce quon attendait.
Pour les explications, plus courte cette fois ci. On vois quon rajoute la section avec la mthode
add_section, pour lequel on affecte les options avec la mthode set qui prends trois arguments :
Lutilisation de with open pour la lecture ou lcriture de fichier lavantage de ne pas avoir
besoin de le refermer, et cel quoi quil arrive, mme si lcriture est defectueuse. Cette faon
de procd est privilgi.
Imaginons que nous voulons sauvegarder un dictionnaire qui nous sert de configuration dans
un fichier, on peut donc effectuer de la sorte :
import ConfigParser
config = ConfigParser.RawConfigParser()
params = {
Linux: Torvalds,
GNU: RMS,
answer: 42,
}
config.add_section(params)
for arg in params:
config.set(params, arg, params[arg])
if __name__ == __main__:
main()
On excute, et regarde le rsultat obtenu, et cest ce que nous voulions, un fichier contenant ce
dictionnaire et sous forme option = valeur.
Voil, cette introduction au module ConfigParser 1 touche sa fin, Cest un module qui nest pas
compliqu prendre en main, il est conseill de lire la documentation fournis pour plus amples
dtails. En esprant motiver certain utiliser un fichier de configuration plutt que dcrire
en dur les variables directement dans le fichier source.
Python et Ncurses
Jai recherch sur le net comment avoir la transparence avec Python et le module curse, mais
linformation ntait pas trs clair, ni dans la documentation officiel, ni sur le web. Cest en
lisant le code des autres que jai trouv.
Donc oui, il est possible davoir la transparence avec le module curses de dfaut de Python. Il
fonctionne avec les versions 2.7.x et 3.2.x.
Attention de ne pas appeler votre fichier curses.py, comme lorsquon fait des essais, on a ten-
dance appeler le fichier avec un nom parlant, mais cela interfrerai avec limport du module
curses.
Cet exemple est galement un bon point de dpart pour afficher un mot avec ncurses.
2.1.1 Le code
Un exemple complet, qui affiche un gros Hello World en rouge, et avec fond transparent :
import curses
def main():
scr = curses.initscr()
curses.start_color()
if curses.can_change_color():
curses.use_default_colors()
background = -1
else:
background = curses.COLOR_BLACK
15
Python en Action
if __name__ == __main__:
main()
if curses.can_change_color():
curses.use_default_colors()
La premire ligne sassure que le terminal est capable dafficher la transparence, et la seconde
lactive, en assignant la valeur -1 la variable background. Si le terminal ne supporte pas la
transparence, on remplace par un fond noir.
Petit exercice, pour une approche de la bibliothque curses en douceur. Le but, tout simple, est
de crer une petite fentre lintrieur de la console, cette fentre est visualisable par un carr
(4x6), et de la dplacer dans sa console laide des flches directionnel. Mme si lmerveille-
ment de voir apparatre une fentre reste limit, ce nest pas moins un bon exercice afin de voir
quelques fonctions propre ncurses.
Lapproche est organis tape par tape, avec en fin de chapitre le code complet de lapplication.
Dans un premier temps, on va afficher une petite fentre et... cest tout.
import curses
import curses.wrapper
def main(scr):
curses.use_default_colors()
curses.curs_set(0)
win = scr.subwin(4, 6, 3, 3)
win.border(0)
win.refresh()
win.getch()
if __name__ == __main__:
curses.wrapper(main)
On rend le tout excutable avec un chmod +x, puis on regarde le rsultat. Une fentre saffiche,
nous sommes merveill, on appuis sur une touche, le programme quitte. Mais dj quelques
explications simpose, en commenant par lappel du main() qui ne se fait pas comme on a
lhabitude de voir.
def main(scr):
curses.use_default_colors()
curses.curs_set(0)
Vient ensuite la cration de la fentre avec une mthode subwin attach lcran scr :
Comme le but tant de faire bouger un peu la fentre, voyons comment faire voluer le code
pour cela. Par commodit, je vais bouger le main dans sa propre classe. Jetons directement un
il au code.
import curses
import curses.wrapper
class MovingWindow(object):
self.scr = scr
self.pos = [3, 3]
self.size = [4, 6]
self.init_curses_mode()
self.draw_window()
self.handle_key_stroke()
def init_curses_mode(self):
curses.use_default_colors()
curses.noecho()
curses.curs_set(0)
def draw_window(self):
self.scr.erase()
self.win = self.scr.subwin(self.size[0], self.size[1],
self.pos[0], self.pos[1])
self.win.border(0)
self.win.refresh
def handle_key_stroke(self):
while True:
ch = self.scr.getch()
if ch == curses.KEY_DOWN:
self.pos[0] += 1
elif ch == curses.KEY_UP:
self.pos[0] -= 1
elif ch == curses.KEY_LEFT:
self.pos[1] -= 1
elif ch == curses.KEY_RIGHT:
self.pos[1] += 1
elif ch == ord(q):
break
self.draw_window()
if __name__ == __main__:
curses.wrapper(MovingWindow)
2.2.3 Explications
Dans un premier temps, nous nappelons plus la fonction main, mais nous initialisons un objet
de la classe MovingWindow.
curses.wrapper(MovingWindow)
Nous crons des attributs, pour avoir la taille (facultatif), mais surtout la position courante de
la fentre afficher, ce qui correspond dans le __init__ aux lignes suivantes :
self.pos = [3, 3]
self.size = [4, 6]
def init_curses_mode(self):
# Toujours les couleurs transarante
curses.use_default_colors()
# On sassure de ne rien afficher si on crit
curses.noecho()
# On dsactive le curseur
curses.curs_set(0)
def draw_window(self):
# On efface ce quon avait
self.scr.erase()
# On crer une nouvelle fentre, avec la position et taille
# indiqu par les attributs
self.win = self.scr.subwin(self.size[0], self.size[1], self.pos[0], self.pos[1])
# On remets une bordure
self.win.border(0)
# Enfin, on affiche le rsultat
self.win.refresh
La dernire mthode handle_key_stroke gre les touches, et son fonctionnement est plutt
simple, curses.KEY_UP par exemple dsigne la touche du haut. Lorsquune des flches est
appuy, on change les attributs de position en fonction. En fin de boucle, on affiche le rsultat
obtenu.
Il est a noter, la ligne suivante :
elif ch == ord(q):
On devine facilement quil sert quitter lapplication, mais le ord est utile pour convertir la
lettre en son quivalant numrique, car les touches saisis sont des chars.
On lance le programme, on joue un peu avec, la fentre ce dplace, on est content... jusqu ce
que... la fentre sort de la console, en faisant planter le programme. Nous savons ce quil nous
reste faire alors, nous assur que cette fentre ne sorte pas de la console.
import curses
import curses.wrapper
class MovingWindow(object):
self.size = [4, 6]
self.maxyx = []
self.init_curses_mode()
self.draw_window()
self.handle_key_stroke()
def init_curses_mode(self):
curses.use_default_colors()
curses.noecho()
curses.curs_set(0)
self.maxyx = self.scr.getmaxyx()
def draw_window(self):
self.scr.erase()
self.win = self.scr.subwin(self.size[0], self.size[1],
self.pos[0], self.pos[1])
self.win.border(0)
self.win.refresh
def move_down(self):
if self.pos[0] + self.size[0] < self.maxyx[0]:
self.pos[0] += 1
def move_up(self):
if self.pos[0] > 0:
self.pos[0] -= 1
def move_left(self):
if self.pos[1] > 0:
self.pos[1] -= 1
def move_right(self):
if self.pos[1] + self.size[1] < self.maxyx[1]:
self.pos[1] += 1
def handle_key_stroke(self):
while True:
ch = self.scr.getch()
if ch == curses.KEY_DOWN:
self.move_down()
elif ch == curses.KEY_UP:
self.move_up()
elif ch == curses.KEY_LEFT:
self.move_left()
elif ch == curses.KEY_RIGHT:
self.move_right()
elif ch == ord(q):
break
self.draw_window()
if __name__ == __main__:
self.maxyx = self.scr.getmaxyx()
Tout le reste nest quun petit peu de calcul et de logique pour sassurer que la fentre ne sorte
pas.
On pourrait trs bien essayer quatre touches qui aurait pour effet dagrandir la fentre par lun
des cts, toujours en sassurant de lespace disponible.
Deux scripts sont propos dans ce chapitre, le second en est une extention.
Un menu complet, dans lequel nous pouvons nous dplacer laide de flche, et slectionner
lun des lments. Llment du menu sera mis en surbrillance en choisissant une couleur
diffrente (ici, il sera rouge). Il faudra sassurer quon ne dpasse pas les limites du menu.
Au lieu dafficher tout le menu, seul quelques lments seront affich, mais la liste doit tre
navigable entirement, on sattend donc voir apparaitre les lments qui ntait pas affich
tout en se dplacent dans la liste. La navigation serra naturel.
import curses
import curses.wrapper
class Menu(object):
menu = [
item 0, item 1, item 2, item 3,
item 4, item 5, item 6, item 7,
]
def init_curses(self):
curses.curs_set(0)
curses.use_default_colors()
curses.init_pair(1, curses.COLOR_RED, -1)
def draw_menu(self):
i = 2
for element in self.menu:
if element == self.get_current_item():
self.scr.addstr(i, 2, element, curses.color_pair(1))
else:
self.scr.addstr(i, 2, element)
i += 1
self.scr.refresh()
def get_current_item(self):
return self.menu[self.item[current]]
def navigate_up(self):
if self.item[current] > 0:
self.item[current] -= 1
def navigate_down(self):
if self.item[current] < len(self.menu) -1:
self.item[current] += 1
def show_result(self):
win = self.scr.subwin(5, 40, 2, 10)
win.border(0)
win.addstr(2, 2, Vous avez selectionn %s
% self.menu[self.item[current]])
win.refresh()
win.getch()
win.erase()
def handle_keybinding(self):
while True:
ch = self.scr.getch()
if ch == curses.KEY_UP:
self.navigate_up()
elif ch == curses.KEY_DOWN:
self.navigate_down()
elif ch == curses.KEY_RIGHT:
self.show_result()
elif ch == ord(q):
break
self.draw_menu()
if __name__ == __main__:
curses.wrapper(Menu)
import curses
import curses.wrapper
class Menu(object):
menu = [
item 0, item 1, item 2, item 3,
item 4, item 5, item 6, item 7,
]
def init_curses(self):
curses.curs_set(0)
curses.use_default_colors()
curses.init_pair(1, curses.COLOR_RED, -1)
def draw_menu(self):
first = self.item[first]
last = self.item[first] + self.item[show]
menu = self.menu[first:last]
i = 2
for element in menu:
if element == self.get_current_item():
self.scr.addstr(i, 2, element, curses.color_pair(1))
else:
self.scr.addstr(i, 2, element)
i += 1
self.scr.refresh()
def get_current_item(self):
return self.menu[self.item[current]]
def navigate_up(self):
if self.item[current] > 0:
self.item[current] -= 1
if self.item[current] < self.item[first]:
self.item[first] -= 1
def navigate_down(self):
if self.item[current] < len(self.menu) -1:
self.item[current] += 1
if self.item[current] >= self.item[show] + self.item[first]:
self.item[first] += 1
def show_result(self):
win = self.scr.subwin(5, 40, 2, 10)
win.border(0)
win.addstr(2, 2, Vous avez selectionn %s
% self.menu[self.item[current]])
win.refresh()
win.getch()
win.erase()
def handle_keybinding(self):
while True:
ch = self.scr.getch()
if ch == curses.KEY_UP:
self.navigate_up()
elif ch == curses.KEY_DOWN:
self.navigate_down()
elif ch == curses.KEY_RIGHT:
self.show_result()
elif ch == ord(q):
break
self.draw_menu()
if __name__ == __main__:
curses.wrapper(Menu)
La particularit est surtout de ne pas afficher tout le menu, il est ainsi dcoup avant dtre
affich, ce ntait pas la seul possibilit daborder le problme.
first = self.item[first]
last = self.item[first] + self.item[show]
menu = self.menu[first:last]
Lautre point faire attention, cest de bien sassurer que les mthodes de navigation fassent
Note : Jai utilis ici Python 3 pour crire le script, bien que a ne fasse que peu de diffrence, il
est utile de changer quelques dtails, pour Python2. Il est cependant intressant ici de diversifier
un peu les exemples.
import sys
import curses
import curses.wrapper
class Scroll(object):
self.run_scroller()
def run_scroller(self):
self.init_curses()
self.init_file()
self.init_pad()
self.key_handler()
def init_curses(self):
Quelques banalits pour le fonctionnement de curses
Voir les billets prcdent sil y a des doutes dessus
curses.use_default_colors()
curses.curs_set(0)
self.maxyx = self.scr.getmaxyx()
def init_file(self):
On ouvre le fichie, en gardant son contenu, sous forme de tableau
avec une ligne par entre, on compte galement le nomble de ligne
afin de savoir la hauteur utile pour le "pad"
f = open(self.filename, r)
self.content = f.readlines()
self.count = len(self.content)+1
f.close()
def init_pad(self):
On crer le pad, dans lequel on affiche ligne par ligne le contenu
du fichier, avec son numro de ligne. Le pad est finalement affich
avec lappel la mthode refresh_pad quon va voir en dessous.
self.pad = curses.newpad(self.count, self.maxyx[1])
self.refresh_pad()
def refresh_pad(self):
Le plus gros du concept est ici, voir le billet pour plus
dexplication
self.pad.refresh(self.first_line, 0, 0, 0,
self.maxyx[0]-1, self.maxyx[1]-1)
def scroll_down(self):
On scroll, tout en sassurant de laffichage restant
if self.maxyx[0] + self.first_line < self.count:
self.first_line += 1
def scroll_up(self):
On scroll, en sassurant quon est pas dj en dbut
de fichier.
if self.first_line > 0:
self.first_line -= 1
if __name__ == __main__:
try:
# On essaie de lire largument fournis en console
# correspondant au nom du fichier
filename = sys.argv[1]
except IndexError as e:
# Si aucun argument est trouv, on affiche lusage
print(Erreur: {}.format(e))
print(Usage: {} filename.format(sys.argv[0]))
sys.exit(0)
# On appelle la classe avec le wrapper curses.
curses.wrapper(Scroll, filename)
def refresh_pad(self):
self.pad.refresh(self.first_line, 0, 0, 0,
self.maxyx[0]-1, self.maxyx[1]-1)
ne sais pas encore o je vais afficher, ni quel quantit, mais je connais le commencement de
mon contenu. Dans lexemple, on veux bien sr afficher ds le 1er caractre et commencer
la premire ligne, pour scroller, il suffira dincrmenter la premire ligne.
coordonnes y, x de dpart sur la console. Puisque je naffiche quune partie, je dcide donc
de commencer laffichage tout en haut gauche de ma console, cest dire 0, 0.
coordonnes y, x de fin sur la console. Pour avoir notre cadre, il faut le dbut (voir ci-dessus)
et la fin, correspondant au coin infrieur droit de la console, obtenu grce la fonction
scr.getmaxyx().
Je pense quavec les explications, le systme de scroll est plus clair, on peut noter que le script
est assez simplifi, quil manque par exemple la gestion de redimension de la console.
Les sources de Python sont fournis avec une poigne de scripts pouvant servir apprendre
quelques bases. Dans ce billet, nous allons voir ceux prvu pour le module curses de Python2.7.
Le choix de Python2.7, et non pas 3.2, est du au fait quil y a plus dexemples pour curses dans
celui-ci.
Sans plus attendre, on prend les sources de Python, avec quelques commandes comme suit :
$ wget http://www.python.org/ftp/python/2.7.2/Python-2.7.2.tar.bz2
$ tar xf Python-2.7.2.tar.bz2
$ cd Python-2.7.2/Demo/curses
$ ls
life.py* ncurses.py rain.py README repeat.py* tclock.py xmas.py
Ok, on voit quelques scripts disponible dont la curiosit nous pousse les essayer tous.
Petite prcision, si vous prenez Python3.2, les scripts ne se trouvent plus au mme endroit, mais
dans Python-3.2/Tools/demo/, et on voit quil y en a nettement moins, sauf sils sont cachs
ailleurs.
Jetons un il sur le contenu de chacun.
2.5.1 README
Fournis un petit rcapitulatif, moins complet que dans ce billet cela dit, et quelques crdits.
2.5.2 ncurses.py
273 lignes
Un bon exemple pour montrer lintrt du module curses.panel, qui est une forme de fentre
un peu particulire, car elle permet de grer le super-positionnement les unes par rapport aux
autres. Le programme positionne simplement des panels, et passe un positionnement prdfini
chaque frappe de touche.
2.5.3 rain.py
93 lignes
Un script un peu plus simple dont le but est dafficher des dessins asciiart, dont la suite repr-
sente une goutte sclatant. Idal pour les premiers pas avec Python et ncurses.
2.5.4 tclock.py
148 lignes
De conception plus avanc, ce script reproduit une horloge aiguille, avec mme une trotteuse
reprsent par le point rouge. Assez amusant.
2.5.5 xmas.py
906 lignes
Ce script correspond une animation dasciiart, do le nombre important de ligne, il est amu-
sant de le regarder, mais napporte peut tre pas normment pour lusage ncurses je crois.
2.5.6 life.py
216 lignes
Une jolie adaptation du jeu de la vie de Conway en 200 lignes, un grand classique de la pro-
grammation et de la communaut de hacker, dont jen ai fait une prsentation sur [ce billet][2].
Ce script peut intresser pour le ct curses, mais galement pour lire une implmentation
simple de ce jeu.
2.5.7 repeat.py
58 lignes
Le but est de passer en argument une commande, dont laffichage sera rafrachis toute les
secondes. On peut essayer par exemple :
repeat.py uptime
Voil un petit tour dhorizon des quelques programmes de dmonstration que fournis Python,
ils peuvent tre utile pour se faire la main. Le but du billet ntait pas den faire une description
dtaill, mais surtout de motiver aller voir de plus prs le code. vos diteurs !
Utilisation dAPI
Lorsquon utilise un projet ou une bibliothque, il est souvent agrable de gnrer la documen-
tation localement, plusieurs raisons a.
Temps de raction aux chargement de pages plus rapide.
Pas besoin de connexion Internet pour consulter la documentation, pouvant tre utile certain
(train, dplacement, Internet coup par Hadpi...)
Pas de mince le site est down...
Pour les petit projet, la documentation sur le site ne correspond pas forcment lactuel car
le dveloppeur ne prend pas forcment le temps de mettre jour. Et je pense quon est plus
dun avoir le reflex de comparer le numro de version entre le site et lapi rellement utilis.
Par chance, les projets bas sur Python utilises presque tous un mme outil pour gnrer la
documentation, Sphinx 1 . Consistant parser des fichiers en ReStructuredText (extention .rst).
Spinx tant lui mme crit en Python, et sappuyant sur pigments pour la coloration syntaxique.
Pour Arch Linux, Sphinx est disponible dans [community], il est galement disponible pour
Ubuntu et Debian, et trs certainement pour toutes autres distributions tant ce projet est un
classique. Il est par ailleurs utilis pour gnrer la documentation en ligne officielle de Python.
pacman -S python-sphinx
apt-get install python-sphinx
Le fonctionnement de sphinx est simple, un utilitaire rgle les dtails en crivant un fichier
Makefile, pour lequel il suffira dutiliser avec un make. Le fichier Makefile peut tre gnrer
avec la commande sphinx-quickstart par exemple.
Par exemple, prenons Lettuce comme projet, un projet pour les tests unitaires, qui sera vu par
la suite. Cest un projet permettant de grer les tests, qui sera vu un peu plus en dtail dans la
partie tests unitaires.
1. http ://sphinx.pocoo.org/
31
Python en Action
$ make html
rm -rf _build/*
sphinx-build -b html -d _build/doctrees . _build/html
Making output directory...
Running Sphinx v1.0.7
loading pickled environment... not yet created
building [html]: targets for 17 source files that are out of date
updating environment: 17 added, 0 changed, 0 removed
reading sources... [100%] tutorial/tables
looking for now-outdated files... none found
pickling environment... done
checking consistency... /home/nicolas/test/lettuce/docs/tutorial/django.rst::
WARNING: document isnt included in any toctree
done
preparing documents... done
writing output... [100%] tutorial/tables
writing additional files... genindex search
copying images... [100%] tutorial/flow.png
copying static files... done
dumping search index... done
dumping object inventory... done
build succeeded, 1 warning.
Comme indiqu, le rsultat se trouve dans _build/html dont il suffit de lancer index.html.
Il est donc possible de gnrer rapidement la documentation avec un simple :
make html
Dautre format sont support, notamment le PDF. Cette opration passe par la gnration dun
document LaTeX.
make latex
make latexpdf
Pour anecdote, le prsent document est gnr en utilisant Sphinx et le make latexpdf.
Pour gnrer le fichier Makefile, Sphinx fournit un utilitaire en ligne de commande, permet-
tant, une fois quelques questions basique rpondu, de gnrer le Makefile. Heureusement les
questions sont assez simple, et les valeurs par dfaut sont souvent suffisante.
sphinx-quickstart
Ce billet est une trs courte introduction de ce que Sphinx peut faire. Cet outil vaut tre connu.
On va voir ici, comment envoyer et lire des tweets depuis la console vers twitter ou Identi.ca,
en passant par le systme Oauth pour les deux cas. Les tapes sont expliqu du dbut la fin,
et ne require pas spcialement de connaissances importantes en Python.
Pour arriver nos fins, nous allons utiliser une API, python-twitter. Ce billet est plus une in-
troduction cette API que davoir un intrt rel dans lapplication. Surtout dans le cas de
identi.ca o il serait bien plus simple dutiliser une authentification http basique au lieu du
systme Oauth. Il reste cependant intressant de voir que Identi.ca propose un comportement
similaire Twitter dans son API, permettant dintgrer deux services avec un quasiment le
mme code, vitant une recriture pour sadapter lun ou lautre des services.
Pour utiliser Oauth, il faut enregistrer son application, aussi bien pour Twitter que pour
Identi.ca. Rien de bien mchant mais indispensable pour obtenir les cls de votre application.
Une fois identifi, on se rend sur la page de gestion dapplication : https ://dev.twitter.com/apps
On click sur Register a new app , possible quil soit crit en franais chez vous.
Je vous pargne le screenshot, nous remplissont le formulaire suivant :
Application name : Un nom dapplication
Description : Faut vraiment expliqu ? :p
Application website : Cest le lien qui vient lorsquon a la source dun client. Pas super utile
si cest juste un client fait La Rache mais bon.
Application type : Important, faut choisir client .
Default access type : Cest ce que twitter chang rcemment, il faut au minimum read &
write , mais le read, write & direct message peut tre utile pour avoir accs au messages
direct.
Application icon : bah. . . un avatar na jamais tu personne.
On remplit le captcha, on valide leurs conditions dutilisation, et nous voil avec une application
frachement cre. Vous obtenez quelques renseignements et cls, un peu obscure au premier
abord, vous ne relevez uniquement les deux informations suivantes :
Consumer key (Cl de lutilisateur)
Consumer secret (Secret de lutilisateur)
Identi.ca
Le principe est similaire, on sidentifie son compte Identi.ca, puis on enregistre son applica-
tion sur le lien suivant : http ://identi.ca/settings/oauthapps
On click sur Register a new application, puis on remplit galement le petit formulaire, qui est
similaire celui de twitter, pour lequel je ne reprendrai que les dtails qui peuvent laisser un
doute :
Dans la premire tape, nous avons obtenu les cls spcifique lapplication, maintenant, il
faut obtenir les cls pour un utilisateur, li cette application, et lautoriser accder son
compte.
Comme nous voulons aller au plus simple, et quil est pratique courante de rutiliser un code
de quelquun dautre lorsque celui-ci est libre. On utilise lutilitaire fournis avec lAPI python-
twitter.
On tlcharge la version de python-twitter (0.8.2, la dernire), on dcompresse larchive, et
rentre dans le rpertoire :
wget http://python-twitter.googlecode.com/files/python-twitter-0.8.2.tar.gz
tar xvf python-twitter-0.8.2.tar.gz
cd python-twitter-0.8.2
Le fichier important ce niveau est get_access_token.py puisquil permet dobtenir les cls.
Je ne copie-colle pas tout le code, mais vous devez rechercher les deux paramtres suivant la
ligne 34 et 35 :
consumer_key = None
consumer_secret = None
consumer_key = vIxy85s7r2jOBmr7m7bQ
consumer_secret = PyLzYa3WLMqv6xziFAiOqMlQlSxP9vXyXsTemqyB7c
Lapplication vous donne un lien visiter, un code rentrer, puis vous fournit vos deux codes.
$ python2.7 get_access_token.py
Requesting temp token from Twitter
Please visit this Twitter page and retrieve the pincode to be used
in the next step to obtaining an Authentication Token:
https://api.twitter.com/oauth/authorize?oauth_token=c5X[...]31Y
Pincode? 4242424
Identi.ca
3.2.3 Lapplication
La partie fastidieuse tant pass, les cls pour le bon droulement obtenu, place enfin ce qui
nous intresse, le code !
On veut quoi ?
Appeller mon application, avec un argument, on va mettre -s comme send, puis le tweet
envoyer directement dans la ligne de commande.
Lapplication doit tre correctement authentifier au service voulu.
Utiliser python-twitter comme API pour grer lauthentification et lenvoi.
Un nom pour lapplication, va pour... Clitter... Bon un peu pourris, mais je vais faire avec :
(Clitter pour CLI, Commande Line Interface, je prfre prciser, on sait jamais)
Dans un premier temps, on veut passer correctement largument -s. Jutilise dans lexemple
getopt, qui est un parser ressemblant celui de C, mais on peut utiliser galement argparse, qui
est moins verbeux mettre en place.
Je reprends un peu lexemple pris sur la page de documentation de getopt 2 , on fait un premier
essai, voir si largument est bien pris en compte.
clitter.py :
2. http ://docs.python.org/library/getopt.html
def main():
try:
opts, args = getopt.getopt(sys.argv[1:], "s:", ["send="])
except getopt.GetoptError, err:
print str(err) # will print something like "option -a not recognized"
sys.exit(2)
for o, a in opts:
if o in ("-s", "--send"):
tweet = a
print a
else:
assert False, "unhandled option"
if __name__ == "__main__":
main()
Ce quon veut ici, cest uniquement avoir en sortie le tweet entre, on essaye :
On a ce quoi lon sattendait en retour en console, le mon tout premier test , trs bien,
passons aux choses srieuses.
Il nous reste plus qua rajouter lauthentification, et lenvoi sur twitter, ce nest pas bien com-
pliqu maintenant.
On va crer un rpertoire dans lequel on place : - __init__.py avec rien dedans - clitter.py
correspondant notre script - twitter.py lapi quon a tlcharg tout lheure, l o se
trouvait
get_access_token.py, il nous suffit de le dplacer dans le rpertoire.
Voici le code permettant denvoyer un tweet :
def main():
try:
opts, args = getopt.getopt(sys.argv[1:], "s:", ["send="])
except getopt.GetoptError, err:
print str(err) # will print something like "option -a not recognized"
sys.exit(2)
for o, a in opts:
if o in ("-s", "--send"):
tweet = a
else:
assert False, "unhandled option"
consumer_key = vIxy85s[...]7m7bQ
consumer_secret = PyLzYa[...]9vXyXsTemqyB7c
api.PostUpdate(tweet)
if __name__ == "__main__":
main()
import twitter
Puis on renseigne les quatres cles pour sidentifier, que jai un peu raccourcis ici.
Vient ensuite lauthentification proprement dite, avec un double rle, instancier lobjet de lapi,
et sidentifier celle-ci :
api.PostUpdate(tweet)
Identi.ca
url = "https://identi.ca/api"
api = twitter.Api(consumer_key, consumer_secret,
oauth_token, oauth_token_secret,
base_url=url)
Comme il est assez facile denvoyer un tweet, quon a finalement pas vu grand chose de python-
twitter, compliquons un peu les choses, en essayant de lire ses tweets en console. Une bonne
occasion de rorganiser un peu le code dans la foule.
La mthode utilis pour lire ses tweets est :
api.GetFriendsTimeline(retweets=True)
On rcupre la timeline de nos amis , et on active les retweets. On veut appeler cette fonction
lorsquon utilisera loption -r pour read, en console.
Voici le code final de notre petite application ( adapt comme vu plus haut pour identi.ca)
def authentication():
consumer_key = 2jO[...]Bm
consumer_secret = PyLzYa[...]3WLM
oauth_token = tsr[...]ds5
oauth_token_secret = PSd3[...]tSt
def send(tweet):
api = authentication()
api.postUpdate(tweet)
def read():
api = authentication()
statuses = api.GetFriendsTimeline(retweets=True)
def main():
try:
opts, args = getopt.getopt(sys.argv[1:], "rs:", ["send="])
except getopt.GetoptError, err:
print str(err) # will print something like "option -a not recognized"
sys.exit(2)
for o, a in opts:
if o in ("-s", "--send"):
tweet = a
send(tweet)
elif o in ("-r", "--read"):
read()
else:
if __name__ == "__main__":
main()
Pas besoin de plus de code que a ! Quelques explications encore. On retrouve nos tweets
avec le GetFriendsTimeline, auquel on rajoute les retweets dans la timeline, sinon vous ne
verriez pas les retweets de vos follower, ce qui est gnralement le comportement par dfaut
des applications twitter.
statuses = api.GetFriendsTimeline(retweets=True)
On obtient une liste, sur lequel on va lire le pseudo et le tweet en accdant directement au
attribut de lobject Status.
Et on appele le script :
python2.7 clitter.py -r
Et voil !
3.2.4 Conclusion
Grce ce script dune cinquantaine de lignes tout mouill, vous pouvez lire et envoyer des
tweets directement en console. Cest lavantage de sappuyer sur une bibliothque, mme si
au dbut cela peut faire peur de sy plonger. Il ne faut pas hsiter ouvrir le code, lire la
documentation tout au long de lAPI pour en apprendre plus.
Dsirant essayer un peu le service de Google pour raccourcir les url goo.gl, jai voulu lintgrer
mon petit client Twitter Tyrs 3 , et surtout intgrer lidentification Oauth. Nous allons voir ici
comment utiliser lAPI pour raccourcir un lien, et la documentation pour aller plus loin.
Pas de dbats sur Google, mais simplement une bonne occasion dessayer un nouveau jouet et
de se plonger un peu dans du code.
3. http ://tyrs.nicosphere.net Tyrs est un client twitter en console se basant sur ncurses et sous licence GPL.
Jen ai commencer lcriture dbut mai (2011), il est fonctionnel et je men sers encore au quotidien.
Comme toujours avec Oauth, il faut dans un premier temps enregistrer votre application, en
allant sur le lien suivant 4 , comme lest expliqu ici 5 . Vous remplissez les quelques cases, at-
tention, il faut choisir ici Installed application, correspondant un usage Desktop, et non un
service sur un site web. Vous devez galement activer le support pour lurlshortener, comme le
montre limage suivante, disponible toujours au mme endroit :
Note : Pour suivre ce tutoriel, vous ntes pas oblig denregistrer une application. Si vous
le faite, vous avez obtenu un classique couple de cle client id / client secret. Un client secret,
qui comme je lexpliquais dans le chapitre prcdant, na rien de secret du tout, et peut tre
partag par tous. Lexemple serra pris avec les cls prpar et fonctionnant, vous pouvez donc
les utiliser.
Une seule dpendance est requise, mais une autre est optionnel on verra par la suite pourquoi.
google-api-python-client
Cette bibliothque est disponible plusieurs endroits, dont le dpt officiel 6 et sur pypi 7 , ser-
vice connu des pythonistes.
Il peut tre utile de regarder dans les dpts de votre distribution avant, pour Arch Linux, jai
regard, et il est disponible avec un :
yaourt -S python2-google-api-python-client
python-gflags
Pas utile si vous suivez _strictement_ le tuto, mais si vous prenez lautre code exemple que je
vais fournir en lien, vous en aurez besoin. La page du projet 8 , et trs certainement disponible
sur pypi galement.
Pour Arch Linux :
yaourt -S python-gflags
4. https ://code.google.com/apis/console/
5. http ://code.google.com/apis/accounts/docs/OAuth2.html#Registering
6. http ://code.google.com/p/google-api-python-client/
7. http ://pypi.python.org/pypi/google-api-python-client/1.0beta2
8. http ://code.google.com/p/python-gflags/
Le code suivant, est une version simplifi du code exemple pouvant tre trouv sur le lien
suivant 9 , on peut not quon trouve galement des exemples pour dautres langages tel que
Ruby, Java, PHP, .NET. . .
La version que je fournis plus bas, bas sur leur exemple est allg du systme de logging et de
leur systme de commande, qui ncessite galement la dpendance python-gflags qui napporte
pas un plus ici (surtout que leur lien raccourcir est crit en dure dans le code)
Lusage voulu ici est par exemple ./googl.py http://www.nicosphere.net, et ainsi obtenir
en retour le lien raccourcis.
import httplib2
import sys
FLOW = OAuth2WebServerFlow(
client_id=382344260739.apps.googleusercontent.com,
client_secret=fJwAFxKWyW4rBmzzm6V3TVsZ,
scope=https://www.googleapis.com/auth/urlshortener,
user_agent=urlshortener-tyrs/1.0)
def main(argv):
9. http ://code.google.com/p/google-api-python-client/source/browse/samples/urlshortener/urlshortener.py
try:
url = service.url()
short_url = resp[id]
# On imprime le rsultat
print short_url
except AccessTokenRefreshError:
print ("The credentials have been revoked or expired, please re-run"
"the application to re-authorize")
if __name__ == __main__:
main(sys.argv)
Warning : Python 2.x est utilis ici, et ne fonctionnera pas avec Python3
la premire utilisation, Il vous serra demand de suivre un lien, afin dautoriser lapplication
sassocier avec votre compte, vous devez tre identifi votre compte Google. Exactement
comme lorsque vous utilisez un nouveau client Twitter avec un compte, sauf que ici, pas besoin
de PIN code de validation.
Pour que lapplication soit utilisable une fois sur lautre sans avoir valider, les accs sont
enregistr dans un fichier, ct de votre executable, dans cet exemple : googl.tok
La sortie console ressemble cel :
Finallement, vous devez voir dans la console le lien raccourcis correspondant, si lopration est
renouvel il ny a pas besoin de sauthentifier comme la premire fois.
3.3.5 Conclusion
Ici on ne fait que retourner le lien racourcis de Google, lAPI permet den faire bien plus encore,
pour cela, il faut consulter le guide de dmarrage 10 ou la rfrence de lAPI 11 . Mais ce petit
morceau de code permet davoir une ide, et montre que lutilisation Oauth / API Google est
plus accessible quon pourrait le croire au premire abord.
Urllib est le module par dfaut pour grer les requetes HTTP avec Python. Le cookbook 12
du site officiel donne un petit aperu de lusage durllib2. Certains dtails peuvent surprendre
aux premires utilisations. Ce quon aime avec Python, cest avant tout de pouvoir penser
lapplication, plutt quau code lui mme.
Requests 13 se veux tre un petit module rpondant cette problmatique, HTTP for hu-
mains comme titre le site. On va voir quelques exemples, qui seront certainement plus mar-
quant lorsque les besoins se complexifie.
10. http ://code.google.com/apis/urlshortener/v1/getting_started.html
11. http ://code.google.com/apis/urlshortener/v1/reference.html
12. http ://docs.python.org/howto/urllib2.html
13. http ://docs.python-requests.org/en/latest/index.html
Warning : Requests nexiste pour le moment que pour Python 2.x, il na pas encore t
port pour la version 3. Cest donc un choix faire.
Linstallation est on ne peut plus classique avec pip ou autre installeur de Python.
import urllib2
req = urllib2.Request(http://www.voidspace.org.uk)
response = urllib2.urlopen(req)
page = response.read()
import requests
req = requests.get(http://www.nicosphere.net)
page = req.content
Un autre exemple tir du site officiel, dont le but ici est de remplir un formulaire.
import urllib
import urllib2
url = http://www.someserver.com/cgi-bin/register.cgi
values = {name : Michael Foord,
location : Northampton,
language : Python }
data = urllib.urlencode(values)
req = urllib2.Request(url, data)
response = urllib2.urlopen(req)
page = response.read()
import requests
url= http://wathever.url
values = {name: Nicolas,
location: Somewhere,
handler = urllib2.HTTPBasicAuthHandler(password_mgr)
Un peu verbeux pour une simple authentification basique... Mais cest lexemple officiel encore
une fois, jai laiss les commentaires du code pour plus de lisibilit . Maintenant, ce mme
code crit avec Requests, exemple pris sur leur site.
3.4.4 Conclusion
Voil un petit survole de Requests, et bien que je nai pas encore eu tellement loccasion de
lutiliser, cest trs certainement un module que je vais garder sous le coude. Pour des scripts
jetables, ou des petites applications personnel, il me semble vident que a peut tre un gain
de temps et de confort. Pour ce qui est de son utilisation pour une application redistribu, je
comprends quon puisse prfrer lutilisation dun module core tel que urllib, cependant, avec
un usage de setup.py pour redistribuer, les dpendances sont install trs facilement sans actions
supplmentaires de lutilisateur, pourquoi pas utiliser Requests donc.
Les forums (oui, je nutiliserai pas fora), sont un bon moyen de visualiser ltat de sant dune
communaut. Si on me prsente un projet comme rvolutionnaire et incontournable, et que le
forum contient trois sujets et deux membres, jaurais comme des doutes.
Dans le cas darchlinux.fr, il pourrait tre intressant de visualiser le nombre dinscriptions en
fonction du temps, den visualiser une tendance gnrale. Et en prime, comparer les rsultats
avec le forum anglophone darchlinux.org. Cest exactement ce qui va tre fait dans ce billet.
Les forums fournissent gnralement une liste de membre, avec la date dinscription, le plus
souvent accessible uniquement aux membres dj inscrit (et a tombe bien, a rend le jeu plus
intressant). Lide est simplement de rcuprer toutes les dates dinscriptions, den faire un
cumule pour chaque mois, et de faire un petit graphique pour prsenter et visualiser le rsultat.
videmment, comme cest un blog aussi sur la programmation, on va voir comment obtenir se
graphique ici.
Javoue, le but tait bien plus dessayer de nouveaux jouets, et de programmer un peu, que de
vraiment connatre le rsultat qui ne sont pas prendre au pied de la lettre, dautres solutions
certainement plus simple existe, mais je voulais vraiment essayer Scrapy dans un exemple rel.
Scrapy semble tre un outil vraiment puissant et modulable, il est vidant que lutiliser ici pour
rsoudre se problme si simple est utiliser une solution dmesur. Cependant, je pense que
pour dcouvrir un outil, il est prfrable de lessayer avec un problme simple. Cest pourquoi
je lemploie ici.
3.5.1 Prrequis
Ce billet sappuie sur Scrapy pour crawler le forum et Pylab pour obtenir le graphique, pylab est
fournis par matplotlib. Il convient de linstaller avant avec pip, easy_install ou des gestion-
naires de paquets comme pacman et apt-get. Dans le cas darchlinux.org, il nest accessible
uniquement par du https, il convient dinstaller pyopenssl
Pip
La premire chose faire est de crer un nouveau projet, scrapy vient avec une suite de com-
mande shell, dont startproject nous facilite la mise en place de notre nouveau projet.
Gnration du projet
$ tree archlinux
archlinux
|-- archlinux
| |-- __init__.py
| |-- items.py
| |-- pipelines.py
| |-- settings.py
| -- spiders
| -- __init__.py
-- scrapy.cfg
2 directories, 6 files
La structure peut drouter au dbut, mais elle est logique est sa prise en main rapide. En gros,
on va crer un spider dans le rpertoire, qui rcuprera les donnes, et suivra les urls quon lui
indiquera, il se chargera de sidentifier au dbut sur le forum, chaque donnes, ou items seront
dfini dans items.py, et traite dans pipelines, dans ce cas afin de les sauvegarder.
Les items
Commenons par le plus simple, dfinir les items, qui sera tout seul en fait dans ce cas, seul la
date nous intresse.
archlinux/items.py :
from scrapy.item import Item, Field
class ArchlinuxItem(Item):
date = Field()
Comme le gnrateur de projet fournis un squelette bien avanc dj, il nous est utiles que de
rajouter la ligne date = Field() permettant de dfinir litem date.
Le spider
Cette partie est la plus compliqu, je vais essayer de lexpliquer au mieux. Dans le fichier
archlinux/spiders/archlinux_spider.py. On commence par les imports.
Puis on va crer une classe hritant dun spider de base, dans lexemple, on prends en compte
le forum darchlinux.fr.
propos de lextracteur de lien extractor, Cest linitialisation de ce qui extrait les liens
suivre, dune part on le restreint memberlist, mais lide est de toujours suivre le lien suivant
un peu comme on cliquerai pour aller du dbut la fin de la liste de membre, en appuyant
frntiquement sur Suivant. Le suivant est trouv en restreignant au bon lien avec Xpath.
class ArchLinuxSpider(BaseSpider):
name = "archlinux"
allowed_domains = ["forums.archlinux.fr"]
extractor = SgmlLinkExtractor(allow=memberlist,
restrict_xpaths=//a[. = "Suivant"])
start_requests est la premire fonction appel aprs linitialisation du bot, je men sers ici
pour demander une page de login, afin de la remplir avec mes identifiants, cette faon de pro-
cder permet de massurer que les champs cach (token, csrf...) soit correctement remplis, ou
encore de grer les sessions, cookies... On retourne un Request, avec comme callback la fonc-
tion qui grera le login.
def start_requests(self):
login_url =http://forums.archlinux.fr/ucp.php?mode=login
return [Request(login_url, callback=self.login)]
La page de login est reu, on traite ici en utilisant une classe un peu spciale FormRequest
et surtout avec la mthode from_response. On renseigne les lments, et la rponse de cette
demande de login sera gr par la mthode after_login (callback).
Le Pipeline
Dans cette exemple, on va simplement rajouter chaque lment dans un fichier, et le trater par
un petit script python plus tard, il serait faisable de le faire en mme temps ici.
archlinux/pipelines.py :
class ArchlinuxPipeline(object):
def __init__(self):
self.file = open(result.txt, wb)
Ce fichier moins besoin dexplication, il sassure surtout de ne stocker que la date, dans un
format correct.
Pour que ce fichier soit pris en compte, il faut le rajouter dans la configuration, cest la dernire
ligne qui nous intresse ici :
archlinux/settings.py :
BOT_NAME = archlinux
BOT_VERSION = 1.0
SPIDER_MODULES = [archlinux.spiders]
NEWSPIDER_MODULE = archlinux.spiders
DEFAULT_ITEM_CLASS = archlinux.items.ArchlinuxItem
USER_AGENT = %s/%s % (BOT_NAME, BOT_VERSION)
ITEM_PIPELINES = [archlinux.pipelines.ArchlinuxPipeline]
Mise en route
La mise en fonctionnement du bot est trs simple et se fait en ligne de commande. Une longue
suite de ligne de dbug apparatra en console, mais le plus import est de vrifier le rsultat
obtenu. Et aprs avoir parser une trentaine de pages, on obtient le fichier result.txt voulu.
$ cat result.txt | wc -l
3017
Un rapide coup dil au forum, qui indique le nombre dinscrit, je tombe exactement sur le
mme chiffre, ce qui est rassurant. Nous voil avec un grand fichier, avec tout plein de dates. Il
nous faut maintenant trouver le moyen de traiter ces informations.
Les dates sont sous la forme jour mois anne, on souhaite cumuler le nombre de mois anne
identique, et lafficher sous forme de graphique. Le script suivant rpond ce besoin.
import pylab
with open(result.txt, r) as f:
dates = []
values = []
for line in f:
line = line.strip().split( )[2:]
line = .join(line)
try:
if dates[-1] == line:
values[-1] += 1
else:
dates.append(line)
values.append(1)
except IndexError:
dates.append(line)
values.append(1)
Le script fait ce quon demande, mais en y repensant, il y avait plus simple et plus lgant
comme mthode, lide est de comparer la date avec le dernire lment et dincrmenter ou
de le rajouter selon le cas. Comme il existe une mthode permettant davoir le nombre doccur-
rence dun tableau, il aurait t prfrable que je marrange de navoir que le mois anne dans
mon table et de traiter les occurences. Mais bon. . . Pylab gre galement les abscisses avec les
dates, je nai pas vu en dtail ce fonctionnement bien quil aurait t pertinent de le faire.
3.5.4 Rsultat
Il est temps de lancer le script, et de regarder les rsultats obtenu, notons que le mois dAot
ntant pas fini (au moment de la rduction du billet), il est normal de se retrouver avec une
baisse pour le dernier mois.
Le principe est le mme, il faut simplement adapter certain dtail pour le forum anglophone,
qui nutilise plus phpbb mais fluxbb, je place les codes ici, sans plus dexplications.
Il faut tout de mme parser 760 pages pour obtenir les 34000 membres. Bien sr, on retrouve
dans notre fichier le nombre exacte de membres.
Tout de fois, un petit traitement du fichier en ligne de commande t utile, dune part avec
vim (ou sed) car les inscription du jour et dhier sont not Yesterday et Today, au lieu de la date,
a pourrait fausser le rsultat. Dautre part, pour que les dates soient dans lordre, un sort est
requis. Si vraiment on y tient, il aurait t facile de le placer directement dans le script aprs.
archlinux/archlinux_spider.py :
class ArchLinuxSpider(BaseSpider):
name = "archlinux"
allowed_domains = ["bbs.archlinux.org"]
extractor = SgmlLinkExtractor(allow=userlist,
restrict_xpaths=//a[. = "Next"])
hxs = HtmlXPathSelector(response)
dates = hxs.select(//td[contains(@class, "tcr")]/text()).extract()
for date in dates:
item = ArchlinuxItem()
item[date] = date
yield item
def start_requests(self):
login_url =https://bbs.archlinux.org/login.php
return [Request(login_url, callback=self.login)]
class ArchlinuxPipeline(object):
import pylab
with open(result.txt, r) as f:
dates = []
values = []
for line in f:
line = line.strip()
try:
if dates[-1] == line:
values[-1] += 1
else:
dates.append(line)
values.append(1)
except IndexError:
dates.append(line)
values.append(1)
Et le rsultat en image :
3.5.7 Conclusion
Beaucoup de code pour pas grand chose pourrait on dire, cependant cela ma t instructif sur
bien des points.
Mme si le titre du billet (originalement publi sous le titre Petit bilan de sant dArch Li-
nux) nest pas prendre au pied de la lettre, ces deux graphiques donnent tout de mme une
petite indication sur ltat de sant des deux communauts dArch Linux. Chacun trouvera les
interprtations faire partir des graphiques. Pour ma part, en conclusion, je dirai simplement :
Arch Linux se porte plutt bien, et votre forum favori ?
4.1 Unittest
4.1.1 Introduction
Quelques mots pour les personnes naillant jamais crit de tests. Il y a beaucoup davantages
en crire, par exemple tre plus confient lors damlioration de code existant, tre sr de ne rien
casser . Il sagit bien souvent (pour les tests unitaires du moins) de tester une petite partie
dun code, afin de sassurer quon obtient les valeurs auxquelles on sattendait. Ce billet nest
pas l pour faire une introduction sur les avantages de tester son code, mais sachez que cest
une pratique indispensable et courante pour tout code.
Nous allons utiliser ici un module, unittest 1 qui est disponible directement avec Python. Jau-
rais pu commencer avec doctest 2 , galement un module natif Python, permettant dcrire les
tests directement sous forme de commentaire, pour les personnes intresses, la documentation
officiel est certainement un bon endroit pour commencer. Ce billet nest quun rapide aperu
de unittest, et ne se veux pas dtre complet.
4.1.2 Le code
Commenons par un exemple _trs_ simple. En crant une fonction add() qui... additionne
deux chiffres !
Crez un rpertoire de test, dans lequel on cre la fonction suivante dans un fichier chiffres.py
1. http ://docs.python.org/library/unittest.html
2. http ://docs.python.org/library/doctest.html
55
Python en Action
import unittest
from chiffres import add
class TestChiffres(unittest.TestCase):
def test_addition(self):
result = add(36, 6)
self.assertEqual(result, 42)
if __name__ == __main__:
unittest.main()
4.1.3 Explications
import unittest
from chiffres import add
On cre une classe qui doit commencer par Test, et hritant de unittest.TestCase, correspondant
la ligne suivante :
class TestChiffres(unittest.TestCase):
La fonction suivante est celle qui est utilis pour le test, et doit commencer par test_ pour
quelle soit pris en compte. Le plus important, cest de vrifier la valeur que la fonction retourne
avec celle auquel on sattend, et cest ce que fais la ligne suivante
self.assertEqual(result, 42)
if __name__ == __main__:
unittest.main()
$ python test_chiffres.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Maintenant, on veut sassurer que la fonction lve une exception si donne comme argument
une chaine de caractres, on crit donc le test :
class TestChiffres(unittest.TestCase):
def test_addition(self):
result = add(36, 6)
self.assertEqual(result, 42)
def test_add_string(self):
self.assertRaises(ValueError, add, coin, pan)
if __name__ == __main__:
unittest.main()
On utilise assertRaises, avec comme argument, lexception attendu, la fonction et une liste
dargument fournis la fonction. On essaye le test :
$ python test_chiffres.py
F.
======================================================================
FAIL: test_add_string (__main__.TestChiffres)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_chiffres.py", line 13, in test_add_string
self.assertRaises(ValueError, add, coin, pan)
AssertionError: ValueError not raised by add
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=1)
Le test choue, on sy attendait un peu, puisque le rsultat retourn est la concatnation des
deux chaines, cest dire coinpan.
On crit un bout de code qui lvera une erreur en cas de chaines pass en argument, comme
suit :
$ python test_chiffres.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
4.1. Unittest 57
Python en Action
Trs bien, cest ce quon voulait. Mais que ce passe-t-il si on envoie comme argument des floats
(nombre virgules) ?
crivont le test, quon rajoute la suite, et regardons
def test_add_with_float(self):
result = add(1.01, 2.001)
self.assertEqual(result, 3.011)</code>
$ python test_chiffres.py
.F.
======================================================================
FAIL: test_add_with_float (__main__.TestChiffres)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_chiffres.py", line 17, in test_add_with_float
self.assertEqual(result, 3.011)
AssertionError: 3 != 3.011
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=1)</code>
AssertionError: 3 != 3.011
Et effectivement int(a)+int(b) ne retournera pas de float, changeons le code pour que le test
passe maintenant avec succs :
Note : basestring, qui ici nous permet de comparer lobjet une chane de caractre, est spci-
fique Python 2, puisque dans la version 3, elles ne sont plus gr de la mme manire.
$ python test_chiffres.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
Voil, grce au test, on sait que la fonction un comportement auquel on sattend, on pourrait
essayer de faire quelques modification, mais avec les tests, on sassure que son comportement
ne change pas de faon inattendu.
4.1.4 Prcisions
Verbosit
On peut rajouter de la verbosit pour les tests, avec lune des deux mthodes suivante :
Directement en ligne de commande
if __name__ == __main__:
suite = unittest.TestLoader().loadTestsFromTestCase(TestChiffres)
unittest.TextTestRunner(verbosity=2).run(suite)</code>
4.1. Unittest 59
Python en Action
Discover
Ce module vient maintenant avec une nouvelle option, discover, permettant dautomatiser la
recherche des diffrents fichier de test, je ne mtendrais pas sur ce dtail, et on verra pourquoi
dans le prochain billet.
$ python -m unittest discover
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
Pour le moment, on a vu quelques bases du module Python unittest. Dans ce billet, on va voir
comment faciliter lexcution dune suite de tests, mme htrogne, et plus encore.
Nose, sintgre avec unittest et doctest, mais propose galement sa propre syntaxe simplifi
pour crire des tests. Dans ce billet, on prendra la mme fonction aillant servis au dernier billet,
cest dire une fonction add(), additionnant simplement deux nombres entre eux.
4.2.1 Installation
Comme souvent, plusieurs faon dinstaller Nose, pour Arch Linux par exemple, on peut ins-
taller lun des deux :
yaourt -S python2-nose
yaourt -S python-nose
Reprenons o nous en tions la dernire fois, mais en sparant les fichiers sources et les fichiers
tests, selon la structure suivante :
$ tree
.
|-- src
| -- chiffres.py
-- tests
-- test_chiffres.py
2 directories, 2 files</code>
tests/test_chiffres.py :
import unittest
from chiffres import add
class TestChiffres(unittest.TestCase):
def test_addition(self):
result = add(36, 6)
self.assertEqual(result, 42)
def test_add_string(self):
self.assertRaises(ValueError, add, coin, pan)
def test_add_with_float(self):
result = add(1.01, 2.001)
self.assertEqual(result, 3.011)
if __name__ == __main__:
unittest.main()
Nose, contrairement unittest, est prvu nativement pour parcourir les fichiers et arborescences
afin de dcouvrir les diffrents tests. Le code est dans ltat laiss dans le dernier billet. Regar-
dons le rsultat de nose, plac vous dans la racine du projet, et entrez la commande :
$ nosetests
...
----------------------------------------------------------------------
Ran 3 tests in 0.016s
OK
Un avantage de nose, cest quil est possible de le faire fonctionner avec un fichier de configu-
ration (~/.noserc) permettant denregistrer les prfrences.
On peut ainsi rajouter de la verbosit comme paramtre de dfaut, mais galement inclure les
doctests. De faon plus gnral, chaque argument dans la liste obtenu avec nosetests -h peut
tre rajout dans votre .noserc. Prenons un exemple :
[nosetests]
verbosity=3
with-doctest=1
doctest-extention=txt
De cette faon, je nai plus moccuper de la verbosit, elle est automatiquement incluse, on
va galement rajouter un fichier .txt, contenant un test tout simple en doctest, pour voir quil
est bien inclue directement.
tests/test_doctest_chiffres.txt :
$ nosetests
test_add_string (test_chiffres.TestChiffres) ... ok
test_add_with_float (test_chiffres.TestChiffres) ... ok
test_addition (test_chiffres.TestChiffres) ... ok
Doctest: test_doctest_chiffres.txt ... ok
----------------------------------------------------------------------
Ran 4 tests in 0.032s
OK
Les trois tests venant de unitest, auquel on rajoute doctest, le compte y est.
Bien que Nose soit compatible avec lusage de unittest, il comporte galement une version
un peu allg, mais pas moins complte de test. Incluant lusage de setUp() et tearDown(),
permettant de donner des directives avant et aprs le tests, souvent utile la connexion de base
de donne par exemple. Les tests nont pas besoin dhriter de unittest.Test.Case, et peut tre
de simples fonctions.
Dans cette exemple, on va reproduire dans un nouveau fichier, les tests dj crit avec unittest.
tests/test_nose_chiffres.py :
def test_add_int():
assert add(3, 4) == 7
def test_add_float():
assert add(2.01, 1.01) - 3.02 < 0.001
La premire chose quon remarque, cest la lgret de la syntaxe, labsence de classe (bien
quon aurait pu) et lusage dassert.
Pour le test de float, je ne crois pas avoir trouv un almostEqual, jai donc mis le code suivant
qui revient un peu prs au mme donc :
Lautre diffrence notable est pour la gestion de lexception, qui se fait maintenant par un
dcorateur, et limport correspondant (ne pas loublier) :
@raises(ValueError)
$ nosetests
test_add_string (test_chiffres.TestChiffres) ... ok
test_add_with_float (test_chiffres.TestChiffres) ... ok
test_addition (test_chiffres.TestChiffres) ... ok
Doctest: test_doctest_chiffres.txt ... ok
test_nose_chiffres.test_add_int ... ok
test_nose_chiffres.test_add_float ... ok
test_nose_chiffres.test_add_chaine ... ok
----------------------------------------------------------------------
Ran 7 tests in 0.036s
OK
Comme prvu, nosetests parcourus larborescence pour trouver les trois fichiers de tests
(test_chiffres.py, test_doctest_chiffres.txt et test_nose_chiffres.py), dans ce cas les tests passent
tous, mais on peut aisment partir de l jouer un peu avec, pour voir son comportement. On
note aussi que nosetests ne se soucis pas de passer dun format (unittest, doctest) son propre
format de tests, le tout la vole.
Comme indiqu plus haut, il est possible dutiliser un setUp() et tearDown(), cest dire dap-
peler une fonction avant et aprs lexcution de chaque tests, utile lors de connexion une base
de donne par exemple, le tout ce fait avec un dcorateur (cest pas la seul possibilit) voici
lexemple tir de la documentation officiel.
@with_setup(setup, teardown)
def test_something():
" ... "
Pinocchio 3 est un plugin pour utiliser nose dune faon plus proche de RSpec 4 . Lide est de
rendre les tests un peu plus parlant, un peu de la faon dont on procde pour le BDD (Behavior
Driven Development), il est prfrable ed se reporter quelques documentations ce sujet si
vous ntes pas familier avec ce terme. Pinocchio ne semble pas fonctionner avec Python3, ou
du moins, je nai pas russi.
Installons le plugin :
def test_should_add_two_integer():
assert add(3, 4) == 7
def test_should_add_two_float():
assert add(2.01, 1.01) - 3.02 < 0.001
@raises(ValueError)
def test_should_raise_an_exception_with_two_string():
add(coin, pan)</code>
[nosetests]
verbosity=3
with-doctest=1
doctest-extension=txt
with-spec=1
spec-color=1
spec-doctests=1
On excute maintenant (dans lexemple, je force lusage pour python 2.7, naillant pas russi
lautre, et que Arch Linux fonctionne avec Python 3.x par dfaut)
$ nosetests-2.7
Chiffres
- add string
- add with float
3. https ://github.com/infrared/pinocchio
4. http ://en.wikipedia.org/wiki/RSpec
test_doctest_chiffres.txt
- add(2,5) returns 7
Nose chiffres
- add int
- add float
- add chaine
Spec chiffres
- should add two integer
- should add two float
- should raise an exception with two string
----------------------------------------------------------------------
Ran 10 tests in 0.040s
OK
Bien quil napparat pas ici, la sortie diffrencie les erreurs des bons tests avec les couleurs
rouge/vert, et rajoutant un gros (ERROR) si besoin.
Limportant remarquer, cest la lisibilit des tests pour spec, dont il est conseill de faire des
phrases qui ont un sens. Lexemple nest certainement pas le plus parlant pour ce genre de tests,
mais afin den garder une continuit, jai prfr garder toujours le mme exemple.
Pour donner un exemple de sortie comportant une erreur :
$ nosetests-2.7
Chiffres
- add string
- add with float
- addition
test_doctest_chiffres.txt
- add(2,5) returns 7
Nose chiffres
- add int
- add float
- add chaine
Spec chiffres
- should add two integer
- should add two float
- should raise an exception with two string (ERROR)
======================================================================
ERROR: test_spec_chiffres.test_should_raise_an_exception_with_two_string
----------------------------------------------------------------------
----------------------------------------------------------------------
Ran 10 tests in 0.041s
FAILED (errors=1)
Pinocchio peut rpondre un besoin de faire des tests un peu diffrent, si le plugin fait ce quon
lui demande, il ne semble pas tre trs activement maintenu. La raison est certainement que ce
plugin ne correspond plus vraiment un besoin, et que lapproche vu dans le prochain chapitre
est plus pertinante pour ce type de dveloppement. On y verra deux projets bien plus jour et
maintenu. Pour aller plus loin avec ce plugin, [une documentation][4] est disposition.
4.2.6 Conclusion
Ce billet devrait faire dcouvrir un outil fort pratique pour effectuer des tests unitaires avec
Python, et nous en avons vu quune petite partie de ses capacits. Il vient avec beaucoup de
fonctionnalits et des plugins tiers. Les Plugins nativement prvu pour Nose sont compatible
avec Python3, ce qui nest pas toujours le cas avec les plugins tiers.
Lettuce 5 est principallement un portage sur Python de Cucumber 6 . Cucumber a t crit par la
communaut de Ruby. Il permet une approche des tests et du dveloppement de faon BDD
Behavior Driven Developpement. Cet outil t port pour PHP (Behat 7 ) et pour Python. Cest
une pratique courante, comportant son lot dadeptes et de rcalcitrant..
Python dispose de deux outils semblable, Lettuce 3 et Freshen 8 . Les deux projets sont actif et
fonctionnel. Lettuce est un standalone, tandis que Freshen est un plugin de Nose (voir mes deux
prcdents articles consacr Nose). Bien que lutilisation est peut tre plus courante pour un
dveloppement web (Django par exemple) il est possible de sen servir en tout autre contexte.
5. https ://github.com/gabrielfalcao/lettuce
6. http ://cukes.info/
7. http ://behat.org/
8. https ://github.com/rlisagor/freshen
Un peu droutant la premire approche, mais les tests se range dans features.
Limportant comprendre, cest quon va se retrouver avec deux types de fichiers.
1. *.feature : Sont les scnarios rdig dans un anglais comprhensible par tous, dcrivant une
suite de comportement et de rsultat attendu. 2. step.py : Le code servant interprter les
scnarios.
Note : Il existe une coloration pour les features avec Vim, celui-ci le reconnais comme syntaxe
de Cucumber. Emacs et tout autre diteurs doivent certainement en faire de mme.
Le code suivant, est l surtout pour donner une ide de la syntaxe utilis pour lettuce, et du
rsultat obtenu. La documentation, que vous pouvez gnr en local comme vu plus haut, donne
un bon exemple de petit tutoriel reprenant un peu plus en dtail.
Pour mieux visualis, voici les rpertoires et fichier aprs criture du code :
$ tree
.
|-- __init__.py
|-- src
| |-- fact.py
| -- __init__.py
-- tests
|-- features
| |-- __init__.py
| |-- steps.py
| -- zero.feature
-- __init__.py
3 directories, 7 files
tests/features/zero.feature :
Scenario: Factorial of 0
Given I have the number 0
When I compute its factorial
Then I see the number 1
Scenario: Factorial of 1
Given I have the number 1
When I compute its factorial
Then I see the number 1
Scenario: Factorial of 2
Given I have the number 2
When I compute its factorial
Then I see the number 2
Scenario: Factorial of 3
Given I have the number 3
When I compute its factorial
Then I see the number 6
Scenario: Factorial of 4
Given I have the number 4
When I compute its factorial
Then I see the number 24
tests/features/steps.py :
src/fact.py :
def factorial(number):
number = int(number)
if (number == 0) or (number == 1):
return 1
else:
return number*factorial(number-1)
Pour excuter les tests, on se place dans tests, puis on excute lettuce.
$ lettuce
1 feature (1 passed)
5 scenarios (5 passed)
15 steps (15 passed)
4.3.3 Conclusion
Encore une fois, limportant ici est dcrire les tests avant den crire le code, de sassurer quil
ne passe pas, et ensuite dcrire le code correspondant afin que le test passe, principe du BDD.
Je suis aller assez vite sur le fonctionnement car je pense que si le rapide aperu vous intrigue,
vous aurez de toute faon bien assez envi dattaquer leur tutoriel, et creuser les quelques pistes
dont le billet tait sujet, avant tout la prsentation dun outil, que lexplication de son fonction-
nement.
Nous allons voir cette fois ci une faon de visualiser, par console ou html, la quantit de code
que couvre les tests. Lorsque le code et le nombre de tests correspondant grossi, il est utile de
trouver un moyen den faire un bilan, il existe pour cela la couverture de code 9 , srement plus
connu sous son nom anglais de code coverage. Cest ce que lon va regarder dans ce billet,
laide de Nose et de Coverage.
4.4.1 Installation
Si vous navez pas dj install Nose, faite le comme suit, ou consultez le chapitre sur nose
pour plus de dtails.
Pour installer coverage, la procdure est similaire, on peut dj not que coverage nest utilis
uniquement ici pour gnrer lhtml.
9. http ://fr.wikipedia.org/wiki/Couverture_de_code
Reprenons lexemple prcdant, une fonction add, auquel on rajoute une fonction multiply,
mais sans lui rajouter de tests. La premire fonction est test de diverses faons (doctest, unit-
test, framework test de nose), alors que la seconde nen comporte aucun.
Le fichier src/chiffres.py ressemble maintenant a :
$ nosetests-2.7 --with-coverage
test_add_string (test_chiffres.TestChiffres) ... ok
[.....]
test_spec_chiffres.test_should_raise_an_exception_with_two_string ... ok
OK
$ coverage report
Name Stmts Miss Cover
----------------------------------------------
src/chiffres 8 3 63%
tests/test_chiffres 13 1 92%
tests/test_nose_chiffres 8 0 100%
tests/test_spec_chiffres 8 0 100%
----------------------------------------------
TOTAL 37 4 89%
Il manque cependant lindication sur les lignes manquantes, sauf si je lai rat, mais il nest
peut tre pas utile de toujours lavoir.
Nose vient avec un argument --cover-html, cependant il est inutile de perdre du temps es-
sayer de le faire fonctionner, il comporte un bug depuis quelques annes, le dveloppeur le sait,
et il est probable que cela reste comme a encore longtemps, lauteur encourage lutilisation de
coverage qui produit de toute faon un meilleur html (selon ses dires).
Utiliser nosetests --with-coverage avant coverage lui sert de hook.
$ nosetests-2.7 with-coverage [...] $ coverage html
Le rsultat se trouvera dans le rpertoire htmlcov/ pour lequel il suffit par exemple de faire :
$ firefox htmlcov/index.html
4.5.1 Introduction
Watchr [1]_est un petit utilitaire pouvant tre trs pratique, il permet dautomatiser des tests
la sauvegarde de fichier. Bien quil soit crit en Ruby, il peut tre utilis avec nimporte quel
langage, je men suis servis par exemple pour Symfony2 (avec PHPUnit), mais plus rcemment
avec Python. Voici une liste davantage quon peut trouver ce genre doutil :
Entirement scriptable
Fonctionne avec des regex
Indpendant de tout langages ou frameworks (PHPunit, unittest, nosetests...)
Possibilit dutiliser la notification du systme dexploitation
Rutilisable dun projet lautre
Met disposition toutes les richesses de Ruby
4.5.2 Installation
Linstallation se fait simplement avec rubygem, ou directement avec le dpt git. Comme luti-
lisation de rubygem est plus propre, voici comment, en root :
Lide, et de faire correspondre des fichiers surveills une suite de commande, que lon
aura script dans un fichier de configuration au pralable. Ces rgles vont tre crites dans un
fichier appel watchrc.rb, et la syntaxe est celle du Ruby comme on peut le deviner. Le nom du
fichier est arbitraire et pass en argument.
Un premier exemple permet de vrifier la syntaxe du fichier chaque sauvegarde dans une
console spar. Certes des plugins de Vim on une approche similaire pour la syntaxe de Python,
mais comme on le vera, watchr offre dautres possibilits.
watchrc.rb :
On lance lapplication :
watchr watchrc.rb
Puis on commence diter un fichier Python dans le rpertoire src. Alors quune fois lap-
plication lanc, rien ne saffichait, et semblait attendre, ds lors quon sauvegarde le fichier, la
vrification syntaxique se fait automatiquement, avec la sortie en console.
On va automatiser les tests la sauvegarde de fichier, en surveillant dune part le rpertoire src
et tests. Dans un premier temps, on va au plus simple, cest dire que si lun est dclench,
on lance la totalit des tests. Cependant, dans un cas o le projet est plus gros, on ne souhaite
pas forcment lancer tout les tests, mais seulement ceux correspondant au fichier dit. Mais
chaque exemple en son temps.
watchrc.rb :
def unittest
system("nosetests")
end
watchr watchrc.rb
Comme dit plus haut, si la suite de tests prend trop de temps sexcuter, il est prfrable de
ne slectionner quun seul effectuer. Pour faire cela, a dpends des conventions utiliss par
chacun, pour ma part, je fais toujours correspondre :
src/mon_fichier.py
tests/test_mon_fichier.py
Lastuce sera donc de passer en argument le fichier de tests a excuter, si le fichier dit est le
fichier source, une substitution de la chaine sera requise, si le fichier dit est le fichier de test,
on peut lexcuter tel quel.
watchrc.rb :
Maintenant que le script fonctionne, rajoutons une notification, afin de ne pas avoir besoin de
garder la console sur le bureau courent, tout en aillant une indication. La notification est simple,
indiquant si le test passe ou non, en rajoutant une petite icon (ok vert, pas bon rouge) afin
de garder un reperd visuel, pour un gain de confort et de temps.
watchrc.rb :
Et le rsultat en image :
Il est vident que ce code peut tre adapt tout framework de test unitaire ou de test de syntaxe,
correspondant au divers langages, comme par exemple PHP avec phpunit. Il suffit dadapter la
commande et les structures de rpertoires selon le besoin.
titre informatif, je mets un exemple dune personne aillant rdig pour du php un billet
[2]_(en), et une configuration 11 assez complte pour Rails.
4.5.9 Conclusion
Watchr est un utilitaire bien pratique et surtout mallable, toujours bon connatre.