Input (schema .proto)

Output (Python)

Cosa fa questo strumento

Hai uno schema Protocol Buffers e un servizio o uno script Python che ha bisogno dei tipi corrispondenti. La via ufficiale è protoc con il plugin Python (vedi il tutorial Python ufficiale), che produce classi di messaggio funzionanti ma scomode da leggere e rumorose nei diff. Questo strumento emette invece dataclass normali — pulite, idiomatiche e facili da mockare nei test. Incolli lo schema, copi l’output, lo butti nel progetto.

La mappatura dei tipi è quella che scriveresti a mano. string/bytes diventano str/bytes, bool diventa bool, ogni larghezza intera (da int32 a sfixed64) diventa int, e double/float diventano float. repeated T diventa list[T] con field(default_factory=list), map<K, V> diventa dict[K, V] con field(default_factory=dict), e i riferimenti singoli ai messaggi diventano Optional[Msg] con default None, così riferimenti circolari e forward reference funzionano subito.

Gli enum diventano sottoclassi di IntEnum, che è quello che usa internamente il runtime ufficiale di protobuf in Python e quello che la maggior parte dei revisori si aspetta di vedere. from __future__ import annotations sta in cima così la valutazione posticipata gestisce i forward ref puliti — niente type hint tra virgolette nel corpo. I messaggi annidati vengono appiattiti a dataclass di primo livello; in Python l’annidamento non porta gli stessi vantaggi che in Java, e i nomi piatti sono più comodi da importare. Tutto gira nel tuo browser; nulla del tuo schema lascia la pagina.

Come si usa

Tre passi. L’output è pronto da mettere in un file <code>.py</code>.

1

Incolla il tuo schema .proto

Butta lo schema nell’editor di sinistra. syntax = "proto3"; in cima va bene ma è opzionale. Il parser gestisce blocchi message annidati, dichiarazioni enum, oneof, map<K, V> e le opzioni dei campi. Gli import vengono riconosciuti ma saltati — se il tuo schema si estende su più file, incolla i tipi importati inline.

I nomi dei campi restano com’è: order_id in .proto resta order_id in Python. snake_case è già pythonico. Anche i nomi delle classi restano in PascalCase, in linea con le convenzioni del PEP 8.

2

Leggi l’output

Il pannello di destra mostra Python con un @dataclass per ogni messaggio e una sottoclasse IntEnum per ogni enum. Prima gli enum, poi i messaggi in ordine di dipendenza (figli prima dei genitori). Aggiungi il file al progetto, importa le dataclass che ti servono e hai finito.

3

Usa le dataclass

Costruisci le istanze con argomenti keyword, modificale come oggetti normali, serializza con dataclasses.asdict() o json.dumps per il trasporto HTTP. Se ti serve l’encoding Protobuf in wire format completo, collega le dataclass a protobuf-python oppure usale come uno shim tipato davanti al tuo client gRPC.

Quando fa davvero risparmiare tempo

Buttare giù i tipi per un nuovo servizio gRPC in Python

Stai avviando un nuovo servizio che consuma un’API Protobuf esistente. Vuoi dataclass pulite per le forme di richiesta/risposta senza ancora lanciare protoc. Incolla lo schema, butta l’output in types.py, scrivi la business logic contro le dataclass, attaccaci gRPC Python più tardi quando sei pronto.

Mockare dati Protobuf in pytest

Le classi di messaggio Protobuf generate sono fastidiose da costruire nei test perché ogni campo ha il suo setter dedicato e i costruttori non accettano tutti i campi come kwargs. Le dataclass fatte a mano sì — Order(order_id="ORD-42", customer_name="Ava Chen", total_amount=99.50) funziona e basta. Usa questo output come fixture e mock e tieni le vere classi Protobuf per la serializzazione in wire format.

Recensire un cambiamento di API Protobuf

Un collega del backend ha aggiunto campi a Order e un nuovo valore OrderStatus. Vuoi sapere cosa deve gestire il tuo client Python senza far girare la build completa. Incolla il nuovo .proto, fai il diff dell’output dataclass contro i tuoi tipi attuali e lascia un commento di review mirato.

Script veloci e job ETL una tantum

Stai scrivendo uno script da 50 righe per fare il backfill dei dati da un dump JSON che segue uno schema Protobuf. Tirare su protoc per uno script usa-e-getta è un’esagerazione. Prendi le dataclass da qui, parsi il JSON dentro, lanci lo script, lo butti via. Niente passo di build, niente toolchain, niente file generati lasciati nel repository.

Domande frequenti

Il mio schema viene inviato da qualche parte?

No. Parser ed emitter Python girano interamente nel tuo browser come JavaScript. Apri DevTools e osserva il tab Network mentre incolli — zero richieste. Utile quando il tuo schema contiene nomi di tipi interni, percorsi di package o qualunque cosa che preferiresti non spedire a un servizio di terze parti.

Perché le dataclass invece delle classi ufficiali di protobuf-python?

Le classi generate da protoc funzionano, ma sono verbose, difficili da mockare nei test e rumorose in code review. Le dataclass ti danno kwargs tipati, confronto di uguaglianza e repr pulito gratis. Se ti serve l’encoding in wire format, puoi mappare tra le dataclass e i tipi di messaggio ufficiali con un sottile layer di adapter — la maggior parte dei team trova questa divisione migliore che tipizzare tutto contro le classi generate.

Perché IntEnum e non enum a valore str?

Gli enum Protobuf sono a valore int a livello wire — ogni valore ha un numero di tag. IntEnum ci si incastra perfettamente: OrderStatus.ORDER_STATUS_PAID è insieme un membro con nome e l’intero 2, che fa round-trip pulito con JSON o wire format. Se vuoi un StrEnum (Python 3.11+) per l’encoding JSON, fai un find-replace di IntEnum nell’output.

Perché i campi messaggio sono Optional[Msg] e non solo Msg?

In proto3 un campo singolo di messaggio può non essere impostato (l’assenza è significativa, a differenza degli scalari dove il default è il valore zero). Avere None come default rispetta questa semantica e tiene compilabili i riferimenti circolari — se Order incorpora un Address e l’address rimanda indietro, nessuna delle due dataclass deve essere definita per prima. from __future__ import annotations in cima al file fa risolvere i forward ref a runtime tramite PEP 563.

Come gestisce map<K, V>?

Lo rende come dict[K, V] con field(default_factory=dict) di default. Le map Protobuf con chiavi non stringa (map<int32, string>) diventano dict[int, str]. JSON ammette solo chiavi stringa, quindi serializzando il dict in JSON le chiavi int diventano stringhe — è una stranezza della spec JSON di proto3, non del convertitore.

Gestisce oneof?

Ogni campo di un oneof viene emesso come campo dataclass normale. L’output non impone il vincolo "esattamente uno" — per quello vorresti un tipo Union o una struttura discriminata, e dipende da come il tuo runtime modella l’esclusività. Il layout piatto si legge bene e corrisponde a ciò che fa la maggior parte delle codebase Python in pratica. Modifica a mano se ti serve una tipizzazione più stretta.

Strumenti correlati

Se lavori con Protobuf, JSON e Python, questi si abbinano bene: