Skip to content

Commit 1b6e1d6

Browse files
Raise appropriate exception when error_body is dict | Fix exception raised during handling of another exception (Azure#18147)
* Raise appropriate exception when error_body is dict * Raise appropriate exception when error_body is dict * Raise appropriate exception when error_body is dict * cleaned up and accounted for missing error code * Cleaned up - fixed py2 and fixed error during handling * py2 check * fixed errors and added more changes * python 2 issue * ci * ci * fixing ci * fixed ci * fixed ci * linter * linter * linter * fixed ci * raise exception when there is no response in storage error
1 parent ee0af9b commit 1b6e1d6

File tree

5 files changed

+232
-71
lines changed

5 files changed

+232
-71
lines changed

sdk/storage/azure-storage-blob/azure/storage/blob/_shared/response_handlers.py

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
# Licensed under the MIT License. See License.txt in the project root for
44
# license information.
55
# --------------------------------------------------------------------------
6-
76
from typing import ( # pylint: disable=unused-import
87
Union, Optional, Any, Iterable, Dict, List, Type, Tuple,
98
TYPE_CHECKING
109
)
1110
import logging
11+
from xml.etree.ElementTree import Element
1212

1313
from azure.core.pipeline.policies import ContentDecodePolicy
1414
from azure.core.exceptions import (
@@ -22,7 +22,6 @@
2222
from .parser import _to_utc_datetime
2323
from .models import StorageErrorCode, UserDelegationKey, get_enum_value
2424

25-
2625
if TYPE_CHECKING:
2726
from datetime import datetime
2827
from azure.core.exceptions import AzureError
@@ -83,29 +82,46 @@ def return_context_and_deserialized(response, deserialized, response_headers):
8382
return response.http_response.location_mode, deserialized
8483

8584

86-
def process_storage_error(storage_error):
87-
# If storage_error is one of the two then it has already been processed and serialized to the specific exception.
88-
if isinstance(storage_error, (PartialBatchErrorException, ClientAuthenticationError)):
89-
raise storage_error
85+
def process_storage_error(storage_error): # pylint:disable=too-many-statements
9086
raise_error = HttpResponseError
87+
serialized = False
88+
if not storage_error.response:
89+
raise storage_error
90+
# If it is one of those three then it has been serialized prior by the generated layer.
91+
if isinstance(storage_error, (PartialBatchErrorException,
92+
ClientAuthenticationError, ResourceNotFoundError, ResourceExistsError)):
93+
serialized = True
9194
error_code = storage_error.response.headers.get('x-ms-error-code')
9295
error_message = storage_error.message
9396
additional_data = {}
97+
error_dict = {}
9498
try:
9599
error_body = ContentDecodePolicy.deserialize_from_http_generics(storage_error.response)
96-
if error_body:
97-
for info in error_body.iter():
98-
if info.tag.lower() == 'code':
99-
error_code = info.text
100-
elif info.tag.lower() == 'message':
101-
error_message = info.text
102-
else:
103-
additional_data[info.tag] = info.text
100+
# If it is an XML response
101+
if isinstance(error_body, Element):
102+
error_dict = {
103+
child.tag.lower(): child.text
104+
for child in error_body
105+
}
106+
# If it is a JSON response
107+
elif isinstance(error_body, dict):
108+
error_dict = error_body.get('error', {})
109+
elif not error_code:
110+
_LOGGER.warning(
111+
'Unexpected return type % from ContentDecodePolicy.deserialize_from_http_generics.', type(error_body))
112+
error_dict = {'message': str(error_body)}
113+
114+
# If we extracted from a Json or XML response
115+
if error_dict:
116+
error_code = error_dict.get('code')
117+
error_message = error_dict.get('message')
118+
additional_data = {k: v for k, v in error_dict.items() if k not in {'code', 'message'}}
104119
except DecodeError:
105120
pass
106121

107122
try:
108-
if error_code:
123+
# This check would be unnecessary if we have already serialized the error
124+
if error_code and not serialized:
109125
error_code = StorageErrorCode(error_code)
110126
if error_code in [StorageErrorCode.condition_not_met,
111127
StorageErrorCode.blob_overwritten]:
@@ -137,17 +153,30 @@ def process_storage_error(storage_error):
137153
# Got an unknown error code
138154
pass
139155

156+
# Error message should include all the error properties
140157
try:
141158
error_message += "\nErrorCode:{}".format(error_code.value)
142159
except AttributeError:
143160
error_message += "\nErrorCode:{}".format(error_code)
144161
for name, info in additional_data.items():
145162
error_message += "\n{}:{}".format(name, info)
146163

147-
error = raise_error(message=error_message, response=storage_error.response)
164+
# No need to create an instance if it has already been serialized by the generated layer
165+
if serialized:
166+
storage_error.message = error_message
167+
error = storage_error
168+
else:
169+
error = raise_error(message=error_message, response=storage_error.response)
170+
# Ensure these properties are stored in the error instance as well (not just the error message)
148171
error.error_code = error_code
149172
error.additional_info = additional_data
150-
error.raise_with_traceback()
173+
# error.args is what's surfaced on the traceback - show error message in all cases
174+
error.args = (error.message,)
175+
try:
176+
# `from None` prevents us from double printing the exception (suppresses generated layer error context)
177+
exec("raise error from None") # pylint: disable=exec-used # nosec
178+
except SyntaxError:
179+
raise error
151180

152181

153182
def parse_to_internal_user_delegation_key(service_user_delegation_key):

sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_deserialize.py

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
# Licensed under the MIT License. See License.txt in the project root for
44
# license information.
55
# --------------------------------------------------------------------------
6-
76
import logging
87
from typing import ( # pylint: disable=unused-import
98
TYPE_CHECKING
109
)
10+
from xml.etree.ElementTree import Element
1111

1212
from azure.core.pipeline.policies import ContentDecodePolicy
1313
from azure.core.exceptions import HttpResponseError, DecodeError, ResourceModifiedError, ClientAuthenticationError, \
@@ -111,26 +111,46 @@ def deserialize_metadata(response, obj, headers): # pylint: disable=unused-argu
111111
return {k[10:]: v for k, v in raw_metadata.items()}
112112

113113

114-
def process_storage_error(storage_error):
114+
def process_storage_error(storage_error): # pylint:disable=too-many-statements
115115
raise_error = HttpResponseError
116+
serialized = False
117+
if not storage_error.response:
118+
raise storage_error
119+
# If it is one of those three then it has been serialized prior by the generated layer.
120+
if isinstance(storage_error, (ResourceNotFoundError, ClientAuthenticationError, ResourceExistsError)):
121+
serialized = True
116122
error_code = storage_error.response.headers.get('x-ms-error-code')
117123
error_message = storage_error.message
118124
additional_data = {}
125+
error_dict = {}
119126
try:
120127
error_body = ContentDecodePolicy.deserialize_from_http_generics(storage_error.response)
121-
if error_body:
122-
for info in error_body:
123-
if info == 'code':
124-
error_code = error_body[info]
125-
elif info == 'message':
126-
error_message = error_body[info]
127-
else:
128-
additional_data[info] = error_body[info]
128+
# If it is an XML response
129+
if isinstance(error_body, Element):
130+
error_dict = {
131+
child.tag.lower(): child.text
132+
for child in error_body
133+
}
134+
# If it is a JSON response
135+
elif isinstance(error_body, dict):
136+
error_dict = error_body.get('error', {})
137+
elif not error_code:
138+
_LOGGER.warning(
139+
'Unexpected return type % from ContentDecodePolicy.deserialize_from_http_generics.', type(error_body))
140+
error_dict = {'message': str(error_body)}
141+
142+
# If we extracted from a Json or XML response
143+
if error_dict:
144+
error_code = error_dict.get('code')
145+
error_message = error_dict.get('message')
146+
additional_data = {k: v for k, v in error_dict.items() if k not in {'code', 'message'}}
147+
129148
except DecodeError:
130149
pass
131150

132151
try:
133-
if error_code:
152+
# This check would be unnecessary if we have already serialized the error.
153+
if error_code and not serialized:
134154
error_code = StorageErrorCode(error_code)
135155
if error_code in [StorageErrorCode.condition_not_met]:
136156
raise_error = ResourceModifiedError
@@ -166,15 +186,27 @@ def process_storage_error(storage_error):
166186
# Got an unknown error code
167187
pass
168188

189+
# Error message should include all the error properties
169190
try:
170191
error_message += "\nErrorCode:{}".format(error_code.value)
171192
except AttributeError:
172193
error_message += "\nErrorCode:{}".format(error_code)
173194
for name, info in additional_data.items():
174195
error_message += "\n{}:{}".format(name, info)
175196

176-
error = raise_error(message=error_message, response=storage_error.response,
177-
continuation_token=storage_error.continuation_token)
197+
# No need to create an instance if it has already been serialized by the generated layer
198+
if serialized:
199+
storage_error.message = error_message
200+
error = storage_error
201+
else:
202+
error = raise_error(message=error_message, response=storage_error.response)
203+
# Ensure these properties are stored in the error instance as well (not just the error message)
178204
error.error_code = error_code
179205
error.additional_info = additional_data
180-
raise error
206+
# error.args is what's surfaced on the traceback - show error message in all cases
207+
error.args = (error.message,)
208+
try:
209+
# `from None` prevents us from double printing the exception (suppresses generated layer error context)
210+
exec("raise error from None") # pylint: disable=exec-used # nosec
211+
except SyntaxError:
212+
raise error

sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_shared/response_handlers.py

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
# Licensed under the MIT License. See License.txt in the project root for
44
# license information.
55
# --------------------------------------------------------------------------
6-
76
from typing import ( # pylint: disable=unused-import
87
Union, Optional, Any, Iterable, Dict, List, Type, Tuple,
98
TYPE_CHECKING
109
)
1110
import logging
11+
from xml.etree.ElementTree import Element
1212

1313
from azure.core.pipeline.policies import ContentDecodePolicy
1414
from azure.core.exceptions import (
@@ -83,26 +83,46 @@ def return_context_and_deserialized(response, deserialized, response_headers):
8383
return response.http_response.location_mode, deserialized
8484

8585

86-
def process_storage_error(storage_error):
86+
def process_storage_error(storage_error): # pylint:disable=too-many-statements
8787
raise_error = HttpResponseError
88+
serialized = False
89+
if not storage_error.response:
90+
raise storage_error
91+
# If it is one of those three then it has been serialized prior by the generated layer.
92+
if isinstance(storage_error, (PartialBatchErrorException,
93+
ClientAuthenticationError, ResourceNotFoundError, ResourceExistsError)):
94+
serialized = True
8895
error_code = storage_error.response.headers.get('x-ms-error-code')
8996
error_message = storage_error.message
9097
additional_data = {}
98+
error_dict = {}
9199
try:
92100
error_body = ContentDecodePolicy.deserialize_from_http_generics(storage_error.response)
93-
if error_body:
94-
for info in error_body.iter():
95-
if info.tag.lower() == 'code':
96-
error_code = info.text
97-
elif info.tag.lower() == 'message':
98-
error_message = info.text
99-
else:
100-
additional_data[info.tag] = info.text
101+
# If it is an XML response
102+
if isinstance(error_body, Element):
103+
error_dict = {
104+
child.tag.lower(): child.text
105+
for child in error_body
106+
}
107+
# If it is a JSON response
108+
elif isinstance(error_body, dict):
109+
error_dict = error_body.get('error', {})
110+
elif not error_code:
111+
_LOGGER.warning(
112+
'Unexpected return type % from ContentDecodePolicy.deserialize_from_http_generics.', type(error_body))
113+
error_dict = {'message': str(error_body)}
114+
115+
# If we extracted from a Json or XML response
116+
if error_dict:
117+
error_code = error_dict.get('code')
118+
error_message = error_dict.get('message')
119+
additional_data = {k: v for k, v in error_dict.items() if k not in {'code', 'message'}}
101120
except DecodeError:
102121
pass
103122

104123
try:
105-
if error_code:
124+
# This check would be unnecessary if we have already serialized the error
125+
if error_code and not serialized:
106126
error_code = StorageErrorCode(error_code)
107127
if error_code in [StorageErrorCode.condition_not_met,
108128
StorageErrorCode.blob_overwritten]:
@@ -134,17 +154,30 @@ def process_storage_error(storage_error):
134154
# Got an unknown error code
135155
pass
136156

157+
# Error message should include all the error properties
137158
try:
138159
error_message += "\nErrorCode:{}".format(error_code.value)
139160
except AttributeError:
140161
error_message += "\nErrorCode:{}".format(error_code)
141162
for name, info in additional_data.items():
142163
error_message += "\n{}:{}".format(name, info)
143164

144-
error = raise_error(message=error_message, response=storage_error.response)
165+
# No need to create an instance if it has already been serialized by the generated layer
166+
if serialized:
167+
storage_error.message = error_message
168+
error = storage_error
169+
else:
170+
error = raise_error(message=error_message, response=storage_error.response)
171+
# Ensure these properties are stored in the error instance as well (not just the error message)
145172
error.error_code = error_code
146173
error.additional_info = additional_data
147-
error.raise_with_traceback()
174+
# error.args is what's surfaced on the traceback - show error message in all cases
175+
error.args = (error.message,)
176+
try:
177+
# `from None` prevents us from double printing the exception (suppresses generated layer error context)
178+
exec("raise error from None") # pylint: disable=exec-used # nosec
179+
except SyntaxError:
180+
raise error
148181

149182

150183
def parse_to_internal_user_delegation_key(service_user_delegation_key):

0 commit comments

Comments
 (0)