Skip to content

Commit 62f9d7d

Browse files
hive-botclaude
andcommitted
fix: raise DeepLException on malformed spoken-terms responses
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent c06900f commit 62f9d7d

2 files changed

Lines changed: 83 additions & 6 deletions

File tree

‎deepl/deepl_client.py‎

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,18 @@
4242
from typing import (
4343
Any,
4444
BinaryIO,
45+
Callable,
4546
Dict,
4647
Iterable,
4748
List,
4849
Optional,
4950
TextIO,
51+
TypeVar,
5052
Union,
5153
)
5254

55+
_T = TypeVar("_T")
56+
5357

5458
class DeepLClient(Translator):
5559
def __init__(
@@ -750,7 +754,7 @@ def create_spoken_terms(
750754
"v3/spoken-terms", json=request_data
751755
)
752756
self._raise_for_status(status, content, json)
753-
return SpokenTerms.from_json(json)
757+
return self._parse_spoken_terms_response(SpokenTerms.from_json, json)
754758

755759
def list_spoken_terms(self) -> List[SpokenTerms]:
756760
"""Retrieves all spoken-terms collections.
@@ -764,7 +768,10 @@ def list_spoken_terms(self) -> List[SpokenTerms]:
764768
if (json and isinstance(json, dict))
765769
else []
766770
)
767-
return [SpokenTerms.from_json(item) for item in spoken_terms]
771+
return self._parse_spoken_terms_response(
772+
lambda items: [SpokenTerms.from_json(item) for item in items],
773+
spoken_terms,
774+
)
768775

769776
def get_spoken_terms(
770777
self, spoken_terms: Union[str, SpokenTerms]
@@ -780,7 +787,7 @@ def get_spoken_terms(
780787
f"v3/spoken-terms/{spoken_terms_id}", method="GET"
781788
)
782789
self._raise_for_status(status, content, json)
783-
return SpokenTerms.from_json(json)
790+
return self._parse_spoken_terms_response(SpokenTerms.from_json, json)
784791

785792
def update_spoken_terms(
786793
self,
@@ -816,7 +823,7 @@ def update_spoken_terms(
816823
json=request_data,
817824
)
818825
self._raise_for_status(status, content, json)
819-
return SpokenTerms.from_json(json)
826+
return self._parse_spoken_terms_response(SpokenTerms.from_json, json)
820827

821828
def get_spoken_terms_entries(
822829
self, spoken_terms: Union[str, SpokenTerms], lang: str
@@ -834,7 +841,9 @@ def get_spoken_terms_entries(
834841
method="GET",
835842
)
836843
self._raise_for_status(status, content, json)
837-
return SpokenTermsEntries.from_json(json)
844+
return self._parse_spoken_terms_response(
845+
SpokenTermsEntries.from_json, json
846+
)
838847

839848
def replace_spoken_terms_list(
840849
self,
@@ -859,7 +868,9 @@ def replace_spoken_terms_list(
859868
json=self._spoken_terms_json(entries),
860869
)
861870
self._raise_for_status(status, content, json)
862-
return SpokenTermsList.from_json(json)
871+
return self._parse_spoken_terms_response(
872+
SpokenTermsList.from_json, json
873+
)
863874

864875
def delete_spoken_terms(
865876
self, spoken_terms: Union[str, SpokenTerms]
@@ -891,6 +902,17 @@ def delete_spoken_terms_list(
891902
)
892903
self._raise_for_status(status, content, json)
893904

905+
@staticmethod
906+
def _parse_spoken_terms_response(
907+
parse: Callable[[Any], _T], json: Any
908+
) -> _T:
909+
try:
910+
return parse(json)
911+
except (AttributeError, KeyError, TypeError, ValueError) as e:
912+
raise DeepLException(
913+
f"Malformed spoken-terms response: {e!r}"
914+
) from e
915+
894916
@staticmethod
895917
def _spoken_terms_json(entries: SpokenTermsEntries) -> dict:
896918
# Terms are sent newline-separated: a term with a newline would

‎tests/offline/test_spoken_terms.py‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,58 @@ def test_rejects_a_whitespace_only_term(mocked):
9090
make_deepl_client().create_spoken_terms(
9191
"terms", [deepl.SpokenTermsEntries("en", ["a", " "])]
9292
)
93+
94+
95+
@pytest.mark.parametrize(
96+
"method, path, body, call",
97+
[
98+
(
99+
"POST",
100+
"v3/spoken-terms",
101+
{"name": "terms"},
102+
lambda c: c.create_spoken_terms("terms", []),
103+
),
104+
(
105+
"GET",
106+
"v3/spoken-terms/st-1",
107+
{**SPOKEN_TERMS_JSON, "creation_time": "yesterday"},
108+
lambda c: c.get_spoken_terms("st-1"),
109+
),
110+
(
111+
"PATCH",
112+
"v3/spoken-terms/st-1",
113+
"not json",
114+
lambda c: c.update_spoken_terms("st-1", name="renamed"),
115+
),
116+
(
117+
"GET",
118+
"v3/spoken-terms",
119+
{"spoken_terms": [{"name": "terms"}]},
120+
lambda c: c.list_spoken_terms(),
121+
),
122+
(
123+
"GET",
124+
"v3/spoken-terms/st-1/entries",
125+
"not json",
126+
lambda c: c.get_spoken_terms_entries("st-1", "en"),
127+
),
128+
(
129+
"PUT",
130+
"v3/spoken-terms/st-1/term-lists",
131+
{"lang": "en", "entry_count": "many"},
132+
lambda c: c.replace_spoken_terms_list(
133+
"st-1", deepl.SpokenTermsEntries("en", ["a"])
134+
),
135+
),
136+
],
137+
)
138+
def test_raises_deepl_exception_on_a_malformed_response(
139+
mocked, method, path, body, call
140+
):
141+
if isinstance(body, dict):
142+
mocked.add(method, url(path), json=body)
143+
else:
144+
mocked.add(method, url(path), body=body)
145+
146+
with pytest.raises(deepl.DeepLException, match="Malformed"):
147+
call(make_deepl_client())

0 commit comments

Comments
 (0)