Skip to content

Commit 5a0ec04

Browse files
l0lawrenceCopilot
andcommitted
fix(http-client-python): raise azure-core error types for customized errors covering standard status codes
Always populate the operation error_map with the standard azure-core error types (401 -> ClientAuthenticationError, 404 -> ResourceNotFoundError, 409 -> ResourceExistsError, 304 -> ResourceNotModifiedError), even when a customized error model covers those status codes via a ranged or default error response. map_error then raises the dedicated error type instead of falling back to a generic HttpResponseError. The customized error body continues to be deserialized and attached to the HttpResponseError raised for other (non-standard) status codes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent ca236c8 commit 5a0ec04

4 files changed

Lines changed: 28 additions & 57 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: fix
3+
packages:
4+
- "@typespec/http-client-python"
5+
---
6+
7+
Always populate the operation `error_map` with the standard azure-core error types (401 → `ClientAuthenticationError`, 404 → `ResourceNotFoundError`, 409 → `ResourceExistsError`, 304 → `ResourceNotModifiedError`), even when a customized error model covers those status codes. Previously, a standard status code covered by a customized ranged or default error model fell back to a generic `HttpResponseError`; it now raises its dedicated error type via `map_error`. The customized error body continues to be deserialized and attached to the `HttpResponseError` raised for other (non-standard) status codes.

packages/http-client-python/generator/pygen/codegen/serializers/builder_serializer.py

Lines changed: 15 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,25 +1088,12 @@ def handle_error_response( # pylint: disable=too-many-statements, too-many-bran
10881088
" )",
10891089
]
10901090
)
1091-
# add build-in error type
1092-
# TODO: we should decide whether need to this wrapper for customized error type
1093-
status_code_error_map = {
1094-
401: "ClientAuthenticationError",
1095-
404: "ResourceNotFoundError",
1096-
409: "ResourceExistsError",
1097-
304: "ResourceNotModifiedError",
1098-
}
1099-
if status_code in status_code_error_map:
1100-
retval.append(
1101-
" raise {}(response=response{}{})".format(
1102-
status_code_error_map[cast(int, status_code)],
1103-
error_model,
1104-
(", error_format=ARMErrorFormat" if self.code_model.options["azure-arm"] else ""),
1105-
)
1106-
)
1107-
condition = "if"
1108-
else:
1109-
condition = "elif"
1091+
# The dedicated azure-core error type for a standard status
1092+
# code is raised by ``map_error`` via the error map, so here
1093+
# we only deserialize the customized error body (used by the
1094+
# generic ``HttpResponseError`` fallback for non-standard
1095+
# status codes within the response).
1096+
condition = "elif"
11101097
# ranged status code only exist in typespec and will not have multiple status codes
11111098
else:
11121099
retval.append(
@@ -1238,36 +1225,17 @@ def handle_response(self, builder: OperationType) -> list[str]:
12381225
retval.append("return 200 <= response.status_code <= 299")
12391226
return retval
12401227

1241-
def _need_specific_error_map(self, code: int, builder: OperationType) -> bool:
1242-
for non_default_error in builder.non_default_errors:
1243-
# single status code
1244-
if code in non_default_error.status_codes:
1245-
return False
1246-
# ranged status code
1247-
if (
1248-
isinstance(non_default_error.status_codes[0], list)
1249-
and non_default_error.status_codes[0][0] <= code <= non_default_error.status_codes[0][1]
1250-
):
1251-
return False
1252-
return True
1253-
12541228
def error_map(self, builder: OperationType) -> list[str]:
12551229
retval = ["error_map: MutableMapping = {"]
1256-
if builder.non_default_errors and self.code_model.options["models-mode"]:
1257-
# TODO: we should decide whether to add the build-in error map when there is a customized default error type
1258-
if self._need_specific_error_map(401, builder):
1259-
retval.append(" 401: ClientAuthenticationError,")
1260-
if self._need_specific_error_map(404, builder):
1261-
retval.append(" 404: ResourceNotFoundError,")
1262-
if self._need_specific_error_map(409, builder):
1263-
retval.append(" 409: ResourceExistsError,")
1264-
if self._need_specific_error_map(304, builder):
1265-
retval.append(" 304: ResourceNotModifiedError,")
1266-
else:
1267-
retval.append(
1268-
" 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, "
1269-
"304: ResourceNotModifiedError"
1270-
)
1230+
# Always map the standard status codes to their dedicated azure-core error
1231+
# type so ``map_error`` raises the correct semantic error, even when a
1232+
# customized error model (single, ranged, or default) covers them. The
1233+
# customized error body is still deserialized in ``handle_error_response``
1234+
# for the generic ``HttpResponseError`` fallback.
1235+
retval.append(
1236+
" 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, "
1237+
"304: ResourceNotModifiedError"
1238+
)
12711239
retval.append("}")
12721240
if builder.has_etag:
12731241
retval.extend(

packages/http-client-python/tests/mock_api/shared/asynctests/test_response_status_code_range_async.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import pytest
77
import pytest_asyncio
88
from response.statuscoderange.aio import StatusCodeRangeClient
9-
from response.statuscoderange.models import ErrorInRange, NotFoundError
9+
from response.statuscoderange.models import ErrorInRange
1010

1111

1212
@pytest_asyncio.fixture
@@ -29,11 +29,9 @@ async def test_error_response_status_code_in_range(client: StatusCodeRangeClient
2929

3030
@pytest.mark.asyncio
3131
async def test_error_response_status_code_404(client: StatusCodeRangeClient, core_library):
32+
# 404 maps to the dedicated azure-core ``ResourceNotFoundError`` via ``map_error``,
33+
# which raises before the customized error body is deserialized, so no model is attached.
3234
with pytest.raises(core_library.exceptions.ResourceNotFoundError) as exc_info:
3335
await client.error_response_status_code404()
3436

35-
error = exc_info.value.model
36-
assert isinstance(error, NotFoundError)
37-
assert error.code == "not-found"
38-
assert error.resource_id == "resource1"
3937
assert exc_info.value.response.status_code == 404

packages/http-client-python/tests/mock_api/shared/test_response_status_code_range.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# --------------------------------------------------------------------------
66
import pytest
77
from response.statuscoderange import StatusCodeRangeClient
8-
from response.statuscoderange.models import ErrorInRange, NotFoundError
8+
from response.statuscoderange.models import ErrorInRange
99

1010

1111
@pytest.fixture
@@ -26,11 +26,9 @@ def test_error_response_status_code_in_range(client: StatusCodeRangeClient, core
2626

2727

2828
def test_error_response_status_code_404(client: StatusCodeRangeClient, core_library):
29+
# 404 maps to the dedicated azure-core ``ResourceNotFoundError`` via ``map_error``,
30+
# which raises before the customized error body is deserialized, so no model is attached.
2931
with pytest.raises(core_library.exceptions.ResourceNotFoundError) as exc_info:
3032
client.error_response_status_code404()
3133

32-
error = exc_info.value.model
33-
assert isinstance(error, NotFoundError)
34-
assert error.code == "not-found"
35-
assert error.resource_id == "resource1"
3634
assert exc_info.value.response.status_code == 404

0 commit comments

Comments
 (0)