PROJET AUTOBLOG


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

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

⇐ retour index

Des astuces avec pytest 9

mardi 30 août 2016 à 11:36

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.

Choper une exception avec un message particulier

On peut vérifier qu’une fonction lève bien une exception avec la fonction raises():

import pytest
 
def test_truc():
    with pytest.raises(MachinError):
        foo()

Ce test réussira si foo() lève bien de manière consistante MachinError. 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(MachinError) 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.

Par exemple:

[pytest]
addopts = --exitfirst --capture=no --ignore="virtualenv" -vv --showlocals

Mais bon comme j’ai toujours ces trucs-là activés, je force ces options dans mon .bashrc via la variable d’env:

export PYTEST_ADDOPTS="--exitfirst --capture=no -ignore="virtualenv" -vv --showlocals"

Autres options sympas:

testpaths qui permet de choisir les chemins dans lesquels chercher les tests:

Ex:

[pytest]
testpaths =
    tests
    foo
    bar

Si à l’intérieur de ces dossiers il a un truc à ne pas choper, norecursedirs est là pour ça :

[pytest]
norecursedirs =
    tests/media
    tests/scripts

Les options pratiques de tous les jours

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écifiant 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.

Ceux que j’utilise très souvent:

pytest-pythonpath :

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 fixtures

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:

@pytest.fixture(autouse=True, scope="module")
def ma_fixture():
    return foo()

Et cette fixture sera instanciée une seule fois pour tout le module. On peut aussi avoir un scope 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.

L’ordre des choses 21

mardi 16 août 2016 à 16:45

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.

from textwrap import dedent
from collections import OrderedDict
 
class OrderedDictFactory(object):
 
    # __getitem__ est la méthode magique appelée quand on fait objet[trucs]
    # et keys contient la liste des trucs
    def __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 fin
            if not isinstance(key, slice):
 
                # on check qu’on a bien une liste de slices
                raise SyntaxError(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 classique
        return 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.

Récupérer le premier et le dernier élément d’un OrderedDict 15

mardi 22 mars 2016 à 19:18

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):

>>> from collections import OrderedDict
>>> 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…

Nouvel alias pour Python et .bashrc 7

jeudi 17 mars 2016 à 16:27

J’en avais marre de taper Python en entier. Et surtout, je voulais lancer Python3.5 si il est dispo, et si possible ptpython, ou ipython. Sauf si je passe des arguments. Et que ça pete pas tout dans un virtualenv.

Bref:

function p {
    local SUFFIX="$@"
    if [[ "$VIRTUAL_ENV" != "" ]]
    then
      local PREFIX="$VIRTUAL_ENV"/bin
      COMMANDS=("python")
    else
      local PREFIX=/usr/bin
      COMMANDS=("python3.5" "python")
    fi
 
    if [[ "$#" -eq 0 ]]; then
        SUFFIX=""
        local COMMANDS=("python3.5 -m ptpython"
                        "python3.5 -m ipython"
                        "python -m ptpython"
                        "python -m ipython"
                        "python")
    fi
 
    for i in "${COMMANDS[@]}"
    do
       $PREFIX'/'$i $SUFFIX;
       [ "$?" -eq 0 ] && return 0
    done
 
}

Ce ne sont pas les services pirates qui ont une jambe de bois 30

lundi 14 mars 2016 à 17:21

Quand le Divx est sorti, il y a eu tout un débat sur la valeur du DVD. Le truc qui prend de la place, qui s’abime, qui est une plaie à sauvegarder, qui est zoné… En prime, quand tu payais un DVD, tu avais ça:

Dvd pirate vs légal

Attendez : il fallait aussi se déplacer pour acheter physiquement ce putain de DVD


Mais les industriels ont tapé du pied sur le piratage depuis les K7 audio. Jamais, au grand jamais, ils ne se sont remis en question.

Et est venu l’ère du streaming.

Et on a eu le droit à une redite.

Limite par pays, peu de sous-titre, catalogue limité et gros délais entre certaines sortis selon les médias et/ou les zones. Or the pirate bay et pop corn time n’ont pas tous ces problèmes.

Mais alors qu’en est-il du livre numérique ?

Le livre, c’est fait par des gens qui utilisent leur cerveau, non ?

Je veux dire, ils lisent, en théorie…

Et bien, après avoir galéré pour trouvé certains titres légalement et perdu ma collection suite au bricage de ma première tablette, je n’étais déjà pas très convaincu. J’aime l’outil pour le voyage, mais le service laisse à désirer. Nénamoins, je suis un geek, peut etre que les gens moins attachés à la cause étaient moins sensibles à ces problématiques.

Il faut croire que non, puisque mon frère m’a écrit dernièrement ce mail bien rageux:

ALORS j´ai acheté mon livre sur un site.

DRM ADOBE donc j´installe tout etc..1h30 à faire les inscriptions les mots de passe,
les confirmations. Et je peux même pas lire mon livre…
Les mecs sont les plus nazes de la terre ils se tirent un balle dans le pieds.

Du coup je vais devoir pirater mon propre livre que j´ai acheté légalement

Youpi.

Et du coup bah qu´ils aillent bien se faire mettre pour la suite ce sera le premier et le dernier livre avec DRM que achète et si je trouve pas mon livre en Epub sans DRM bah je le piraterai.

Dés que j´ai mon livre sans DRM je te l´envois.

Bisoux

Notez que lui sait ce qu’est un DRM. Je n’ose pas imaginer Mme Michu.