PROJET AUTOBLOG


Sam et Max

source: Sam et Max

⇐ retour index

Utiliser des UUID comme primary key avec l’ORM de Django

jeudi 6 décembre 2012 à 15:59

Par défaut Django ajoute automatiquement un champ id à tous les modèles, et le configure pour être un entier qui s’auto incrémente puis le désigne comme la clé primaire. Il est néanmoins possible d’utiliser un autre champ comme clé primaire pour sa table: un slug ou un identifiant métier. Dans notre cas, on va voir comment utiliser un UUID.

Un UUID est un identifiant généré de manière pseudo aléatoire qui a une forte probabilité d’être unique dans le monde entier, il y a 4×10³⁷ combinaisons possibles pour les versions des algos récents. Selon le paradoxe des anniversaires, il faudrait créer un milliard de UUID par seconde pendant 100 ans pour que le prochain ait 50% de chance d’être un doublon. Normalement pour votre site de fans club des limules hermaphrodites, ça devrait être suffisant.

Utiliser des UUID possède de nombreux avantages car ils sont plus ou moins garantis d’être uniques, et ainsi:

Pour toutes ces raisons, les bases de données NoSQL (CouchDB, MongoDB, etc) utilisent depuis longtemps les UUID comme clés primaires par défaut. Le moteur de base de données de Google, Big Table, utilise des UUID.

Pourtant les UUID ne sont pas exempts de défauts:

Les UUID ne sont donc pas la solution miracle à tous les soucis, mais ils sont tout de même mon choix par défaut en ce moment ne serait-ce que pour les fixtures. Je gagne les performances sur le caching et le parallélisme, et un peu de lenteur sur les JOINS est quelque chose que je peux supporter. Pour les gros sites, les JOINS sont de tout façon votre ennemi juré et on finit toujours par tweaker à grand coup de dé-normalisation.

On peut faire passer ses modèles Django aux UUID en faisant:

from uuid import uuid4
 
class MotDElle(object):
    ...
    id = models.CharField(max_length=36, primary_key=True,
                          default=lambda: str(uuid.uuid4()), editable=False)

Ou, si vous utilisez django_extensions (et vous devriez, ne serait-ce que pour shell_plus et runserver_plus):

from django_extensions.db.fields import UUIDField
 
class MotDElle(object):
    ...
    id = UUIDField(primary_key=True)

Ce qui a l’avantage de caster la valeur en un objet UUID, et non une bête string, à la lecture.

Malheureusement, pour le moment il n’y a aucune implémentation officielle de Django pour utiliser les UUID. Cela a des conséquences fort ennuyeuses:

Un ticket pendouille depuis 5 ans sur la question. Je vous invite d’ailleurs à le spammer jusqu’à ce que réouverture s’en suive. Et le premier qui me dit “t’as qu’à l’implémenter, toi”, je lui retourne un tampon dans la poire.

Usages et dangers du null object pattern en Python

mardi 4 décembre 2012 à 16:46

Le motif de conception de l’objet nul ou “Null object pattern” dans la langue de Justin Bieber, est une technique qui consiste à créer un objet qui ne fait rien. Et quand on lui demande de faire quelque chose, il se renvoie lui-même.

Voici à quoi ça ressemble en Python:

class Null(object):
 
    _instances = {}
 
    def __new__(cls, *args, **kwargs):
        """
            On s'assure que Null est un singleton, c'est à dire qu'on ne peut
            pas créer deux fois Null. Ainsi, id(Null()) == id(Null()).
 
            Je devrais peut être faire un article sur le Singleton.
        """
        if cls not in cls._instances:
            cls._instances[cls] = super(Null, cls).__new__(cls, *args, **kwargs)
        return cls._instances[cls]
 
 
    def __init__(self, *args, **kwargs):
        """
            Null accepte n'importe quel argument
        """
        pass
 
    def __repr__(self):
        """
            On lui met quand même quelque représentation pour le debug
        """
        return "<Null>"
 
    def __str__(self):
        """
            Null se cast sous forme de chaîne vide. Comme ça vous pouvez
            faire open('fichier').write(Null()) et ça ne fait rien.
        """
        return ""
 
 
    def __eq__(self, other):
        """
            Null est égal à lui même, ou à None car on peut les utiliser dans
            des endroits similaires. On peut toujours les distinguer avec "is".
        """
        return id(self) == id(other) or other is None
 
    # Comme None, Null() est faux dans un contexte booléen
    __nonzero__ = __bool__ = lambda self: False
 
    # Et là, on fait le gros travail d'annihilation: toutes les méthodes
    # et opérations du Null() renvoient Null(). Et comme Null() accepte tout
    # et ne fait rien, toute opération marche toujours, ne fait rien, et
    # renvoie une valeur qui assure que les suivantes feront pareil.
    nullify = lambda self, *x, **kwargs: self
 
    __call__ = nullify
    __getattr__ = __setattr__ = __delattr__ = nullify
    __cmp__ = __ne__ = __lt__ = __gt__ = __le__ = __ge__ = nullify
    __pos__ = __neg__ = __abs__ = __invert__ = nullify
    __add__ = __sub__ = __mul__ = __mod__ = __pow__ = nullify
    __floordiv__ = __div__ = __truediv__ = __divmod__ = nullify
    __lshift__ = __rshift__ = __and__ = __or__ = __xor__ = nullify
    __radd__ = __rsub__ = __rmul__ = __rmod__ = __rpow__ = nullify
    __rfloordiv__ = __rdiv__ = __rtruediv__ = __rdivmod__ = nullify
    __rlshift__ = __rrshift__ = __rand__ = __ror__ = __rxor__ = nullify
    __iadd__ = __isub__ = __imul__ = __imod__ = __ipow__ = nullify
    __ifloordiv__ = __idiv__ = __itruediv__ = __idivmod__ = nullify
    __ilshift__ = __irshift__ = __iand__ = __ior__ = __ixor__ = nullify
    __getitem__ = __setitem__ = __delitem__ = nullify
    __getslice__ = __setslice__ = __delslice__ = nullify
    __reversed__ = nullify
    __contains__ = __missing__ = nullify
    __enter__ = __exit__ = nullify

Certaines méthodes ne peuvent pas retourner Null() car elle sont tenues
par l’implémentation en C de retourner un certain type. C’est le cas
des méthode de conversion, d’itération, d’approximation ou certaines
renvoyant des metadata comme: __int__ , __iter__, __round__ ou __len__.

Donc il y aura toujours des cas extrêmes où Null ne marchera pas, mais croyez moi, avec le code ci-dessus, on a déjà couvert un paquet de trucs.

Comment on s’en sert

C’est la beauté de l’objet Null, il n’y a rien à faire d’autre que l’instancier. Après il se débrouille tout seul… pour ne rien faire !

Null() accepte tout à la création, et renvoie toujours le même objet :

>>> n = Null()
>>> n
<Null>
>>> id(n) == id(Null('value')) == id(Null('value', param='value'))
True

On peut lui demander tout ce qu’on veut, il retourne Null() :

>>> n() == n('value') == n('value', param='value') == n
True
>>> n.attr1
<Null>
>>> n.attr1.attr2
<Null>
>>> n.method1()
<Null>
>>> n.method1().method2()
<Null>
>>> n.method('value')
<Null>
>>> n.method(param='value')
<Null>
>>> n.method('value', param='value')
<Null>
>>> n.attr1.method1()
<Null>
>>> n.method1().attr1
<Null>

On peut le modifier, ça ne change rien :

>>> n.attr1 = 'value'
>>> n.attr1.attr2 = 'value'
>>> del n.attr1
>>> del n.attr1.attr2.attr3

Les opérations sur lui le laissent de glace:

>>> str(n) == ''
True
>>> n + 1 / 7 % 3
<Null>
>>> n[1] == n[:4] == n
True
>>> 'test' in n
False
>>> n['test']
<Null>
>>> Null() >> 1
<Null>
>>> Null() == None
True

Chouetttteeeeeee ! Mais ça sert à quoi ?

Et bien à simplifier les vérifications, et supprimer pas mal de if. Quand vous avez un état inconnu dans votre programme, au lieu de renvoyer None, ou faire des tests dans tous les sens pour éviter de planter, vous pouvez juste renvoyer Null(). Il va continuer sa petite vie dans votre programme, et accepter tout ce qu’on lui demande, sans rien faire crasher.

Attention cependant, Null() accepte tout silencieusement, et donc votre programme ne plantera jamais là où Null() est utilisé. En clair, si vous merdez et que Null() se retrouve là où il ne doit pas, il va se multiplier et remplir tout votre programme et le transformer en une soupe d’appels à Null() qui ne foutent rien, mais le font bien, et en silence.

Un design pattern puissant, mais dangereux.

L’édit obligatoire

Suite aux objections tout à fait valides de Moumoutte, je rajoute un exemple d’usage.

Il faut bien comprendre en effet que Null() n’est pas un passe droit pour faire n’importe quoi avec la gestion des erreurs, et n’est en aucun cas quelque chose à utiliser massivement.

Il est particulièrement utile quand les tests à faire sont juste trop nombreux. Par exemple, dans des secteurs comme la banque ou la biologie, on traite des données en masse, et hétérogènes, mais on fait des opérations entre les différents types.

Certains types sont compatibles, d’autres pas. Si on a des milliers de types, et des milliers d’opérations différentes, cela fait des milliers de milliers de combinaisons possibles à évaluer. Dans ce cas, Null() permet de s’affranchir de tous ces tests et simplifier énormément l’algo.

Voici un exemple simple de ce que ça donne à petit échelle (multipliez ça par mille types et opérations pour avoir un ordre de grandeur d’un cas réel):

# Chaque type peut faire des opérations uniquement sur d'autres types
 
class TypeA(object):
    def operation(self, other):
        if isinstance(other, TypeB):
            return other
        return Null()
 
 
class TypeB(object):
    def operation(self, other):
        if isinstance(other, TypeC):
            return other
        return Null()
 
 
class TypeC(object):
    def operation(self, other):
        if isinstance(other, TypeD):
            return other
        return Null()
 
 
class TypeD(object):
    def operation(self, other):
        if isinstance(other, TypeE):
            return other
        return Null()
 
 
class TypeE(object):
    def operation(self, other):
        if isinstance(other, TypeA):
            return other
        return Null()
 
 
# Votre pool de données vous arrive en masse et complètement mélangé.
# Vous n'avez AUCUN contrôle là dessus.
pool = (TypeA, TypeB, TypeC, TypeD, TypeE)
 
data1 = (random.choice(pool)() for x in xrange(1000))
data2 = (random.choice(pool)() for x in xrange(1000))
data3 = (random.choice(pool)() for x in xrange(1000))
data4 = (random.choice(pool)() for x in xrange(1000))
 
# Avec le Null() object pattern, vous ne vous posez pas la question de quel type
# va avec quoi. Vous faites l'opération comme si tout était homogène. Tout
# va se trier automatiquement.
 
res1 = (z.operation(y) for z, y in zip(data1, data2))
res2 = (z.operation(y) for z, y in zip(res1, data3))
res3 = (z.operation(y) for z, y in zip(res2, data4))
 
# Et à la fin, il est très facile de récupérer uniquement les données
# significatives.
 
print list(x.__class__.__name__ for x in res3 if x)
 
# Ceci va printer quelque chose comme:
# ['TypeD', 'TypeA', 'TypeE', 'TypeE', 'TypeC', 'TypeE', 'TypeE', 'TypeA']

On a ainsi réduit un énorme jeu de données hétérogènes en un petit jeu de données significatif, avec très très peu d’effort et un algo simple et lisible.

Nginx en reverse proxy + Gunicorn pour vos apps Django

lundi 3 décembre 2012 à 20:48

Nginx est bien connu depuis quelques années. Puissant serveur HTTP, il peut être utilisé en tant que reverse proxy (mandataire inverse en Français) afin d’améliorer les performances.
Je vous copie/colle les avantages depuis Wikipédia parce que je suis une feignasse:

cache : le mandataire inverse ou (reverse proxy) peut décharger les serveurs Web de la charge de pages/objets statiques (pages HTML, images) par la gestion d’un cache local. La charge des serveurs Web est ainsi généralement diminuée, on parle alors d’« accélérateur web » ou d’« accélérateur HTTP ».
Intermédiaire de sécurité : le mandataire inverse protège un serveur Web des attaques provenant de l’extérieur. En effet, la couche supplémentaire apportée par les mandataires inverses peut apporter une sécurité supplémentaire. La ré-écriture programmable des URL permet de masquer et de contrôler, par exemple, l’architecture d’un site web interne. Mais cette architecture permet surtout le filtrage en un point unique des accès aux ressources Web.
Chiffrement SSL : le mandataire inverse peut être utilisé en tant que « terminateur SSL », par exemple par le biais de matériel dédié,
Répartition de charge : le mandataire inverse peut distribuer la charge d’un site unique sur plusieurs serveurs Web applicatifs. Selon sa configuration, un travail de ré-écriture d’URL sera donc nécessaire,
Compression : le mandataire inverse peut optimiser la compression du contenu des sites.

Pour résumé:

Visiteur > Web > Votre Serveur > Nginx > Gunicorn > Django

Dans la configuration suivante Nginx va servir les fichiers statiques (images, js, etc) et envoyer le reste des requêtes sur Gunicorn. Gunicorn est une interface WSGI entre le serveur HTTP (nginx) et Python, je n’ai pas trouvé de bonne explication sur WSGI à part le site officiel fait par des autistes.

Soit la configuration de Gunicorn suivante (on peut le lancer avec supervisor):

gunicorn_django  myapp.py --bind 127.0.0.1:8080 --log-file /var/log/myapp.gunicorn.log --log-level info --workers 2 --pid /tmp/myapp.pid  --worker-class gevent

Gunicorn va “écouter” en local sur le port 8080, il va logger des infos dans le fichier /var/log/myapp.gunicorn.log (pratique pour le debug si besoin).
Pour les workers et worker-class regardez la doc.

Configurer Nginx:
Dans la conf Nginx on va avoir.

server {
        listen       80;
        server_name mysite.com www.mysite.com;
 
        location /static/ {
            root  /home/myapp/prod;
            gzip  on;
        }
 
        location / {
            proxy_pass http://127.0.0.1:8080; # Pass to Gunicorn
            proxy_set_header X-Real-IP $remote_addr; # get real Client IP
        }
}

location va indiquer à Nginx ce qu’il faut faire lorsqu’il rencontre certaines URL.

Dans notre cas le location /static/ va dire à Nginx de servir directement tous les fichiers du répertoire /home/myapp/prod/static pour les URL http://mysite.com/static/… En général les images, javascript, etc.

Pour le reste location / va renvoyer les requêtes sur votre serveur Gunicorn qui va les traiter.

proxy_set_header sert à passer à Gunicorn certaines infos comme l’IP réelle du client (sinon Gunicorn verrait celle de NGinx 127.0.0.1).

De cette manière on ne surcharge pas Gunicorn pour servir nos fichiers statiques, on ajoute une protection (Nginx).
On peut éventuellement mettre en cache certaines pages mais je n’ai jamais utilisé cette option.
Nginx peut également distribuer la charge sur plusieurs serveurs et se charger de la compression des pages (gzip).

Des idées cadeaux pour Noël

dimanche 2 décembre 2012 à 16:48

Tous les ans c’est pareil. On repousse au maximum, on traine et puis arrivent les derniers jours.
Alors on se presse pour aller acheter les cadeaux de Noël.
Et souvent ça se fait le dernier jour dans la grande surface du coin bien sûr bondée d’autres personnes ayant aussi attendu le dernier moment.

J’espère que ce post sera complété par vos coms car je fais partie de ces gens là :)

Ma méthode (imparfaite):
Elle consiste en une chose simple préférée des feignasses. Je fuis le centre ville, la périphérie pour rester…. devant mon ordi.
En fait 90% de mes achats se concentrent sur Amazon, ils ont une option de wishlist que je remplis au fur et à mesure dans l’année et arrivé à la date fatidique je la complète avec souvent des suggestions et je commande.
Les 10% restant sont en général Champagne, foie gras, hommard.
De cette façon même si mes cadeaux sont pourris on a toujours un bon repas.

N’achetez jamais un seul gros cadeau !
Un seul cadeau a plus de chance de décevoir et coutera plus cher que plusieurs petits cadeaux. Par exemple je ne vais pas acheter un service à Thé mais plutôt quelques tasses design + un échantillons de Thé + un livre sur l’histoire du Thé. ça fait plus de cadeaux donc plus de chance d’en avoir un qui plait.

Procéder par association thématique:
Pour aller encore plus vite je procède par association pour les cadeaux. Comme dit ci-dessus je groupe les cadeaux par famille, la bouffe avec la bouffe, les vêtements avec les vêtements, etc.
De la sorte je lance une thématique par personne et ça accélère encore les choix.
Les suggestions Amazon servent bien en général pour ça.

Un conseiller qui répond à vos attentes...

Des cadeaux ciblés:
Avant de commencer à chercher les cadeaux je “profile” mes cibles pour connaitre leurs envies du moment et en général. Ça m’aide à mettre une étiquette sur chacune d’entre elles et commencer à choisir ses futurs cadeaux.

Quelques idées d’associations:

personne 1: cycliste amateur :-> casque, gants, gourde, lumières, chaussettes, etc
personne 2: aime la cuisine :-> livre de recettes pratiques, kit à fabriquer des sushis, couteau céramique, planche à pizza pour four, etc
personne 3: ado attardé :-> un jeu pour sa gameboy, écouteurs Dr DREE megabass, trousse de rangement pour sa GB, etc
personne 4: la soeur Hilton :-> un rdv chez Sephora pour nettoyage de peau, 1 seance d’UV, quelques rouges à lèvres copiés sur ceux qu’elle utilise souvent. Pas de parfum c’est perso.

Lancer la commande sur Amazon.fr en prenant aussi les rouleaux de papier d’emballages et y a plus qu’à attendre.

Et à tout ça en général je propose mes services de cuisinier.

Et la terrine de chaton ?..

Le matin de Noël je vais faire les courses avec 2, 3 personnes, on fait le marché et on choisit les produits frais que l’on va manger le soir.
C’est un moment de partage que j’apprécie, on parle, on est en famille, on déconne, on goûte les produits.

Les cadeaux deviennent moins importants que la bouffe du coup et c’est pas plus mal car c’est là que ça se passe, autour d’une bonne table, en famille ou entre amis.

 

Je sais que Sam n’aime pas Amazon, que certains disent qu’on fait mourir le commerce de proximité, etc, j’ai déjà fait quelques aller/retour en ville et je n’ai jamais trouvé ce que je voulais, obligé donc de rentrer commander sur le Net.
Je garde cependant le côté shopping pour la bouffe et là je ne regarde pas à la dépense, que de la qualité (on a qu’une vie).

 

Bon, à vous de partager vos astuces (celle de voler un caddy plein à la sortie du Mamouth je la connais déjà)…

 

Passez de bonnes Vacances et un joyeux Noël

 

Transformer des caractères spéciaux en ASCII

samedi 1 décembre 2012 à 15:23

Dans beaucoup de cas, plutôt que de se taper la gestion de l’encodage, on préfère tout ramener au plus petit dénominateur commun: l’ASCII. Pas d’accent, pas de problème, comme disait mon grand-père juif. Ça devait être un autre contexte. Mais quand même.

Remplacer les accents par leurs équivalents ASCII les plus proches

Une astuce que j’ai lue pour la toute première fois dans les excellents snippets Python de Sauvage (encore et toujours…).

>>> import unicodedata
>>> chaine_unicode = u"Vous êtes le Père Noël ? s'étonna le petit garçon."
>>> unicodedata.normalize('NFKD', chaine_unicode).encode('ascii', 'ignore')
"Vous etes le Pere Noel ? s'etonna le petit garcon."

Si un caractère n’est pas vraiment remplaçable, on peut choisir plusieurs stratégies. Ignorer le caractère:

>>> unicodedata.normalize('NFKD', u"Bei Jing en chinois s'écrit 北亰").encode('ascii', 'ignore')
"Bei Jing en chinois s'ecrit "

Remplacer le caractère par un point d’interrogation:

>>> unicodedata.normalize('NFKD', u"Bei Jing en chinois s'écrit 北亰").encode('ascii', 'replace')
"Bei Jing en chinois s'e?crit ??"

Notez l’effet sur le caractère accentué…

Ou se vautrer comme une grosse loutre bourrée à la bière:

>>> unicodedata.normalize('NFKD', u"Bei Jing en chinois s'écrit 北亰").encode('ascii')
Traceback (most recent call last):
  File "<ipython-input-21-24181c80fc7f>", line 1, in <module>
    unicodedata.normalize('NFKD', u"Bei Jing en chinois s'écrit 北亰").encode('ascii')
UnicodeEncodeError: 'ascii' codec can't encode character u'\u0301' in position 23: ordinal not in range(128)

Ce qui pour nous a un intérêt limité.

Certains alphabets, comme le hongrois, ont des caractères qui pourraient être extrapolés vers l’ASCII, mais la normalisation NFKD ne le permet pas. On peut alors faire appel à une bibliothèque externe.

Unidecode, la cafetière de l’encoding

Unidecode est une lib pip installable qui permet la translittération de caractères. Ce n’est pas toujours très rigoureux, mais c’est toujours très pratique.

Par exemple, vous voulez créer un site Web de mode à destination du marché Hongrois. Si, si. C’est un marché en pleine expansion, j’vous dis.

Et bien entendu vous avez une rubrique manteau, qui se dit en hongrois, comme nous le savons tous: “kožušček”.

Bon, si vous essayé de mettre ça en slug dans vos URL, ça va être un peu chiant à taper, et malheureusement l’astuce précédente ne va pas vous aider:

In [5]: unicodedata.normalize('NFKD', u"kožušček").encode('ascii', 'replace')
Out[5]: 'koz?us?c?ek'

Partant de là, vous pouvez soit:

In [7]: from unidecode import unidecode
In [8]: unidecode(u"kožušček")
Out[8]: 'kozuscek'

Encore plus fastoche, dans le XML et HTML

Dans ces formats à balise, on peut s’affranchir des problème d’encodage en transformant chaque caractère en une entité XML / HTML. On récupère alors du texte ASCII et le client se tapera le boulot d’afficher tous les caractères tordus auxquels l’humanité a pu penser:

In [11]: print u"Le Père Noël m'a offert un kožušček à 北亰".encode('ascii', 'xmlcharrefreplace')
Le P&#232;re No&#235;l m'a offert un ko&#382;u&#353;&#269;ek &#224; &#21271;&#20144;

Et dans tous les autres cas ?

Ben c’est comme d’hab: str.decode() pour tout ce qui rentre pour récupérer une chaîne unicode, et unicode.encode() pour tout ce qui sort pour rebalancer une chaîne de caractères au monde extérieur.

Ça mérite peut être un article d’ailleurs…

P.S: Lazarus vient de me sauver la mise en restaurant cet article suite à un clic malheureux. Vive lazarus.

I'm richer than you! infinity loop