Si on teste du code qui utilise des uuid, on veut vérifier qu’on récupère bien des uuid valides là où on les attend.
Le plus simple est de déléguer cette vérification au module uuid qui est déjà celui qu’on utilise pour les produire.
from uuid import UUID
def is_uuid(uuid_string, version=4):
try:
# Si uuid_string est un code hex valide mais pas un uuid valid,# UUID() va quand même le convertir en uuid valide. Pour se prévenir# de se problème, on check la version original (sans les tirets) avec# le code hex généré qui doivent être les mêmes.
uid = UUID(uuid_string, version=version)return uid.hex== uuid_string.replace('-','')exceptValueError:
returnFalse
À chaque fois que j’utilise un uuid, j’ai un peu l’impression d’user la fabrique de l’univers, comme si je fabriquais quelque chose d’unique pour le mettre la poubelle, et qui n’existera plus jamais. Le gâchis d’un flocon de neige virtuel :)
J’ai twitté il y a quelque temps l’article hilarant It’s the future. Mais j’ai bien conscience que plein de gens ne lisent pas l’anglais et ne peuvent profiter de cette perle. Comme on a ouvert le blog S&M en français, car on voulait justement créer des bonnes ressources dans notre langue, je traduis de temps en temps ces trucs fantastiques, comme par exemple Le bonheur des frameworks.
Donc voici la traduction de celui-ci. Faites-vous plaiz’
Hey, mon boss m’a dit de venir te voir. Il paraît que tu gères niveau web apps ?
– Ouais, je suis plus un spécialiste des systèmes distribués maintenant. Je reviens du ContainerCamp et de la Gluecon et je vais à la Dockercon la semaine prochaine. Je suis emballé par la direction que prend l’industrie – tout est plus simple et fiable. C’est le futur !
Cool. Je veux juste faire une simple Web app pour le moment – un CRUD normal avec Rails, que je vais déployer sur Heroku. C’est toujours d’actu ?
– Oh non. C’est old school. Heroku, c’est mort – plus personne ne l’utilise. Il te faut Docker maintenant. C’est le futur.
Oh, OK. Qu’est-ce que c’est ?
– Docker est la nouvelle façon de faire de la conteneurisation. Comme LXC, mais c’est aussi un format de package, une plateforme de distribution et des outils pour rendre la création de systèmes distribués très facile.
– No, Vagrant c’est mort. Tout va être conteneurisé maintenant, c’est le futur.
Ok, donc je n’ai pas besoin de savoir quoi que ce soit sur la virtualisation ?
– Non, tu as quand même besoin de la virtualisation, parce que les conteneurs ne fournissent pas toutes les couches de sécurité pour le moment. Donc ce que tu veux, c’est faire tout tourner dans un environnement multitiers de telle sorte qu’on ne puisse s’échapper de la sandbox.
Ok, je suis un peu perdu là. On rembobine. Donc il y a un truc comme la virtualisation qu’on appelle les conteneurs. Et je peux l’utiliser sur Heroku ?
– Et bien, Heroku a un support pour docker, mais je viens de le dire : Heroku c’est mort. Ce que tu veux c’est faire tourner tes conteneurs sur CoreOS.
Ok, c’est quoi ?
– C’est un OS hôte très cool qu’on peut utiliser avec Docker. Tu n’as même pas besoin de Docker, tu peux utiliser rkt.
Rocket ?
– No, rkt.
Ouais, Rocket.
– Non, ça s’appelle rkt maintenant. Rien à voir. C’est un format de conteneurisation alternatif qui n’est pas aussi intégré que Docker par défaut, et donc plus composable.
C’est une bonne chose.
– Bien entendu. La composabilité c’est le futur.
Ok, ça s’utilise comment ?
– Je ne sais pas. Je ne pense pas que qui que ce soit l’utilise.
Arf. Tu disais quoi à propos de CoreOS ?
– Ah oui, c’est un OS hôte pour Docker.
C’est quoi un OS hôte ?
– Un OS hôte fait tourner tous tes conteneurs.
Tourner mes conteneurs ?
– Ouais, tu dois avoir quelque chose pour faire tourner tes conteneurs. Donc tu te crées genre une instance EC2, tu mets CoreOS dessus, tu lances le daemon Docker, puis tu déploies tes images Docker dessus.
Quelle partie est le conteneur ?
– Tout. Écoute, tu prends ton app, écris un Dockerfile, tu en fais une image locale, puis tu la push sur n’importe quel hôte Docker.
Ah, comme Heroku ?
– Non, pas Heroku. Je te l’ai dit. Heroku c’est mort. Tu gères ton propre cloud en utilisant Docker.
Écoute, j’ai vraiment pas envie d’héberger tout ça.
– Na, c’est super simple. Tu te setup juste un cluster Kubernetes.
J’ai besoin d’un cluster ?
– Un cluster Kubernetes. Ça gère le déploiement de tous tes services.
J’ai seulement un service.
– Qu’est-ce que tu racontes. Tu as une app non? donc tu as bien au moins 8-12 services ?
Quoi ? Non. Juste une app. Service, on s’en branle. Juste un truc.
– Non, jette un coup d’oeil à la notion de microservices. C’est le futur. C’est comme ça qu’on fait tout de nos jours. Tu prends ton app monolithique et tu la divises en quelque chose comme 12 services, un pour chaque tâche.
Ça semble excessif.
– C’est le seul moyen de s’assurer que c’est fiable. Ainsi si ton service authentification tombe…
Service authentification ? J’allais juste utiliser une gem que j’avais déjà mis en place plusieurs fois avant.
– Super. Utilise la gem. Mets la dans son propre projet. Fous une API RESTful par dessus. Et fait en sorte que les autres services utilisent cette API et gèrent gracieusement les échecs. Mets ça dans un conteneur et push le bordel en déploiement continu.
Ok, donc là j’ai une douzaine de services ingérables, et maintenant ?
– Ouais, d’où Kubernetes. Il orchestre tes services.
Orchestre ?
– Ouais, donc tu as ces services, ils doivent être fiables donc il t’en faut plusieurs copies. Donc Kubernetes s’assure que tu en as assez, et qu’ils sont distribués à travers de multiples hôtes dans ta flotte de serveurs, afin qu’ils soient toujours disponibles.
Ah parce qu’il me faut une flotte maintenant ?
– Ouais, pour la fiabilité. Mais Kubernetes la gère pour toi. Et tu sais que Kubernetes ça marche vu que Google l’a fabriqué et que ça tourne sur etcd.
Putain, jusqu’où ça va le terrier du lapin blanc là ? Je veux juste lancer une app. Arf. Bordel, Ok, on respire. Fiou. Ok, donc Paxos c’est quoi ?
– Paxos c’est ce très vieux protocole de consensus distribué des années 70 que personne ne comprend ou n’utilise.
Super, merci de m’en avoir parlé. Et donc Raft ?
– Comme personne ne pige Paxos, y a ce mec, Diego…
Oh, tu le connais ?
– Non, il travaille a CoreOS. Bref, Diego a créé Raft pour sa thèse de doctorat, car Paxos était trop compliqué. Futé le gars. Et il a écrit etcd comme implémentation, et Aphyr a dit que c’était pas de la merde.
C’est quoi Aphyr ?
– Aphyr est le mec qui a écrit Call Me Maybe. Tu vois, le mec spécialiste des systèmes distribués et du BDSM ?
WUT ? BDSM ?
– Ouais, BDSM. C’est San Francisco hein, tout le monde est à fond dans les systèmes distribués et le BDSM
Hum, OK. Donc il a écrit cette chanson de Katy Perry ?
– Nan, il a écrit des posts sur son blog comme quoi toutes les databases n’atteignaient jamais le CAP.
C’est quoi le CAP ?
– Le théorème CAP. Ça dit que tu peux avoir des données consistantes, disponibles et tolérantes au partitionnement, mais qu’il faut choisir 2 de ces caractéristiques sur les 3.
Ok, et donc toutes les bases de données foirent CAP. C’est à dire ?
– Ça veut dire qu’elles sont à chier. Comme Mongo.
Je pensais que Mongo scalait ?
– Ben personne d’autre ne le pensait.
Ok, et etcd.
– Ouais, etcd c’est un système de stockage de clé/valeur distribué.
– Non, rien à voir. etcd est distribué. Redis perd la moitié de ce que tu écris en cas d’échec réseau.
Ok, donc c’est un système de stockage de clé/valeur distribué. Pourquoi c’est utile ?
– Kubernetes setup par défaut un cluster de 5 nodes et utilise etcd comme bus de transport de messages. En combinaison avec quelques services de Kubernetes lui-même, ça fournit un système d’orchestration assez résilient.
5 nodes ? J’ai une app. Combien de machines je vais devoir prendre pour tout ça ?
– Et bien tu vas avoir environ 12 services, et bien sûr il te faut des copies de chaque pour la redondance, quelques load balancers, le cluster etcd, ta base de données, et le cluster kubernetes. Je dirais peut-être 50 conteneurs.
WTF ?!
– Aucun problème ! Les conteneurs sont vraiment efficaces, donc tu peux les distribuer sur genre, 8 machines. C’est pas incroyable ?
C’est une manière de voir les choses. Et avec tout ça, je vais être capable de simplement déployer mon app ?
– Yep. Je veux dire, la question du stockage est toujours en suspend avec Docker et Kubernetes, et la partie réseau va demander un peu de travail, mais techniquement, tu es à 2m du bol de sangria.
Je vois. Ok, je pense que j’ai pigé.
– Super !
Merci pour l’explication.
– No problem.
Laisse-moi faire un résumé pour voir si j’ai bien tout compris.
– Bien sûr !
Donc j’ai besoin de diviser mon app CRUD toute simple, en 12 microservices, chacun avec leur propre API qui s’appellent les uns et les autres, mais qui gèrent les erreurs de manière résiliente, les mettre dans des conteneurs Docker, lancer une flotte de 8 machines qui sont des hôtes Docker tournant sous CoreOS, les orchestrer avec un petit cluster Kubernetes qui tourne avec etcd, me démerder avec la question du réseau et du stockage, et je fais tout ça en déploiement continu pour de multiples copies redondantes des microservices de ma flotte. J’ai bon ?
– Yep ! C’est pas fabuleux ?
Je retourne sous Heroku.
En relisant l’article, je réalise deux choses :
Putain il en faut des références rien que pour comprendre la blague. Plein de devs ne savent même pas ce qu’est Heroku.
Et franchement cette parodie est vraiment gentille, dans la réalité cette stack pose beaucoup d’autres problèmes : il y a plus de trucs que ça, ça s’emboîte mal, les versions changent, la doc est merdique, faut former les nouveaux arrivants car personne ne connaît ces technos, y a plein de roues à réinventer, il faut un temps fou pour choisir les briques technos, etc. Mais on peut aussi utiliser Docker très simplement, avec un image, à la main, sous Ubuntu. C’est presque simple et pratique.
Pytest est fantastique. En fait si la lib n’est pas dispo je n’ai même plus la volonté d’écrire des tests unitaires tellement je suis habitué aux facilités qu’elle offre.
Au fur et à mesure de son usage, j’ai noté quelques astuces qui, je le sais, vous serviront bien sur le long terme.
import pytest
def test_truc():
with pytest.raises(MarchinError):
foo()
Ce test réussira si foo() lève bien de manière consistante MarchinError. Mais parfois on a plein d’erreurs similaires, et on en veut une avec un message particulier. Dans ce cas, on peut matcher le message sur une regex:
def test_truc():
with pytest.raises(MarchinError)as excinfo:
foo()
excinfo.match(r"votre regex du message d'erreur")
Configurez, il en restera toujours quelque chose
On peut mettre la config de votre projet dans un fichier pytest.ini (mais le fichier tox.ini ou setup.cfg marche aussi \o/). Parmi les options les plus pratiques:
addopts, qui permet de forcer des options à la ligne de commande pytest pour ne pas les passer à chaque fois à la main.
Celles que j’active toujours:
--exitfirst : le premier échec arrête les tests. Pas la peine de me mettre 22 mille erreurs. --capture=no : affiche les print() de mon code. --ignore="virtualenv" : permet de ne pas cherche les tests dans un dossier. Par exemple le virtualenv. -vv : bien verbeux. Je veux de l’info sur mes tests. --showlocals : montre les variables locales sur les tests qui foirent.
Il y a des options qu’on ne veut pas tout le temps, mais qui sont super utiles ponctuellement:
--failed-first : relance tous les tests, mais ceux qui ont foiré en premier. --pdb : lance pdb juste après le premier échec. -k : lance uniquement les tests dont le nom matche cette regex.
Et souvenez-vous que vous pouvez lancer un seul test en spécifier son chemin avec la syntaxe relative/file/path.py::test_func. Par exemple:
pytest test/test_foo.py::test_bar
Vive les plugins
Pytest a une grosse communauté de plugins, il suffit de chercher sur google “pytest + techno” pour avoir tout de suite des helpers qui popent.
Permet d’avoir dans son conf file python_paths = chemins qui seront ajoutés au PYTHON PATH:
Ex:
[pytest]
python_paths =
.
libs
pytest-flake8 permet de lancer le linter flake8 pendant les tests et de considérer son échec comme un test qui foire. En plus flake8 peut mettre sa config dans les mêmes fichiers que pytest donc j’ai souvent ça:
[flake8]
exclude = doc,build,.tox,.git,__pycache__,build # ignorer les dossiers inutiles
max-complexity = 10 # verifier que le code n'est pas trop complexe
max-line-length = 80 # pep8 for ever, mais noqa peut servir parfois
Après j’utilise souvent tox, donc ce plugin n’est plus aussi utile qu’avant.
pytest-django qui ajoute des setup et tear down avec la base de données Django, fourni des fixtures pour le client de test django et permet de configurer DJANGO_SETTINGS_MODULE dans son fichier ini.
pytest-asyncio qui permet de gérer la loop, utiliser await dans les tests, etc.
Gérer ses fixures
Le système de fixtures est une des choses qui rend pytest si cool, particulièrement parce qu’on peut être très précis dans ce qu’on charge. Mais des fois on veut les fixtures pour tout le monde, ou tout un module. autouse fait exactement ça:
Et cette fixture sera instanciée une seule fois pour tout le module. On peut aussi avoir un score de session (une seule fois par lancement de pytest) ou de class (une fois par classe).
Si vous avez besoin de lancer un code avant toute session de tests, par exemple pour vous définir des fixtures qui sont dispo dans tous les tests, il suffit de le mettre dans un fichier conftest.py à la racine de vos tests. Ce fichier est automatiquement détecté par pytest, et lancé avant toute chose. Il permet également de faire des hooks complexes, créer ses propres plugins, etc. Mais je l’utilise surtout pour faire des fixtures globales et m’éviter de les importer.
Des mois et des mois que je n’ai pas écrit :) Je ne compte même pas Max, qui à l’heure où je vous parle est en train découvrir les joies de la sidérurgie et qui ne se souvient du blog que quand on en parle dans le jacuzzi d’un FKK.
Ça va faire 4 ans qu’on a ce truc.
Entre temps lui et moi avons déménagé plusieurs fois, séparément, puis réaménagé ensemble, on a pris des responsabilités comme l’achat d’une machine à glaçons et la culture de plantes carnivores (la mienne est morte, mais il a la main verte apparemment).
Je vais être franc avec vous, l’écriture ne m’a pas manqué. Pas du tout.
Et puis aujourd’hui, j’ai eu envie. Ça ne m’avait pas pris depuis pas mal de temps, mais c’est une bonne chose : le sens du devoir avait depuis longtemps perdu son influence sur ma motivation éditoriale.
Est-ce que ça va durer un article ? 10 ? Un an ?
On va voir.
Mais ce ne sont pas les sujets qui manquent. En fait Python 3.6 est droit devant nous, avec plein de trucs chouettes à la clé. J’ai aussi beaucoup joué avec asyncio, jusqu’à trouver des bugs dedans et les reporter aux core-dev direct.
Pour le cul par contre, je ne sais pas encore, je vais laisser traîner jusqu’à ce que, tel Rocco dans Omar et Fred, ma bite me parle.
Python pour commencer donc.
Plein de changements de perfs, en vitesse, occupation mémoire, etc, prévus pour la prochaine version. Parmi eux, un changement majeur sur l’implémentation des dictionnaires inspiré directement de Pypy, qui les rendront plus compacts et plus rapides.
Et surtout, ordonnés par défaut.
Yep, plus besoin de collections.OrderedDict, qui restera malgré tout pour des raisons de compatibilité.
Enfin s’ils décident que ça fait maintenant partie de la specs et n’est pas juste un détail d’implémentation.
Ca veut dire aussi que **kwargs et __dict__ deviendront ordonnés, ce qui va arranger beaucoup de monde.
Mais l’article, en plus de prêcher la bonne nouvelle, est aussi là pour vous parler d’un petit hack sympa qui peut être pratique dans votre PYTHONSTARTUP pour des sessions shellifiantes : donner l’impression d’avoir un OrderedDict builtin par défaut.
fromtextwrapimport dedent
fromcollectionsimport OrderedDict
class OrderedDictFactory(object):
# __getitem__ est la méthode magique appelée quand on fait objet[trucs]# et keys contient la liste des trucsdef__getitem__(self, keys):
for key in keys:
# slice est un objet builtin fabriqué par Python quand on fait # objet[debut:fin], qui contient le debut et la finifnotisinstance(key,slice):
# on check qu’on a bien une liste de slicesraiseSyntaxError(dedent("""
One element of the dict is not a key/value pair: {!r}.
The syntax is d["key1": "value", "key2": "value", ...].
Check if you haven't missed a semicolon somewhere.
""".format(key)))# et on retourne juste un ordered dict classiquereturn OrderedDict([(k.start, k.stop)for k in keys])# instance de référence pour notre factory abrégé pour des raisons de # facilité
d = OrderedDictFactory()
Et hop, on abuse la notation du slicing :
>>> menu = d["Samurai pizza cats" : "fruits de mer",# un slice"Tortue ninja" : "4 saisons",# un autre slice 'Max': "paysanne"# do you wanna slice ?]>>> menu
OrderedDict([('Samurai pizza cats','fruits de mer'),('Tortues ninjsa','4 saisons'),('Max','paysanne')])
Rien à voir avec la choucroute, mais certaines personnes m’ont parlé de faire un miroir SFW du blog. Je vous rappelle que le blog est sous creative common, alors faites-vous plaiz, vous pouvez tout réutiliser.
collections.OrderedDict est une structure de données que j’utilise de plus en plus, surtout que sa réécriture en C en 3.5 lui donne des performances décentes.
Néanmoins, il n’y a pas dans l’API de moyen de récupérer le premier ou le dernier élément inséré dans dico. Il y a bien popitem(), mais ça retire l’élément du dictionnaire, et c’est pas forcément ce qu’on veut.
Heureusement OrderedDict est un itérable, et implémente __reversed__, et on peut donc utiliser les outils suivant our récupérer les extrémités avec une perf 0(1):
>>>fromcollectionsimport OrderedDic
>>> d = OrderedDict.fromkeys('azerty')>>> next(iter(d.items()))# premier élément'a'>>> next(reversed(d.items()))# dernier élément'y'
Après l’implémentation de OrderedDict reste une liste doublement chainée, et on ne peut donc pas récupérer un élément à un index arbitraire sans le parcourir à la main…