PROJET AUTOBLOG


Sam et Max

source: Sam et Max

⇐ retour index

Mise à jour

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

Hic

mercredi 17 avril 2013 à 01:41

Bon, il est 1 h du mat, et je suis bourré.

N’ayant pas pondu l’article quotidien, j’ai tenté une approche artisanale en rentrant du bar pour taper un truc vite fait mais j’ai l’impression d’avoir un clavier en VREFTY.

Bref, je crois qu’il vaut mieux que je tape rien plutôt que de la merde.

Et je me dis qu’il vaut mieux ne rien publier que cet article inutile, mais en même temps ça fait 20 minutes que j’essaye de le taper, alors maintenant qu’il est là, j’ai pas envie de le jetter.

Pardon aux familles, tout ça.

flattr this!

Décorateur de trace

lundi 15 avril 2013 à 19:33

(Ceci est un post invité de xonop sous licence creative common 3.0 unported.)

Suite aux superbes articles sur les décorateurs et sur l’écriture des logs en python j’ai voulu mettre en pratique dans mon projet.
C’est là que les ennuis ont commencé !

Objectif :

Créer un décorateur qui me permette de tracer le passage dans certaines méthodes.
Celui-ci doit :

Première étape : le logger

Avant toute chose mettons en place l’environnement pour pouvoir tracer en utilisant le module logging.
Pour simplifier les exemples, nous associons un seul handler de type terminal.
La fonction log_debug permet de faire appel au logger pour tracer une information.

logger.py :

import functools
import logging
 
__DECO_ACTIVATED = True
__logger = None
 
def init_logger():
    global __logger
    __logger = logging.getLogger()
    __logger.setLevel(logging.DEBUG)
    terminalHandler = logging.StreamHandler()
    terminalHandler.setLevel(logging.DEBUG)
    __logger.addHandler(terminalHandler)
 
def log_debug(text):
    __logger.debug(text)

Deuxième étape : le décorateur, version basique

Pour commencer, le décorateur débrayable :

logger.py :

def log_decorator(func):
    if not __DECO_ACTIVATED:
        return func
 
    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        __logger.debug("BEGIN")
        data = func(*args, **kwargs)
        __logger.debug("END")
        return data
    return wrapped

Remarques :

Et maintenant un module pour tester ça :

main.py :

import logger
 
class Generic():
    @logger.log_decorator
    def do_it(self, arg1):
        logger.log_debug(arg1)
 
if __name__ == "__main__":
    logger.init_logger()
    generic = Generic()
    generic.do_it("NOW")

Et voilà le résultat :

BEGIN
NOW
END

Troisième étape : les noms de module et de fonction

Ces informations se récupèrent facilement, voici la nouvelle version du décorateur :

logger.py :

def log_decorator(func):
    if not __DECO_ACTIVATED:
        return func
    module_name = func.__module__    func_name = func.__name__ 
    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        msg = "Module={} Function={}".format(module_name, func_name)        __logger.debug("BEGIN " + msg)
        data = func(*args, **kwargs)
        __logger.debug("END " + msg)
        return data
    return wrapped

Et maintenant le résultat :

BEGIN Module=__main__ Function=do_it
NOW
END Module=__main__ Function=do_it

Jusqu’ici tout va bien.

Quatrième étape : le nom de la classe

Et c’est maintenant que les choses se corsent !
Tout d’abord, l’objet func que nous manipulons est une fonction et non une méthode de classe :

print(func)
<function Generic.do_it at 0x00C70348>

En effet lors de l’exécution du décorateur, la classe en tant qu’objet n’existe pas encore.
Il n’y a pas de lien direct entre la fonction et sa future classe.

Bon nombre de développeurs bien intentionnés nous conseillent d’utiliser self pour déterminer sa classe.

Oui mais :

Pour s’en convaincre, voici le nouveau décorateur :

logger.py :

def log_decorator(func):
    if not __DECO_ACTIVATED:
        return func
    module_name = func.__module__
    func_name = func.__name__
 
    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        try:            class_name = args[0].__class__.__name__        except IndexError:            class_name = ""        msg = "Module={} Class={} Function={}".format(            module_name, class_name, func_name)        __logger.debug("BEGIN " + msg)
        data = func(*args, **kwargs)
        __logger.debug("END " + msg)
        return data
    return wrapped

Et maintenant le module de tests :

main.py :

import logger
 
class Generic():
    @logger.log_decorator
    def do_it(self, arg1):
        logger.log_debug(arg1)
 
class Specific(Generic):    @logger.log_decorator    def do_it(self, arg1):        super().do_it(arg1) 
if __name__ == "__main__":
    logger.init_logger()
    specific = Specific()    specific.do_it("NOW")

Et le résultat tant attendu :

BEGIN Module=__main__ Class=Specific Function=do_it
BEGIN Module=__main__ Class=Specific Function=do_it
NOW
END Module=__main__ Class=Specific Function=do_it
END Module=__main__ Class=Specific Function=do_it

Et là c’est le drâme, on a perdu la classe Generic ! Mais analysons plutôt l’exécution :

Comme prévu, la classe de l’objet self ne change pas que l’on soit dans une méthode de la classe ou de l’une de ses super-classes.

Quatrième étape : autre approche

Ne pouvant déterminer directement la classe d’appartenance de la fonction, essayons de la chercher dans le module.
Pour commencer il nous faut l’objet module alors que nous ne connaissons que son nom.
Le module inspect propose justement ce service grâce à getmodule.
Voici le décorateur modifié :

logger.py :

import inspect
 
def log_decorator(func):
    if not __DECO_ACTIVATED:
        return func
    module_name = func.__module__
    module_obj = inspect.getmodule(func)    class_name = "UNKNOWN"    for key, obj in module_obj.__dict__.items():        try:            members = obj.__dict__            method = members[func.__name__]            if method == func:                class_name = key                break        except (KeyError, AttributeError):            pass    func_name = func.__name__
 
    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        msg = "Module={} Class={} Function={}".format(
            module_name, class_name, func_name)
        __logger.debug("BEGIN " + msg)
        data = func(*args, **kwargs)
        __logger.debug("END " + msg)
        return data
 
    return wrapped

Pour rechercher la fonction, le décorateur doit :

Nous obtenons alors :

BEGIN Module=__main__ Class=UNKNOWN Function=do_it
BEGIN Module=__main__ Class=UNKNOWN Function=do_it
NOW
END Module=__main__ Class=UNKNOWN Function=do_it
END Module=__main__ Class=UNKNOWN Function=do_it

Pas glop ça marche pas !
Regardons le contenu du module avant la recherche :

print(module_obj.__dict__.keys())
dict_keys(['__builtins__', '__name__', '__file__', '__doc__', '__loader__',
           '__cached__', 'logger', '__package__'])

Curieusement les classes n’apparaissent pas, mais c’est tout à fait normal.
Comme vu précédemment, lors de l’exécution du décorateur la classe est en instance de création.
Il faut donc faire cette recherche lors de l’exécution de la fonction wrappée.

Le décorateur devient donc :

logger.py :

def log_decorator(func):
    if not __DECO_ACTIVATED:
        return func
    module_name = func.__module__
    module_obj = inspect.getmodule(func)
    func_name = func.__name__
 
    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        class_name = "UNKNOWN"        for key, obj in module_obj.__dict__.items():            try:                members = obj.__dict__                method = members[func.__name__]                if method == func:                    class_name = key                    break            except (KeyError, AttributeError):                pass        msg = "Module={} Class={} Function={}".format(            module_name, class_name, func_name)        __logger.debug("BEGIN " + msg)
        data = func(*args, **kwargs)
        __logger.debug("END " + msg)
        return data
 
    return wrapped

Et le résultat :

BEGIN Module=__main__ Class=UNKNOWN Function=do_it
BEGIN Module=__main__ Class=UNKNOWN Function=do_it
NOW
END Module=__main__ Class=UNKNOWN Function=do_it
END Module=__main__ Class=UNKNOWN Function=do_it

Pas glop 2 le retour !
Faisons appel au débogueur suprême : print

key    = Generic
method = <function Generic.do_it at 0x00D07C90>
func   = <function Specific.do_it at 0x00D07CD8>
 
key    = Specific
method = <function Specific.do_it at 0x00D07D20>
func   = <function Specific.do_it at 0x00D07CD8>

Effectivement les références ne correspondent pas, et encore une fois, rien de plus normal.
La fonction d’origine a été wrappée donc celle présente dans le module n’est plus celle d’origine.
Qu’à cela ne tienne, recherchons-là !

Après la déclaration de la fonction wrapped, le décorateur la mémorise dans la variable wrapped_function.
Elle sera utilisée à l’exécution de la fonction.

logger.py :

def log_decorator(func):
    if not __DECO_ACTIVATED:
        return func
    module_name = func.__module__
    module_obj = inspect.getmodule(func)
    func_name = func.__name__
 
    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        class_name = "UNKNOWN"
        for key, obj in module_obj.__dict__.items():
            try:
                members = obj.__dict__
                method = members[func.__name__]
                if method == wrapped_function:                    class_name = key
                    break
            except (KeyError, AttributeError):
                pass
        msg = "Module={} Class={} Function={}".format(
            module_name, class_name, func_name)
        __logger.debug("BEGIN " + msg)
        data = func(*args, **kwargs)
        __logger.debug("END " + msg)
        return data
 
    wrapped_function = wrapped    return wrapped

Et maintenant le résultat :

BEGIN Module=__main__ Class=Specific Function=do_it
BEGIN Module=__main__ Class=Generic Function=do_it
NOW
END Module=__main__ Class=Generic Function=do_it
END Module=__main__ Class=Specific Function=do_it

Ouf ! Ca marche !

Au menu du prochain épisode : logger la valeur de certains paramètres passés à la fonction décorée.

Xavier O. avec l’aide précieuse de Laurent B.

flattr this!

Appeler une fonction fabric, hors d’un fichier fabfile

dimanche 14 avril 2013 à 19:57

C’est con mais c’est bon à savoir : si vous avez une un tâche fabric, vous pouvez tout à fait l’appeler en dehors de fabfile dans n’importe quel script Python.

Il suffit de faire dans ce script :

from fabric.api import run, execute, env
from fabfile import la_tache
 
execute(la_tache)

Et on peut changer n’importe paramètre en le passant en keyword à execute. Par exemple pour changer l’host :

hosts = ['user@serveurdistant.com', ...]
execute(la_tache, hosts=[host])

flattr this!

Soyez paranos avec les codes de suppression

samedi 13 avril 2013 à 23:25

L’informatique est généralement une discipline rationnelle (sauf bien entendu dans le cas de l’administration d’un système Windows), et pondre un bout de code pour un cas d’utilisation qui n’aura jamais lieu n’est pas vraiment la qualité première qu’on demande à un développeur.

Sauf. Sauf dans un cas.

Dans le cadre d’un code de suppression.

Au delà de RM

Un code de suppression peut se cacher derrière bien plus qu’un remove :

C’est valable dans les scripts shell, mais également dans les scripts Python (ou tout langage qui vous sert à faire des opérations sur des systèmes critiques) : os.remove, os.rename, shutil.move, open('file', 'w').write('...'), etc.

Mais c’est aussi toutes les requêtes SQL en écriture.

Les codes à surveiller le plus sont ceux qui peuvent entrainer une suppression massive :

Mais tous les autres restent importants si tant est que la donnée cible est critique (contenu de l’utilisateur, donnée non récupérable par un autre moyen ou alors de manière lente et coûteuse). Même quand vous avez un backup dont vous êtes certains de la perfection – et franchement je me méfie toujours des backups – une restauration, c’est long, ça implique souvent une interruption de service ou au moins une dégradation, et de toute façon c’est du taff dont vous n’aviez vraiment pas besoin.

Pensez au petit fils de Murphy et François Perrin

Quand on a un petit site ou un logiciel en local de taille moyenne, on ne pense pas à ce genre de choses. Mais quand on travaille avec des grosses DB qui sont tout le temps sollicitées et des systèmes de fichiers qui sont blindés de Tera octets de contenus qui prennent des semaines à uploader sur un serveur (j’ai bien dis des SEMAINES), on ne veut pas que ça disparaisse.

La réplication et le load balancing peuvent vous sauver d’un plantage matériel ou d’un bug logique. Mais une corruption de données en cascade, ça se propage à tout un système. Et un RM massif, c’est exactement ça, surtout à l’heure des surcouches d’abstraction d’outils de haut niveau automatiques.

Alors vous allez me dire que les chances que ça arrive sont minimes. En fait votre code est propre, vous faites des checks normaux, il n’y a pas d’injections possibles, tout est sanitizé, bref, il n’y a pas de raison que ça chie.

Et vous n’auriez pas tort, si il n’y avait pas 2 importantes particularité du monde de l’informatique :

Et comme la gravité des conséquences d’une merde de suppression massive est du genre “Code Rouge” dans “28 semaines plus tard”, vous ne pouvez pas vous permettre de ne pas blinder les suppressions.

Quelques scénarios à la con qui peuvent vous tuer sur un code aussi con que ça :

from fabric import cd, sudo
 
from django.conf import settings
 
 
...
 
for content in contents:
 
    if not content.published:
 
        with cd(settings.MEDIA_ROOT):
            sudo('rm -fr {}'.format(content.path))

A priori, rien de dangereux : vous contrôlez vos settings, vous contrôlez votre base de données, pas de malveillances, vous vérifiez que le contenu à expiré. Que peut-il arriver de mauvais ?

Plein de choses :

Le plus drôle, c’est quand plusieurs trucs improbables se cumulent, là on peut faire des combos qui déchirent bien toute l’infrastructure.

Les tests unitaires et la sanitization en vous protégera pas de la fatigue, inattention, l’éternuement au dessus en pleine session SSH, le chat qui saute sur le clavier… Souvenez-vous de la mouche dans Brazil.

Vous protéger contre vous même

Sur des codes comme ça, il faut donc prendre des mesures supplémentaires. Faire les vérifications qu’on ne fait jamais, de ce qui ne peut pas arriver.

 
import re
 
from fabric import cd, sudo
 
from django.conf import settings
 
 
...
 
for content in contents:
 
    # stylistiquement on ne fait jamais ça en Python, mais ici, on ne prend
    # pas le risque
    if content.expired == False: # None != False
 
        # on évite autant que possible les CD qui peuvent merder grave
        path = os.path.join(settings.MEDIA_ROOT, content.path)
 
        # on check strictement le chemin qu'on va degommer avec une regex qui
        # ne laisse pas la place "/", "/bidule/*" et autre ".."
        # et qui réplique au plus prêt la structure de l'arbo qu'on vise
        if re.match(r'^regex_de_nazi$', path):
            sudo('rm -fr {}'.format(path))

Idéalement il faudrait avoir les droits nécessaires pour éviter le sudo. On peut même exceptionnellement casser le DRY en mettant MEDIA_ROOT en dur dans cette fonction. C’est à double tranchant, mais on ne vit pas dans un monde idéal.

Une regex qu’on a en production ressemble à ça:

re.match(r’^/[\w-]+/[\w-]+/\d/\d/\d/\d/\d/\d+$’, path):

Qui ne laissera passer qu’une suppression d’un truc qui à la forme “/1nom-de_dossier/una_autre-nom/8/1/9/3/5/890709″. Pas une arbo plus courte ou plus longue. Pas d’étoiles ou de guillemets. Pas un dossier qui ne contienne pas exactement cet enchaînent de sous-dossiers numériques.

Et ce, malgré le fait que path est généré depuis un ID extrait d’un auto ID en DB (non accessible dans l’admin). Parce qu’on ne sait jamais. Et on ne peut pas se permettre que ça pète.

Pareil pour le SQL, n’hésitez pas à faire des requêtes en plus avant le DELETE pour voir si les données sont cohérentes. La plupart du temps on s’en branle si une suppression est lente.

Bref, les bonnes pratiques que sont de bien nettoyer ses données, de bien setter ses permissions et de ne pas tout lancer à 3 heures du matin après une session de debugging de 6 heures au Red Bull sont toujours des priorités. Cependant souvenez vous que vous êtes humains, vous allez faire des conneries, les autres aussi.

Vous allez avoir un système imparfait (non, vous n’avez pas mis des checks et des convertions partout, le 100% ça ne marche que dans les labos de recherche, pas quand on a une dead line dans 15 jours).

Alors soyez paranos avec les codes de suppression.

Ce n’est pas une grosse partie du code, ça ne va pas tuer votre productivité ou plomber votre maintenance, mais contrairement aux autres snippets, vous n’aurez probablement pas de seconde chance pour le debug.

C’est l’histoire d’un mec…

Bon ok, de 3 mecs dans un bagnole : un matheux, un physicien et un informaticien.

A la sortie d’un virage, la voiture dérape et s’arrête deux roues dans le vide.

Les 3 compères sortent de la voiture, un peu choqués, et commencent à discuter :

Le mathématicien : “Il faudrait qu’on calcule la probabilité que ça nous arrive sur ce virage, avec cette voiture, dans ces conditions climatiques.”

Le physicien : “Il faudrait qu’on calcule le coefficient de friction et notre énergie cinétique pour mieux comprendre ce qui nous est arrivé.”

L’informaticien : “On reprend le virage avec la caisse pour voir si c’est reproductible ?”

flattr this!

10 lieux de baise pas glop

vendredi 12 avril 2013 à 16:15

Remercions la télé pour le nombre de clichés de cul qu’elle a propagé, et notamment toutes ces parties de jambes en l’air torrides dans des endroits bien loin de votre king size bed. Des fois, vouloir se l’a jouer exotique n’a pas l’effet escompté.

Le Hammam

C’était en Espagne, on sortait d’un jacuzzi plein d’oranges (pourquoi il avait foutu des oranges dans le bain ?), et on a rejoint le hammam du Spa. Personne en vu, c’était trop tentant.

Sauf qu’après s’être ramolli dans de l’eau chaudes pleines de bulles, la levrette en rafale dans une salle à 10000 degré dans laquelle on respire de la vapeur d’eau c’est dur. Mon cœur à pas du tout aimé, et j’ai du m’arrêter.

Pour la pipe ça doit être pas mal ceci dit.

La baignoire

Autant la douche, si on a de quoi tenir le pommeau tout seul et un débit d’eau généreux, ça le fait (ma salle de bain est une douche italienne avec une vitre transparente qui donne sur un miroir…). Autant se serrer dans ces putains de baignoires françaises avec les bouts du robinets qui dépassent, c’est mort. Impossible de copuler là dedans.

La plage

Le mythe. Le truc le plus bidon par excellence. Supposons que l’on ne se soit pas baigné et donc pas recouvert de sel, les cheveux dégueulasses et un goût d’algue dans la bouche. Admettons qu’il n’y ai pas d’enfants de 6 ans et leurs parents psychorigides qui traînent (voir qu’on soit sur une plage naturiste). Subodorons que nous sommes à l’ombre, sans le soleil qui rouge la peau et sans cette crème de protection dégueulasse qui dégoûterait un teckel de lécher un téton plein de Nutella.

Ça fait beaucoup de conditions, mais soit.

Il reste le sable. Cette saloperie de sable. Qui se glisse partout, même quand on a une serviette qui reste de toute façon 23 seconds en place avant de rejoindre les crabes pendant que chaque grain de mica vous irrite les zones érogènes que vous avez tenté de frictionner. Et si vous avez le malteur d’en avoir qui se met sur la capote…

Dans le lit d’un pote pendant une soirée

Vous êtes bourré. Elle aussi. Tout le reste de la fête aussi. Alors personne ne va vous voir aller tranquillement dans la chambre à Nico pour un petit quicky. Cette fameuse chambre dans laquelle il y a tous les manteaux entassés de tous les invités.

Vous foutez tout par terre, et faites fît l’odeur de transpiration qui émane des draps que ce petit con d’étudiant en histoire de l’art n’a pas lavé depuis son mémoire sur l’icônification du consumérisme de 1981 à 1983. Vous ignorez aussi les poils torsadés qui traînent ici et là. Nico est bouclé, c’est peut être des cheveux.

Mais la porte ne ferme pas à clé. Et si ça excite peut être mademoiselle cette idée que quelqu’un peut arriver à tout moment, n’empêche que quand le quelqu’un entre dans la salle pour récupérer son briquet dans la poche de sa veste, elle arrête tout. Et ne reprend pas.

Pas assez bourrée, sans doute.

Dans les chiottes d’une boîte

C’est dégueu, ça pu, on peut s’appuyer sur rien. Je dois vous faire un dessin ? Il vaut mieux rien faire que faire ça, sérieux.

Le seul point fort c’est le côté “yes, je viens de rencontrer une nana, et je peux déjà ma l’envoyer tout de suite”. Allez aux putes, ça vous coûtera à peine plus que le prix de l’entrée et des consos.

Dans la voiture

Certes, c’est une nécessité logistique. Et oui, tout le monde l’a fait, et on le refera, parce que des fois il y a juste pas le choix, pas le temps ou la motiv. Mais il n’y a pas de position confortable sur ces sièges pourris. Et on finit toujours par appuyer par erreur sur le klaxon. Sauf quand on est à l’arrière où il manque toujours la place pour un membre : le cul, les jambes, la têtes. Deux maximum au choix.

Ou alors il faut une limo. Là, ok.

Dans la paille

Qu’on se le dise, la paille, ça gratte. Ça gratte pendant, et ça gratte après. On en a dans les cheveux, dans les vêtements, les chaussures… On en retrouve encore des petits bouts le soir, et avec un peu de chance des morceaux dans le lit le lendemain matin. Alors oui c’est confortable. Oui c’est pratique si la miss est pas allergique, mais bon, si on est amateur de blé, autant le faire dans un champ.

En la portant contre un mur

J’ai rien contre les murs. J’adore les murs. Y en a de très bien. On peut faire des tas de choses géniales avec une bonne surface verticale solide et rassurante.

Mais combien de mecs ont la force pour porter une nana pendant tout un coït à la force de ses petits bras ?

Généralement ça donne un truc comme ça:

Étape 1 : lui lancer un regard sauvage, la soulever et la plaquer contre le premier plan opposable qu’on trouve.

Étape 2 : se lancer dans une expédition exploratoire de tout le territoire, occuper les places fortes et adapter son rythme à la campagne.

Étape 3 : comprendre que la meuf n’a aucune intention de s’accrocher avec ses bras et que vos deltoïdes vont faire tout le boulot.

Étape finale : au bout de quelques minutes, ralentir, se rendre compte que ça va finir en flamby et trouver une excuse pour changer de position en gardant la face.

Facultatif : Réaliser que c’est un mur en crépi quand elle hurle que tu lui arrache le dos. Oui, c’est vécu, oui.

Sur ton ordinateur portable neuf

Qu’on avait pas vu sous la couette. Très surfait. Et très cher aussi.

Existe également en version tablette.

Dans une chambre avec un chat

Il regarde. Et il comprend. Et vous savez qu’il regarde et qu’il comprend. Et il sait que vous savez.

Vous pensez à autre chose, et là le chat saute sur le lit pour bien vous rappeler qu’il est là, comme quand vous travaillez sur votre laptop, sauf que là que ne pouvez pas lui donner un coup de latte car c’est pas VOTRE chat.

flattr this!