Skip to content

Commit a78bd0f

Browse files
author
Ziang Zhang
committed
fix: wrap all type-hint resolution errors in CodecError
Codec.decode documented that it raises CodecError on any structural mismatch or conversion failure, but get_type_hints can also raise AttributeError (stale qualified references), TypeError (malformed expressions), and SyntaxError (invalid annotations). These escaped the CodecError contract. Fix: broaden the except clause to catch all four exception types. Closes #38
1 parent d3ff3f4 commit a78bd0f

2 files changed

Lines changed: 28 additions & 2 deletions

File tree

packages/dexpace-sdk-core/src/dexpace/sdk/core/serde/codec.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -908,10 +908,12 @@ def _resolve_info(target: type) -> _ModelInfo:
908908
localns = {tp.__name__: tp for tp in type_params} or None
909909
try:
910910
hints = get_type_hints(target, include_extras=True, localns=localns)
911-
except NameError as err:
911+
except (NameError, AttributeError, TypeError, SyntaxError) as err:
912912
# An unresolvable forward reference (a string annotation whose name is
913913
# not in scope) surfaces as a bare ``NameError`` from ``get_type_hints``;
914-
# wrap it so the codec keeps its ``CodecError`` contract.
914+
# other evaluation failures (stale qualified references, malformed
915+
# expressions) raise ``AttributeError``, ``TypeError``, or ``SyntaxError``.
916+
# Wrap them all so the codec keeps its ``CodecError`` contract.
915917
raise CodecError(
916918
f"cannot resolve a type hint on {target.__name__}: {err}",
917919
target_name=target.__name__,

packages/dexpace-sdk-core/tests/serde/test_codec.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,30 @@ class HasBadRef:
786786
assert "resolve" in str(info.value)
787787

788788

789+
def test_decode_stale_qualified_ref_raises_codec_error(codec: Codec) -> None:
790+
# ``get_type_hints`` raises ``AttributeError`` for a string annotation
791+
# referencing a non-existent attribute on a valid module.
792+
@dataclass(frozen=True, slots=True)
793+
class HasStaleRef:
794+
value: "os.ThisDoesNotExist" # type: ignore[name-defined] # noqa: F821
795+
796+
with pytest.raises(CodecError) as info:
797+
codec.decode({"value": 1}, HasStaleRef)
798+
assert "resolve" in str(info.value)
799+
800+
801+
def test_decode_malformed_expression_raises_codec_error(codec: Codec) -> None:
802+
# ``get_type_hints`` raises ``SyntaxError`` for a string annotation
803+
# that is not a valid expression.
804+
@dataclass(frozen=True, slots=True)
805+
class HasBadSyntax:
806+
value: "def f(" # type: ignore[name-defined] # noqa: F821
807+
808+
with pytest.raises(CodecError) as info:
809+
codec.decode({"value": 1}, HasBadSyntax)
810+
assert "resolve" in str(info.value)
811+
812+
789813
@dataclass(frozen=True, slots=True)
790814
class _Box[T]:
791815
item: T

0 commit comments

Comments
 (0)