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) |
Decoding and encoding errors are reported with DecodeError and EncodeError exception
variants. The following is an exhaustive list of error conditions and the reported exception.
DecodeErrorParent class for all decoding exceptions.EnumValueErrorThe decoded value for an enum field does not match any of the declared enum tags or tag ranges.FixedValueErrorThe decoded value for a fixed scalar or enum field does not match the expected value.ConstraintValueErrorThe decoded value for a scalar or enum field does not match the constraint value.LengthErrorField decoding triggers an overflow on the input bytes.ArraySizeErrorThe decoded size of an array is not a multiple of the known element size.TrailingBytesErrorInput bytes remain after decoding the entire packet.