Skip to content

Commit a539a04

Browse files
Clarify serdes dict contract in API docs
The serialize()/deserialize() public API operates on composite values represented as dictionaries, but the docstrings used wording that implied other return types were expected ("typically a dict"). This change tightens the wording to state the dict contract explicitly for both arguments and return values. The module-level description in pydsdl/_serdes.py is also corrected to match actual behavior for array decoding: arrays are usually lists, while UTF-8 and byte arrays decode to str and bytes respectively. To prevent regressions, a dedicated API test now verifies that deserialize() always returns dict for structure/union/delimited composites (including with delimiter headers) and that serialize() rejects non-dict composite inputs for the same schema categories.
1 parent 37b4cd3 commit a539a04

2 files changed

Lines changed: 44 additions & 3 deletions

File tree

pydsdl/_serdes.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
The binary serialization module is a tiny addition to the main functionality of the library that uses instances of
77
:class:`pydsdl.CompositeType` to build and parse serialized representations. This is an alternative approach to
88
serialization that does not involve code generation compared to Nunavut et al.
9-
Deserialized objects are represented using Python primitives: composites are dicts, arrays are lists, etc.
9+
Deserialized objects are represented using Python primitives: composites are dicts; arrays are usually lists
10+
(UTF-8 arrays become ``str`` and byte arrays become ``bytes``).
1011
"""
1112

1213
from __future__ import annotations
@@ -84,7 +85,7 @@ def serialize(schema: CompositeType, obj: _Obj, *, with_delimiter_header: bool =
8485
Serialize a Python object to bytes according to the given schema.
8586
8687
:param schema: The composite type schema defining the structure.
87-
:param obj: The Python object to serialize (typically a dict).
88+
:param obj: The Python object to serialize as a dict keyed by field name.
8889
:param with_delimiter_header: If True, prepend a delimiter header to the output.
8990
:return: The serialized bytes.
9091
:raises SerDesError: If serialization fails.
@@ -147,7 +148,7 @@ def deserialize(
147148
:param schema: The composite type schema defining the structure.
148149
:param data: The bytes to deserialize.
149150
:param with_delimiter_header: If True, expect and parse a delimiter header from the input.
150-
:return: The deserialized Python object (typically a dict).
151+
:return: The deserialized Python object as a dict keyed by field name.
151152
:raises SerDesError: If deserialization fails.
152153
:raises TypeError: If schema is a ServiceType.
153154
:raises ValueError: If with_delimiter_header=True on a non-delimited type.

pydsdl/_test_serdes.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,46 @@ class MockStructureType(StructureType):
129129
assert hasattr(pydsdl, "SerDesError")
130130

131131

132+
def _unittest_serdes_api_dict_contract() -> None:
133+
"""
134+
The public API operates on composite objects represented strictly as dict instances.
135+
"""
136+
structure = _mk_structure("test.APIDictContractStruct", [Field(UnsignedIntegerType(8, CM.TRUNCATED), "x")])
137+
union = _mk_union(
138+
"test.APIDictContractUnion",
139+
[Field(UnsignedIntegerType(8, CM.TRUNCATED), "a"), Field(UnsignedIntegerType(8, CM.TRUNCATED), "b")],
140+
)
141+
delimited_inner = _mk_structure("test.APIDictContractDelimitedInner", [Field(BooleanType(), "flag")])
142+
delimited = DelimitedType(delimited_inner, delimited_inner.extent)
143+
144+
structure_result = deserialize(structure, serialize(structure, {"x": 42}))
145+
assert isinstance(structure_result, dict)
146+
assert structure_result == {"x": 42}
147+
148+
union_result = deserialize(union, serialize(union, {"b": 99}))
149+
assert isinstance(union_result, dict)
150+
assert union_result == {"b": 99}
151+
152+
delimited_payload = serialize(delimited, {"flag": True})
153+
delimited_result = deserialize(delimited, delimited_payload)
154+
assert isinstance(delimited_result, dict)
155+
assert delimited_result == {"flag": True}
156+
157+
delimited_with_header = serialize(delimited, {"flag": True}, with_delimiter_header=True)
158+
delimited_header_result = deserialize(delimited, delimited_with_header, with_delimiter_header=True)
159+
assert isinstance(delimited_header_result, dict)
160+
assert delimited_header_result == {"flag": True}
161+
162+
with pytest.raises(ValueError, match="Structure value must be a dict"):
163+
serialize(structure, typing.cast(_Obj, typing.cast(object, 123)))
164+
165+
with pytest.raises(ValueError, match="Union value must be a dict"):
166+
serialize(union, typing.cast(_Obj, typing.cast(object, 123)))
167+
168+
with pytest.raises(ValueError, match="Structure value must be a dict"):
169+
serialize(delimited, typing.cast(_Obj, typing.cast(object, 123)))
170+
171+
132172
def _unittest_serdes_bit_writer() -> None:
133173
"""
134174
Test _BitWriter with various bit lengths, cross-byte writes, and alignment.

0 commit comments

Comments
 (0)