AviGNU - Mot-clé - PythonGroupe d'utilisateurs de logiciels libres du Grand Avignon2024-01-02T17:40:01+01:00Administrateururn:md5:8ed776652564792dba453bac492a5a6cDotclearComment forcer urllib3 à vérifier les requêtes HTTPSurn:md5:77745f3cea33bca8181fd0112e69ee9c2016-10-14T16:30:00+02:002016-10-14T15:48:35+02:00Olivier DuchateauProgrammationcertificatPythonSSL-TLSurllib3 <p><a href="https://github.com/shazow/urllib3">urllib3</a> est un module python extrêmement puissant, qui enrichit les différents modules relatifs aux réseaux présents dans la bibliothèque standard.</p>
<p>Il existe différentes manières pour effectuer une requête (il suffit de voir la <a href="https://urllib3.readthedocs.io/en/latest/user-guide.html">documentation</a>). Jusqu'ici je ne m'étais pas penché sur le cas du protocole <a href="https://fr.wikipedia.org/wiki/HyperText_Transfer_Protocol_Secure"><abbr title="HyperText Transfer Protocol Secure">HTTPS</abbr></a>.</p>
<p>Par défaut on obtient ce message :</p>
<blockquote><p>/usr/local/lib/python2.7/site-packages/urllib3/connectionpool.py:841: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: <a href="https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings" title="https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings">https://urllib3.readthedocs.io/en/l...</a></p>
<p>
InsecureRequestWarning)</p></blockquote>
<p>Même si l'on utilise la classe <a href="https://urllib3.readthedocs.io/en/latest/reference/index.html#urllib3.connectionpool.HTTPSConnectionPool">urllib3.connectionpool.HTTPSConnectionPool</a>, le <em>warning</em> est toujours présent.</p>
<p>Il faut faire appel à un autre module, <a href="https://certifi.io/en/latest/">certifi</a>, donc l'instance de <strong>HTTPSConnectionPool</strong> s'écrit alors :</p>
<pre>
[...]
conn = urllib3.connectionpool.HTTPSConnectionPool(host, cert_reqs='CERT_REQUIRED', ca_certs=certifi.where())
res = conn.request('GET', '/')
[...]
</pre>
<p>En annexe, vous trouverez un exemple complet.</p>
<p>On le lance de cette manière :</p>
<pre>
python npmjs.py
HTTPHeaderDict({'Content-Length': '3143', 'Via': '1.1 varnish', 'X-Cache': 'MISS', 'Accept-Ranges': 'bytes', 'X-Timer': 'S1476454061.911429,VS0,VE292', 'Vary': 'Accept-Encoding', 'X-Served-By': 'cache-fra1231-FRA', 'server': 'CouchDB/1.5.0 (Erlang OTP/R16B03)', 'Connection': 'keep-alive', 'etag': '"ETFO48QX32KIQMANPF4IXAOMS"', 'X-Cache-Hits': '0', 'Cache-Control': 'max-age=300', 'Date': 'Fri, 14 Oct 2016 14:07:42 GMT', 'Content-Type': 'application/json', 'Age': '0'})
</pre>Un logiciel au code source ouvert c'est bienurn:md5:5d6e5ac554bd2069407f55d80566f3f92015-09-12T21:18:00+02:002015-09-12T20:36:34+02:00Olivier DuchateauLogicielGLibGObjectGtk3PythonXfce <p>Certains d'entre vous le savent peut-être, mais je suis un utilisateur heureux du bureau <a href="http://www/xfce.org/">Xfce</a> <sup>[<a href="http://avignu.tuxfamily.org/index.php?post/2015/09/12/Un-logiciel-au-code-source-ouvert-c-est-bien#wiki-footnote-1" id="rev-wiki-footnote-1">1</a>]</sup>, je suis avec attention tout ce qui touche de près ou de loin à ce projet. Or des développeurs de la distribution <a href="http://xubuntu.org/">Xubuntu</a> ont développé un petit logiciel <sup>[<a href="http://avignu.tuxfamily.org/index.php?post/2015/09/12/Un-logiciel-au-code-source-ouvert-c-est-bien#wiki-footnote-2" id="rev-wiki-footnote-2">2</a>]</sup> permettant de sauvegarder la disposition du (ou des) panel(s). Il repose sur Gtk3 (grâce à la couche GObject Introspection) et Python3.</p>
<p>Avec la dernière version (<strong>1.0.1</strong>), des pré-réglages <sup>[<a href="http://avignu.tuxfamily.org/index.php?post/2015/09/12/Un-logiciel-au-code-source-ouvert-c-est-bien#wiki-footnote-3" id="rev-wiki-footnote-3">3</a>]</sup> sont déjà disponibles (je me tâte à les laisser dans le paquet pour FreeBSD), seulement quand on en sélectionne un, et par mégarde on clique sur le bouton de suppression on obtient ceci :</p>
<p><em>(Cliquez sur l'image pour la voir dans son format original)</em></p>
<p><a href="http://avignu.tuxfamily.org/news-custom/public/screenshots/xfpanel-switch-error.png" title="xfpanel-switch-error.png"><img src="http://avignu.tuxfamily.org/news-custom/public/screenshots/.xfpanel-switch-error_m.png" alt="xfpanel-switch-error.png" style="display:block; margin:0 auto;" title="Traceback, permission denied" /></a></p>
<p>Je trouve que ce n'est pas élégant. J'ai donc proposé un <em><a href="https://bugs.launchpad.net/xfpanel-switch/+bug/1494846">patch</a></em> qui vérifie si l'utilisateur est le propriétaire de l'archive. Si ce n'est pas le cas, une boîte de dialogue apparaît.</p>
<p><a href="http://avignu.tuxfamily.org/news-custom/public/screenshots/xfpanel-switch-rewrite-error.png" title="xfpanel-switch-rewrite-error.png"><img src="http://avignu.tuxfamily.org/news-custom/public/screenshots/.xfpanel-switch-rewrite-error_m.png" alt="xfpanel-switch-rewrite-error.png" style="display:block; margin:0 auto;" title="Apparition d'une boîte de dialogue" /></a></p>
<p>C'est mieux non ?</p>
<div class="footnotes"><h4>Notes</h4>
<p>[<a href="http://avignu.tuxfamily.org/index.php?post/2015/09/12/Un-logiciel-au-code-source-ouvert-c-est-bien#rev-wiki-footnote-1" id="wiki-footnote-1">1</a>] Sous <a href="http://www.freebsd.org/">FreeBSD</a> bien évidemment.</p>
<p>[<a href="http://avignu.tuxfamily.org/index.php?post/2015/09/12/Un-logiciel-au-code-source-ouvert-c-est-bien#rev-wiki-footnote-2" id="wiki-footnote-2">2</a>] <a href="https://launchpad.net/xfpanel-switch">Xfpanel-switch</a></p>
<p>[<a href="http://avignu.tuxfamily.org/index.php?post/2015/09/12/Un-logiciel-au-code-source-ouvert-c-est-bien#rev-wiki-footnote-3" id="wiki-footnote-3">3</a>] Pour Xubuntu et Debian</p></div>
Monitorer un média amovible sous FreeBSD ou DragonFlyBSDurn:md5:6568e3bc539d49878a975a7758d301a42014-04-16T23:12:00+02:002014-04-16T22:51:30+02:00Olivier DuchateauProgrammationDragonFlyFreeBSDkqueue-keventPythonsocket <p>Quant on est sous GNU/Linux, il existe <a href="http://www.freedesktop.org/software/systemd/libudev/">udev</a>, très largement utilisé sur ce système d'exploitation (<acronym title="Système d'Exploitation">SE</acronym>), mais lorsque l'on utilise un système <a href="http://fr.wikipedia.org/wiki/Berkeley_Software_Distribution">BSD</a>, et en particulier <a href="http://www.freebsd.org/">FreeBSD</a> il nous est impossible d'utiliser cette bibliothèque. Cependant les développeurs de FreeBSD ont développé un outil similaire <a href="http://www.freebsd.org/cgi/man.cgi?query=devd&sektion=8&manpath=FreeBSD+10.0-RELEASE">devd(8)</a>.</p>
<p>Il est accessible via un <a href="http://fr.wikipedia.org/wiki/Berkeley_sockets#Socket_unix">socket unix</a>.</p>
<p>J'ai voulu voir comment l'utiliser grâce aux systèmes de notifications du noyau <a href="http://www.freebsd.org/cgi/man.cgi?query=kqueue&manpath=FreeBSD+10.0-RELEASE">kqueue(2)/kevent(2)</a>, avec le langage <a href="http://www.python.org/">Python</a>.</p>
<p>Je détaille un peu le script, tout d'abord nous allons initialiser un <em>socket</em> (le programme va fonctionner comme un client).</p>
<pre>
[...]
s_file = os.path.join("/var", "run", "devd.pipe")
# Create new socket object
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect(s_file)
# Return the socket's file descriptor
fd = s.fileno()
[...]
</pre>
<p><code>fd</code> (<em>file descriptor</em>) est nécessaire pour <code>kevent</code>.</p>
<p>Les fonctions utilisées par kqueue/kevent sous Python sont accessible via le module <a href="https://docs.python.org/2.7/library/select.html">select</a>. Nous allons uniquement lire les données qui transitent par <code>/var/run/devd.pipe</code>.</p>
<p>Ci-dessous l'initialisation du mécanisme de notification.</p>
<pre>
[...]
# Kernel queue object
kq = select.kqueue()
# Event to monitor (only attach)
event = [select.kevent(fd, filter=select.KQ_FILTER_READ, flags=select.KQ_EV_ADD),]
# Initialize kevent structure (like EV_SET macro in sys/event.h)
ev_set = kq.control(event, 0, 0)
[...]
</pre>
<p>Nous pouvons « boucler » pour afficher les résultats quand un périphérique est branché. <strong>Un truc magique avec <code>kqueue/kevent</code> on a directement accès à la taille du tampon à lire.</strong></p>
<pre>
[...]
for event in events:
# Display data from socket (event.data is size to read)
print s.recv(event.data)
[...]
</pre>
<p>Voici le résultat lorsqu'une clé USB est branchée.</p>
<pre>
!system=DEVFS subsystem=CDEV type=CREATE cdev=usb/2.2.0
!system=DEVFS subsystem=CDEV type=CREATE cdev=ugen2.2
!system=DEVFS subsystem=CDEV type=CREATE cdev=usb/2.2.1
!system=DEVFS subsystem=CDEV type=CREATE cdev=usb/2.2.2
!system=USB subsystem=DEVICE type=ATTACH ugen=ugen2.2 cdev=ugen2.2 vendor=0x0930
product=0x653d devclass=0x00 devsubclass=0x00 sernum="0B4085607142DAD4" release
=0x0100 mode=host port=6 parent=ugen2.1
!system=USB subsystem=INTERFACE type=ATTACH ugen=ugen2.2 cdev=ugen2.2 vendor=0x0
930 product=0x653d devclass=0x00 devsubclass=0x00 sernum="0B4085607142DAD4" rele
ase=0x0100 mode=host interface=0 endpoints=2 intclass=0x08 intsubclass=0x06 intp
rotocol=0x50
+umass0 at bus=1 hubaddr=6 port=2 devaddr=2 interface=0 vendor=0x0930 product=0x
653d devclass=0x00 devsubclass=0x00 sernum="0B4085607142DAD4" release=0x0100 mod
e=host intclass=0x08 intsubclass=0x06 intprotocol=0x50 on uhub2
!system=DEVFS subsystem=CDEV type=CREATE cdev=pass2
!system=DEVFS subsystem=CDEV type=CREATE cdev=da0
!system=DEVFS subsystem=CDEV type=CREATE cdev=da0s1
</pre>
<p>La dernière ligne est intéressante, car c'est le nom de la partition que l'on pourra « monter » sur le système.</p>Aperçu du widget, GtkComboBoxurn:md5:8b61fedf749c7f122fea577b3ce2ba6f2013-08-12T13:59:00+02:002013-08-12T14:41:00+02:00Olivier DuchateauProgrammationGtkHowtoPyGObjectPython<p>Nous continuons notre série de billets consacrés aux principaux <em>widgets</em> sous <strong><a href="http://fr.wikipedia.org/wiki/GTK%2B">Gtk</a></strong>.</p>
<p>Cette fois-ci, il s'agit de <a href="https://developer.gnome.org/gtk3/stable/GtkComboBox.html">GtkComboBox</a>, cet élément permet de créer une liste déroulante.</p> <p>Dans le <a href="http://avignu.tuxfamily.org/index.php?post/2013/08/05/D%C3%A9couverte-du-widget-GtkEntry-avec-Python">précédent billet</a> <sup>[<a href="http://avignu.tuxfamily.org/index.php?post/2013/08/12/Aper%C3%A7u-du-widget%2C-GtkComboBox#pnote-79-1" id="rev-pnote-79-1">1</a>]</sup> les différents éléments étaient placés dans une « <a href="https://developer.gnome.org/gtk3/stable/GtkGrid.html">grille</a> », je vais opté pour l'agencement dans une « <a href="https://developer.gnome.org/gtk3/stable/GtkBox.html">boîte</a> ».</p>
<p>Si l'on lit la description consacré à ce <em>widget</em>, on constate qu'il en existe un autre.</p>
<blockquote><p>[...]</p>
<p>
For a simple list of textual choices, the model-view API of GtkComboBox can be a bit overwhelming. In this case, <a href="https://developer.gnome.org/gtk3/stable/GtkComboBoxText.html">GtkComboBoxText</a> offers a simple alternative. [...]</p></blockquote>
<p>Nous allons donc commencé par celui-là.</p>
<p><img src="http://avignu.tuxfamily.org/news-custom/public/exemples-gtk/exemple-06.png" alt="Exemple 06 GtkComboBoxText" style="display:block; margin:0 auto;" title="Exemple 06 GtkComboBoxText" /></p>
<p>Il n'y a rien de particulèrement compliqué, on initialise <a href="https://developer.gnome.org/gtk3/stable/GtkComboBoxText.html">Gtk.ComboBoxText()</a>, auquel on ajoute des éléments grâce à la méthode <a href="https://developer.gnome.org/gtk3/stable/GtkComboBoxText.html#gtk-combo-box-text-append-text">append_text()</a> <sup>[<a href="http://avignu.tuxfamily.org/index.php?post/2013/08/12/Aper%C3%A7u-du-widget%2C-GtkComboBox#pnote-79-2" id="rev-pnote-79-2">2</a>]</sup>.</p>
<p>Pour récupérer un des éléments de la liste déroulante, il faut lui associé un signal, <a href="https://developer.gnome.org/gtk3/stable/GtkComboBox.html#GtkComboBox-changed">changed</a> dans notre cas.</p>
<p><em>La sélection affiche l'élément dans la console.</em></p>
<p><img src="http://avignu.tuxfamily.org/news-custom/public/exemples-gtk/exemple-06_b.png" alt="Affiche le résultat d'un des élément de GtkComboBoxText" style="display:block; margin:0 auto;" title="Affiche le résultat d'un des élément de GtkComboBoxText" /></p>
<p>Dans l'exemple suivant, on a simplement rajouter un bouton (<a href="https://developer.gnome.org/gtk3/stable/GtkButton.html">GtkButton</a>) associé au signal, <a href="https://developer.gnome.org/gtk3/stable/GtkButton.html#GtkButton-clicked">clicked</a>.</p>
<p><img src="http://avignu.tuxfamily.org/news-custom/public/exemples-gtk/exemple-07.png" alt="Exemple 07 GtkComboBoxText et GtkButton" style="display:block; margin:0 auto;" title="Exemple 07 GtkComboBoxText et GtkButton" /></p>
<p>Dans le troisième exemple, on va afficher une info-bulle (<a href="https://developer.gnome.org/gtk3/stable/GtkTooltip.html">GtkTooltip</a>) quand rien n'est sélectionné (et uniquement dans ce cas).</p>
<p>L'affichage du texte dans la console, se fait grâce à la fonction <a href="https://developer.gnome.org/gtk3/stable/GtkComboBoxText.html#gtk-combo-box-text-get-active-text">get_active_text()</a>. Elle renvoie <code>None</code> si rien n'est sélectionné, donc on peut jouer sur cette condition pour afficher ou non l'info-bulle.</p>
<p><img src="http://avignu.tuxfamily.org/news-custom/public/exemples-gtk/exemple-08.png" alt="Exemple 08 GtkComboBoxText, GtkButton et GtkTooltip" style="display:block; margin:0 auto;" title="Exemple 08 GtkComboBoxText, GtkButton et GtkTooltip" /></p>
<p>Voilà nous avons fait le tours de <a href="https://developer.gnome.org/gtk3/stable/GtkComboBoxText.html">GtkComboBoxText</a>, il nous reste à voir son « frère » <a href="https://developer.gnome.org/gtk3/stable/GtkComboBox.html">GtkComboBox</a>.</p>
<p>Dans un premier temps on doit définir un modèle <a href="https://developer.gnome.org/gtk3/stable/GtkListStore.html">GtkListStore</a>. Ensuite la liste déroulante est définie grâce à la fonction <a href="https://developer.gnome.org/gtk3/stable/GtkComboBox.html#gtk-combo-box-new-with-model">new_with_model()</a>. L'affichage de chaque élément se fait grâce à <a href="https://developer.gnome.org/gtk3/stable/GtkCellRendererText.html">GtkCellRendererText</a>.</p>
<p>Pour récupérer l'élément sélectionné, il faut utiliser la fonction <a href="https://developer.gnome.org/gtk3/stable/GtkComboBox.html#gtk-combo-box-get-active">get_active()</a>. Elle nous renvoie uniquement un numéro (celui de la ligne). Comme notre modèle « s'interface » avec <a href="https://developer.gnome.org/gtk3/stable/GtkTreeModel.html">GtkTreeModel</a>, on peut donc utiliser ces méthodes, en particulier <a href="https://developer.gnome.org/gtk3/stable/GtkTreeModel.html#gtk-tree-model-get-value">get_value()</a>.</p>
<p><img src="http://avignu.tuxfamily.org/news-custom/public/exemples-gtk/exemple-09.png" alt="Exemple 09 GtkComboBox et GtkButton" style="display:block; margin:0 auto;" title="Exemple 09 GtkComboBox et GtkButton" /></p>
<p>Dans notre dernier exemple, on affiche uniquement une info-bulle, quand rien n'est sélectionné.</p>
<p><img src="http://avignu.tuxfamily.org/news-custom/public/exemples-gtk/exemple-10.png" alt="Exemple 10 GtkComboBox, GtkButton et GtkTooltip" style="display:block; margin:0 auto;" title="Exemple 10 GtkComboBox, GtkButton et GtkTooltip" /></p>
<div class="footnotes"><h4 class="footnotes-title">Notes</h4>
<p>[<a href="http://avignu.tuxfamily.org/index.php?post/2013/08/12/Aper%C3%A7u-du-widget%2C-GtkComboBox#rev-pnote-79-1" id="pnote-79-1">1</a>] Consacré à l'élément <a href="https://developer.gnome.org/gtk3/stable/GtkEntry.html">GtkEntry</a>.</p>
<p>[<a href="http://avignu.tuxfamily.org/index.php?post/2013/08/12/Aper%C3%A7u-du-widget%2C-GtkComboBox#rev-pnote-79-2" id="pnote-79-2">2</a>] C'est dans la fonction <code>create_rows()</code></p></div>
Découverte du widget GtkEntry avec Pythonurn:md5:42347de222bbb555e914d9c8ff9747882013-08-05T17:23:00+02:002013-08-05T16:33:47+02:00Olivier DuchateauProgrammationGtkHowtoPyGObjectPython<p>Dans ce billet nous allons découvrir le <em>widget</em>, <a href="https://developer.gnome.org/gtk3/stable/GtkEntry.html">GtkEntry</a>.</p>
<p>Cet élément graphique permet à un utilisateur de rentrer du texte (qui pourra, par la suite être traité).</p> <p>Notre premier exemple est très simple. Il s'agit d'afficher dans une « fenêtre mère » (<a href="https://developer.gnome.org/gtk3/stable/GtkWindow.html">GtkWindow</a>) un champ, dans lequel on peut y écrire (<em>Cf.</em> <code>entry-01.py</code>).</p>
<p>Si l'on valide en pressant la touche <strong>Entrée</strong>, le texte s'affiche dans la console.</p>
<p><img src="http://avignu.tuxfamily.org/news-custom/public/exemples-gtk/exemple-01.png" alt="Exemple 01 GtkEntry" style="display:block; margin:0 auto;" title="Exemple 01 GtkEntry" /></p>
<p>Pour l'exécuter, dans une console tapez simplement</p>
<pre>
olivier@chouffe:~ $ python entry-01.py
</pre>
<p>Dans l'exemple suivant, nous allons rajouté une nouvelle fonctionnalité, à savoir, permettre la possibilité de supprimer du texte précédemment entré.</p>
<p>Pour cela, on va utilisé la propriété <a href="https://developer.gnome.org/gtk3/stable/GtkEntry.html#GtkEntry--secondary-icon-stock">secondary-icon-stock</a> (affiche une icône) à laquelle on connecte un évènement <a href="https://developer.gnome.org/gtk3/stable/GtkEntry.html#GtkEntry-icon-press">icon-press</a> (<em>Cf.</em> <code>entry-02.py</code>).</p>
<p><img src="http://avignu.tuxfamily.org/news-custom/public/exemples-gtk/exemple-02.png" alt="Exemple 02 GtkEntry" style="display:block; margin:0 auto;" title="Exemple 02 GtkEntry" /></p>
<p>Lorsque l'on clique sur l'icône située à gauche, le contenu du champ est supprimé.</p>
<p><img src="http://avignu.tuxfamily.org/news-custom/public/exemples-gtk/exemple-02_b.png" alt="Exemple 02 GtkEntry vide" style="display:block; margin:0 auto;" title="Exemple 02 GtkEntry vide" /></p>
<p>Dans l'exemple suivant, nous allons rajouté un <a href="https://developer.gnome.org/gtk3/stable/GtkButton.html">bouton</a> (<strong>GtkButton</strong>) pour fermer la fenêtre. Il sera associé à l'évènement <a href="https://developer.gnome.org/gtk3/stable/GtkButton.html#GtkButton-clicked">clicked</a> (<em>Cf.</em> <code>entry-03.py</code>).</p>
<p><img src="http://avignu.tuxfamily.org/news-custom/public/exemples-gtk/exemple-03.png" alt="Exemple 03 GtkEntry avec GtkButton" style="display:block; margin:0 auto;" title="Exemple 03 GtkEntry avec GtkButton" /></p>
<p>Dans ce quatrième exemple, nous allons rajouter un <em>widget</em> (<a href="https://developer.gnome.org/gtk3/stable/GtkLabel.html">GtkLabel</a>) devant le formulaire (<em>Cf.</em> <code>entry-04.py</code>).</p>
<p><img src="http://avignu.tuxfamily.org/news-custom/public/exemples-gtk/exemple-04.png" alt="Exemple 04 GtkEntry avec GtkButton et GtkLabel" style="display:block; margin:0 auto;" title="Exemple 04 GtkEntry avec GtkButton et GtkLabel" /></p>
<p>Dans notre exemple le contenut du <em>label</em> est aligné à gauche, mais on peut en choisir un autre parmi la <a href="https://developer.gnome.org/gtk3/stable/gtk3-Standard-Enumerations.html#GtkJustification">liste</a> :</p>
<ul>
<li><code>Gtk.Justification.LEFT</code></li>
<li><code>Gtk.Justification.RIGHT</code></li>
<li><code>Gtk.Justification.CENTER</code></li>
<li><code>Gtk.Justification.FILL</code></li>
</ul>
<p>En plus de jouer sur l'alignement du text, on peut également lui appliquer un style. Il faut pour cela utiliser la bibliothèque <a href="https://developer.gnome.org/pango/stable/PangoMarkupFormat.html">Pango</a> (<em>Cf.</em> <code>entry-04_b.py</code>).</p>
<p><img src="http://avignu.tuxfamily.org/news-custom/public/exemples-gtk/exemple-04_b.png" alt="Exemple 04 GtkEntry avec GtkButton, GtkLabel et Pango markup" style="display:block; margin:0 auto;" title="Exemple 04 GtkEntry avec GtkButton, GtkLabel et Pango markup" /></p>
<p>Dans le dernier exemple, nous allons rajouter une info-bulle (<a href="https://developer.gnome.org/gtk3/stable/GtkTooltip.html">GtkTooltip</a>) au survole du pointeur de la souris dans le champ (<strong>GtkEntry</strong>) quand il n'y a aucun texte (<em>Cf.</em> <code>entry-05.py</code>).</p>
<p><img src="http://avignu.tuxfamily.org/news-custom/public/exemples-gtk/exemple-05.png" alt="Exemple 05 GtkEntry avec GtkTooltip" style="display:block; margin:0 auto;" title="Exemple 05 GtkEntry avec GtkTooltip" /></p>Transformer des dates avec le module python-dateutilurn:md5:8d35172976ae5b10b82daff678d5c53a2013-02-13T21:10:00+01:002013-02-14T20:46:24+01:00Olivier DuchateauProgrammationPythonTips<p>Récemment, j'ai eu besoin de transformer des dates écrites dans ce format : <code>Wed, 13 Feb 2013 05:34:15 +0100</code> en quelque chose de plus « normé », <a href="http://fr.wikipedia.org/wiki/ISO_8601">ISO 8601</a>.</p> <p>J'ai obtenu ce que je souhaitais grâce au module <a href="http://pypi.python.org/pypi/python-dateutil">python-dateutil</a>, et je tiens à vous en faire profiter grâce à un exemple.</p>
<p>Au départ j'ai des dates sous cette forme <strong>Wed, 13 Feb 2013 05:34:15 +0100</strong> que l'on peut schématiser de cette façon <code>%a, %d %b %Y %H:%M:%S %z</code> <sup>[<a href="http://avignu.tuxfamily.org/index.php?post/2013/02/13/Transformer-des-dates-avec-le-module-python-dateutil#pnote-67-1" id="rev-pnote-67-1">1</a>]</sup>. Je voulais quelque chose proche de la norme iso 8601, soit ça : <code>%Y-%m-%dT%H:%M</code> ou pour reprendre l'exemple, <strong>2013-02-13T05:34</strong>.</p>
<p>Voilà ce que cela donne dans une session interactive Python (dans un programme, on peut créer une fonction).</p>
<pre>olivier@tuborg:~ $ python
Python 2.7.3 (default, Jun 25 2012, 07:16:42)
[GCC 4.2.2 20070831 prerelease [FreeBSD]] on freebsd8
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime
>>> import dateutil.parser
>>>
>>> s = 'Wed, 13 Feb 2013 05:34:15 +0100'
>>> dt = dateutil.parser.parse(s)
>>> dt
datetime.datetime(2013, 2, 13, 5, 34, 15, tzinfo=tzoffset(None, 3600))
</pre>
<p>Avant d'aller plus je vais expliquer les grandes lignes du programme.</p>
<ul>
<li>Les lignes commençant par <strong>import ....</strong> permettent de charger les modules qui vont nous être utiles</li>
<li><strong>s</strong> est une variable, elle représente la date à transformer</li>
<li><strong>dt</strong> est un objet <strong>datetime.datetime</strong> que l'on va pouvoir manipuler grâce aux fonctions du module <code>datetime</code></li>
</ul>
<p>On peut supprimer l'information du fuseau horaire.</p>
<pre>
>>> dt = dateutil.parser.parse(s, ignoretz=True)
>>> dt
datetime.datetime(2013, 2, 13, 5, 34, 15)
</pre>
<p>Il nous reste plus qu'à « formater » notre objet <code>datetime.datetime</code>.</p>
<pre>
>>> dt.strftime('%Y-%m-%dT%H:%M')
'2013-02-13T05:34'
>>>
</pre>
<div class="footnotes"><h4>Note</h4>
<p>[<a href="http://avignu.tuxfamily.org/index.php?post/2013/02/13/Transformer-des-dates-avec-le-module-python-dateutil#rev-pnote-67-1" id="pnote-67-1">1</a>] Si l'on se réferre au <a href="http://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior">tableau</a> pour le formatage des fonctions <code>strftime()</code> et <code>strptime()</code>.</p></div>
Comment exclure un module avec setuptools ?urn:md5:d432580fa662e61bf2ca4e13bf0ebbea2012-01-31T16:54:00+01:002012-01-31T17:38:39+01:00Olivier DuchateauProgrammationBSDLinuxPythonsetuptoolsTips <p>Pour ceux ou celles, qui maintiennent (ou on l'intention de créer) des paquets <a href="http://python.org/">Python</a> pour leur distribution favorite, je vous livre une astuce si vous voulez exclure un, ou plusieurs modules.</p>
<p>Dans mon cas, je ne voulais pas installer le dossier <code>tests/</code> (il dépend bien souvent de la bibliothèque <a href="http://readthedocs.org/docs/nose/en/latest/">nose</a>), lors de la création de mon <em>package</em>.</p>
<p>Nous allons utiliser la fonction <strong><a href="http://packages.python.org/distribute/setuptools.html#using-find-packages">find_packages()</a></strong> du module <strong>setuptools</strong>.</p>
<p>Voici ce que nous dit la <em><a href="http://www.python.org/dev/peps/pep-0257/">docstring</a></em> :</p>
<blockquote><p>[...]</p>
<p>
<strong>FUNCTIONS</strong></p>
<p>
<strong>find_packages</strong>(where='.', exclude=())
Return a list all Python packages found within directory 'where'</p>
<p>
'where' should be supplied as a "cross-platform" (i.e. URL-style) path; it
will be converted to the appropriate local path syntax. 'exclude' is a
sequence of package names to exclude; '*' can be used as a wildcard in the
names, such that 'foo.*' will exclude all subpackages of 'foo' (but not
'foo' itself).</p>
<p>
[...]</p></blockquote>
<p>La documentation :</p>
<blockquote><p>[...]</p>
<p>
find_packages() takes a source directory, and a list of package names or patterns to exclude. If omitted, the source directory defaults to the same directory as the setup script.</p>
<p>
Exclusion patterns are package names, optionally including wildcards.</p>
<p>
[...]</p></blockquote>
<p>Un exemple va nous aider à bien comprendre (on se place dans les « mêmes conditions », qu'au moment de la création d'un paquet) :</p>
<pre>olivier@bornem:~ $ tar -xzf /usr/ports/distfiles/waitress-0.8.tar.gz
olivier@bornem:~ $ cd waitress-0.8/
olivier@bornem:~/waitress-0.8 $ python
Python 2.7.2 (default, Jul 4 2011, 20:20:01)
[GCC 4.2.1 20070719 [FreeBSD]] on freebsd8
Type "help", "copyright", "credits" or "license" for more information.
>>> from setuptools import find_packages
>>> l = find_packages(where='.')
>>> l
['waitress', 'waitress.tests', 'waitress.tests.fixtureapps']
>>>
</pre>
<p>on obient une liste, de tous les dossiers contenant un fichier <code>__init__.py</code>.</p>
<p>Si l'on ne souhaite pas installer le répertoire <code>waitress/tests</code>, il faut le mentionner :</p>
<pre>>>> l = find_packages(where='.', exclude=['waitress.tests'])
>>> l
['waitress', 'waitress.tests.fixtureapps']
>>>
</pre>
<p><code>waitress/tests/fixtureapps/</code> est toujours présent (la compilation échouera, car le module <strong>tests</strong> sera absent). Il faut également le préciser tous les sous-dossiers</p>
<pre>>>> l = find_packages(where='.', exclude=['waitress.tests', 'waitress.tests.fixtureapps'])
>>> l
['waitress']
>>>
</pre>
<p>On peut utiliser un joker (ou <em>wildcard</em>).</p>
<pre>>>> l = find_packages(where='.', exclude=['waitress.test*'])
>>> l
['waitress']
>>>
</pre>
<p>Le plus gros du travail est fait, il ne reste plus qu'à rechercher un fichier <code>MANIFEST.in</code>, ou <code>SOURCES.txt</code>, et de supprimer les lignes correspondantes à ce module (on crée alors un patch).</p>
<p>Une dernière chose, la fonction <strong>find_packages()</strong> se trouve dans le fichier <code>setup.py</code>, on va également le <em>patcher</em>.</p>Exécuter une application WSGI avec systemd sous openSUSEurn:md5:304f279f3c1d30ef82be67f722f84e632012-01-29T20:42:00+01:002012-01-29T21:36:42+01:00Olivier DuchateauDocumentationhginitMercurialNginxopenSUSEPythonSystemdWSGI <p>Ce billet est une « mise à jour » du <a href="http://avignu.tuxfamily.org/index.php?post/2012/01/23/D%C3%A9ployer-Mercurial-%28hg%29-%C2%AB-derri%C3%A8re-%C2%BB-un-serveur-Web-%28Nginx%29-sous-openSUSE">précédent</a>, concernant le déploiement de <a href="http://mercurial.selenic.com/">Mercurial</a>, sous forme d'application <acronym title="Web Server Gateway Interface">WSGI</acronym>.</p>
<p>Le fait de passer par le script <code>/etc/init.d/after.local</code>, pour lancer une telle application, ne m'a pas entièrement satisfait. C'est pourquoi j'ai décidé de me repencher sur ce point.</p>
<p>À la fin de l'article, j'évoqué, <a href="http://gunicorn.org/">Gunicorn</a>, comme serveur WSGI, en proposant même un exemple de fichier <code>.service</code>. Je me suis donc inspiré de celui-ci pour en créer un.</p>
<p>Le but c'est de pouvoir exécuter le script, <code>hgweb.wsgi</code> au démarrage.</p>
<p>Je vous propose donc, <code>wsgi-hg.service</code>. Il permet de lancer (ou d'arrêter) notre script WSGI. Il n'y a rien de particulier, à part la condition <strong>ConditionPathExistsGlob</strong>, qui me sert à tester si un fichier <code>.wsgi</code> (en réalité <code>hgweb.wsgi</code>) est présent sur le serveur, si c'est le cas, le service pourra être lancé.</p>
<p>Il faut bien sur, avoir correctement configuré son serveur Web. Par exemple pour <a href="http://nginx.org/">Nginx</a> :</p>
<pre>[...]
# Subdomain settings
#
# Mercurial
#
server {
listen 80;
server_name hg.errements.net;
access_log /var/log/nginx/access-hg.log;
location / {
root /srv/www/htdocs/vhosts/hg;
autoindex off;
proxy_path http://127.0.0.1:8500;
proxy_set_header Host $host;
}
}
[...]</pre>
<p>Mais on peut aller encore plus loin, actuellement dans notre script wsgi, le socket réseau (le port, et l'adresse IP) sont codés en « dur ». On pourrait les passer en paramètre. Il faut pour cela utiliser le module <a href="http://docs.python.org/dev/library/argparse.html">argparse</a>.</p>
<pre>olivier@bornem:~ $ python hgweb-opts.wsgi -h
usage: hgweb-opts.wsgi [-h] host port
positional arguments:
host Add IP address
port Add port number
optional arguments:
-h, --help show this help message and exit
olivier@bornem:~ $ </pre>
<p>On peut voir que deux paramètres sont obligatoires, l'ordre à une importance.</p>
<ul>
<li><em>host</em>, par exemple 127.0.0.1</li>
<li><em>port</em>, par exemple 8500</li>
</ul>
<pre>olivier@bornem:~ $ python hgweb-opts.wsgi 127.0.0.1 8500
serving on http://127.0.0.1:8500
</pre>
<p>On peut maintenant adapter le fichier <code>wsgi-hg.service</code>, pour pouvoir passer ces paramètres à la ligne <strong>ExecStart</strong>.</p>
<p>Voici la ligne à copier.</p>
<pre>[...]
ExecStart=/usr/bin/python2.7 /srv/www/htdocs/vhosts/hg/hgweb-opts.wsgi 127.0.0.1 8500
[...]</pre>
<p>Pour lancer le service, on place ce fichier dans <code>/etc/systemd/system/default.target.wants/</code> (ou <code>/lib/systemd/system/</code>).</p>
<pre>root@bornem:~ # systemctl start wsgi-hg.service
root@bornem:~ # systemctl status wsgi-hg.service
wsgi-hg.service - Starts WSGI script (mercurial)
Loaded: loaded (/lib/systemd/system/wsgi-hg.service; disabled)
Active: active (running) since Sun, 29 Jan 2012 21:26:45 +0100; 38s ago
Process: 17745 ExecStartPre=/bin/echo Starting WSGI script for mercurial (code=exited, status=0/SUCCESS)
Main PID: 17747 (python2.7)
CGroup: name=systemd:/system/wsgi-hg.service
└ 17747 /usr/bin/python2.7 /srv/www/htdocs/vhosts/hg/hgweb...
root@bornem:~ # </pre>Déployer Mercurial (hg) « derrière » un serveur Web (Nginx) sous openSUSEurn:md5:4a1c40ca03dab23f76d073ee3c81ccd82012-01-23T19:41:00+01:002012-01-25T19:44:35+01:00Olivier DuchateauDocumentationGunicornhginitLinuxMercurialNginxopenSUSEPythonSystemdWSGI<p><img src="http://avignu.tuxfamily.org/news-custom/public/logos.png" alt="Logos" style="display:block; margin:0 auto;" title="Logos, openSUSE + Mercurial et Nginx" /></p>
<p>Dans un précédent <a href="http://avignu.tuxfamily.org/index.php?post/2011/11/05/Mercurial-en-tant-qu-application-WSGI">billet</a>, j'avais montré comment l'on pouvait exécuter une application Web écrite dans le langage <a href="http://python.org/">Python</a> sans faire intervenir de serveurs Web.</p>
<p>Aujourd'hui, nous allons voir le cas, où un serveur (en l'occurence <a href="http://nginx.org/">Nginx</a>) est déjà en place.</p>
<p>En fait, le but inavoué de cet article est de comprendre le système <em>d'init</em>, <a href="http://www.freedesktop.org/wiki/Software/systemd">systemd</a> utilisé par <a href="http://fr.opensuse.org/">openSUSE</a> en autre.</p> <p>Dans les près-requis, on peut citer :</p>
<ul>
<li>Savoir utiliser <a href="http://mercurial.selenic.com/">mercurial</a></li>
<li>Être root sur la machine</li>
</ul>
<p>Dans un premier temps, nous allons voir la configuration du serveur, Nginx</p>
<h2>Configuration du serveur</h2>
<p>Rien de bien compliqué, la <a href="http://wiki.nginx.org/Main">documentation</a> est très complète.</p>
<p>Voici <strong>ma</strong> configuration du fichier <code>nginx.conf</code> (situé dans <code>/etc/nginx</code>).</p>
<pre>
user nginx nginx;
worker_processes 1;
error_log /var/log/nginx/error.log;
#error_log /var/log/nginx/error.log notice;
#error_log /var/log/nginx/error.log info;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log /var/log/nginx/access.log main;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
server_tokens off;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name errements.net;
charset utf-8;
autoindex on;
index index.html;
# favicon.ico
location = /favicon.ico {
log_not_found off;
access_log off;
}
location / {
root /srv/www/htdocs/bornem/;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /srv/www/htdocs/;
}
# Users directory support
location ~ ^/~(.+?)(/.*)?$ {
alias /home/$1/public_html$2;
}
}
}</pre>
<p>Quelques explications concernant ce fichier.</p>
<ul>
<li>Les journaux (d'erreurs et d'accès) seront stockés dans le dossier <code>/var/log/nginx</code>.</li>
<li>Le numéro du processus sera stocké dans le fichier <code>/var/run/nginx.pid</code>.</li>
<li>L'affichage du numéro de version du serveur est désactivé dans les en-têtes <acronym title="HyperText Transfer Protocol">HTTP</acronym>, la directive <strong>server_tokens</strong> est sur <strong>off</strong>.</li>
</ul>
<p><strong>Remarque :</strong> On peut analyser ces en-têtes avec le module <a href="http://docs.python-requests.org/en/latest/index.html">Requests</a>. (Il faut faire tourner le serveur, et lancer l'interpréteur <strong>Python</strong>) :</p>
<p>Voici le cas où la directive <strong>server_tokens</strong> est sur <strong>on</strong> (valeur par défaut).</p>
<pre>olivier@bornem:~ $ python
Python 2.7.2 (default, Aug 19 2011, 20:41:43) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> r = requests.get("http://errements.net/")
>>> r.headers
{'date': 'Sat, 21 Jan 2012 09:41:08 GMT', 'transfer-encoding': 'chunked', 'connection': 'keep-alive', 'content-type': 'text/html; charset=utf-8', 'server': 'nginx/1.0.10'}</pre>
<p>La valeur <strong>server</strong> vaut <strong>nginx/1.0.10</strong> (où <strong>1.0.10</strong> représente le numéro de version).</p>
<p>Si l'on désactive cette directive (<code>server_tokens off;</code>), on obtient ceci :</p>
<pre>>>> r = requests.get("http://errements.net/")
>>> r.headers
{'date': 'Sat, 21 Jan 2012 09:54:32 GMT', 'transfer-encoding': 'chunked', 'connection': 'keep-alive', 'content-type': 'text/html; charset=utf-8', 'server': 'nginx'}</pre>
<p>Cette fois-ci, la valeur de <strong>serveur</strong> vaut <strong>nginx</strong>.</p>
<ul>
<li>L'absence de <a href="http://fr.wikipedia.org/wiki/Favicon">favicon</a> <sup>[<a href="http://avignu.tuxfamily.org/index.php?post/2012/01/23/D%C3%A9ployer-Mercurial-%28hg%29-%C2%AB-derri%C3%A8re-%C2%BB-un-serveur-Web-%28Nginx%29-sous-openSUSE#pnote-47-1" id="rev-pnote-47-1">1</a>]</sup>, à la racine du site, n'est pas « stockée » dans les <em>logs</em>.</li>
</ul>
<pre>[...]
# favicon.ico
location = /favicon.ico {
log_not_found off;
access_log off;
}
[...]</pre>
<ul>
<li>La racine du serveur est située dans <code>/srv/www/htdocs/bornem/</code>. Pour les utilsateurs, elle est située dans <code>$HOME/public_html</code> (cette fonctionnalité est connue sous l'appellation <a href="http://wiki.nginx.org/UserDir">UserDir</a>).</li>
</ul>
<pre>[...]
# Users directory support
location ~ ^/~(.+?)(/.*)?$ {
alias /home/$1/public_html$2;
}
[...]</pre>
<p>Nous pouvons maintenant lancer le serveur.</p>
<h3>Démarrer Nginx</h3>
<p>Sous openSUSE, les programmes pour lancer les <em>daemons</em> au démarrage sont situés dans <code>/etc/init.d/</code>.</p>
<pre>root@bornem:~# ls /etc/init.d/ | grep nginx
nginx
root@bornem:~# sh /etc/init.d/nginx start
redirecting to systemctl
Failed to issue method call: Unit name .d/nginx.service is not valid.
root@bornem:~# </pre>
<p>On obtient une erreur, car il manque un fichier <strong>nginx.service</strong>.</p>
<p>Depuis la version <strong>12.1</strong>, openSUSE intègre désormais <strong>systemd</strong>, comme service <em>d'init</em>.</p>
<p>Nous allons rechercher ce fichier.</p>
<pre>root@bornem:~# find /etc/systemd/ -type f -name 'nginx.service' -print
root@bornem:~#
root@bornem:~# find /lib/systemd/ -type f -name 'nginx.service' -print
root@bornem:~# </pre>
<p>Parmis les dossiers susceptibles de posséder un tel fichier, la recherche n'abouti pas, il faut donc le créer <sup>[<a href="http://avignu.tuxfamily.org/index.php?post/2012/01/23/D%C3%A9ployer-Mercurial-%28hg%29-%C2%AB-derri%C3%A8re-%C2%BB-un-serveur-Web-%28Nginx%29-sous-openSUSE#pnote-47-2" id="rev-pnote-47-2">2</a>]</sup>.</p>
<p><em>Dans les pièces jointes vous trouverez le fichier <code>nginx.service</code>.</em></p>
<p>Vous constaterez que j'ai commenté la ligne <code>#WantedBy=multi-user.target</code>, car j'ai fait les tests avec une interface graphique, d'où la cible <strong>graphical.target</strong>.</p>
<p>Nous allons placé ce fichier, dans le dossier <code>/etc/systemd/system/default.target.wants</code> (si il n'existe pas il faut le créer).</p>
<p>Désormais, nous pouvons lancer le serveur de cette façon :</p>
<pre>root@bornem:~# systemctl start nginx.service</pre>
<h2>Mercurial</h2>
<p>Dans cette partie nous allons nous intéressé à <strong>Mercurial</strong>.</p>
<p>L'affichage du « dépôt » se fera par l'intermédiaire du module <strong>hgweb</strong>. Pour cela j'ai écris un script Python (<em>Cf.</em> <code>hgweb.wsgi</code>, en pièces jointes).</p>
<p>Ce script est très simple, si aucun fichier de configuration lui est donné, il va rechercher tous les utilisateurs présents sur la machine (<code>/etc/passwd</code>), puis il va examiner le <code>$HOME</code> de chaque utilisateur à la recherche de fichiers <code>.cfg</code> contenant le mot clé <strong>[paths]</strong> <sup>[<a href="http://avignu.tuxfamily.org/index.php?post/2012/01/23/D%C3%A9ployer-Mercurial-%28hg%29-%C2%AB-derri%C3%A8re-%C2%BB-un-serveur-Web-%28Nginx%29-sous-openSUSE#pnote-47-3" id="rev-pnote-47-3">3</a>]</sup> (pour l'instant, le script fonctionne uniquement pour un seul utilisateur). Le tout sera renvoyé à un serveur <a href="http://www.wsgi.org/en/latest/servers.html">WSGI</a>. Par défaut j'utilise <a href="http://pypi.python.org/pypi/waitress">waitress</a> <sup>[<a href="http://avignu.tuxfamily.org/index.php?post/2012/01/23/D%C3%A9ployer-Mercurial-%28hg%29-%C2%AB-derri%C3%A8re-%C2%BB-un-serveur-Web-%28Nginx%29-sous-openSUSE#pnote-47-4" id="rev-pnote-47-4">4</a>]</sup>, mais si il est absent, le script utilisera le module <a href="http://docs.python.org/library/wsgiref.html">wsgiref</a> (en standard dans Python).</p>
<p>Le serveur WSGI passe par un <em>socket</em> réseau TCP/IP (<em>network socket</em>), c'est à dire qu'il transmet les données à une adresse IP locale (l'adresse de bouclage <em>localhost</em>).</p>
<p>Il nous faut maintenant trouver un moyen pour rediriger l'adresse IP locale (inaccessible depuis « l'extérieur ») vers le serveur (accessible grâce à une adresse IP connue). On parle alors de <em><a href="http://fr.wikipedia.org/wiki/Proxy">proxy</a></em>.</p>
<p>Nginx possède une particularité intéressante, il est capable d'être utilisé comme <em>reverse proxy</em>, c'est à dire on passe par lui pour accéder aux ressources internes.</p>
<h3>Configurer Nginx pour prendre en compte Mercurial</h3>
<p>Nous allons donc utiliser le module <a href="http://wiki.nginx.org/HttpProxyModule">proxy</a>, de plus l'accès au dépôt se fera par l'intermédiaire d'un sous-domaine (<em>vhost</em>).</p>
<p>Vous devez avoir au préalable correctement configuré votre serveur <acronym title="Domain Name System">DNS</acronym>. Pour des tests en local, on peut passer par le fichier <code>/etc/hosts</code>.</p>
<p>On rajoute ces lignes :</p>
<pre>[...]
http {
}
[...]
# Subdomain settings
#
# Mercurial
#
server {
listen 80;
server_name hg.errements.net;
access_log /var/log/nginx/access-hg.log;
location / {
root /srv/www/htdocs/vhosts/hg;
autoindex on;
}
}
[...]
}</pre>
<p>On peut (re)lancer le serveur pour voir que tout fonctionne :</p>
<pre>root@bornem:~# systemctl start nginx.service</pre>
<p>Maintenant, on place notre script <code>hgweb.wsgi</code> dans le répertoire <code>/srv/www/htdocs/vhosts/hg</code>.</p>
<p>On exécute le script et on stoppe le serveur.</p>
<pre>root@bornem:~# systemctl stop nginx.service
root@bornem:~# python /srv/www/htdocs/vhosts/hg/hgweb.wsgi &
serving on http://127.0.0.1:8500</pre>
<p>ll s'agit de l'adresse IP mentionnée (vous pouvez changer le port) dans le script pour qu'il s'exécute. Il faut donc rajouter cette information au fichier de configuration de Nginx.</p>
<pre>[...]
# Subdomain settings
#
# Mercurial
#
server {
listen 80;
server_name hg.errements.net;
access_log /var/log/nginx/access-hg.log;
location / {
root /srv/www/htdocs/vhosts/hg;
autoindex off;
proxy_path http://127.0.0.1:8500;
proxy_set_header Host $host;
}
}
[...]</pre>
<p>On relance le serveur, et on ouvre notre navigateur favori à l'adresse indiquée par <strong>server_name</strong>.</p>
<h3>Automatiser le lancement du programme</h3>
<p>Il va falloir créer un fichier <code>.service</code>, je me suis inspiré de ce <a href="http://forums.opensuse.org/blogs/jdmcdaniel3/systemd-using-after-local-script-opensuse-12-1-71/" hreflang="en">billet</a>, pour écrire le mien.</p>
<p>Dans le fichier <code>/etc/init.d/after-local</code> j'ai rajouté les mêmes lignes que celles tapées en console :</p>
<pre>PYTHON_BIN=/usr/bin/python
NGINX_DIR="/srv/www/htdocs"
NGINX_VHOSTS="$NGINX_DIR/vhosts"
## Mercurial settings (subdomain, hg.)
if [ -e "$NGINX_VHOSTS/hg/hgweb.wsgi" ]; then
$PYTHON_BIN $NGINX_VHOSTS/hg/hgweb.wsgi
fi</pre>
<p>Si l'on veut lancer plusieurs scripts, il faut placer les commandes en « arrière-plan » (on rajoute le caractère <strong>&</strong> à la fin de la ligne).</p>
<p>Le fichier <code>.service</code> qui va exécuter ce programme <em>d'init</em> s'appelle <code>after-local.service</code> (<em>Cf.</em> les pièces jointes).</p>
<pre>root@bornem:~# systemctl start after-local.service</pre>
<p>On peut rajouter ce fichier au répertoire <code>/etc/systemd/system/default.target.wants</code> pour qu'il soit lancé automatiquement au démarrage.</p>
<h2>Cas pratique avec <a href="http://gunicorn.org/" title="Green Unicorn">Gunicorn</a></h2>
<p><img src="http://avignu.tuxfamily.org/news-custom/public/gunicorn-45.png" alt="Logo du serveur WSGI, Gunicorn" style="display:block; margin:0 auto;" title="Gunicorn, serveur WSGI" /></p>
<p>Dans cette dernière partie, nous allons nous intéressé à un serveur WSGI particulier, Gunicorn.</p>
<p>Nous allons supprimer notre premier script (<code>hgweb.wsgi</code>), pour le remplacer par <code>web.py</code> (<em>Cf.</em> pièces jointes). Ce serveur à un mode de fonctionnement <a href="http://gunicorn.org/run.html#gunicorn">particulier</a>.</p>
<p>Ce programme, nous allons l'utilisé comme un « simple module Python » (je ne suis pas sûr à 100%, qu'il soit en mémoire dans <code>sys.path</code>), de ce fait, nous allons créer en plus un fichier <code>__init__.py</code> vide.</p>
<pre>root@bornem:~# touch /srv/www/htdocs/vhosts/hg/__init__.py
root@bornem:~# chown -R nginx:nginx /srv/www/htdocs/
</pre>
<p>On peut lancer le programme et relancer le serveur :</p>
<pre>root@bornem:~# cd /srv/www/htdocs/vhosts ; /usr/bin/gunicorn -p /var/run/gunicorn.pid -g nginx -u nginx -b 127.0.0.1:8500 hg.web:app &
root@bornem:~# systemctl start nginx.service</pre>
<p>Nous sommes obligé de « descendre » dans le répertoire <code>vhosts/</code> car les autres dossiers ne sont pas considérés comme des modules par Python.</p>
<p>On peut passer d'autres paramètres, notamment le nombre de <em>workers</em> (nombre de cœurs de la machine), ou passer par un fichier de configuration.</p>
<h3>Gunicorn et systemd ?</h3>
<p>La difficulté ici, c'est de pouvoir se déplacer dans le dossier souhaité. La lecture de la documentation, <a href="http://0pointer.de/public/systemd-man/systemd.exec.html">systemd.exec</a> nous apprend, qu'il existe une directive <strong>WorkingDirectory</strong>, elle ne peut fonctionner que si le type est <strong>simple</strong>, alors que l'on pourrait s'attendre à mettre <strong>forking</strong>.</p>
<blockquote><p>WorkingDirectory=</p>
<p>
Takes an absolute directory path. Sets the working directory for executed processes.</p></blockquote>
<p>En annexe vous trouverez le fichier <code>gunicorn-hg.service</code>, qui reprend la même commande lancée dans une console avec les spécificités de systemd.</p>
<p>Avec Gunicorn, au lieu de passé par un <em>socket</em> TCP/IP, on peut utiliser un <em>socket</em> unix (ou <em>Berkeley socket</em>), le fichier <code>gunicorn-hg.service</code> devient alors :</p>
<pre>[Unit]
Description=Gunicorn for mercurial
Requires=local-fs.target syslog.target
After=syslog.target local-fs.target
Before=nginx.service
[Service]
Type=simple
PIDFile=/var/run/gunicorn.pid
WorkingDirectory=/srv/www/htdocs/vhosts
ExecStartPre=/bin/echo 'Starting gunicorn (mercurial)'
ExecStart=/usr/bin/gunicorn -p /var/run/gunicorn.pid -g nginx -u nginx -b unix:/tmp/gunicorn.socket hg.web:app
StandardOutput=syslog
StandardError=syslog
KillMode=process
[Install]
#WantedBy=multi-user.target
WantedBy=graphical.target</pre>
<p>Et la partie du serveur :</p>
<pre>[...]
# Subdomain settings
#
# Mercurial
#
server {
listen 80;
server_name hg.errements.net;
access_log /var/log/nginx/access-hg.log;
location / {
root /srv/www/htdocs/vhosts/hg;
autoindex off;
proxy_path http://unix:/tmp/gunicorn.socket;
proxy_set_header Host $host;
}
}
[...]</pre>
<p>J'avoue que dans ce cas, je préfère de loin cette configuration, à celle de passer par le script <code>/etc/init.d/after-local</code>. Je trouve que c'est plus « propre », et on exploite davantage systemd.</p>
<div class="footnotes"><h4>Notes</h4>
<p>[<a href="http://avignu.tuxfamily.org/index.php?post/2012/01/23/D%C3%A9ployer-Mercurial-%28hg%29-%C2%AB-derri%C3%A8re-%C2%BB-un-serveur-Web-%28Nginx%29-sous-openSUSE#rev-pnote-47-1" id="pnote-47-1">1</a>] Il s'agit d'une « petite image », qui s'affiche dans la barre du navigateur, lorsque l'on consulte une page <acronym title="HyperText Markup Language">HTML</acronym>.</p>
<p>[<a href="http://avignu.tuxfamily.org/index.php?post/2012/01/23/D%C3%A9ployer-Mercurial-%28hg%29-%C2%AB-derri%C3%A8re-%C2%BB-un-serveur-Web-%28Nginx%29-sous-openSUSE#rev-pnote-47-2" id="pnote-47-2">2</a>] Je vous conseille de lire la <a href="http://0pointer.de/public/systemd-man/systemd.service.html">documentation</a> concernant les fichiers <code>.service</code>.</p>
<p>[<a href="http://avignu.tuxfamily.org/index.php?post/2012/01/23/D%C3%A9ployer-Mercurial-%28hg%29-%C2%AB-derri%C3%A8re-%C2%BB-un-serveur-Web-%28Nginx%29-sous-openSUSE#rev-pnote-47-3" id="pnote-47-3">3</a>] <em>Cf.</em> la <a href="http://mercurial.selenic.com/wiki/PublishingRepositories#Configuration_of_hgweb">documentation</a></p>
<p>[<a href="http://avignu.tuxfamily.org/index.php?post/2012/01/23/D%C3%A9ployer-Mercurial-%28hg%29-%C2%AB-derri%C3%A8re-%C2%BB-un-serveur-Web-%28Nginx%29-sous-openSUSE#rev-pnote-47-4" id="pnote-47-4">4</a>] Je mets à disposition le fichier <code>python-waitress.spec</code> permettant de construire un RPM (testé uniquement sous openSUSE).</p></div>
Présentation du micro-framework Flaskurn:md5:be31c7c92e5f9503b2232f9b0c4910022011-11-23T18:15:00+01:002011-11-23T19:32:42+01:00Olivier DuchateauProgrammationFastCGIflaskJinja2PythonWerkzeugWSGI<p><img src="http://avignu.tuxfamily.org/news-custom/public/flask.png" alt="logo framework Flask" style="display:block; margin:0 auto;" title="logo du framework Python Flask" /></p>
<p>Ce billet constitue une présentation du <em>framework</em>, <a href="http://flask.pocoo.org/">Flask</a>. Il permet de créer rapidement et très simplement des applications Web (blog, forum, etc.).</p>
<p>Par la suite d'autres billets seront publiés pour présenter d'une manière plus approfondi quelques unes de ses caractéristiques.</p> <h3>Introduction</h3>
<p><strong>Flask</strong> s'inspire d'un projet écrit en <a href="http://www.ruby-lang.org/fr/">Ruby</a>, <a href="http://www.sinatrarb.com/">Sinatra</a>. Contrairement à ce dernier, il est entièrement développé en <a href="http://www.python.org/">Python</a>.</p>
<p>Il repose sur la bibliothèque <a href="http://werkzeug.pocoo.org/">Werkzeug</a> (ensemble « d'utilisatires » <acronym title="Web Server Gateway Interface">WSGI</acronym> en autre), et utilise le système de <em>template</em> <a href="http://jinja.pocoo.org/">Jinja 2</a> <sup>[<a href="http://avignu.tuxfamily.org/index.php?post/2011/11/23/Pr%C3%A9sentation-du-micro-framework-Flask#pnote-39-1" id="rev-pnote-39-1">1</a>]</sup>.</p>
<p>Il possède de nombreux atouts :</p>
<ul>
<li>Une excellente <a href="http://flask.pocoo.org/docs/">documentation</a>, tout est expliqué</li>
<li>La possibilité de l'étendre (en ajoutant des <a href="http://flask.pocoo.org/extensions/">extensions</a>)</li>
<li>Un système de vues assez clair (facilement compréhensible)</li>
</ul>
<h3>Installation</h3>
<h4>Sous openSUSE</h4>
<p>Il faut tout d'abord installer le dépôt <strong>devel:languages:python</strong> (Cf. <a href="http://avignu.tuxfamily.org/index.php?post/2011/11/21/Upgrade-openSUSE">Upgrade openSUSE</a>, la gestion des dépôts). Pensez à bien vérifier que <strong>Werkzeug</strong> et <strong>Jinja2</strong> soient installés.</p>
<h4>Sous FreeBSD</h4>
<p>Je suis le mainteneur, donc ce port est mis à jour régulièrement.</p>
<h5>À partir des ports (par compilation)</h5>
<p>Il faut mettre à jour l'arbre des ports, si vous avez correctement configuré votre fichier <code>/etc/make.conf</code>, il suffit de lancer cette commande :</p>
<pre>root@bornem:~ # cd /usr/ports/ ; make update</pre>
<p>On va installer le paquet, en le compilant (c'est assez rapide, car il y a très peu de dépendances) :</p>
<pre>root@bornem:/usr/ports # cd www/py-flask && make install clean</pre>
<h5>À partir d'un paquet pré-compilé</h5>
<pre>root@bornem:~ # pkg_add -r py27-Flask</pre>
<h3>Hello World !!!</h3>
<p>Notre premier exemple se fera à partir du <em>shell</em> Python.</p>
<pre>olivier@bornem:~ $ python
Python 2.7.2 (default, Aug 19 2011, 20:41:43) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> from flask import Flask
>>> app = Flask(__name__)
>>>
>>> @app.route("/")
... def index():
... return "Hello World!"
...
>>> with app.test_request_context():
... print index()
...
Hello World!
>>> </pre>
<p>Voici une explication concernant la déclaration de notre première variable <strong><code>app</code></strong> :</p>
<blockquote><p>About the First Parameter</p>
<p>
The idea of the first parameter is to give Flask an idea what belongs to your application. This name is used to find resources on the file system, can be used by extensions to improve debugging information and a lot more.</p>
<p>
So it's important what you provide there. If you are using a single module, `<strong>name</strong>` is always the correct value. If you however are using a package, it's usually recommended to hardcode the name of your package there.</p></blockquote>
<p>On peut bien sûr passer des paramètres à notre fonction :</p>
<pre>>>> @app.route("/hello/<username>")
... def hello_user(username=""):
... return "Hello %s" % username
...
>>> with app.test_request_context():
... print hello_user()
... print hello_user("Olivier")
...
Hello
Hello Olivier
>>> </pre>
<p>Voilà j'espère que les prochains billets vous intéresseront.</p>
<div class="footnotes"><h4>Note</h4>
<p>[<a href="http://avignu.tuxfamily.org/index.php?post/2011/11/23/Pr%C3%A9sentation-du-micro-framework-Flask#rev-pnote-39-1" id="pnote-39-1">1</a>] La syntaxe est proche de celle utilisée dans <a href="https://www.djangoproject.com/">Django</a>.</p></div>