Pylint et Flask SQLAlchemy

Pour un projet client nous développons une solution qui sert une API construite autour de Flask et SQLAlchemy. Dans nos outils d'intégration continue (et dans nos environnement de développement) nous utilisons plusieurs outils de linting pour maintenir une certaine qualité de code et simplifier le débogage : black (formatage pep-8), mypy (typage statique) et Pylint (les deux à la fois).

Pylint est très utile pour détecter des erreurs dans le code source, en inspectant le projet et les librairies utilisées, il n'est cependant pas omnipotent et se retrouve souvent impuissant devant certains objets dont les propriétés sont déclarées dynamiquement (un __init__ avec des setattr par exemple).

C'est exactement ce qu'il se passe avec Flask-SQLAlchemy : cette extension flask propose une interface à SQLAlchemy au travers de plusieurs objets ( SQLAlchemy, scoped_session, etc.) que Pylint n'arrive pas à inspecter et qui remplit nos rapports de qualité de code d'une liste interminable de faux positifs du type :

  • E1101: Instance of 'SQLAlchemy' has no 'Column' member (no-member)
  • E1101: Instance of 'SQLAlchemy' has no 'String' member (no-member)
  • E1101: Instance of 'scoped_session' has no 'add' member (no-member)
  • etc.

Et pourtant si Pylint, je t'assure, flask_sqlalchemy.SQLAlchemy.Column existe bien, tout comme le reste...

Pour pallier à cela, Pylint propose une architecture de greffons qui permet de décrire ces chaînons manquants spécifiques sans pour autant faire exploser la taille de Pylint en lui même (mypy a choisi la solution inverse et embarque avec lui une sacrée quantité de stubs pour mieux analyser les cadriciels et librairies populaires python). On peut donc écrire un Transform plugin  qui va reproduire le mécanisme interne de Flask-SQLAlchemy et enfin rendre accessible les objets, méthodes et propriétés des différentes interfaces qu'il propose.

Le résultat est pylint_flask_sqlalchemy, un greffon encore expérimental mais fonctionnel pour répondre à nos problèmes de faux positifs :

🎉 démo

Le code source est publié sous licence GPLv3 pour celles et ceux qui souhaiteraient participer.