입력 (.proto 스키마)

출력 (Python)

이 도구가 하는 일

Protocol Buffers 스키마가 있고, 그에 맞는 타입이 필요한 Python 서비스나 스크립트가 있다고 합시다. 공식 경로는 Python 플러그인이 붙은 protoc입니다(공식 Python 튜토리얼). 생성된 메시지 클래스는 동작하지만 읽기 불편하고 diff에서도 시끄럽습니다. 이 도구는 그 대신 일반 dataclass를 출력합니다 — 깔끔하고 관용적이며 테스트에서 mock하기 쉽습니다. 스키마를 붙여넣고 출력을 복사해 프로젝트에 넣기만 하면 됩니다.

타입 매핑은 손으로 직접 작성했을 법한 그대로입니다. string/bytesstr/bytes로, boolbool로, 모든 정수 폭(int32부터 sfixed64까지)은 int로, double/floatfloat로 변환됩니다. repeated Tfield(default_factory=list)가 붙은 list[T]로, map<K, V>field(default_factory=dict)가 붙은 dict[K, V]로, 단일 메시지 참조는 기본값 NoneOptional[Msg]가 되어 순환 참조와 전방 참조가 그냥 동작합니다.

enum은 IntEnum의 서브클래스가 됩니다. 공식 Python protobuf 런타임이 내부적으로 사용하는 형태이며, 대부분의 코드 리뷰어가 익숙하게 보는 모양이기도 합니다. from __future__ import annotations가 파일 상단에 자리해 지연 평가가 전방 참조를 깔끔하게 처리합니다 — 본문에서 문자열로 감싼 타입 힌트가 필요 없습니다. 중첩 메시지는 최상위 dataclass로 평탄화됩니다. Python은 Java처럼 중첩에서 이득을 보지 않고, 평평한 이름이 import하기에도 편합니다. 모든 처리는 브라우저에서 일어나며, 스키마 어떤 부분도 페이지를 떠나지 않습니다.

사용 방법

세 단계입니다. 출력은 <code>.py</code> 파일에 그대로 넣을 수 있도록 준비되어 있습니다.

1

.proto 스키마를 붙여넣으세요

왼쪽 에디터에 스키마를 떨궈 넣으세요. 상단의 syntax = "proto3";는 있어도 좋지만 선택 사항입니다. 파서는 중첩된 message 블록, enum 선언, oneof, map<K, V>, 필드 옵션을 처리합니다. import는 인식만 하고 건너뜁니다 — 스키마가 여러 파일에 걸쳐 있다면 임포트되는 타입을 인라인으로 붙여넣어 주세요.

필드 이름은 그대로 유지됩니다: .protoorder_id는 Python에서도 order_id로 남습니다. snake_case는 이미 Pythonic합니다. 클래스 이름도 PascalCase 그대로이며 PEP 8 관례와 일치합니다.

2

출력을 읽어보세요

오른쪽 패널에는 메시지마다 @dataclass 하나, enum마다 IntEnum 서브클래스 하나가 표시됩니다. enum이 먼저, 이어서 의존성 순(자식이 부모보다 먼저)으로 메시지가 나옵니다. 파일을 프로젝트에 추가하고 필요한 dataclass를 import하면 끝입니다.

3

dataclass를 사용하세요

키워드 인자로 인스턴스를 만들고, 일반 객체처럼 변경하며, HTTP 전송용으로는 dataclasses.asdict()json.dumps로 직렬화하세요. 완전한 Protobuf 와이어 포맷 인코딩이 필요하다면 dataclass를 protobuf-python에 연결하거나, gRPC 클라이언트 앞단의 타입드 심으로 사용하세요.

실제로 시간을 아낄 때

새 gRPC Python 서비스의 타입 스케치

기존 Protobuf API를 소비하는 새 서비스를 시작하는 상황. 아직 protoc는 돌리고 싶지 않지만 request/response 형태에 대한 깔끔한 dataclass는 필요합니다. 스키마를 붙여넣고, 출력을 types.py에 넣고, dataclass에 대해 비즈니스 로직을 작성한 뒤, 준비가 되면 나중에 gRPC Python을 연결하세요.

pytest에서 Protobuf 데이터 mock하기

생성된 Protobuf 메시지 클래스는 필드마다 전용 setter가 있고 생성자가 모든 필드를 kwargs로 받지 않아서 테스트에서 만들기 까다롭습니다. 손수 만든 dataclass는 받습니다 — Order(order_id="ORD-42", customer_name="Ava Chen", total_amount=99.50)가 그냥 동작합니다. 이 출력을 픽스처와 mock으로 사용하고, 와이어 포맷 직렬화에는 진짜 Protobuf 클래스를 그대로 두세요.

Protobuf API 변경 리뷰

백엔드 동료가 Order에 필드를 추가하고 새 OrderStatus 값을 넣었습니다. 풀 빌드를 돌리지 않고도 Python 클라이언트 코드가 무엇을 처리해야 하는지 알고 싶을 때, 새 .proto를 붙여넣고 dataclass 출력과 현재 타입을 diff한 뒤, 핵심을 짚는 리뷰 코멘트를 남기면 됩니다.

빠른 스크립트와 일회성 ETL 작업

Protobuf 스키마를 따르는 JSON 덤프에서 데이터를 백필하는 50줄짜리 스크립트를 짜고 있는 상황. 일회성 스크립트에 protoc를 세팅하는 건 과합니다. 여기서 dataclass를 가져와 JSON을 그쪽으로 파싱하고, 스크립트를 돌린 뒤 버리세요. 빌드 스텝도, 툴체인도, 리포에 남는 생성 파일도 없습니다.

자주 묻는 질문

제 스키마가 어딘가로 전송되나요?

아니요. 파서와 Python 이미터는 모두 브라우저 안에서 JavaScript로 실행됩니다. DevTools를 열고 붙여넣는 동안 Network 탭을 지켜보세요 — 요청은 0건입니다. 스키마에 내부 타입명, 패키지 경로, 또는 서드파티 서비스로 보내고 싶지 않은 무언가가 들어 있을 때 유용합니다.

왜 공식 protobuf-python 메시지 클래스 대신 dataclass인가요?

protoc로 생성된 클래스는 동작하지만, 장황하고 테스트에서 mock하기 어렵고 코드 리뷰에서도 시끄럽습니다. dataclass는 타입드 kwargs, 동등성 비교, 깔끔한 repr을 공짜로 줍니다. 와이어 포맷 인코딩이 필요하다면 dataclass와 공식 메시지 타입 사이를 얇은 어댑터 레이어로 매핑할 수 있습니다 — 많은 팀이 모든 것을 생성 클래스에 대해 타입 짓는 것보다 이런 분리가 더 낫다고 느낍니다.

왜 str 값 enum이 아니라 IntEnum인가요?

Protobuf의 enum은 와이어 레벨에서 int 값입니다 — 각 값마다 태그 번호가 있습니다. IntEnum은 정확히 그것과 일치합니다: OrderStatus.ORDER_STATUS_PAID는 이름 있는 멤버이자 정수 2이며, JSON이나 와이어 포맷을 깔끔하게 round-trip합니다. JSON 인코딩용으로 StrEnum(Python 3.11+)을 원한다면 출력에서 IntEnum을 찾아 바꾸기만 하면 됩니다.

왜 메시지 필드가 그냥 Msg가 아니라 Optional[Msg]인가요?

proto3에서 단일 메시지 필드는 미설정 상태가 될 수 있습니다(스칼라처럼 기본값이 0인 것과 달리, 부재 자체에 의미가 있습니다). 기본값을 None으로 두면 그 의미와 맞고 순환 참조도 컴파일됩니다 — OrderAddress를 임베드하고 address가 다시 임베드해도 두 dataclass 어느 쪽도 먼저 정의될 필요가 없습니다. 파일 상단의 from __future__ import annotations가 PEP 563을 통해 전방 참조를 런타임에 해결합니다.

map<K, V>는 어떻게 처리하나요?

기본값으로 field(default_factory=dict)가 붙은 dict[K, V]로 렌더링합니다. 문자열이 아닌 키(map<int32, string>)의 Protobuf 맵은 dict[int, str]이 됩니다. JSON은 문자열 키만 가지므로 dict을 JSON으로 직렬화하면 int 키가 문자열이 됩니다 — 이는 proto3 JSON 사양의 특성이지 변환기 탓이 아닙니다.

oneof는 처리하나요?

oneof의 각 필드는 일반적인 dataclass 필드로 출력됩니다. 출력은 "정확히 하나" 제약을 강제하지 않습니다 — 그게 필요하면 Union 타입이나 판별 구조가 필요한데, 이는 런타임이 배타성을 어떻게 모델링하느냐에 달려 있습니다. 평평한 레이아웃은 읽기 쉽고 실무 Python 코드베이스 대다수가 따르는 모양입니다. 더 엄격한 타입이 필요하면 손으로 편집하세요.

관련 도구

Protobuf, JSON, Python을 함께 쓰고 있다면 이들이 잘 어울립니다: