Skip to content

Commit 123f0a4

Browse files
committed
refactor: refines msgpack decoding in api generator and boolean fields in block model
Improves msgpack decoding in Algod, Indexer and KMD clients by handling byte keys and values. This prevents decoding errors when encountering non-UTF-8 byte sequences. Additionally, adds decoding for boolean fields in block models to correctly interpret raw values as booleans. This addresses issues with inconsistent data representation.
1 parent a531367 commit 123f0a4

File tree

14 files changed

+157
-173
lines changed

14 files changed

+157
-173
lines changed

api/oas-generator/src/oas_generator/renderer/templates/client.py.j2

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ class {{ client.class_name }}:
248248
return response.content
249249
content_type = response.headers.get("content-type", "application/json")
250250
if "msgpack" in content_type:
251-
data = msgpack.unpackb(response.content, raw=False, strict_map_key=False)
251+
data = msgpack.unpackb(response.content, raw=True, strict_map_key=False)
252252
data = self._normalize_msgpack(data)
253253
elif content_type.startswith("application/json"):
254254
data = response.json()
@@ -264,7 +264,23 @@ class {{ client.class_name }}:
264264

265265
def _normalize_msgpack(self, value: object) -> object:
266266
if isinstance(value, dict):
267-
return {key: self._normalize_msgpack(item) for key, item in value.items()}
267+
normalized: dict[object, object] = {}
268+
for key, item in value.items():
269+
normalized[self._coerce_msgpack_key(key)] = self._normalize_msgpack(item)
270+
return normalized
268271
if isinstance(value, list):
269272
return [self._normalize_msgpack(item) for item in value]
273+
if isinstance(value, bytes):
274+
try:
275+
return value.decode("utf-8")
276+
except UnicodeDecodeError:
277+
return value
270278
return value
279+
280+
def _coerce_msgpack_key(self, key: object) -> object:
281+
if isinstance(key, bytes):
282+
try:
283+
return key.decode("utf-8")
284+
except UnicodeDecodeError:
285+
return key
286+
return key

api/oas-generator/src/oas_generator/renderer/templates/models/_serde_helpers.py.j2

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ from typing import Callable, TypeAlias, TypeVar
88

99
from algokit_common.serde import from_wire, to_wire
1010

11-
T = TypeVar("T")
12-
E = TypeVar("E", bound=Enum)
13-
KT = TypeVar("KT")
11+
DecodedT = TypeVar("DecodedT")
12+
EnumValueT = TypeVar("EnumValueT", bound=Enum)
13+
MapKeyT = TypeVar("MapKeyT")
1414
BytesLike: TypeAlias = bytes | bytearray | memoryview
1515

1616

@@ -77,11 +77,11 @@ def encode_model_sequence(values: Iterable[object] | None) -> list[dict[str, obj
7777
return encoded or None
7878

7979

80-
def decode_model_sequence(cls_factory: Callable[[], type[T]], raw: object) -> list[T] | None:
80+
def decode_model_sequence(cls_factory: Callable[[], type[DecodedT]], raw: object) -> list[DecodedT] | None:
8181
if not isinstance(raw, list):
8282
return None
8383
cls = cls_factory()
84-
decoded: list[T] = []
84+
decoded: list[DecodedT] = []
8585
for item in raw:
8686
if isinstance(item, Mapping):
8787
decoded.append(from_wire(cls, item))
@@ -99,11 +99,11 @@ def encode_enum_sequence(values: Iterable[object] | None) -> list[object] | None
9999
return encoded or None
100100

101101

102-
def decode_enum_sequence(enum_factory: Callable[[], type[E]], raw: object) -> list[E] | None:
102+
def decode_enum_sequence(enum_factory: Callable[[], type[EnumValueT]], raw: object) -> list[EnumValueT] | None:
103103
if not isinstance(raw, list):
104104
return None
105105
enum_cls = enum_factory()
106-
decoded: list[E] = []
106+
decoded: list[EnumValueT] = []
107107
for item in raw:
108108
try:
109109
decoded.append(enum_cls(item))
@@ -113,7 +113,7 @@ def decode_enum_sequence(enum_factory: Callable[[], type[E]], raw: object) -> li
113113

114114

115115
def encode_model_mapping(
116-
factory: Callable[[], type[T]],
116+
factory: Callable[[], type[DecodedT]],
117117
mapping: Mapping[object, object] | None,
118118
*,
119119
key_encoder: Callable[[object], str] | None = None,
@@ -140,15 +140,15 @@ def encode_model_mapping(
140140

141141

142142
def decode_model_mapping(
143-
factory: Callable[[], type[T]],
143+
factory: Callable[[], type[DecodedT]],
144144
raw: object,
145145
*,
146-
key_decoder: Callable[[object], KT] | None = None,
147-
) -> dict[KT, T] | None:
146+
key_decoder: Callable[[object], MapKeyT] | None = None,
147+
) -> dict[MapKeyT, DecodedT] | None:
148148
if not isinstance(raw, Mapping):
149149
return None
150150
cls = factory()
151-
decoded: dict[KT, T] = {}
151+
decoded: dict[MapKeyT, DecodedT] = {}
152152
for key, value in raw.items():
153153
if isinstance(value, Mapping):
154154
decoded_key = key_decoder(key) if key_decoder is not None else key
@@ -157,7 +157,7 @@ def decode_model_mapping(
157157

158158

159159
def mapping_encoder(
160-
factory: Callable[[], type[T]],
160+
factory: Callable[[], type[DecodedT]],
161161
*,
162162
key_encoder: Callable[[object], str] | None = None,
163163
) -> Callable[[Mapping[object, object] | None], dict[str, object] | None]:
@@ -168,11 +168,11 @@ def mapping_encoder(
168168

169169

170170
def mapping_decoder(
171-
factory: Callable[[], type[T]],
171+
factory: Callable[[], type[DecodedT]],
172172
*,
173-
key_decoder: Callable[[object], KT] | None = None,
174-
) -> Callable[[object], dict[KT, T] | None]:
175-
def _decode(raw: object) -> dict[KT, T] | None:
173+
key_decoder: Callable[[object], MapKeyT] | None = None,
174+
) -> Callable[[object], dict[MapKeyT, DecodedT] | None]:
175+
def _decode(raw: object) -> dict[MapKeyT, DecodedT] | None:
176176
return decode_model_mapping(factory, raw, key_decoder=key_decoder)
177177

178178
return _decode

api/oas-generator/src/oas_generator/renderer/templates/models/block.py.j2

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,8 @@ class SignedTxnInBlock:
156156
)
157157
config_asset: int | None = field(default=None, metadata=wire("caid"))
158158
application_id: int | None = field(default=None, metadata=wire("apid"))
159-
has_genesis_id: bool | None = field(default=None, metadata=wire("hgi"))
160-
has_genesis_hash: bool | None = field(default=None, metadata=wire("hgh"))
159+
has_genesis_id: bool | None = field(default=None, metadata=wire("hgi", decode=lambda raw: bool(raw)))
160+
has_genesis_hash: bool | None = field(default=None, metadata=wire("hgh", decode=lambda raw: bool(raw)))
161161

162162

163163
@dataclass(slots=True)

api/specs/compatibility.md

Lines changed: 0 additions & 79 deletions
This file was deleted.

src/algokit_algod_client/client.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1937,7 +1937,7 @@ def _decode_response(
19371937
return response.content
19381938
content_type = response.headers.get("content-type", "application/json")
19391939
if "msgpack" in content_type:
1940-
data = msgpack.unpackb(response.content, raw=False, strict_map_key=False)
1940+
data = msgpack.unpackb(response.content, raw=True, strict_map_key=False)
19411941
data = self._normalize_msgpack(data)
19421942
elif content_type.startswith("application/json"):
19431943
data = response.json()
@@ -1953,7 +1953,23 @@ def _decode_response(
19531953

19541954
def _normalize_msgpack(self, value: object) -> object:
19551955
if isinstance(value, dict):
1956-
return {key: self._normalize_msgpack(item) for key, item in value.items()}
1956+
normalized: dict[object, object] = {}
1957+
for key, item in value.items():
1958+
normalized[self._coerce_msgpack_key(key)] = self._normalize_msgpack(item)
1959+
return normalized
19571960
if isinstance(value, list):
19581961
return [self._normalize_msgpack(item) for item in value]
1962+
if isinstance(value, bytes):
1963+
try:
1964+
return value.decode("utf-8")
1965+
except UnicodeDecodeError:
1966+
return value
19591967
return value
1968+
1969+
def _coerce_msgpack_key(self, key: object) -> object:
1970+
if isinstance(key, bytes):
1971+
try:
1972+
return key.decode("utf-8")
1973+
except UnicodeDecodeError:
1974+
return key
1975+
return key

0 commit comments

Comments
 (0)