Bonjour à tous! Aujourd'hui, je vais vous raconter l'histoire du développement de la dactylographie sur l'exemple d'un des projets de
Ostrovok.ru .

Cette histoire a commencé bien avant le battage médiatique sur
python3.5 , d'ailleurs, elle a commencé dans un projet écrit en
python2.7 .
2013 : tout récemment, il y avait une version de
python3.3 , il n'y avait aucun intérêt à migrer vers la nouvelle version, car elle
n'ajoutait aucune fonctionnalité spécifique, et il y aurait beaucoup de douleur et de souffrance pendant la transition.
J'ai été impliqué dans le projet Partners chez Ostrovok.ru - ce service était responsable de tout ce qui concernait les intégrations de partenaires, les réservations, les statistiques et mon compte personnel. Nous avons utilisé à la fois des API internes pour d'autres microservices de l'entreprise et une API externe pour nos partenaires.
À un moment donné, l'équipe a formé l'approche suivante pour écrire des gestionnaires HTTP ou une sorte de logique métier:
1) les données d'entrée et de sortie doivent être décrites par une structure (classe),
2) le contenu des instances de structures doit être validé conformément à la description,
3) une fonction qui prend une structure à l'entrée et donne la structure à la sortie devrait vérifier les types de données à l'entrée et à la sortie, respectivement.
Je ne m'attarderai pas sur chaque point en détail, l'exemple ci-dessous devrait suffire à comprendre ce qui est en jeu.
Exemple.
import datetime as dt from contracts import new_contract, contract from schematics.models import Model from schematics.types import IntType, DateType
L'exemple utilise des bibliothèques:
schémas et
pycontracts .
*
schémas - une façon de décrire et de valider les données.
*
pycontracts - un moyen de vérifier l'entrée / sortie d'une fonction lors de l'exécution.
Cette approche vous permet de:
- il est plus facile d'écrire des tests - les problèmes de validation ne se posent pas et seule la logique métier est couverte.
- pour garantir le format et la qualité de la réponse dans l'API - un cadre rigide apparaît pour ce que nous sommes prêts à accepter et ce que nous pouvons donner.
- il est plus facile de comprendre / refactoriser le format de réponse s'il s'agit d'une structure complexe avec différents niveaux d'imbrication.
Il est important de comprendre que la vérification de type (non-validation) ne fonctionne que lors de l'
exécution , ce qui est pratique pour le développement local, l'exécution de tests dans CI et la vérification que la version candidate fonctionne dans un environnement
intermédiaire . Dans un environnement de production, cela doit être désactivé, sinon le serveur ralentira.
Des années ont passé, notre projet a grandi, une logique métier plus nouvelle et plus complexe est apparue, le nombre de descripteurs d'API au moins n'a pas diminué.
À un moment donné, j'ai commencé à remarquer que le lancement du projet prenait déjà quelques secondes notables - c'était ennuyeux, car chaque fois que je modifiais le code et exécutais les tests, je devais m'asseoir et attendre longtemps. Lorsque cette attente a commencé à prendre 8 à 10 secondes, nous avons finalement décidé de comprendre ce qui se passait sous le capot.
En fait, tout s'est avéré assez simple. Lors du démarrage d'un projet, la bibliothèque pycontracts analyse toute la
docstring couverte par
@contract afin d'enregistrer toutes les structures en mémoire puis de les vérifier correctement. Lorsque le nombre de structures dans un projet s'élève à des milliers, tout cela commence à ralentir.
Que faire à ce sujet? La bonne réponse est de chercher d'autres solutions, heureusement dans la cour est déjà
2018 (
python3.5 -
python3.6 ), et nous avons déjà migré notre projet vers
python3.6 .
J'ai commencé à étudier des solutions alternatives et à réfléchir à la façon de migrer un projet de «
pycontracts + description de type dans
docstring » vers «quelque chose + description de type dans
annotation de frappe ». Il s'est avéré que si vous
mettez à niveau
pycontracts vers la dernière version, vous pouvez décrire les types dans le style d'
annotation de frappe , par exemple, cela pourrait ressembler à ceci:
@contract def get_order_info(data_in: OrderInfoData) -> OrderInfoResult: return OrderInfoResult( dict( order_id=data_in.order_id, checkin_at=dt.datetime.today(), checkout_at=dt.datetime.today() + dt.timedelta(days=1), cancelled_at=None, ) )
Les problèmes commencent si vous devez utiliser des structures de
frappe , par exemple
Facultatif ou
Union , car
pycontracts ne sait PAS comment les utiliser:
from typing import Optional @contract def get_order_info(data_in: OrderInfoData) -> Optional[OrderInfoResult]: return OrderInfoResult( dict( order_id=data_in.order_id, checkin_at=dt.datetime.today(), checkout_at=dt.datetime.today() + dt.timedelta(days=1), cancelled_at=None, ) )
J'ai commencé à chercher des bibliothèques alternatives pour la vérification de type lors de l'
exécution :
*
appliquer*
typeguard*
pytypesEnforce à cette époque ne supportait pas
python3.7 , mais nous l'avons déjà mis à jour, les
pytypes n'aimaient pas la syntaxe, par conséquent, le choix s'est
porté sur
typeguard .
from typeguard import typechecked @typechecked def get_order_info(data_in: OrderInfoData) -> Optional[OrderInfoResult]: return OrderInfoResult( dict( order_id=data_in.order_id, checkin_at=dt.datetime.today(), checkout_at=dt.datetime.today() + dt.timedelta(days=1), cancelled_at=None, ) )
Voici des exemples d'un vrai projet:
@typechecked def view( request: HttpRequest, data_in: AffDeeplinkSerpIn, profile: Profile, contract: Contract, ) -> AffDeeplinkSerpOut: ... @typechecked def create_contract( user: Union[User, AnonymousUser], user_uid: Optional[str], params: RegistrationCreateSchemaIn, account_manager: Manager, support_manager: Manager, sales_manager: Optional[Manager], legal_entity: LegalEntity, partner: Partner, ) -> tuple: ... @typechecked def get_metaorder_ids_from_ordergroup_orders( orders: Tuple[OrderGroupOrdersIn, ...], contract: Contract ) -> list: ...
En conséquence, après un long processus de refactoring, nous avons pu transférer complètement le projet vers
les annotations de typeguard +
typing .
Quels résultats avons-nous obtenus:
- Le projet démarre en 2-3 secondes, ce qui n'est au moins pas gênant.
- la lisibilité du code s'est améliorée.
- le projet est devenu plus petit tant dans le nombre de lignes que dans les fichiers, puisqu'il n'y a plus d'enregistrement de structure via @new_contract .
- les IDE PyCharm intelligents sont devenus meilleurs pour indexer un projet et faire des conseils différents, car maintenant ce ne sont plus des commentaires, mais des importations honnêtes.
- Vous pouvez utiliser des analyseurs statiques comme mypy et pyre -check , car ils prennent en charge le travail avec les annotations de frappe .
- La communauté python dans son ensemble s'oriente vers la saisie sous une forme ou une autre, c'est-à-dire que les actions en cours sont des investissements dans l'avenir du projet.
- il y a parfois des problèmes avec les importations cycliques, mais il y en a peu et ils peuvent être négligés.
J'espère que cet article vous sera utile!
Références:
*
appliquer*
typeguard*
pytypes*
pycontracts*
schémas