Skip to content

Commit 0475f51

Browse files
Rakshith Bhyravabhotlakristapratico
andauthored
Update handling error in query (#20765)
* samples * hide row * Handle errors * lint * lint * extract inner message * lint * lint * lint * api view fixes * more changes * Update sdk/monitor/azure-monitor-query/CHANGELOG.md * Apply suggestions from code review Co-authored-by: Krista Pratico <[email protected]> * changes Co-authored-by: Krista Pratico <[email protected]>
1 parent 1a9b633 commit 0475f51

17 files changed

+459
-789
lines changed

sdk/monitor/azure-monitor-query/CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44

55
### Features Added
66

7-
- Added `QueryPartialErrorException` and `LogsQueryError` to handle errors.
8-
- Added `partial_error` and `is_error` attributes to `LogsQueryResult`.
9-
- Added an option `allow_partial_errors` that defaults to False, which can be set to not throw if there are any partial errors.
7+
- Added `LogsQueryPartialResult` and `LogsQueryError` to handle errors.
8+
- Added `status` attribute to `LogsQueryResult`.
9+
- Added `LogsQueryStatus` Enum to describe the status of a result.
1010
- Added a new `LogsTableRow` type that represents a single row in a table.
1111

1212
### Breaking Changes
1313

1414
- `LogsQueryResult` now iterates over the tables directly as a convinience.
15+
- `query` API now returns a union of `LogsQueryPartialResult` and `LogsQueryResult`.
16+
- `query_batch` API now returns a union of `LogsQueryPartialResult`, `LogsQueryError` and `LogsQueryResult`.
1517
- `metric_namespace` is renamed to `namespace` and is a keyword-only argument in `list_metric_definitions` API.
1618

1719
### Bugs Fixed

sdk/monitor/azure-monitor-query/azure/monitor/query/__init__.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@
77
from ._logs_query_client import LogsQueryClient
88
from ._metrics_query_client import MetricsQueryClient
99

10-
from ._exceptions import (
11-
LogsQueryError,
12-
QueryPartialErrorException
13-
)
10+
from ._exceptions import LogsQueryError
1411

1512
from ._models import (
1613
MetricAggregationType,
1714
LogsQueryResult,
1815
LogsTable,
16+
LogsQueryPartialResult,
17+
LogsQueryStatus,
1918
LogsTableRow,
2019
MetricsResult,
2120
LogsBatchQuery,
@@ -27,7 +26,7 @@
2726
Metric,
2827
MetricValue,
2928
MetricClass,
30-
MetricAvailability
29+
MetricAvailability,
3130
)
3231

3332
from ._version import VERSION
@@ -36,8 +35,9 @@
3635
"MetricAggregationType",
3736
"LogsQueryClient",
3837
"LogsQueryResult",
38+
"LogsQueryPartialResult",
39+
"LogsQueryStatus",
3940
"LogsQueryError",
40-
"QueryPartialErrorException",
4141
"LogsTable",
4242
"LogsTableRow",
4343
"LogsBatchQuery",
@@ -51,7 +51,7 @@
5151
"Metric",
5252
"MetricValue",
5353
"MetricClass",
54-
"MetricAvailability"
54+
"MetricAvailability",
5555
]
5656

5757
__version__ = VERSION

sdk/monitor/azure-monitor-query/azure/monitor/query/_exceptions.py

Lines changed: 16 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
# Licensed under the MIT License. See License.txt in the project root for
55
# license information.
66
# --------------------------------------------------------------------------
7-
from azure.core.exceptions import HttpResponseError
7+
from ._models import LogsQueryStatus
8+
89

910
class LogsQueryError(object):
1011
"""The code and message for an error.
@@ -15,65 +16,26 @@ class LogsQueryError(object):
1516
:vartype code: str
1617
:ivar message: A human readable error message.
1718
:vartype message: str
18-
:ivar details: error details.
19-
:vartype details: list[~monitor_query_client.models.ErrorDetail]
20-
:ivar innererror: Inner error details if they exist.
21-
:vartype innererror: ~azure.monitor.query.LogsQueryError
22-
:ivar additional_properties: Additional properties that can be provided on the error info
23-
object.
24-
:vartype additional_properties: object
25-
:ivar bool is_error: Boolean check for error item when iterating over list of
26-
results. Always True for an instance of a LogsQueryError.
19+
:ivar status: status for error item when iterating over list of
20+
results. Always "Failure" for an instance of a LogsQueryError.
21+
:vartype status: ~azure.monitor.query.LogsQueryStatus
2722
"""
28-
def __init__(
29-
self,
30-
**kwargs
31-
):
32-
self.code = kwargs.get('code', None)
33-
self.message = kwargs.get('message', None)
34-
self.details = kwargs.get('details', None)
35-
self.innererror = kwargs.get('innererror', None)
36-
self.additional_properties = kwargs.get('additional_properties', None)
37-
self.is_error = True
23+
24+
def __init__(self, **kwargs):
25+
self.code = kwargs.get("code", None)
26+
self.message = kwargs.get("message", None)
27+
self.status = LogsQueryStatus.FAILURE
3828

3929
@classmethod
4030
def _from_generated(cls, generated):
4131
if not generated:
4232
return None
43-
details = None
44-
if generated.details is not None:
45-
details = [d.serialize() for d in generated.details]
33+
34+
innererror = generated
35+
while innererror.innererror is not None:
36+
innererror = innererror.innererror
37+
message = innererror.message
4638
return cls(
4739
code=generated.code,
48-
message=generated.message,
49-
innererror=cls._from_generated(generated.innererror) if generated.innererror else None,
50-
additional_properties=generated.additional_properties,
51-
details=details,
40+
message=message,
5241
)
53-
54-
class QueryPartialErrorException(HttpResponseError):
55-
"""There is a partial failure in query operation. This is thrown for a single query operation
56-
when allow_partial_errors is set to False.
57-
58-
:ivar code: A machine readable error code.
59-
:vartype code: str
60-
:ivar message: A human readable error message.
61-
:vartype message: str
62-
:ivar details: error details.
63-
:vartype details: list[~monitor_query_client.models.ErrorDetail]
64-
:ivar innererror: Inner error details if they exist.
65-
:vartype innererror: ~azure.monitor.query.LogsQueryError
66-
:ivar additional_properties: Additional properties that can be provided on the error info
67-
object.
68-
:vartype additional_properties: object
69-
"""
70-
71-
def __init__(self, **kwargs):
72-
error = kwargs.pop('error', None)
73-
if error:
74-
self.code = error.code
75-
self.message = error.message
76-
self.details = [d.serialize() for d in error.details] if error.details else None
77-
self.innererror = LogsQueryError._from_generated(error.innererror) if error.innererror else None
78-
self.additional_properties = error.additional_properties
79-
super(QueryPartialErrorException, self).__init__(message=self.message)

sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py

Lines changed: 52 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,101 +13,121 @@
1313
if TYPE_CHECKING:
1414
from azure.core.credentials import TokenCredential
1515

16+
1617
def get_authentication_policy(
17-
credential, # type: TokenCredential
18+
credential, # type: TokenCredential
1819
):
1920
# type: (...) -> BearerTokenCredentialPolicy
20-
"""Returns the correct authentication policy
21-
"""
21+
"""Returns the correct authentication policy"""
2222

2323
if credential is None:
2424
raise ValueError("Parameter 'credential' must not be None.")
2525
if hasattr(credential, "get_token"):
26-
return BearerTokenCredentialPolicy(credential, "https://api.loganalytics.io/.default")
26+
return BearerTokenCredentialPolicy(
27+
credential, "https://api.loganalytics.io/.default"
28+
)
2729

2830
raise TypeError("Unsupported credential")
2931

32+
3033
def get_metrics_authentication_policy(
31-
credential, # type: TokenCredential
34+
credential, # type: TokenCredential
3235
):
3336
# type: (...) -> BearerTokenCredentialPolicy
34-
"""Returns the correct authentication policy
35-
"""
37+
"""Returns the correct authentication policy"""
3638

3739
if credential is None:
3840
raise ValueError("Parameter 'credential' must not be None.")
3941
if hasattr(credential, "get_token"):
40-
return BearerTokenCredentialPolicy(credential, "https://management.azure.com/.default")
42+
return BearerTokenCredentialPolicy(
43+
credential, "https://management.azure.com/.default"
44+
)
4145

4246
raise TypeError("Unsupported credential")
4347

44-
def order_results(request_order, mapping, obj, err, allow_partial_errors=False):
48+
49+
def order_results(request_order, mapping, **kwargs):
4550
ordered = [mapping[id] for id in request_order]
4651
results = []
4752
for item in ordered:
4853
if not item.body.error:
49-
results.append(obj._from_generated(item.body)) # pylint: disable=protected-access
54+
results.append(
55+
kwargs.get("obj")._from_generated(item.body) # pylint: disable=protected-access
56+
)
5057
else:
5158
error = item.body.error
52-
if allow_partial_errors and error.code == 'PartialError':
53-
res = obj._from_generated(item.body) # pylint: disable=protected-access
54-
res.partial_error = err._from_generated(error) # pylint: disable=protected-access
59+
if error.code == "PartialError":
60+
res = kwargs.get("partial_err")._from_generated( # pylint: disable=protected-access
61+
item.body, kwargs.get("raise_with")
62+
)
5563
results.append(res)
5664
else:
57-
results.append(err._from_generated(error)) # pylint: disable=protected-access
65+
results.append(
66+
kwargs.get("err")._from_generated(error) # pylint: disable=protected-access
67+
)
5868
return results
5969

70+
6071
def construct_iso8601(timespan=None):
6172
if not timespan:
6273
return None
6374
try:
6475
start, end, duration = None, None, None
65-
if isinstance(timespan[1], datetime): # we treat thi as start_time, end_time
76+
if isinstance(timespan[1], datetime): # we treat thi as start_time, end_time
6677
start, end = timespan[0], timespan[1]
67-
elif isinstance(timespan[1], timedelta): # we treat this as start_time, duration
78+
elif isinstance(
79+
timespan[1], timedelta
80+
): # we treat this as start_time, duration
6881
start, duration = timespan[0], timespan[1]
6982
else:
70-
raise ValueError('Tuple must be a start datetime with a timedelta or an end datetime.')
83+
raise ValueError(
84+
"Tuple must be a start datetime with a timedelta or an end datetime."
85+
)
7186
except TypeError:
72-
duration = timespan # it means only duration (timedelta) is provideds
87+
duration = timespan # it means only duration (timedelta) is provideds
7388
if duration:
7489
try:
75-
duration = 'PT{}S'.format(duration.total_seconds())
90+
duration = "PT{}S".format(duration.total_seconds())
7691
except AttributeError:
77-
raise ValueError('timespan must be a timedelta or a tuple.')
92+
raise ValueError("timespan must be a timedelta or a tuple.")
7893
iso_str = None
7994
if start is not None:
8095
start = Serializer.serialize_iso(start)
8196
if end is not None:
8297
end = Serializer.serialize_iso(end)
83-
iso_str = start + '/' + end
98+
iso_str = start + "/" + end
8499
elif duration is not None:
85-
iso_str = start + '/' + duration
86-
else: # means that an invalid value None that is provided with start_time
87-
raise ValueError("Duration or end_time cannot be None when provided with start_time.")
100+
iso_str = start + "/" + duration
101+
else: # means that an invalid value None that is provided with start_time
102+
raise ValueError(
103+
"Duration or end_time cannot be None when provided with start_time."
104+
)
88105
else:
89106
iso_str = duration
90107
return iso_str
91108

109+
92110
def native_col_type(col_type, value):
93-
if col_type == 'datetime':
111+
if col_type == "datetime":
94112
value = Deserializer.deserialize_iso(value)
95-
elif col_type in ('timespan', 'guid'):
113+
elif col_type in ("timespan", "guid"):
96114
value = str(value)
97115
return value
98116

117+
99118
def process_row(col_types, row):
100119
return [native_col_type(col_types[ind], val) for ind, val in enumerate(row)]
101120

121+
102122
def process_error(error, model):
103123
try:
104-
model = model._from_generated(error.model.error) # pylint: disable=protected-access
105-
except AttributeError: # model can be none
124+
model = model._from_generated( # pylint: disable=protected-access
125+
error.model.error
126+
)
127+
except AttributeError: # model can be none
106128
pass
107-
raise HttpResponseError(
108-
message=error.message,
109-
response=error.response,
110-
model=model)
129+
raise HttpResponseError(message=error.message, response=error.response, model=model)
130+
111131

112132
def process_prefer(server_timeout, include_statistics, include_visualization):
113133
prefer = ""

0 commit comments

Comments
 (0)