Entrada (esquema .proto)

Salida (Python)

Qué hace esta herramienta

Tienes un esquema de Protocol Buffers y un servicio o script de Python que necesita los tipos correspondientes. El camino oficial es protoc con el plugin de Python (consulta el tutorial oficial de Python), que genera clases de mensaje funcionales pero incómodas de leer y ruidosas en los diffs. Esta herramienta emite dataclasses normales — limpias, idiomáticas y fáciles de mockear en tests. Pega el esquema, copia la salida y suéltala en tu proyecto.

El mapeo de tipos es justo lo que escribirías a mano. string/bytes se convierten en str/bytes, bool en bool, todos los enteros (int32 hasta sfixed64) en int, y double/float en float. repeated T pasa a list[T] con field(default_factory=list), map<K, V> a dict[K, V] con field(default_factory=dict), y las referencias singulares a mensajes pasan a Optional[Msg] con valor por defecto None, así las referencias circulares y forward references funcionan sin más.

Los enums se convierten en subclases de IntEnum, que es lo que el runtime oficial de protobuf en Python usa internamente y lo que la mayoría de revisores espera ver. from __future__ import annotations aparece arriba para que la evaluación pospuesta resuelva los forward refs limpiamente — sin comillas en los type hints del cuerpo. Los mensajes anidados se aplanan a dataclasses de nivel superior; Python no se beneficia de anidarlos como sí ocurre en Java, y los nombres planos son más fáciles de importar. Todo se ejecuta en tu navegador; nada de tu esquema sale de la página.

Cómo usarla

Tres pasos. La salida está lista para soltarla en un archivo <code>.py</code>.

1

Pega tu esquema .proto

Suelta el esquema en el editor de la izquierda. syntax = "proto3"; arriba está bien pero es opcional. El parser maneja bloques message anidados, declaraciones enum, oneof, map<K, V> y opciones de campo. Los imports se reconocen pero se omiten — pega los tipos importados en línea si tu esquema abarca varios archivos.

Los nombres de los campos se quedan tal cual: order_id en .proto sigue siendo order_id en Python. Snake_case ya es pythonico. Los nombres de clase también permanecen en PascalCase, según las convenciones de PEP 8.

2

Lee la salida

El panel derecho muestra Python con un @dataclass por mensaje y una subclase IntEnum por enum. Primero los enums, luego los mensajes en orden de dependencia (hijos antes que padres). Añade el archivo a tu proyecto, importa las dataclasses que necesites y listo.

3

Usa las dataclasses

Construye instancias con argumentos por nombre, mútalas como objetos normales y serialízalas con dataclasses.asdict() o json.dumps para transporte HTTP. Si necesitas codificación completa en wire-format de Protobuf, conecta las dataclasses con protobuf-python o úsalas como una capa tipada delante de tu cliente gRPC.

Cuándo ahorra tiempo de verdad

Esbozar tipos para un nuevo servicio gRPC en Python

Estás arrancando un servicio nuevo que consume una API de Protobuf existente. Quieres dataclasses limpias para las formas de request/response sin ejecutar todavía protoc. Pega el esquema, suelta la salida en types.py, escribe la lógica de negocio contra las dataclasses y conecta gRPC Python más adelante cuando esté listo.

Mockear datos de Protobuf en pytest

Las clases de mensaje generadas por Protobuf son dolorosas de construir en tests porque cada campo tiene su propio setter y los constructores no aceptan todos los campos como kwargs. Las dataclasses hechas a mano sí — Order(order_id="ORD-42", customer_name="Ava Chen", total_amount=99.50) funciona sin más. Usa esta salida como fixtures y mocks mientras conservas las clases reales de Protobuf para serialización en wire-format.

Revisar un cambio en una API de Protobuf

Un compañero de backend ha añadido campos a Order y un nuevo valor de OrderStatus. Quieres saber qué tiene que manejar tu código cliente en Python sin lanzar la build completa. Pega el nuevo .proto, haz diff de la salida de dataclasses contra tus tipos actuales y deja un comentario de revisión enfocado.

Scripts rápidos y ETL puntuales

Estás escribiendo un script de 50 líneas para hacer backfill de datos desde un volcado JSON que sigue un esquema de Protobuf. Configurar protoc para un script de un solo uso es exagerado. Coge las dataclasses de aquí, parsea el JSON dentro de ellas, ejecuta el script y tíralo. Sin paso de build, sin toolchain y sin archivos generados sobrantes en el repo.

Preguntas habituales

¿Se envía mi esquema a algún sitio?

No. El parser y el emisor de Python se ejecutan íntegramente en tu navegador como JavaScript. Abre las DevTools y mira la pestaña Network mientras pegas — cero peticiones. Útil cuando tu esquema incluye nombres de tipos internos, rutas de paquetes o cualquier cosa que prefieras no enviar a un servicio de terceros.

¿Por qué dataclasses en lugar de las clases oficiales de protobuf-python?

Las clases generadas por protoc funcionan, pero son verbosas, difíciles de mockear en tests y ruidosas en code review. Las dataclasses te dan kwargs tipados, comparación de igualdad y un repr limpio gratis. Si necesitas codificación en wire-format, puedes mapear entre las dataclasses y los tipos de mensaje oficiales en una capa adaptadora fina — la mayoría de equipos encuentra que es mejor partición que tipar todo contra las clases generadas.

¿Por qué IntEnum y no enums con valor de tipo str?

Los enums de Protobuf son enteros a nivel de wire — cada valor tiene un número de tag. IntEnum encaja exactamente: OrderStatus.ORDER_STATUS_PAID es a la vez un miembro con nombre y el entero 2, que hace round-trip limpio por JSON o wire format. Si quieres un StrEnum (Python 3.11+) para la codificación JSON, haz find-replace de IntEnum en la salida.

¿Por qué los campos de mensaje son Optional[Msg] en vez de solo Msg?

En proto3 un campo singular de mensaje puede estar sin asignar (la ausencia es significativa, a diferencia de los escalares donde el default es el valor cero). Que el default sea None encaja con esa semántica y permite que las referencias circulares compilen — si Order embebe un Address y la dirección lo embebe de vuelta, ninguna dataclass necesita que la otra esté definida primero. from __future__ import annotations en lo alto del archivo hace que los forward refs se resuelvan en tiempo de ejecución vía PEP 563.

¿Cómo maneja map<K, V>?

Lo renderiza como dict[K, V] con field(default_factory=dict) como default. Los maps de Protobuf con claves no string (map<int32, string>) pasan a dict[int, str]. JSON solo tiene claves string, así que cuando serialices el dict a JSON las claves int se convierten en strings — eso es una peculiaridad de la spec JSON de proto3, no del conversor.

¿Maneja oneof?

Cada campo de oneof se emite como un campo de dataclass normal. La salida no impone la restricción de "exactamente uno" — para eso querrías un tipo Union o una estructura discriminada, lo que depende de cómo modele tu runtime la exclusividad. La disposición plana es fácil de leer y coincide con lo que la mayoría de las bases de código de Python hace en la práctica. Edita a mano si necesitas tipado más estricto.

Herramientas relacionadas

Si trabajas con Protobuf, JSON y Python, estas combinan bien: