The Python backend is integrated into the pdlc tool.
Example invocation:
pdlc --output-format python my-protocol.pdl > my-protocol.pyIf your PDL file uses custom types or checksums, you must provide the module
where they are defined using the --custom-field option:
pdlc --output-format python --custom-field my_protocol.custom_types my-protocol.pdl > my-protocol.pyThe generator produces a pure Python implementation of the parser and serializer
for the selected grammar, using only builtin features of the Python language.
The generated constructs are all type annotated and pass ruff validation.
All packets inherit either from their parent declaration or at the root
a blanket Packet class implementation.
@dataclass
class Packet:
payload: Optional[bytes] = field(repr=False, default_factory=bytes, compare=False)
@classmethod
def parse_all(cls, span: bytes) -> 'Packet':
...
@property
def size(self) -> int:
return 0
def show(self, prefix: str = '') -> None:
...enum TestEnum : 8 {
A = 1,
B = 2..3,
C = 4,
OTHER = ..,
}
|
class TestEnum(enum.IntEnum):
A = 1
C = 4
@staticmethod
def from_int(v: int) -> Union[int, 'TestEnum']:
pass |
Note
Python enums are closed by construction, default cases in enum declarations are ignored.
The static method from_int provides validation for enum tag ranges.
packet TestPacket {
a: 8,
b: TestEnum,
}
|
@dataclass
class TestPacket(Packet):
a: int = field(kw_only=True, default=0)
b: TestEnum = field(kw_only=True, default=TestEnum.A)
def __post_init__(self) -> None:
pass
@staticmethod
def parse(span: bytes) -> Tuple['TestPacket', bytes]:
pass
def serialize(self, payload: Optional[bytes] = None)
-> bytes:
pass
@property
def size(self) -> int:
pass |
packet TestPacket: ParentPacket {
a: 8,
b: TestEnum,
}
|
@dataclass
class TestPacket(ParentPacket):
a: int = field(kw_only=True, default=0)
b: TestEnum = field(kw_only=True, default=TestEnum.A)
@staticmethod
def parse(span: bytes) -> Tuple['TestPacket', bytes]:
pass
def serialize(self, payload: bytes = None) -> bytes:
pass
@property
def size(self) -> int:
pass |
Fields without a binding name do not have a concrete representation in the generated class, but are nonetheless validated during parsing or implicitely generated during serialization.
a: 8 |
a: int = field(kw_only=True, default=0) |
a: TestEnum, b: TestStruct |
a: TestEnum = field(kw_only=True, default=TestEnum.A)
b: TestStruct = field(kw_only=True,
default_factory=TestStruct) |
a: 8[], b: 16[128], c: TestEnum[], d: TestStruct[] |
a: List[int] = field(kw_only=True, default_factory=list)
b: List[int] = field(kw_only=True, default_factory=list)
c: List[TestEnum] = field(kw_only=True,
default_factory=list)
d: List[TestStruct] = field(kw_only=True,
default_factory=list) |
a: 8 if c_a = 1, b: TestEnum if c_b = 1, c: TestStruct if c_c = 1, |
a: Optional[int] = field(kw_only=True, default=None)
b: Optional[TestEnum] = field(kw_only=True, default=None)
c: Optional[TestStruct] = field(kw_only=True,
default=None) |