Entrée (schéma .proto)

Sortie (Python)

Ce que fait cet outil

Vous avez un schéma Protocol Buffers et un service ou un script Python qui a besoin des types correspondants. La voie officielle, c’est protoc avec le plugin Python (voir le tutoriel Python officiel), qui produit des classes de message générées qui marchent mais qui sont pénibles à lire et bruyantes dans les diffs. Cet outil émet à la place des dataclasses ordinaires — propres, idiomatiques et faciles à mocker en test. Collez le schéma, copiez la sortie, déposez-la dans votre projet.

Le mapping de types est exactement celui que vous écririez à la main. string/bytes deviennent str/bytes, bool devient bool, toutes les largeurs d’entier (int32 jusqu’à sfixed64) deviennent int, et double/float deviennent float. repeated T devient list[T] avec field(default_factory=list), map<K, V> devient dict[K, V] avec field(default_factory=dict), et les références singulières à des messages deviennent Optional[Msg] avec valeur par défaut None, donc les références circulaires et forward references fonctionnent du premier coup.

Les enums deviennent des sous-classes de IntEnum, c’est ce que le runtime officiel protobuf en Python utilise en interne et ce que la plupart des relecteurs s’attendent à voir. from __future__ import annotations trône en haut du fichier pour que l’évaluation différée gère proprement les forward refs — pas besoin de type hints entre guillemets dans le corps. Les messages imbriqués sont aplatis en dataclasses de premier niveau ; Python ne tire pas le même bénéfice de l’imbrication que Java, et les noms à plat sont plus simples à importer. Tout tourne dans votre navigateur ; rien de votre schéma ne quitte la page.

Comment l’utiliser

Trois étapes. La sortie est prête à déposer dans un fichier <code>.py</code>.

1

Collez votre schéma .proto

Déposez le schéma dans l’éditeur de gauche. syntax = "proto3"; en haut, c’est bien mais facultatif. Le parser gère les blocs message imbriqués, les déclarations enum, oneof, map<K, V> et les options de champ. Les imports sont reconnus mais ignorés — collez les types importés en ligne si votre schéma s’étale sur plusieurs fichiers.

Les noms de champs restent tels quels : order_id dans .proto reste order_id en Python. Snake_case, c’est déjà pythonique. Les noms de classe restent en PascalCase aussi, conformément aux conventions PEP 8.

2

Lisez la sortie

Le panneau de droite affiche du Python avec un @dataclass par message et une sous-classe IntEnum par enum. D’abord les enums, puis les messages dans l’ordre des dépendances (les enfants avant les parents). Ajoutez le fichier à votre projet, importez les dataclasses dont vous avez besoin, et c’est plié.

3

Utilisez les dataclasses

Construisez des instances avec des arguments nommés, mutez-les comme des objets normaux, sérialisez avec dataclasses.asdict() ou json.dumps pour le transport HTTP. Si vous avez besoin de l’encodage Protobuf complet en wire-format, branchez les dataclasses sur protobuf-python ou utilisez-les comme couche typée devant votre client gRPC.

Quand ça fait vraiment gagner du temps

Esquisser des types pour un nouveau service gRPC en Python

Vous démarrez un nouveau service qui consomme une API Protobuf existante. Vous voulez des dataclasses propres pour les formes de request/response sans encore lancer protoc. Collez le schéma, déposez la sortie dans types.py, écrivez votre logique métier face aux dataclasses, branchez gRPC Python plus tard quand vous serez prêt.

Mocker des données Protobuf dans pytest

Les classes de message Protobuf générées sont pénibles à construire dans les tests parce que chaque champ a son propre setter et que les constructeurs n’acceptent pas tous les champs comme kwargs. Les dataclasses faites à la main, si — Order(order_id="ORD-42", customer_name="Ava Chen", total_amount=99.50) marche du premier coup. Servez-vous de cette sortie comme fixtures et mocks tout en gardant les vraies classes Protobuf pour la sérialisation en wire-format.

Reviewer un changement d’API Protobuf

Un coéquipier backend a ajouté des champs à Order et une nouvelle valeur OrderStatus. Vous voulez savoir ce que votre code client Python va devoir gérer sans lancer la build complète. Collez le nouveau .proto, faites un diff de la sortie dataclass contre vos types actuels, laissez un commentaire de review ciblé.

Scripts rapides et ETL one-shot

Vous écrivez un script de 50 lignes pour faire un backfill de données depuis un dump JSON qui suit un schéma Protobuf. Configurer protoc pour un script jetable, c’est trop. Récupérez les dataclasses ici, parsez le JSON dedans, lancez le script, jetez-le. Pas d’étape de build, pas de toolchain, pas de fichiers générés qui traînent dans le repo.

Questions fréquentes

Mon schéma est-il envoyé quelque part ?

Non. Le parser et l’émetteur Python tournent entièrement dans votre navigateur en JavaScript. Ouvrez les DevTools et regardez l’onglet Network pendant que vous collez — zéro requête. Pratique quand votre schéma contient des noms de types internes, des chemins de package ou autre chose que vous préféreriez ne pas envoyer à un service tiers.

Pourquoi des dataclasses plutôt que les classes officielles de protobuf-python ?

Les classes générées par protoc fonctionnent, mais elles sont verbeuses, difficiles à mocker en test et bruyantes en code review. Les dataclasses vous offrent kwargs typés, comparaison d’égalité et repr propre gratuitement. Si vous avez besoin d’encodage en wire-format, vous pouvez mapper entre les dataclasses et les types de message officiels dans une fine couche adaptateur — la plupart des équipes trouvent que c’est un meilleur découpage que de tout typer face aux classes générées.

Pourquoi IntEnum et pas des enums à valeur str ?

Les enums Protobuf sont à valeur entière au niveau wire — chaque valeur a un numéro de tag. IntEnum correspond exactement : OrderStatus.ORDER_STATUS_PAID est à la fois un membre nommé et l’entier 2, qui fait un round-trip propre via JSON ou wire format. Si vous voulez un StrEnum (Python 3.11+) pour l’encodage JSON, faites un find-replace de IntEnum dans la sortie.

Pourquoi les champs message sont en Optional[Msg] et pas juste Msg ?

En proto3 un champ singulier de message peut être non défini (l’absence a un sens, contrairement aux scalaires où le défaut est la valeur zéro). Avoir None par défaut colle à cette sémantique et permet aux références circulaires de compiler — si Order embarque un Address et que l’adresse réembarque Order, aucune des deux dataclasses n’a besoin que l’autre soit définie en premier. from __future__ import annotations en haut du fichier fait que les forward refs sont résolus à l’exécution via PEP 563.

Comment gère-t-il map<K, V> ?

Rendu en dict[K, V] avec field(default_factory=dict) par défaut. Les maps Protobuf à clés non string (map<int32, string>) deviennent dict[int, str]. JSON ne supporte que des clés string, donc quand vous sérialisez le dict en JSON les clés int deviennent des strings — c’est une particularité de la spec JSON de proto3, pas du convertisseur.

Gère-t-il oneof ?

Chaque champ de oneof est émis comme un champ de dataclass classique. La sortie ne fait pas respecter la contrainte « exactement un » — pour ça il vous faudrait un type Union ou une structure discriminée, ce qui dépend de la façon dont votre runtime modélise l’exclusivité. La disposition à plat est facile à lire et correspond à ce que la plupart des bases de code Python font en pratique. Éditez à la main si vous voulez du typage plus strict.

Outils connexes

Si vous travaillez avec Protobuf, JSON et Python, ceux-ci s’associent bien :