Retrouvez cet article dans : Linux Magazine 87
Python est un langage de programmation de haut niveau complet. Ses différentes librairies permettent de réaliser des projets divers et variés. Cet article vous présentera plusieurs d’entre elles. Vous apprendrez ainsi à manipuler des widgets GTK, à interpréter du HTML et à envoyer du courriel à partir du langage Python. Bien sûr, il est impossible de tout couvrir en quelques pages, mais vous aurez les bases pour commencer.
L’ensemble de l’article s’articule autour du même programme. Il s’agit d’une interface permettant de récupérer une page HTML sur le disque dur et de l’envoyer par courrier électronique. Les images devront être récupérées pour être envoyées par le même message. Le cahier des charges est désormais connu. Le programme complet peut être téléchargé sur le site internet du magazine (http://www.gnulinuxmag.com) dans la catégorie " Sources ".

Figure 1 : La fenêtre principale
NOTE: Où trouver de l’aide ?
Bien sûr, pour réaliser un programme tel que celui présenté ici, il vous faudra un minimum de documentation. Il est malheureusement difficile de vous donner les détails complets de toutes les possibilités du langage dans un article aussi court. Il faudrait pour cela un livre entier, au minimum ! Cependant, vous trouverez toutes les informations utiles sur le site de Python (http://www.python.org) et de PyGTK (http://www.pygtk.org). Si malgré tout, vous ne trouvez pas de réponse à votre problème, plusieurs communautés existent. Vous pourrez, par exemple, trouver de l’aide sur le canal #python-fr du réseau IRC Freenode. Le canal #python sur le même serveur est plus actif, mais il reste réservé aux anglophones.
Tout d’abord, nous aurons besoin d’utiliser la librairie PyGTK et de réaliser une interface. Il nous faut donc importer le tout.
|
|
import pygtk
pygtk.require("2.0")
import gtk |
La deuxième ligne permet de vérifier que nous avons la bonne version de GTK. Une fois fait, nous devons définir une classe qui correspondra à notre interface. L’ensemble des widgets (éléments graphiques) sont définis dans l’
__init__. Nous définissons ensuite une classe pour la fenêtre principale. Elle s’appellera
HtmlSender. Dans le code de l’
__init__ – la fonction appelée lors de l’instanciation – nous ajoutons les différents éléments graphiques. Nous commençons bien sûr avec la fenêtre principale.
|
|
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) |
Maintenant, il faut organiser les futurs widgets de l’application. En effet, une fenêtre ne contient qu’un seul élément. Nous emploierons alors une VBox, une boîte contenant plusieurs widgets placés verticalement. Le premier élément sera une table. Encore une fois, il s’agit d’un conteneur de widgets. Mais ce coup-ci, chacun des éléments sera inséré dans une grille. Le deuxième widget à insérer dans la VBox est une HBox. Cette dernière organise les widgets horizontalement, contrairement à la VBox.
|
|
# La fenêtre avec son agencement
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.connect("destroy", self.destroy)
self.window.set_title(“Envoyer un fichier HTML”)
self.mainBox = gtk.VBox(False, 10)
self.window.add(self.mainBox)
self.table = gtk.Table(3, 3, False)
self.table.set_col_spacings(20)
self.mainBox.pack_start(self.table, False, False, 0)
self.controleBox = gtk.HBox(False, 0)
self.mainBox.pack_end(self.controleBox, False, False, 0) |
Ensuite, nous définissons les différents widgets visibles de l’application. Il en existe plusieurs sortes comme les " labels " ou étiquettes, les " entries " ou zones de saisie de texte et, pour terminer, les boutons. Les premiers sont de simples textes affichés sans fioriture. Les seconds correspondent aux cadres où l’utilisateur peut saisir un texte. Enfin, les derniers ont un nom suffisamment explicite.
|
|
self.htmlLabel = gtk.Label("Fichier HTML")
self.table.attach(self.htmlLabel, 0, 1, 0, 1, xoptions=0)
self.htmlEntry = gtk.Entry()
self.table.attach(self.htmlEntry, 1, 2, 0, 1)
self.htmlButton = gtk.Button(„Parcourir...“)
self.htmlButton.connect(„clicked“, self.actionChooseHtml)
self.table.attach(self.htmlButton, 2, 3, 0, 1, xoptions=0) |
Nous remarquons ici que les boutons et les étiquettes s’instancient en prenant comme paramètre le texte qui sera affiché. Il ne faut bien sûr pas oublier d’ajouter les widgets à la grille. C’est ce que fait la méthode
attach. Cette dernière prend en paramètre le widget, l’ordonnée de la case de début, l’ordonnée de la case de fin ajoutée de un, l’abscisse de la case de début, l’abscisse de la case de fin ajoutée de un et des options complémentaires. En fait, dans une grille, un widget peut occuper plus d’une case. C’est ce qui rend cet appel aussi long. L’option donnée par
xoptions permet de changer le comportement au redimensionnement. Ainsi, les widgets ayant pour
xoptions 0 ne seront pas étirés sur l’axe des abscisses. Mais que fait la méthode
connect utilisée juste après sur les boutons ? En fait, lorsque l’utilisateur clique sur un de ces widgets particuliers, le développeur aimerait bien exécuter un bout de code. Le premier paramètre correspond au type de l’action. Ici, il s’agit de la chaîne
"clicked". L’action se lancera au clic de souris sur le bouton. Le deuxième, quant à lui, est une fonction. Remarquez qu’il n’y a pas le couple de parenthèses après la fonction. La raison est simple. Nous voulons donner la fonction elle-même. Avec les parenthèses, nous renverrions la valeur de résultat de l’appel de la fonction. Le même principe est appliqué à la fenêtre elle-même, avec l’action
"destroy". En effet, pour pouvoir quitter un programme GTK, il faut appeler explicitement la fonction suivante.
La dernière partie de cette fonction initiale permet juste d’afficher les widgets un à un. L’utilisateur peut ainsi voir et manipuler l’interface. Bien sûr, cette dernière ne réagit pas aux interactions pour le moment. Il faut alors ajouter les actions nécessaires. Tout d’abord, occupons-nous des boutons " Parcourir... ". Chacun d’eux est relié à une fonction. Cette dernière contient le code suivant :
|
|
def actionChooseHtml(self, widget, data = None):
if self.htmlfileselector is None:
self.htmlfileselector = gtk.FileSelection(“Choisissez le fichier HTML”)
self.htmlfileselector.set_select_multiple(False)
self.htmlfileselector.ok_button.connect("clicked", self.getHtmlFilename)
self.htmlfileselector.cancel_button.connect("clicked",
lambda w: self.htmlfileselector.destroy())
self.htmlfileselector.show() |
Le paramètre
widget de notre nouvelle fonction correspond à l’entité sur laquelle l’action a eu lieu. Cela permet d’utiliser le même code pour plusieurs événements. Ici, si le widget
htmlfileselector n’existe pas, nous remplaçons la valeur
None par un widget de type
gtk.FileSelection. Ce dernier est une boîte de dialogue de sélection de fichiers (voir la figure 2). Vous devez informer GTK du nom de la fenêtre lors de l’instanciation. Les deux boutons ok et cancel sont liés à des fonctions. Il est évidemment possible d’utiliser les lambdas du python. Ces fonctions anonymes permettent d’éviter une certaine lourdeur dans le code. La méthode appelée lors du clic sur le bouton
ok n’aura qu’à récupérer le nom du fichier via la méthode
get_filename du
widget htmlfileselector.
Le programme doit donc charger un fichier HTML – ce qui est relativement facile en Python – pour récupérer les images du disque dur et distantes. Ce dernier point est évidemment plus délicat. Comment pouvons-nous analyser le HTML pour en sortir l’emplacement des images ? Il existe bien la solution des expressions régulières, mais il faut alors gérer tous les cas. Le plus simple reste d’utiliser la classe
HTMLParser du module
htmllib. Pour notre cas, il suffit de dériver la classe.

Figure 2 : Le choix d’un fichier
Nous allons créer notre propre parser HTML très rapidement. La première difficulté est de préparer un formateur. En fait, nous n’avons pas besoin de générer de HTML, mais juste de récupérer les images. Ainsi, nous utiliserons le formateur NullFormatter. Nous utiliserons une expression régulière lors de la substitution des adresses des images.
La partie la plus intéressante de notre analyseur est bien sûr la fonction handle_image. Cette dernière est appelée pour le traitement de toutes les balises img. Parmi les arguments de la méthode, nous retrouvons la source. Cette dernière contient l’emplacement de l’image. Si l’adresse commence par http, l’image est sur un site web. Dans le cas contraire, elle est sur le disque dur. Dans le premier cas, nous récupérons l’image ainsi.
Nous allons créer notre propre parser HTML très rapidement. La première difficulté est de préparer un formateur. En fait, nous n’avons pas besoin de générer de HTML, mais juste de récupérer les images. Ainsi, nous utiliserons le formateur
NullFormatter. Nous utiliserons une expression régulière lors de la substitution des adresses des images.
La partie la plus intéressante de notre analyseur est bien sûr la fonction
handle_image. Cette dernière est appelée pour le traitement de toutes les balises
img. Parmi les arguments de la méthode, nous retrouvons la source. Cette dernière contient l’emplacement de l’image. Si l’adresse commence par
http, l’image est sur un site web. Dans le cas contraire, elle est sur le disque dur. Dans le premier cas, nous récupérons l’image ainsi.
|
|
image = urllib.urlopen(source).read() |
Dans le second cas, nous utilisons l’ouverture classique d’un fichier. À chaque image chargée, une variable
id est incrémentée. Cette dernière sert à la génération du CID ou Content ID. Cette propriété sera utile pour notre courriel. Des dictionnaires enregistrent les correspondances entre CID et image, et entre CID et URL. La plupart du code de cette fonction traite les erreurs. En cas de problème, un message est affiché à l’écran et l’image est ignorée.
NOTE: Les boîtes de messages
Il convient d’informer convenablement un utilisateur en cas d’erreur, de débogage ou tout simplement d’avertissement. Pour cela, la majorité des toolkits graphiques proposent les boîtes de message. Ainsi, GTK propose un widget spécialisé. Il s’agit de
gtk.MessageDialog. Il suffit d’instancier ce widget en renseignant le parent, le type de message, les boutons à afficher et le message à donner. Vous trouverez un exemple typique à la page suivante.
|
|
errorDialog = gtk.MessageDialog(parent = self.window, type=gtk.MESSAGE_ERROR,
buttons = gtk.BUTTONS_OK,
message_format = u"L’image %s n’a pas pu être téléchargée." % source)
error = errorDialog.run()
if error == gtk.RESPONSE_OK:
errorDialog.destroy() |
La méthode
run lance l’exécution du message. Le code est bloqué jusqu’à l’appui sur un bouton. Cette méthode renvoie la réponse donnée par l’utilisateur. Il est alors possible d’agir en conséquence.
L’envoi de mail n’est pas compliqué en soit. En fait, Python intègre une librairie prête à l’emploi. Mais avant tout, il faut lancer l’exécution du parser et transformer légèrement le code du fichier HTML.
|
|
htmlParser = Parser(self.window, path)
htmlParser.feed(html)
htmlParser.close()
images = htmlParser.getImages()
replacedImages = htmlParser.getReplacedImages()
charstoreplace = [‘.’, ‘?’, ‘*’, ‘{‘, ‘}’, ‘[‘, ‘]’, ‘$’, ‘^’]
for src in replacedImages:
fixed_src = src
for character in charstoreplace:
fixed_src = fixed_src.replace(character, "\\" + character)
regexp_src = ‘(?P<begin><img[^>]*src=)(?P<toreplace>["\’]’ + fixed_src + ‘)(?P<end>["\’])’
image_regexp = re.compile(regexp_src, re.IGNORECASE | re.DOTALL)
html = image_regexp.sub(r’\g<begin>"’ + replacedImages[src] + ‘"’, html) |
La première partie de ce code permet de lancer l’analyseur déjà étudié. La boucle
for utilisée ici, quant à elle, permet le remplacement des URL des images par les Content ID.
Pour cela, nous utilisons des expressions régulières. Tout d’abord, la variable
regexp_src contient la chaîne de définition des critères de recherche. Une fois que cette dernière est prête, il faut la compiler grâce à la fonction
re.compile.
Il ne reste alors qu’à exécuter la substitution à partir de l’expression ainsi préparée. Vous constatez également que certains caractères sont remplacés avant de générer l’expression régulière. Dans le cas contraire, les caractères spéciaux seraient interprétés alors qu’il ne le faut pas.
Par exemple, le point d’interrogation est très employé dans les URL. Il a une signification particulière dans les expressions régulières. Nous sommes donc dans l’obligation de l’inclure dans une séquence d’échappement.
|
|
email = MIMEMultipart(‘related’)
email[‘Subject’] = subject
email[‘From’] = fromAddress
email[‘To’] = contacts
email.preamble = ‘This is a multi-part message in MIME format.’
bodyPart = MIMEText(html, ‘html’)
email.attach(bodyPart)
for source in images:
imagePart = MIMEImage(images[source], name=source[4:])
imagePart.add_header(‘Content-ID’, ‘<’ + source[4:] + ‘>’)
email.attach(imagePart) |
Les quelques lignes ci-dessus gèrent à elles seules la génération du courriel avec l’ensemble des pièces jointes. Tout d’abord, le mail est instancié. Il s’agit d’un
MIMEMultiPart. Le message sera donc au format MIME et composé de plusieurs parties. Les paramètres de l’en-tête sont ensuite affectés comme si notre variable était un dictionnaire. Il faudra ensuite attacher les différentes parties avec la méthode
attach. Chaque partie est une autre entité MIME comme
MIMEText pour le corps ou
MIMEImage pour les images. La méthode
add_header pour ce dernier type nous permet de renseigner ici le Content ID. Ainsi, le client mail des destinataires affichera les images au bon endroit. Il ne reste qu’à envoyer notre message !
|
|
smtp = smtplib.SMTP()
if smtpPort is None:
smtp.connect(smtpHost)
else:
smtp.connect(smtpHost, smtpPort)
if smtpUser is not None and smtpPass is not None:
smtp.login(smtpUser, smtpPass)
for line in contactsList:
smtp.sendmail(fromAddress, line, email.as_string())
smtp.quit() |
Nous instancions alors une connexion SMTP. Les paramètres sont lus dans le fichier
conf.py par un
import (voir le code complet à la fin de l’article). Nous nous connectons alors via la méthode
connect, puis nous nous identifions au besoin avec
login. La méthode
sendmail, quant à elle, permet l’envoi du message au destinataire. Pour terminer, nous quittons le serveur et la dissection de ce code avec
smtp.quit().
Cet article montre à quel point il peut être rapide d’écrire des applications de haut niveau avec Python. Il reste toujours plus simple que Java, aussi bien dans l’apprentissage que dans l’utilisation. En effet, ce langage a été conçu afin d’éviter au développeur les entraves d’un langage classique. Cependant, certains modules nécessitent une compréhension parfaite avant de pouvoir réaliser quoi que ce soit. Les notions aperçues dans cet article seront étudiées plus en détail dans les prochains mois.
Retrouvez cet article dans : Linux Magazine 87