PROJET AUTOBLOG


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

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

⇐ retour index

Mise à jour

Mise à jour de la base de données, veuillez patienter...

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

S’assurer que SELinux ne bloque pas un programme 7

samedi 3 janvier 2015 à 12:48

Les distros Linux récentes viennent souvent avec des surcouches de protections type policykit, apparmor, SELinux, etc.

Tout ça est bien loin de la simplicité de la notion user/group et leurs permissions, et peut mener à un arrachage de cheveux en règle.

Cette après midi par exemple, on avait notre backend qui ne répondait pas sur le serveur tout neuf. On a changé toutes les permissions, passé de sock:// à http://, tweaké les fichiers de settings de iptables, varnish, nginx, gunicorn, supervisor, django… Puis en dernier recours, on a viré toutes les couches une par une, desinstaller les programmes, réinstaller à neuf, reset les fichiers de config en valeur par défaut, créé des fichiers de config minimaux pour toute la stack, nada. La journée frustrante et foutue en l’air.

On a quand même isolé le point de blocage sur varnish, et après avoir fait tous les tests possibles et des sacrifices humains, Max a eu l’intuition SELinux.

Voici maintenant ce qui se passe généralement dans cette situation.

Dans un élan de colère, l’utilisateur impatient fera un rapide test pour savoir si SELinux le protège comme les USA ont protégé les irakiens :

echo 0 >/selinux/enforce

Ça désactive tout le bouzin jusqu’au prochain reboot, mettre 1 inversant la tendance.

Lançant son programme et voyant que ça marche, notre dev échaudé se fend de l’équivalent du chmod 777 -R pour SELinux, à savoir éditer le fichier de config (/etc/sysconfig/selinux sur Centos) et changer rageusement SELINUX=enforcing pour SELINUX=disabled. Reboot and forget.

Une solution radicale et efficace, certes, mais qui laisse quand même un petit poids sur le conscience : diantre, il y a sûrement un moyen propre d’essuyer toute cette merde. Où sont les 3 coquillages ?

La solution est dans un petit programme appelé audit2allow, qui fait parti d’un package plus gros qu’il vous faudra trouver pour votre OS. Sous CentOs, puisque c’était notre config ce jour là, on a du faire un petit :

yum install policycoreutils-python

Ensuite, on lance notre audit :

audit2allow -a -w

Et après un peu de moulinage, il va vous sortir tout un listing de blocage, et quoi faire pour s’en sortir. Un petit grep votre_prog sur la sortie et vous obtenez ceci :

type=AVC msg=audit(1331500393.450:25): avc:  denied  { name_connect } for  pid=1276 comm="varnishd" dest=81 scontext=unconfined_u:system_r:varnishd_t:s0 tcontext=system_u:object_r:reserved_port_t:s0 tclass=tcp_socket
        Was caused by:
        One of the following booleans was set incorrectly.
        Description:
        Allow varnishd to connect to all ports, not just HTTP.

        Allow access by executing:
        # setsebool -P varnishd_connect_any 1
        Description:
        Allow system to run with NIS

        Allow access by executing:
        # setsebool -P allow_ypbind 1

Nous, c’était varnish qui était bloqué, comme le confirme le discret denied dans ce gros blog d’informations illisibles. A croire que les gars de SELinux ont tout fait pour créer la techno la moins user friendly possible. Je pense que la ligne ne commence pas par “AVC” pour rien, chaque fois que SELinux bloque un truc, un devops stagiaire meurt quelque part.

Il suffit ensuite de suivre docilement les instructions. Pour ce cas, on rentre avec les droits admin la commande :

setsebool -P varnishd_connect_any 1

Et on est bon.

Enfin, jusqu’à la prochaine après midi de debug incompréhensible qui vous fera revenir à SELinux. Max est beaucoup tenté par la solution 1 pour éviter une nouvelle galère.

Les articles les plus importants sur Python 6

vendredi 2 janvier 2015 à 07:54

J’ai beau régulièrement pointer vers la section Cours et tutos, je sens bien que les gens n’en bénéficient pas autant qu’ils le devraient.

Une des raisons est la quantité d’info à lire.

Ok, voici donc une sélection d’articles, qui ne sont pas forcément orientés débutants. Ce sont les trucs à maîtriser. Pas tout de suite. Pas tout d’un coup. Mais au final, un programmeur Python doit savoir ça.

La notation de version SemVer 7

jeudi 1 janvier 2015 à 07:52

Il y a des tas de manières d’indiquer la version d’un logiciel.

Ubuntu utilise l’année et le mois, à l’envers : la 14.04 indique une version sortie en avril 2014.

Avant la 1.2, angular utilisait les versions paires pour signifier la stabilité, et impaire pour l’instabilité.

Quelques projets utilisent des numéros de branche Git ou Svn.

Certains utilisent des noms du genre “kangourou-cosmique” pour marquer le coup.

Et d’autres mélangent plusieurs techniques.

En Python, la méthode recommandée est nommée SemVer, pour Semantic Versioning.

Explications

Tout est basé sur la notion d’API publique. Si vous ne vous souvenez pas de ce qu’est une API publique, c’est par ici.

Une bibliothèque versionnée avec SemVer doit clairement documenter toute son API publique pour dire : voici les bouts de codes que vous pouvez utiliser sans risque.

Beaucoup de devs ne font pas, tout comme on fait souvent du REST non pur, on fait souvent du SemVer non pur, mais je vous le dis sinon des puristes viendront jouer de la moustache en commentaires.

Le principe est simple : votre versioning est noté x.y.z, x, y et z étant des entiers positifs. A moins que le nombre soit 0, aucun nombre ne doit commencer par 0.

Le x représente une version majeure, qui introduit au moins un changement incompatible dans l’API publique.

Le y représente une version mineure, qui introduit au moins une nouvelle fonctionnalité dans l’API publique.

Le z représente une correction de bug ou un changement dans l’API privée.

Et c’est tout.

En pratique

Si vous réparez un bug, z augmente de 1.

Ex : 1.3.4 devient 1.3.5

Si vous introduisez un nouveau paramètre optionnel, une fonction outil indépendante, un nouvel attribut public, y augmente de 1, et z est remis à 0 :

Ex : 1.3.4 devient 1.4.0

Si une fonction retourne maintenant ne serait-ce qu’un tuple au lieu de précédemment une liste, x augmente de 1, y et z sont remis à 0 :

Ex : 1.3.4 devient 2.0.0

FAQ

Le numéro de version va monter très vite non ?

On ne change le numéro de version que pour une version publiée. Si vous faites plein de petits changements mais que vous ne déclarez pas ce nouveau code comme la dernière version officielle, pas besoin de changer de version : vous êtes encore en développement.

N’oubliez pas aussi que c’est VOTRE job de vous assurez que votre programme reste compatible. Faites des adapters, des alias, introduisez les fonctionnalités de manière optionnelle, depréciation à l’avance… Il y a des moyens de faire ça proprement.

C’est quand même over chiant pour une lib qui vient de sortir et que personne n’utilise, non ?

Jusqu’à la version 1.0.0, le SemVer ne s’applique pas. En 0.y.z, on peut faire du freestyle.

Mais si je suis en beta ?

On peut signaler des informations supplémentaires en mettant un tiret après son versioning. Le style est libre mais la convention est généralement x.y.z-status. Ex : 1.3.3-alpha, 1.3.3-beta, 1.3.3-rc1, etc.

Attend, je fais une release parfaitement compatible, mais avec une fonction publique qui ne retourne plus 1 mais True, je vais pas bumper pour ça quand même ?

Si. Si vous ne voulez pas le faire, mettez le dans une branche à part, et attendez une release plus grosse pour introduire ce changement.

J’ai une correction de bug qui introduit une changement incompatible dans l’API publique, je fais quoi ?

On bump. Ça n’arrive pas si souvent que ça qu’un bug change l’API publique, faut pas charrier.

Ouais, genre tu le fais…

Non, je ne fais pas du SemVer pur. Mais sur une lib qui est très utilisée, c’est un système indispensable. Si j’avais la responsabilité de pondre Django, ouais, je ferais du SemVer pur.

Ok, je l’ai fais mais j’ai pas l’impression d’y gagner quoi que ce soit.

Le bénéfice n’est pas pour l’auteur, mais pour les utilisateurs. Si un autre utilisateur à votre lib comme dépendance, il peut déclarer dans son fichier setup.py:

requires=['votrelib>=1.3.1<2.0.0']

Ainsi il est certain que ça ne pétera pas si vous faites votre boulot correctement car la lib installée par pip sera toujours dans une version entre la 1.3.1 qu’il sait compatible, jusqu’à la 2.0.0 qui sera incompatible.

IndexError : plateforme de Questions/Réponses pour Python en français 23

mercredi 31 décembre 2014 à 16:34

Quand des gens demandent de l’aide en comments, on les invite à aller plutôt sur des forums. Les commentaires sont pas pratiques pour ça.

La nouvelle qu’elle est cool

Aujourd’hui Max a installé un petit outil (PHP, mais bon, on va pas faire la fine bouche) genre mini-stackoverflow en beaucoup plus simple :

IndexError

Vous pouvez maintenant poser vos questions techniques dessus, ou répondre à celles des autres. Le contenu est placé sous la même licence que le blog, en creative 3.0 unported.

Je vais traîner un peu dessus, et faire ce que je ne pouvais pas faire auparavant, donner un coup de main. Mais ne prenez pas ça pour une ligne directe en SAV gratuit avec Sam. On a plutôt envie qu’une petite communauté s’entraide, SamOS n’est pas scalable pour ce genre de charge.

La clause en tout petit en bas du contrat

L’espace de discussions est uniquement fait pour parler de l’écosystème Python en français. Pas de cul, de Javascript ou autres trucs déviants ;)

Ça laisse quand même de la marge : Blender, Raspberry, Django, Pygame, Twisted, OpenCV, Scipy, Virtualenv, OpenStack, WAMP, ArcGIS… Y a du Python partout !

Le site est lent – et la raison est simple – on est hébergé sur la même petite instance que le blog pour des raisons de budget. Si il commence à y avoir un peu de monde, on migrera le truc sur quelque chose de plus gros. En attendant, on va croiser les doigts et serrer les fesses, espérant que ça nous explose pas à la tronche dès qu’il y a 3 clampins dessus.

Néanmoins, si ça arrive, ça va coûter des thunes. Contrairement au blog qui ne verra jamais un morceau d’AdSense par principe, il faudra bien trouver un moyen de payer ces frais. Donc en gros, si on commence à avoir du traf, y aura sûrement de la pub quelque part. J’annonce avant qu’on se fasse tomater la gueule sur scène… Ce truc n’est pas comme 0bin, ça fait des requêtes en masse, ça envoie de mails, ça consomme quoi.

On sépare donc bien la partie blog, qui reste un espace de liberté où on peut parler de sodomie sans se taper une pop under, et IndexError, quelque chose de plus classique.

Error happened! 0 - count(): Argument #1 ($value) must be of type Countable|array, null given In: /var/www/ecirtam.net/autoblogs/autoblogs/autoblog.php:428 http://ecirtam.net/autoblogs/autoblogs/sametmaxcom_a844ada43a979e3b1395ab9acb6afafb84340999/?80 #0 /var/www/ecirtam.net/autoblogs/autoblogs/autoblog.php(999): VroumVroum_Blog->update() #1 /var/www/ecirtam.net/autoblogs/autoblogs/sametmaxcom_a844ada43a979e3b1395ab9acb6afafb84340999/index.php(1): require_once('...') #2 {main}