PROJET AUTOBLOG


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

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

⇐ retour index

Le don du mois : Wikimedia 3

samedi 1 août 2015 à 11:42

Oui, y a des dons plus ballots que d’autres.

Certes, les campagnes de pub de Wikimedia, la fondation derrière Wikipedia, pour collecter les dons me gonflent profondément. J’ai l’impression d’assister au téléthon, mais online.

Mais détail ennuyeux mis à part, j’utilise, comme aujourd’hui presque tout le monde, Wikipedia en tant que base de départ pour aborder de nombreux sujets afin ensuite de glisser vers d’autres sources plus spécialisées.

Beaucoup de choses ont été dites sur le projet : remise en cause de sa fiabilité, de l’intention réelle de ses dirigeants, des spams des lobbyistes qui s’y installent, de la rigidité des règles et des modos ou encore de la courbe d’apprentissage de plus en plus grande pour y participer.

Malgré cela, j’ai contribué plusieurs articles, ai fréquenté des gens qui sont engagés au cœur de de l’outil et ai bénéficié de l’outil à un niveau personnel et professionnel.

C’est un magnifique projet.

Plein de défauts, mais vu l’envergure, comment ne pas en avoir ?

J’espère qu’il vivra le plus longtemps possible. Et je fais une donation en bitcoins d’environ 50 dollars.

Attention au télé-achat 30

jeudi 30 juillet 2015 à 23:54

Dès qu’il y a une tendance, on voit les fanboys sortir leurs dentiers éclatant pour vous faire des démos ventant le produit qui retire toutes les tâches et si vous appelez maintenant il y a 20% de réduction.

Mais tout comme à la télé le magic bullet fait le smoothie en seulement 2 minutes parce que le mec commence la vidéo avec 10 fruits déjà choisis/payés/livrés/lavés/épluchés/découpés, la techno qu’on vous vend a ses travers.

C’est normal. Mais c’est rarement dit.

Ces dernières années, NoSQL, la programmation asynchrone, la programmation fonctionnelle et les projets JS, Node en tête, ont été sur toutes les chaînes : si vous avez pas programmé votre chat avec Mongo + MeteorJS avant 50 ans, vous avez raté votre vie ! Appelez le 5555-5555 et recevez un pins Haskell qui vous créé une copie de votre pull quand vous l’accrochez.

C’est pas qu’il y a une arnaque, c’est juste qu’il y a un truc qu’on appelle la réalité.

Par exemple, aujourd’hui, je tombe sur un projet appelé pymaybe qui implémente le pattern fonctionnel monadique maybe. Je déconne pas, c’est le terme technique.

Je taquine, mais je trouve le projet sincèrement intéressant. Néanmoins la page d’exemple reflète typiquement l’esprit Pierre Bellemare.

Ca commence par un exemple Python soit disant trop long :

def get_user_name():
    current_user = request.user
    if current_user:
        return current_user.name
 
    return ''

Converti en :

def get_user_name() :
    return maybe(request.user).name.or_else('')

Mais c’est très maladroit, car on résout ici un problème qui n’existe pas. En effet, personne écrivant du Python professionnellement (et qui serait donc intéressé par ce projet) n’écrirait le premier exemple ainsi. Un code de prod serait :

def get_user_name():
    return getattr(request.user, 'name', '')

Ce qui est plus court que les deux snippets ci-dessus, immédiatement reconnu par des pairs, et ne demande pas d’ajouter une dépendance externe à son code.

Le second exemple est plus trompeur encore, et commence par cet extrait :

stores = json.get('stores')
if stores:
    products = stores[0].get('products')
    if products:
        product_name = products[0].get('details', {}).get('name') or 'unknown'

Et avec “coder builder 3000″ vous pouvez faire :

product_name = maybe(stores)[0]['products'][0]['details']['name'].or_else('unknown')

Impressionant ! C’est la barre de fer !

Sauf que… Avez-vous remarqué ? Le json.get() est passé à la trappe…

stores = json.get('stores')
product_name = maybe(stores)[0]['products'][0]['details']['name'].or_else('unknown')

Et puis d’où vient maybe() ? Ce n’est pas un builtin… Donc il faut l’importer, comme pour les fruits du mixer qui ne se découpent pas tout seuls.

from pymaybe import maybe
stores = json.get('stores')
product_name = maybe(stores)[0]['products'][0]['details']['name'].or_else('unknown')

Et si vous avez un editeur bien réglé, la 3eme ligne va lever un warning PEP8 : 85 charaters, c’est 5 de trop (sinon on peut écrire tout son script sur une ligne aussi).

from pymaybe import maybe
stores = json.get('stores')
product_name = maybe(stores)[0]['products'][0]['details']['name']
product_name = product_name.or_else('unknown')

Ah, plus qu’une seule ligne d’avantage par rapport à la version originale. Mais une seconde, cette version vous l’écririez ainsi ? Bien entendu que non :

try:
    product_name = json.get('stores')[0]['products'][0]['details']['name']
except (TypeError, KeyError, IndexError):
    product_name = 'unknown'

Ce qui a en prime l’avantage de montrer explicitement les cas dans lequels product_name = 'unknown', plutôt que de devoir savoir ce que fait exactement une lib tierce partie (qui doit en plus être déployée). Et si il y a une erreur en plus qu’il faut gérer ? TypeError ? IoError ? C’est juste une clause de plus. Pour l’autre version, on rajoute 4 lignes.

Attendez, ce n’est pas fini. Il est facile de croire, quand on vient d’un autre langage, que Python est un langage simpliste. Il se traine sa réputation de langage jouet depuis bien longtemps. Erreur classique !

Car Python est très simple à prendre en main, mais possède énormément de profondeur.

__getitem__, anyone ?

(test) >>> class Test:
    def __getitem__(self, value):
        return 1
      ...:     
 
(test) >>> maybe(Test())[0]
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-6-e4a755e77e5b> in <module>()
----> 1 maybe(Test())[0]
 
/home/sam/.local/share/virtualenvs/test/local/lib/python2.7/site-packages/pymaybe/__init__.pyc in __getitem__(self, key)
    212 
    213     def __getitem__(self, key):
--> 214         return maybe(self.__value.get(key, None))
    215 
    216     def __setitem__(self, key, value):
 
AttributeError: Test instance has no attribute 'get'
 
(test) >>> Test()[0]
           1

defaultdict ?

(test) >>> maybe(defaultdict(datetime.utcnow))[0]
           None
 
(test) >>> defaultdict(datetime.utcnow)[0]
           datetime.datetime(2015, 7, 30, 19, 39, 28, 298488)

Cela signifie-t-il que ce projet ne mérite pas d’être suivi ? Pas du tout, le principe est sympas, ça doit être creusé. Simplement il faut se méfier du mimi, du rara, du miracle.

Si vous relisez les articles sur WAMP et crossbar, vous noterez que je souligne clairement les problèmes de la techno : doc, debugging, syntax twisted… Personne ne demande d’être impartial, mais faut pas non plus vendre la vessie de l’ours avant la lanterne des boeufs.

cd multiboards; git checkout versionquimarche 6

mardi 28 juillet 2015 à 18:21

Il y a des mises en prod heureuses. Et d’autres non.

Là c’est non.

Je rollback jusqu’à ce qu’on répare tout ça.

Multiboards est désormais customisable 22

dimanche 26 juillet 2015 à 21:23

J’avais pensé à un titre du genre: “Vous aussi, créez votre propre Multiboards et devenez riche” mais ça risquait de faire légèrement pompeux. On garde donc un titre simple.

Pour ceux qui ne connaissaient pas, Multiboards est une simple page web qui affiche les flux rss de certains sites sélectionnés par nos soins. Après quelques années de service on s’est aperçu que pas mal de monde l’utilisait, on a même eu des suggestions d’améliorations (merci Nain Genieu) et des demandes de libération du code source.
Bon le code c’est pas pour tout de suite car il est super crade et que Sam acceptera jamais de laisser ça sans passer un bon coup de balais factoriseur XR-2200. Mais en attendant vous pouvez créer vos pages d’agrégation de flux persos et les partager avec le monde entier ou votre voisine de palier (celle qui a d’énormes nibards).

Pour créer son board on va sur http://multiboards.net/build et on ajoute ses flux RSS, on peut déplacer les flux dans la grille à sa convenance s’ils n’ont pas été rentrés dans le bon ordre. Lorsque l’on est satisfait avec sa page, on clique sur le bouton vert et on peut voir le mimi, le rara le miracle apparaître sous nos yeux ébahis !

mb_custo

Pour le moment vu que ça a été fait à l’arrache dans les chiottes de l’aéroport de Manille y a pas bcp de fonctionnalités marrantes mais lorsque le code sera libéré on espère avoir de jolies contribs ^^.

Quelques idées pour le futur:

NB:
Le player a été changé aussi, plus de flash et place au HTML5, un petit player maison hyper simple mais qui fait son job.

On attend vos retours, ça doit foisonner de bugs

Utilisateurs en ligne avec le sorted set de Redis 7

samedi 25 juillet 2015 à 10:48

Beaucoup de gens sous exploitent Redis en se cantonnant à un usage clé/valeur, voire pour les plus aguérris à la liste ou le hash.

Mais derrière sa simplicité d’usage, l’outil possède une grande richesse de fonctionnalités comme le PUB/SUB, l’hyperloglog et les sorted sets.

C’est de ces derniers dont on va parler.

En effet, dernièrement j’ai dû mettre en place un petit compteur des visiteurs en ligne, et c’est typiquement quelque chose qui fusille une base de données car ça fait une écriture ou une lecture à chaque changement de vue.

Redis est idéal pour ça, le truc tenant 100 000 écritures / seconde, et perdre 4 secondes d’historique du compteur en cas de crash est le cadet de mes soucis.

Pour compter les visiteurs, il nous faut non seulement un moyen d’incrémenter le compteur, mais aussi de le décrémenter quand le visiteur n’est plus en ligne. J’utiliserais WAMP, Crossbar pourrait me donner l’information à tout moment via l’API meta, mais sur un site HTTP only il n’y a aucun événement qui nous signale “l’utilisateur n’est plus connecté”.

Le seule moyen de savoir qu’un utilisateur n’est plus connecté est de garder une trace de lui et de la faire expirer quand on n’a pas de nouvelle depuis un temps supérieur à une durée raisonnable (calculée avec précision ) 10 minutes par l’INDMS).

Problème, on peut bien faire expirer des clés avec Redis, mais on ne peut pas lister ces clés (la fonction keys() le fait mais avec des perfs désastreuses et l’auteur du logiciel la conseille uniquement pour regarder l’état de son serveur depuis la console). D’un autre côté on a des listes, mais l’expiration concerne toute la liste, pas juste un élément.

Une manière de contourner le problème est d’utiliser un sorted set, une structure de données Redis qui a les caractéristiques suivantes :

En gros, c’est comme un dictionnaire, mais ordonné, qui ressemble à :

{
    'cle': 789789
    'cle2': 78999
    'cle3': 6
}

Comme toutes les structures de données un peu exotiques, on se demande à quoi ça sert d’avoir des caratéristiques aussi précises et ce qu’on peut bien en faire.

La réponse est comme d’hab, “plein de choses”, mais nous on va en faire un compteur :

import datetime
 
import redis
 
# connection à redis
con = redis.StrictRedis()
 
# On appelle cette vue avec ajax, ignorant les
# utilisateurs sans JS qui de toute façon sont des 
# anarco communistes qui ne rapportent
# pas d'argent et fument des joins
# Cette partie dépend de votre framework
@ajax('/counter')
def add_user_count(request):
 
    # la clé d'accès à notre sorted set
    counter_key = "users:online"
 
    # un id unique pour notre visiteur. Ca dépend du
    # framework web
    user_id = request.session.id
 
    # Calcul du timestamp de maintenant et d'il y a 10 minutes
    # car ils vont nous servir de score max et min.
    now = (datetime.utcnow() - datetime(1970, 1, 1)).total_seconds()
    ten_minutes_ago = now - (60 * 10)
 
    # On ajoute l'utilisateur dans le sorted set. S'il existait déjà, son
    # ancienne valeur est remplacée, sinon elle est créée. Comme c'est
    # forcément le plus récent timestamp, il est tout en haut du sorted set
    con.zadd(counter_key, now, user_id)
 
    # Le sorted set ressemble donc à ça :
 
    # {
 
    #     "userid": 809809890, # timestamp de dernière requete
    #     "userid2": 809809885, # timestamp plus ancien
    #     "userid3": 809809880 # timestamp encore plus ancien
    #     ...
    # }
 
    # On retire toutes les entrées du sorted set avec un score
    # qui est plus petit que le timestamp d'il y a 10 minutes
    # histoire de pas saturer la mémoire.
    con.zremrangebyscore(online_user_key, 0, ten_minutes_ago)
 
    # Et on récupére le nombre de visiteurs en ligne entre maintenant
    # et 10 minutes.
    visitors = con.zcount(online_user_key, ten_minutes_ago, now)
 
    return visitors

Notez que toutes ces étapes sont très rapides, que ce soit l’insertion, la suppression ou le compte du total grâce aux fantastiques perfs de Redis. J’aime Redis. Je suce la bite de Redis goulument.

La précision n’est pas parfaite, mais compter les utilisateurs est plus un art (divinatoire) qu’une science et avoir un chiffre précis à 2, 3% prêt est suffisant pour nous.