PROJET AUTOBLOG


Sam & Max: Python, Django, Git et du cul

Site original : Sam & Max: Python, Django, Git et du cul

⇐ retour index

Gérer plusieurs versions de Python avec “py” sous Windows 7

samedi 10 janvier 2015 à 10:10

Il est courant de vouloir installer plusieurs versions de Python. Pour faire des tests, pour s’assurer que son code est portable, pour utiliser des libs qui marchent uniquement sur une des versions, etc.

Sous Linux, c’est facile : chaque interpretteur est préfixé. Par exemple, sous Ubuntu, si je veux utiliser Python 3.4, je l’installe :

sudo apt-get install python3.4

Et si je lance python, ça me lance la 2.7 car c’est celle de base. Mais si je lance python3.4, ça me lance bien la 3.4.

Au final, on finit par utiliser des environnements virtuels qui isolent des versions de Python particulières. Mais ça ne retire pas l’envie de pouvoir choisir sa version de Python au niveau du système, ce qui reste difficile à faire sous Windows.

Or, depuis la version 3.3, l’installeur pour cet OS de Python installe la commande py, qui permet de choisir quelle version de Python on lance.

Dans un terminal

Si dans une console vous faites :

py

Il lancera le shell de votre installation Python 2.x la plus récente.

Si vous faites :

py script.py

Il exécutera le script avec votre installation Python 2.x la plus récente.

Mais vous pouvez passez un flag -version pour forcer une version de Python. Lancer le shell Python avec la 3.3 :

py -3.3

Lancer un script avec la 3.4 :

py -3.4 script.py

Cela suppose que vous avez la 3.3 et la 3.4, installés, évidement.

La syntaxe est surprenante. J’aurais pensé qu’ils mettraient un truc du genre py -i 3.4 mais non, c’est direct -numero.

La commande py accepte aussi les paramètres qu’on passerait normalement à la commande python, et notament l’option -m module, qui permet de lancer un module en particulier.

C’est pratique pour lancer ipython ou pip avec une version particulière. Par exemple pour installer autobahn uniquement pour la version 3.4 et donc utiliser asyncio :

py -3.4 -m pip install autobahn

Bang !

La commande py reconnait également la ligne shebang, cette syntaxe unix qui dit quel interpretteur utiliser. Si vous mettez sur la première ligne de votre script :

#! python3.4

Alors :

py script.py

Invoquera python 3.4.

La commande est capable de se débrouiller avec les chemins Unix, afin de garder la portabilité. Donc si vous faites :

#! /usr/bin/env python2.7

Alors :

py script.py

Va ignorer le debut de la ligne, et prendre l’installation locale de Python 2.7 pour lancer le script.

On click

Si vous installez la version Python 3.3 ou 3.4 en premier, les fichiers .py seront associés à la commande py. Donc si vous cliquez sur un script Python avec une ligne shebang, la bonne version sera lancée.

Mais si vous avez installé Python 2.x avant, il est possible que vos fichiers .py soient encore associés directement à la commande python ordinnaire.

Pour changer cela, faites un clic droit sur un fichier .py, modifiez le programme qui ouvre ce fichier et faites le pointer sur "C:\Windows\py.exe".

Debugger Python à distance avec rpdb 7

vendredi 9 janvier 2015 à 09:33

Vous aimez pdb parce que c’est cool. Et vous adorez pdbpp parce que c’est trop cool.

Mais parfois vous n’avez pas accès à une console sur votre process : il est derrière un nginx, ou même sur une machine distante.

rpdb vient résoudre ce problème en lançant un serveur telnet qui donne accès à votre debugger.

pip install rpdb

Puis :

import rpdb; rpdb.set_trace()

Et après vous prenez votre client telnet favoris, et vous accédez à votre débugger :

telnet 127.0.0.1 4444

Bien entendu, si vous êtes à distance, remplacez 127.0.0.1 par l’ip de la machine. Le port est configurable également :

import rpdb
debugger = rpdb.Rpdb(port=12345)
debugger.set_trace()

Et derrière, ça lance pdb, donc pdbpp est lancé automatiquement si il est installé. Joie.

Conclusions de la migration 10   Recently updated !

jeudi 8 janvier 2015 à 10:15

La migration de serveur est terminée. Le blog, le multiboards, IndexError et 0bin on été rétablis. On en a profité pour remettre sur pied AllThatCounts qui avait été délaissé durant le dernier crash.

On quitte donc LeaseWeb, qui nous a forcé à migré 3 fois avec ses machines qui ont planté. En plus deux fois la partition /tmp était corrompue, ce qui rend le backup particulièrement compliqué. On notera que leur SAV nous posait des questions du genre “si vous installez ça, et lancez cette commande, ça donne quoi ?”, alors qu’on leur a bien notifié qu’on avait un disque monté en lecture seul du fait du FS en vrac…

On est passé chez Cinfu, car on peut les payer en Bitcoin. Livraison du serveur rapide, installation sans histoire, et finalement une migration beaucoup moins chiante que la dernière fois car on a fait les gros bourrins : rsync + mysqldump bien large. Ce qui a pris le plus de temps c’était de résoudre les centaines de problèmes de permissions que ça a créé, les trucs que ça aurait pas du écraser, etc.

On avait + de 100000 spams dans la poubelle des commentaires, qui prenaient 300 Mo des 400 mo de la taille totale de la BDD du blog. Un petit :

DELETE FROM wp_comments WHERE comment_approved = 'trash';

A accéléré la migration vu qu’on a du transférer la base 3 fois et la réimporter autant à cause d’erreurs diverses. Note à nous-même : arrêter de mettre des .bak dans /tmp parce que c’est “juste pour 5 minutes”. Après on se fait avoir comme des débutants au reboot.

C’est là qu’on voit qu’on est dev, et pas admin.

Rajouter dans wp-config.php :

define('EMPTY_TRASH_DAYS', 30);

Nous évitera d’avoir à repenser à tout ça. C’est con mais faut le savoir.

J’en profite pour témoigner mon amour immodéré pour mosh. Parce que faire tout ça sur une connexion thai avec 300ms de ping minimum et une coupure toutes les 10 minutes, avec SSH, c’est juste un enfer.

Sous Centos, faut installer les repos EPEL et yum install mosh derrière.

Sous Ubuntu, fait installer le ppa ppa:keithw/mosh et apt-get install mosh derrière.

Certains serveurs ont un pare feu tatillon, et il faut rajouter dans la section :RH-Firewall-1-INPUT - [0:0] de iptable :

-A RH-Firewall-1-INPUT -p udp --dport 60000:61000 -j ACCEPT

D’autres ont des problèmes de locales:

apt-get install --reinstall language-pack-fr
dpkg-reconfigure locales

Parfois y a aucun problème. C’est juste qu’on a pas un parc homogène, avec des bécanes vieilles de 1000 ans, alors forcément…

Mais Max a fait des devis, et si on passe au cloud avec les 2G/s de BP et les To de disque dur qu’on consomme, on multiplie les prix par 10 d’hébergement. Faire les trucs à la main, c’est chiant, mais c’est économe.

Pour mosh, pas de serveur à lancer, juste remplacer ssh par mosh dans la commande quand on se connecte. Des fois je lance avec --predict=experimental car je suis impatient et le retour de frappe est plus rapide, mais le cursor fait des mouvements bizarres, faut s’habituer.

Bon, on retourne faire des trucs plus productifs.

Black awk down 8

mardi 6 janvier 2015 à 11:26

Ceci est un post invité de foX posté sous licence creative common 3.0 unported.

Introduction (sans douleur)

Qui n’a jamais rêvé d’avoir un shell Unix un peu plus pythonic ? Les oneliners en sed et Awk bien chiadés, c’est l’apanage des grands barbus en sandales et ça déchire, mais ça reste cryptique et la manipulation de liste et de chaînes de caractères est tout de même limitée. On peut dégainer perl en one line, comme ça c’est encore plus puissant mais moins lisible…

Et python dans tout ça ? En oneliner, ça craint, car le principe d’indentation ne facilite pas les choses et on finit avec une imbrication de bazar de parenthèses illisible.

La solution ? Pour se faire plaisir : une bonne pyp. Le bidule s’installe avec… pip (c’est une méta pipe).

pip install pyp

pyp (Python Power at the Prompt) va enchanter votre shell unix et vous envoyer au 7ème ciel. Petit tour d’horizon de la merveille.

Pyp et les strings

C’est la base quand on bricole des oneliners en shell, tripoter des strings.

ls *JPG | pyp "'mv', p, p.replace('JPG', 'jpg')"

Revoyons l’action au ralenti :

On voit ici que l’on utilise la méthode replace de la classe str python. On peut utiliser n’importe quelle méthode de str : lower, upper, title, strip(tease), count etc. Avec des petits bonus comme refindall(re) :-)

Ça donne quoi ma brave dame ? Et bien si on avait des fichiers appelés porn1.JPG et ass.JPG notre jolie commande renverrait :

mv porn1.JPG porn1.jpg
mv ass.JPG ass.jpg

Ok, bien gentil me direz-vous, mais qu’en fait-on ? Et bien ou l’on pipe ça dans le shell ( | sh) ou alors on passe l’argument -x (ou –execute) à pyp ou directement exécuter le résultat.

ls *JPG | pyp "'mv', p, p.replace('JPG', 'jpg')" | sh
# ou bien
ls *JPG | pyp -x "'mv', p, p.replace('JPG', 'jpg')"

Pourquoi on aime bien python ? Car il slice le bread comme un chef. Pyp aussi, oeuf corse, quelques exemples :

echo "sam-et-max" | pyp "p.split('-')[2]"
# max
 
# La même chose en plus court. Le m, comme minus, sous-entend qu'on split sur "-"
echo "sam-et-max" | pyp "m[2]"
# max
 
echo "sam-et-max" | pyp "m[0], m[2]"
# sam max

Comme vous avez vu, il y a un petit raccourci sympa avec m (comme minus) au lieu de p. C’est pas fini, vous en avez d’autres : d (dot) pour un point, w (whitespace) pour une espace, u pour underscore, s pour slash… Ils ont pensé à tout.

Des pyp à la chaîne

On tout de suite envie de remettre le couvert avec une autre pyp. C’est possible et même encouragé. Le symbole utilisé est le pipe unix | dans une commande pyp. Ce n’est donc pas un pipe du shell mais un pipe interne de pyp. Vous suivez ? Un petit exemple :

echo "sam et max" | pyp "w[0] | 'avant : ', o, 'après :', p"
# avant :  sam et max après : sam

Le p représente toujours le paramètre courant. Donc dans la seconde partie du pipe, c’est le premier élément de la liste issu du split de la chaîne “sam et max” sur l’espace (w comme whitespace). Vous suivez toujours ? Oui, bon. Et donc le o ? Et bien c’est le paramètre d’origine non modifié. Et il y a h (comme history ou comme hâlibi !) pour avoir le paramètre p à toutes les étapes de la chaîne de pipe… Beaucoup de puissance on vous dit !

On peut aussi rouler^w faire des join facilement avec les mêmes raccourcis. Par exemple on split sur les espaces puis join sur slash :

echo "sam et max aiment le lourd en fin de soirée" | pyp "w[:6]|s"
# sam/et/max/aiment/le/lourd

Une liste de pyp avec une belle pp

S’il y a un bien un truc qui envoie du bois avec python, c’est la gestion des listes. Pyp n’est pas en reste. C’est pp qui contient les listes. En avant :

ls | pyp "pp[3]"
# Affiche le troisième fichier

On peut utiliser toutes les méthodes standards de la classe list (sort, count etc.) plus quelques bonus :

# Équivalent de | sort | uniq

ls | pyp "pp.uniq()"
 
# Additionne tous les PID des process de machine.
# Ça ne sert à rien, mais c'est un exemple hein ?
ps -ef | pyp "w[1] | int(p) | sum(pp)"
 
# Voilà qui est plus utile. 
# Au passage, on note que la ligne d'en-tête est ignoré et ne fait pas planter pyp.
# Malin le lapin.
df -m | pyp "w[1] | int(p) | sum(pp)"

pyp au naturel

Pour compléter les fonctions python, pyp propose quelques fonctions maisons tout à fait sympathiques. Florilège :

echo "Sam Bill Max" | pyp "p.kill('Bill')"  # quand c'est pas bill, c'est kenny...
# Sam  Max
 
echo 124 | pyp "(int(p)/2+30)"
# 91
 
ls  | pyp "n, p"
# Liste des fichiers avec un numéro croissant devant
 
echo /home/fox/video/gangbang.avi  | pyp "'Dir='+p.dir, '\nFile='+p.file, '\nExt='+p.ext" # basename, dirname et ses amis
# Dir=/home/fox/video 
# File=gangbang.avi 
# Ext=avi
 
# On définit le séparateur de liste comme l'espace, 
# puis on regroupe la liste par deux élément
# et enfin on fait un join sur le caractère tiret (minus)
echo "1 2 3 4 5 6" | pyp "pp.delimit(' ') | pp.divide(2) | m"
# 1-2
# 3-4
# 5-6
 
ps -ef | pyp "w[1] | int(p) | max(pp)" 
# (le PID le plus grand)

pyp en intention ou intention de pyp ?

Il ne faut pas se gêner, on peut utiliser les excellentes listes en intention de Python. Voici tous les PID impaires :

ps -ef | pyp "w[1] | int(p) | [i for i in pp if i%2] | p "

L’industrie de la pyp c’est un truc de macro

Les bonnes choses, ça se garde, alors voilà :

ps -ef | pyp "w[1] | int(p) | [i for i in pp if i%2] | p " -s odd  # on enregistre la macro 'odd'
 
ps ef | pyp odd # c'est aussi simple que cela ;-)

pyp sur grand écran

Cette petite pépite est développée par Sony Pictures Imageworks pour ses besoins internes. Le développement n’est pas très collaboratif (un commit par release…) mais reste ouvert aux contributions externes. C’est sous une licence de type BSD.
Les loulous ont fait une jolie vidéo de tutorial très bling bling. C’est l’avantage de bosser dans le milieu :

Et bien plus encore…

Cet outil merveilleux possède encore des fonctionnalités qui vont vous le faire adorer comme son mode interactif avec une coloration syntaxique sympathique, la gestion de l’historique dans le pipe et beaucoup d’autres. Adieu sed, awk, uniq, sort et con sort, vous étiez mes premiers amours, mais pyp vous chasse dehors.

Ce n’est pas la seule ni la première tentative de faire la peau d’awk avec python. Sam avait déjà écrit un article sur une autre solution aussi appelée pyped.

C’est donc parfait ?

Oui, bien entendu. Aucun défaut, aucun bug. Ok… voici les limites :

Amusez vous bien, celui qui fait la plus jolie commande pyp dans les commentaires aura le droit à un bisous.

Les managers le détestent : faites tourner WAMP dans Django avec cette astuce insolite 9

dimanche 4 janvier 2015 à 20:45

Il existe une lib appelée crochet qui permet de faire marcher des API de twisted entre deux bouts de code bloquants. Certes, ça ne marche qu’en 2.7 et c’est pas hyper performant, mais on peut faire des trucs mignons du genre cette démo qui mélange flask et WAMP.

C’est du pur Python, pas de process externe à gérer, c’est presque simple.

Bref, si on veut utiliser WAMP avec une app synchrone comme flask, c’est un bon moyen de s’y mettre. On aura jamais des perfs fantastiques, mais on peut pusher vers le browser.

Du coup je me suis demandé si on pouvait faire ça avec Django.

Évidement, ça a été un peu plus compliqué car par défaut runserver lance plusieurs workers et fait un peu de magie avec les threads. Mais après un peu de bidouillage, ça marche !

On peut utiliser WAMP, directement dans Django.

Suivez le guide

D’abord, on installe tout le bouzin (python 2.7, souvenez-vous) :

pip install crossbar crochet django

Il vous faudra un Django 1.7, le tout dernier, car il possède une fonctionnalité qui nous permet de lancer du code quand tout le framework est chargé.

Vous vous faites votre projet comme d’hab, et vous ouvrez le fichier de settings et au lieu de mettre votre app dans INSTALLED_APPS, vous rajoutez ça :

INSTALLED_APPS = (
    '...',
    'votreapp.app.VotreAppConfig'
)

Puis dans le module de votre app, vous créez un fichier app.py, qui va contenir ça:

# -*- coding: utf-8 -*-
 
import crochet
 
from django.apps import AppConfig
 
# On charge l'objet contenant la session WAMP définie dans la vue
from votreapp.views import wapp
 
class VotreAppConfig(AppConfig):
    name = 'votreapp'
    def ready(self):
        # On dit a crochet de faire tourner notre app wamp dans sa popote qui
        # isole le reactor de Twisted
        @crochet.run_in_reactor
        def start_wamp():
           # On démarre la session WAMP en se connectant au serveur
           # publique de test
           wapp.run("wws://demo.crossbar.io/ws", "realm1", start_reactor=False)
        start_wamp()

On passe à urls.py dans lequel on se rajoute des vues de démo :

    url(r'^ping/', 'votreapp.views.ping'),
    url(r'^$', 'votreapp.views.index')

Puis dans notre fichier views.py, on met :

# -*- coding: utf-8 -*-
 
import uuid
 
from django.shortcuts import render
 
import crochet
 
# Crochet se démerde pour faire tourner le reactor twisted de
# manière invisible. A lancer avant d'importer autobahn
crochet.setup()
 
from autobahn.twisted.wamp import Application
 
# un objet qui contient une session WAMP
wapp = Application()
 
# On enrobe les primitives de WAMP pour les rendre synchrones
@crochet.wait_for(timeout=1)
def publish(topic, *args, **kwargs):
   return wapp.session.publish(topic, *args, **kwargs)
 
@crochet.wait_for(timeout=1)
def call(name, *args, **kwargs):
   return wapp.session.call(name, *args, **kwargs)
 
def register(name, *args, **kwargs):
    @crochet.run_in_reactor
    def decorator(func):
        wapp.register(name, *args, **kwargs)(func)
    return decorator
 
def subscribe(name, *args, **kwargs):
    @crochet.run_in_reactor
    def decorator(func):
        wapp.subscribe(name, *args, **kwargs)(func)
    return decorator
 
# Et hop, on peut utiliser nos outils WAMP !
 
@register('uuid')
def get_uuid():
    return uuid.uuid4().hex
 
@subscribe('ping')
def onping():
    with open('test', 'w') as f:
        f.write('ping')
 
# Et à côté, quelques vues django normales
 
def index(request):
    # pub et RPC en action côté Python
    publish('ping')
    print call('uuid')
 
    with open('test') as f:
        print(f.read())
    return render(request, 'index.html')
 
def ping(request):
    return render(request, 'ping.html')

Après, un peu de templating pour que ça marche…

Index.html :

{% load staticfiles %}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>
       UUID
    </title>
 
    <script src="{% static 'autobahn.min.js' %}"></script>
    <script type="text/javascript">
      var connection = new autobahn.Connection({
         url: "ws://localhost:8080/ws",
         realm: "realm1"
      });
 
     connection.onopen = function (session) {
 
        session.call("uuid").then(function (uuid) {
          var p = document.getElementById('uuid');
          p.innerHTML = uuid;
        });
     };
 
     connection.open();
    </script>
</head>
<body>
<h2>UUID</h2>
<p id="uuid"></p>
</body>
</html>

ping.html :

{% load staticfiles %}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>
       Ping
    </title>
 
    <script src="{% static 'autobahn.min.js' %}"></script>
    <script type="text/javascript">
      var connection = new autobahn.Connection({
         url: "ws://localhost:8080/ws",
         realm: "realm1"
      });
 
     connection.onopen = function (session) {
 
        session.subscribe("ping", function () {
          var ul = document.getElementById('ping');
          var li = document.createElement('li');
          li.innerHTML = 'Ping !'
          ul.appendChild(li);
        });
     };
 
     connection.open();
    </script>
</head>
<body>
<h2>Ping me !</h2>
 
<ul id="ping">
</ul>
</body>
</html>

On ouvre la console, on lance son routeur :

    crossbar init
    crossbar start

On lance dans une autre console son serveur Django :

./manage.py runserver

Et si on navigue sur http://127.0.0.1:8000, on récupère un UUID tout frais via RCP.

On peut aussi voir dans le shell que ça marche côté Python :

94cfccf0899d4c42950788fa655b65ed
ping

D’ailleurs un fichier nommé “test” est créé à la racine du projet.

Et si on navigue sur http://127.0.0.1:8000/ping/ et qu’on refresh http://127.0.0.1:8000 plusieurs fois, on voit la page se mettre à jour.

Achievement unlock : use WAMP and Django code in the same file.

A partir de là

Il y a plein de choses à faire.

On pourrait faire une lib qui wrap tout ça pour pas à avoir à le mettre dans son fichier de vue et qui utilise settings.py pour la configuration.

Il faut tester ça avec des setups plus gros pour voir comment ça se comporte avec gunicorn, plusieurs workers, le logging de Django, etc. Je suis à peu près sûr que les callbacks vont être registrés plusieurs fois et ça devrait faire des erreurs dans les logs (rien de grave ceci dit).

On pourrait aussi adapter le RPC pour qu’il utilise les cookies d’authentification Django, et pouvoir les protéger avec @login_required.

Mais un monde d’opportunités s’offrent à vous à partir de là.

Moi, ça fait 6 h que je taffe dessus, je vais me pieuter.


Télécharger le code de l’article