Input (.proto schema)

Output (Python)

What this tool does

You have a Protocol Buffers schema and a Python service or script that needs matching types. The official path is protoc with the Python plugin (see the official Python tutorial), which produces generated message classes that work but are awkward to read and noisy in diffs. This tool emits plain dataclasses instead — clean, idiomatic, and easy to mock in tests. Paste the schema, copy the output, drop it into your project.

Type mapping is what you would write by hand. string/bytes become str/bytes, bool becomes bool, every integer width (int32 through sfixed64) becomes int, and double/float become float. repeated T becomes list[T] with field(default_factory=list), map<K, V> becomes dict[K, V] with field(default_factory=dict), and singular message references become Optional[Msg] defaulting to None so circular references and forward references just work.

Enums become subclasses of IntEnum, which is what the official Python protobuf runtime uses internally and what most code reviewers expect to see. from __future__ import annotations sits at the top so postponed evaluation handles forward refs cleanly — no string-quoted type hints needed in the body. Nested messages get flattened to top-level dataclasses; Python does not benefit from nesting them the way Java does, and flat names are easier to import. Everything runs in your browser; nothing about your schema leaves the page.

How to use it

Three steps. The output is ready to drop into a <code>.py</code> file.

1

Paste your .proto schema

Drop the schema into the left editor. syntax = "proto3"; at the top is fine but optional. The parser handles nested message blocks, enum declarations, oneof, map<K, V>, and field options. Imports are recognised but skipped — paste imported types inline if your schema spans multiple files.

Field names stay as-is: order_id in .proto stays order_id in Python. Snake_case is already Pythonic. Class names also stay PascalCase, matching PEP 8 conventions.

2

Read the output

The right panel shows Python with one @dataclass per message and one IntEnum subclass per enum. Enums come first, then messages in dependency order (children before parents). Add the file to your project, import the dataclasses you need, and you are done.

3

Use the dataclasses

Construct instances with keyword arguments, mutate them like normal objects, serialise with dataclasses.asdict() or json.dumps for HTTP transport. If you need full Protobuf wire-format encoding, plug the dataclasses into protobuf-python or use them as a typed shim in front of your gRPC client.

When this actually saves time

Sketching types for a new gRPC Python service

You are starting a new service that consumes an existing Protobuf API. You want clean dataclasses for the request/response shapes without running protoc yet. Paste the schema, drop the output into types.py, write your business logic against the dataclasses, hook up gRPC Python later when you are ready.

Mocking Protobuf data in pytest

Generated Protobuf message classes are painful to construct in tests because every field has its own setter, and the constructors do not accept all fields as kwargs. Hand-rolled dataclasses do — Order(order_id="ORD-42", customer_name="Ava Chen", total_amount=99.50) just works. Use this output as fixtures and mocks while keeping the real Protobuf classes for wire-format serialisation.

Reviewing a Protobuf API change

A backend teammate added fields to Order and a new OrderStatus value. You want to know what your Python client code needs to handle without running the full build. Paste the new .proto, diff the dataclass output against your current types, leave a focused review comment.

Quick scripts and one-off ETL jobs

You are writing a 50-line script to backfill data from a JSON dump that follows a Protobuf schema. Setting up protoc for a one-shot script is overkill. Grab the dataclasses from here, parse the JSON into them, run the script, throw it away. No build step, no toolchain, no leftover generated files in the repo.

Common questions

Is my schema sent anywhere?

No. The parser and Python emitter run entirely in your browser as JavaScript. Open DevTools and watch the Network tab while you paste — zero requests. Useful when your schema includes internal type names, package paths, or anything you would rather not ship to a third-party service.

Why dataclasses instead of the official protobuf-python message classes?

The generated classes from protoc work, but they are verbose, hard to mock in tests, and noisy in code review. Dataclasses give you typed kwargs, equality comparison, and clean repr for free. If you need wire-format encoding, you can map between the dataclasses and the official message types in a thin adapter layer — most teams find that a better split than typing everything against the generated classes.

Why IntEnum and not str-valued enums?

Protobuf enums are int-valued at the wire level — every value has a tag number. IntEnum matches that exactly: OrderStatus.ORDER_STATUS_PAID is both a named member and the integer 2, which round-trips cleanly through JSON or wire format. If you want a StrEnum (Python 3.11+) for the JSON encoding instead, find-replace IntEnum in the output.

Why are message fields Optional[Msg] instead of just Msg?

In proto3 a singular message field can be unset (the absence is meaningful, unlike scalars where the default is the zero value). Defaulting to None matches that semantics and keeps circular references compiling — if Order embeds an Address and the address embeds back, neither dataclass needs the other to be defined first. from __future__ import annotations at the top of the file makes the forward refs resolve at runtime via PEP 563.

How does it handle map<K, V>?

Renders as dict[K, V] with field(default_factory=dict) as the default. Protobuf maps with non-string keys (map<int32, string>) become dict[int, str]. JSON only has string keys, so when you serialise the dict to JSON the int keys become strings — that is a quirk of the proto3 JSON spec, not the converter.

Does it handle oneof?

Each oneof field is emitted as a regular dataclass field. The output does not enforce the "exactly one" constraint — for that you would want a Union type or a discriminated structure, which depends on how your runtime models exclusivity. The flat layout is easy to read and matches what most Python codebases do in practice. Edit by hand if you need stricter typing.

Related tools

If you are working with Protobuf, JSON, and Python, these pair well: