From 6233a7b1156b8d8c0e3e3b248089b88b20d35a5f Mon Sep 17 00:00:00 2001 From: Rakshith Bhyravabhotla Date: Thu, 2 Sep 2021 13:10:42 -0700 Subject: [PATCH 01/12] initial commit --- .../query/_generated/models/_models_py3.py | 2 +- .../azure/monitor/query/_helpers.py | 14 ++++- .../azure/monitor/query/_logs_query_client.py | 24 ++++++-- .../azure/monitor/query/_models.py | 61 +++++++++++++++++-- .../samples/sample_log_query_client.py | 15 ++--- 5 files changed, 94 insertions(+), 22 deletions(-) diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_generated/models/_models_py3.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_generated/models/_models_py3.py index 07aad3a671c1..52cffb864a95 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_generated/models/_models_py3.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_generated/models/_models_py3.py @@ -10,7 +10,7 @@ from typing import Dict, List, Optional, Union from azure.core.exceptions import HttpResponseError -import msrest.serialization +from from ._monitor_query_client_enums import * diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py index 8b3194f3dc14..1be5c94b2eee 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py @@ -7,7 +7,7 @@ from datetime import datetime, timedelta from typing import TYPE_CHECKING from msrest import Serializer, Deserializer -from azure.core.exceptions import HttpResponseError +from azure.core.exceptions import HttpResponseError, ODataV4Format from azure.core.pipeline.policies import BearerTokenCredentialPolicy if TYPE_CHECKING: @@ -90,3 +90,15 @@ def native_col_type(col_type, value): def process_row(col_types, row): return [native_col_type(col_types[ind], val) for ind, val in enumerate(row)] + +def process_error(error, raise_with=HttpResponseError): + if not error: + return None + formatted = ODataV4Format(error.serialize()) + return raise_with(error=formatted) + +def process_error_raise(error, raise_with=HttpResponseError): + if not error: + return None + formatted = ODataV4Format(error.serialize()) + raise raise_with(error=formatted) \ No newline at end of file diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py index b555985d44aa..8cc31e29888f 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py @@ -12,8 +12,8 @@ from ._generated._monitor_query_client import MonitorQueryClient from ._generated.models import BatchRequest, QueryBody as LogsQueryBody -from ._helpers import get_authentication_policy, process_error, construct_iso8601, order_results -from ._models import LogsBatchQuery, LogsQueryResult +from ._helpers import get_authentication_policy, process_error, construct_iso8601, order_results, process_error_raise +from ._models import LogsBatchQuery, LogsQueryResult, LogsQueryError if TYPE_CHECKING: from azure.core.credentials import TokenCredential @@ -76,6 +76,8 @@ def query(self, workspace_id, query, **kwargs): :keyword additional_workspaces: A list of workspaces that are included in the query. These can be qualified workspace names, workspace Ids, or Azure resource Ids. :paramtype additional_workspaces: list[str] + :keyword allow_partial_failures: Defaults to False. If set to true, partial errors are not thrown. + :paramtype allow_partial_failures: bool :return: LogsQueryResult, or the result of cls(response) :rtype: ~azure.monitor.query.LogsQueryResult :raises: ~azure.core.exceptions.HttpResponseError @@ -89,6 +91,7 @@ def query(self, workspace_id, query, **kwargs): :dedent: 0 :caption: Get a response for a single Log Query """ + allow_partial_failures = kwargs.pop('allow_partial_failures', False) if 'timespan' not in kwargs: raise TypeError("query() missing 1 required keyword-only argument: 'timespan'") timespan = construct_iso8601(kwargs.pop('timespan')) @@ -117,14 +120,23 @@ def query(self, workspace_id, query, **kwargs): ) try: - return LogsQueryResult._from_generated(self._query_op.execute( # pylint: disable=protected-access + generated_response = self._query_op.execute( # pylint: disable=protected-access workspace_id=workspace_id, body=body, prefer=prefer, **kwargs - )) - except HttpResponseError as e: - process_error(e) + ) + response = LogsQueryResult._from_generated(generated_response) + if not generated_response.error: + return response + if allow_partial_failures: + response.partial_error = process_error(generated_response.error) + else: + process_error_raise(generated_response.error, raise_with=LogsQueryError) + return + return response + except HttpResponseError as err: + process_error_raise(err) @distributed_trace def query_batch(self, queries, **kwargs): diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py index 84e0a05f4cd1..9cb5fb25ba26 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py @@ -9,7 +9,7 @@ import uuid from typing import Any, Optional, List -from ._helpers import construct_iso8601, process_row +from ._helpers import construct_iso8601, process_row, process_error from ._generated.models import ( BatchQueryRequest as InternalLogQueryRequest, BatchQueryResponse @@ -173,7 +173,7 @@ def __init__( **kwargs ): self.tables = kwargs.get('tables', None) - self.error = kwargs.get('error', None) + self.partial_error = None self.statistics = kwargs.get('statistics', None) self.visualization = kwargs.get('visualization', None) @@ -194,8 +194,7 @@ def _from_generated(cls, generated): return cls( tables=tables, statistics=generated.statistics, - visualization=generated.render, - error=generated.error + visualization=generated.render ) @@ -519,3 +518,57 @@ class MetricUnit(str, Enum): MILLI_CORES = "MilliCores" NANO_CORES = "NanoCores" BITS_PER_SECOND = "BitsPerSecond" + + +class LogsQueryError(object): + """The code and message for an error. + + All required parameters must be populated in order to send to Azure. + + :ivar code: A machine readable error code. + :vartype code: str + :ivar message: A human readable error message. + :vartype message: str + :ivar details: error details. + :vartype details: list[~monitor_query_client.models.ErrorDetail] + :ivar innererror: Inner error details if they exist. + :vartype innererror: ~azure.monitor.query.LogsQueryError + :ivar additional_properties: Additional properties that can be provided on the error info + object. + :vartype additional_properties: object + """ + + _validation = { + 'code': {'required': True}, + 'message': {'required': True}, + } + + _attribute_map = { + 'code': {'key': 'code', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'details': {'key': 'details', 'type': '[ErrorDetail]'}, + 'innererror': {'key': 'innererror', 'type': 'ErrorInfo'}, + 'additional_properties': {'key': 'additionalProperties', 'type': 'object'}, + } + + def __init__( + self, + **kwargs + ): + self.code = kwargs.get('code', None) + self.message = kwargs.get('message', None) + self.details = kwargs.get('details', None) + self.innererror = kwargs.get('innererror', None) + self.additional_properties = kwargs.get('additional_properties', None) + + @classmethod + def _from_genearated(cls, generated): + if not generated: + return None + return cls( + code=generated.code, + message=generated.message, + innererror=generated.innererror, + additional_properties=generated.additional_properties, + details=[d.serialize() for d in generated.details] + ) \ No newline at end of file diff --git a/sdk/monitor/azure-monitor-query/samples/sample_log_query_client.py b/sdk/monitor/azure-monitor-query/samples/sample_log_query_client.py index 917f6a16e698..d3a17b06bbd6 100644 --- a/sdk/monitor/azure-monitor-query/samples/sample_log_query_client.py +++ b/sdk/monitor/azure-monitor-query/samples/sample_log_query_client.py @@ -16,21 +16,16 @@ # Response time trend # request duration over the last 12 hours. # [START send_logs_query] -query = """AppRequests | -summarize avgRequestDuration=avg(DurationMs) by bin(TimeGenerated, 10m), _ResourceId""" +query = """let Weight = 92233720368547758; +range x from 1 to 3 step 1 +| summarize percentilesw(x, Weight * 100, 50)""" # returns LogsQueryResult response = client.query(os.environ['LOG_WORKSPACE_ID'], query, timespan=timedelta(days=1)) -if not response.tables: - print("No results for the query") - for table in response.tables: - try: - df = pd.DataFrame(table.rows, columns=table.columns) - print(df) - except TypeError: - print(response.error) + df = pd.DataFrame(table.rows, columns=table.columns) + print(df) # [END send_logs_query] """ TimeGenerated _ResourceId avgRequestDuration From 2abb91e4182be05818315433dffe0f091d537632 Mon Sep 17 00:00:00 2001 From: Rakshith Bhyravabhotla Date: Wed, 8 Sep 2021 09:47:59 -0700 Subject: [PATCH 02/12] batch set --- .../azure/monitor/query/_helpers.py | 16 +++++++- .../azure/monitor/query/_logs_query_client.py | 13 +++++- .../azure/monitor/query/_models.py | 40 +++++++++---------- .../query/aio/_logs_query_client_async.py | 13 +++++- .../samples/sample_batch_query.py | 19 +++++---- 5 files changed, 66 insertions(+), 35 deletions(-) diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py index 1be5c94b2eee..127a37f78e7e 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py @@ -45,9 +45,21 @@ def process_error(exception): raise_error = HttpResponseError raise raise_error(message=exception.message, response=exception.response) -def order_results(request_order, mapping, obj): +def order_results(request_order, mapping, obj, err, allow_partial_errors=False): ordered = [mapping[id] for id in request_order] - return [obj._from_generated(rsp) for rsp in ordered] # pylint: disable=protected-access + results = [] + for item in ordered: + if not item.body.error: + results.append(obj._from_generated(item.body)) # pylint: disable=protected-access + else: + error = item.body.error + if allow_partial_errors and error.code == 'PartialError': + res = obj._from_generated(item.body) # pylint: disable=protected-access + res.partial_error = err._from_generated(error) # pylint: disable=protected-access + results.append(res) + else: + results.append(err._from_generated(error)) # pylint: disable=protected-access + return results def construct_iso8601(timespan=None): if not timespan: diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py index 8cc31e29888f..33c86cfef10b 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py @@ -132,7 +132,7 @@ def query(self, workspace_id, query, **kwargs): if allow_partial_failures: response.partial_error = process_error(generated_response.error) else: - process_error_raise(generated_response.error, raise_with=LogsQueryError) + process_error_raise(generated_response.error) return return response except HttpResponseError as err: @@ -148,6 +148,9 @@ def query_batch(self, queries, **kwargs): :param queries: The list of Kusto queries to execute. :type queries: list[dict] or list[~azure.monitor.query.LogsBatchQuery] + :keyword bool allow_partial_errors: If set to True, a `LogsQueryResult` object is returned + when a partial error occurs. The error can be accessed using the `partial_error` + attribute in the object. :return: List of LogsQueryResult, or the result of cls(response) :rtype: list[~azure.monitor.query.LogsQueryResult] :raises: ~azure.core.exceptions.HttpResponseError @@ -161,6 +164,7 @@ def query_batch(self, queries, **kwargs): :dedent: 0 :caption: Get a response for multiple Log Queries. """ + allow_partial_errors = kwargs.pop('allow_partial_errors', False) try: queries = [LogsBatchQuery(**q) for q in queries] except (KeyError, TypeError): @@ -173,7 +177,12 @@ def query_batch(self, queries, **kwargs): batch = BatchRequest(requests=queries) generated = self._query_op.batch(batch, **kwargs) mapping = {item.id: item for item in generated.responses} - return order_results(request_order, mapping, LogsQueryResult) + return order_results( + request_order, + mapping, + LogsQueryResult, + LogsQueryError, + allow_partial_errors) def close(self): # type: () -> None diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py index 9cb5fb25ba26..3ff1100bc5c9 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py @@ -165,8 +165,11 @@ class LogsQueryResult(object): :ivar visualization: This will include a visualization property in the response that specifies the type of visualization selected by the query and any properties for that visualization. :vartype visualization: object - :ivar error: Any error info. - :vartype error: ~azure.core.exceptions.HttpResponseError + :ivar partial_error: Any error info. This is none except in the case where `allow_partial_errors` + is explicitly set to True. + :vartype partial_error: ~azure.core.exceptions.HttpResponseError + :ivar bool is_error: Boolean check for error item when iterating over list of + results. Always False for an instance of a LogsQueryResult. """ def __init__( self, @@ -176,6 +179,7 @@ def __init__( self.partial_error = None self.statistics = kwargs.get('statistics', None) self.visualization = kwargs.get('visualization', None) + self.is_error = False @classmethod def _from_generated(cls, generated): @@ -194,7 +198,8 @@ def _from_generated(cls, generated): return cls( tables=tables, statistics=generated.statistics, - visualization=generated.render + visualization=generated.render, + is_error=False ) @@ -536,21 +541,9 @@ class LogsQueryError(object): :ivar additional_properties: Additional properties that can be provided on the error info object. :vartype additional_properties: object + :ivar bool is_error: Boolean check for error item when iterating over list of + results. Always True for an instance of a LogsQueryError. """ - - _validation = { - 'code': {'required': True}, - 'message': {'required': True}, - } - - _attribute_map = { - 'code': {'key': 'code', 'type': 'str'}, - 'message': {'key': 'message', 'type': 'str'}, - 'details': {'key': 'details', 'type': '[ErrorDetail]'}, - 'innererror': {'key': 'innererror', 'type': 'ErrorInfo'}, - 'additional_properties': {'key': 'additionalProperties', 'type': 'object'}, - } - def __init__( self, **kwargs @@ -560,15 +553,20 @@ def __init__( self.details = kwargs.get('details', None) self.innererror = kwargs.get('innererror', None) self.additional_properties = kwargs.get('additional_properties', None) + self.is_error = True @classmethod - def _from_genearated(cls, generated): + def _from_generated(cls, generated): if not generated: return None + details = None + if generated.details is not None: + details=[d.serialize() for d in generated.details] return cls( code=generated.code, message=generated.message, - innererror=generated.innererror, + innererror=cls._from_generated(generated.innererror), additional_properties=generated.additional_properties, - details=[d.serialize() for d in generated.details] - ) \ No newline at end of file + details=details, + is_error=True + ) diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/aio/_logs_query_client_async.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/aio/_logs_query_client_async.py index 090fcceb9ed8..0782a3858c08 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/aio/_logs_query_client_async.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/aio/_logs_query_client_async.py @@ -14,7 +14,7 @@ from .._generated.models import BatchRequest, QueryBody as LogsQueryBody from .._helpers import process_error, construct_iso8601, order_results -from .._models import LogsQueryResult, LogsBatchQuery +from .._models import LogsQueryResult, LogsBatchQuery, LogsQueryError from ._helpers_asyc import get_authentication_policy if TYPE_CHECKING: @@ -123,10 +123,14 @@ async def query_batch( :param queries: The list of Kusto queries to execute. :type queries: list[dict] or list[~azure.monitor.query.LogsBatchQuery] + :keyword bool allow_partial_errors: If set to True, a `LogsQueryResult` object is returned + when a partial error occurs. The error can be accessed using the `partial_error` + attribute in the object. :return: list of LogsQueryResult objects, or the result of cls(response) :rtype: list[~azure.monitor.query.LogsQueryResult] :raises: ~azure.core.exceptions.HttpResponseError """ + allow_partial_errors = kwargs.pop('allow_partial_errors', False) try: queries = [LogsBatchQuery(**q) for q in queries] except (KeyError, TypeError): @@ -139,7 +143,12 @@ async def query_batch( batch = BatchRequest(requests=queries) generated = await self._query_op.batch(batch, **kwargs) mapping = {item.id: item for item in generated.responses} - return order_results(request_order, mapping, LogsQueryResult) + return order_results( + request_order, + mapping, + LogsQueryResult, + LogsQueryError, + allow_partial_errors) async def __aenter__(self) -> "LogsQueryClient": await self._client.__aenter__() diff --git a/sdk/monitor/azure-monitor-query/samples/sample_batch_query.py b/sdk/monitor/azure-monitor-query/samples/sample_batch_query.py index c0838643dce4..51da006e3d34 100644 --- a/sdk/monitor/azure-monitor-query/samples/sample_batch_query.py +++ b/sdk/monitor/azure-monitor-query/samples/sample_batch_query.py @@ -20,27 +20,30 @@ workspace_id= os.environ['LOG_WORKSPACE_ID'] ), LogsBatchQuery( - query= """AppRequests | take 10 | - summarize avgRequestDuration=avg(DurationMs) by bin(TimeGenerated, 10m), _ResourceId""", - timespan=(datetime(2021, 6, 2), timedelta(hours=1)), + query= """AppRequestsss | take 10""", + timespan=(datetime(2021, 6, 2), timedelta(days=1)), workspace_id= os.environ['LOG_WORKSPACE_ID'] ), LogsBatchQuery( - query= "AppRequests | take 5", + query= """let Weight = 92233720368547758; + range x from 1 to 3 step 1 + | summarize percentilesw(x, Weight * 100, 50)""", workspace_id= os.environ['LOG_WORKSPACE_ID'], timespan=(datetime(2021, 6, 2), datetime(2021, 6, 3)), include_statistics=True ), ] -responses = client.query_batch(requests) +responses = client.query_batch(requests, allow_partial_errors=True) for response in responses: - try: + if not response.is_error: table = response.tables[0] df = pd.DataFrame(table.rows, columns=table.columns) print(df) print("\n\n-------------------------\n\n") - except TypeError: - print(response.error.innererror) + else: + error = response + print(error.innererror.message) + # [END send_query_batch] \ No newline at end of file From c823dfcd213f1f2244bf494e845b6a6f34eee1b6 Mon Sep 17 00:00:00 2001 From: Rakshith Bhyravabhotla Date: Thu, 9 Sep 2021 14:23:58 -0700 Subject: [PATCH 03/12] batch --- .../azure/monitor/query/_logs_query_client.py | 34 ++++++------- .../azure/monitor/query/_models.py | 48 +++++++++++++++++++ .../samples/sample_log_query_client.py | 9 +++- 3 files changed, 70 insertions(+), 21 deletions(-) diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py index 33c86cfef10b..d98cd54f5752 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py @@ -76,8 +76,8 @@ def query(self, workspace_id, query, **kwargs): :keyword additional_workspaces: A list of workspaces that are included in the query. These can be qualified workspace names, workspace Ids, or Azure resource Ids. :paramtype additional_workspaces: list[str] - :keyword allow_partial_failures: Defaults to False. If set to true, partial errors are not thrown. - :paramtype allow_partial_failures: bool + :keyword allow_partial_errors: Defaults to False. If set to true, partial errors are not thrown. + :paramtype allow_partial_errors: bool :return: LogsQueryResult, or the result of cls(response) :rtype: ~azure.monitor.query.LogsQueryResult :raises: ~azure.core.exceptions.HttpResponseError @@ -91,7 +91,7 @@ def query(self, workspace_id, query, **kwargs): :dedent: 0 :caption: Get a response for a single Log Query """ - allow_partial_failures = kwargs.pop('allow_partial_failures', False) + allow_partial_errors = kwargs.pop('allow_partial_errors', False) if 'timespan' not in kwargs: raise TypeError("query() missing 1 required keyword-only argument: 'timespan'") timespan = construct_iso8601(kwargs.pop('timespan')) @@ -119,24 +119,18 @@ def query(self, workspace_id, query, **kwargs): **kwargs ) - try: - generated_response = self._query_op.execute( # pylint: disable=protected-access - workspace_id=workspace_id, - body=body, - prefer=prefer, - **kwargs - ) - response = LogsQueryResult._from_generated(generated_response) - if not generated_response.error: - return response - if allow_partial_failures: - response.partial_error = process_error(generated_response.error) - else: - process_error_raise(generated_response.error) - return + generated_response = self._query_op.execute( # pylint: disable=protected-access + workspace_id=workspace_id, + body=body, + prefer=prefer, + **kwargs + ) + response = LogsQueryResult._from_generated(generated_response) + if allow_partial_errors and generated_response.error is not None: + response.partial_error = LogsQueryError._from_generated(generated_response.error) return response - except HttpResponseError as err: - process_error_raise(err) + else: + raise LogsQueryError._from_generated(generated_response.error) @distributed_trace def query_batch(self, queries, **kwargs): diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py index 3ff1100bc5c9..4d38abd8059d 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py @@ -9,6 +9,8 @@ import uuid from typing import Any, Optional, List +from azure.core.exceptions import HttpResponseError + from ._helpers import construct_iso8601, process_row, process_error from ._generated.models import ( BatchQueryRequest as InternalLogQueryRequest, @@ -570,3 +572,49 @@ def _from_generated(cls, generated): details=details, is_error=True ) + +class LogsQueryPartialError(HttpResponseError): + """The code and message for an error. + + All required parameters must be populated in order to send to Azure. + + :ivar code: A machine readable error code. + :vartype code: str + :ivar message: A human readable error message. + :vartype message: str + :ivar details: error details. + :vartype details: list[~monitor_query_client.models.ErrorDetail] + :ivar innererror: Inner error details if they exist. + :vartype innererror: ~azure.monitor.query.LogsQueryError + :ivar additional_properties: Additional properties that can be provided on the error info + object. + :vartype additional_properties: object + :ivar bool is_error: Boolean check for error item when iterating over list of + results. Always True for an instance of a LogsQueryError. + """ + def __init__( + self, + **kwargs + ): + self.code = kwargs.get('code', None) + self.message = kwargs.get('message', None) + self.details = kwargs.get('details', None) + self.innererror = kwargs.get('innererror', None) + self.additional_properties = kwargs.get('additional_properties', None) + self.is_error = True + + @classmethod + def _from_generated(cls, generated): + if not generated: + return None + details = None + if generated.details is not None: + details=[d.serialize() for d in generated.details] + return cls( + code=generated.code, + message=generated.message, + innererror=cls._from_generated(generated.innererror), + additional_properties=generated.additional_properties, + details=details, + is_error=True + ) diff --git a/sdk/monitor/azure-monitor-query/samples/sample_log_query_client.py b/sdk/monitor/azure-monitor-query/samples/sample_log_query_client.py index d3a17b06bbd6..3937caeac363 100644 --- a/sdk/monitor/azure-monitor-query/samples/sample_log_query_client.py +++ b/sdk/monitor/azure-monitor-query/samples/sample_log_query_client.py @@ -5,6 +5,7 @@ import pandas as pd from datetime import timedelta from azure.monitor.query import LogsQueryClient +from azure.core.exceptions import HttpResponseError from azure.identity import DefaultAzureCredential # [START client_auth_with_token_cred] @@ -21,8 +22,14 @@ | summarize percentilesw(x, Weight * 100, 50)""" # returns LogsQueryResult -response = client.query(os.environ['LOG_WORKSPACE_ID'], query, timespan=timedelta(days=1)) +try: + response = client.query(os.environ['LOG_WORKSPACE_ID'], query, timespan=timedelta(days=1), allow_partial_errors=True) +except HttpResponseError as error: + print (error) +if response.partial_error: + e = response.partial_error + print(e) for table in response.tables: df = pd.DataFrame(table.rows, columns=table.columns) print(df) From f87d70174b2dd7d932bbc31cb0a9403f85fb74cc Mon Sep 17 00:00:00 2001 From: Rakshith Bhyravabhotla Date: Sun, 12 Sep 2021 15:18:11 -0700 Subject: [PATCH 04/12] tests + changes --- .../azure/monitor/query/__init__.py | 7 + .../azure/monitor/query/_exceptions.py | 83 +++++++++ .../azure/monitor/query/_helpers.py | 17 +- .../azure/monitor/query/_logs_query_client.py | 28 +-- .../azure/monitor/query/_models.py | 101 +---------- .../query/aio/_logs_query_client_async.py | 24 ++- .../samples/sample_batch_query.py | 4 +- .../samples/sample_log_query_client.py | 24 ++- .../tests/async/test_exceptions_async.py | 171 ++++++++++++++++++ .../tests/test_exceptions.py | 163 +++++++++++++++++ 10 files changed, 478 insertions(+), 144 deletions(-) create mode 100644 sdk/monitor/azure-monitor-query/azure/monitor/query/_exceptions.py create mode 100644 sdk/monitor/azure-monitor-query/tests/async/test_exceptions_async.py create mode 100644 sdk/monitor/azure-monitor-query/tests/test_exceptions.py diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/__init__.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/__init__.py index 9043ee10d44c..d33a6f0a5c28 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/__init__.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/__init__.py @@ -7,6 +7,11 @@ from ._logs_query_client import LogsQueryClient from ._metrics_query_client import MetricsQueryClient +from ._exceptions import ( + LogsQueryError, + QueryPartialErrorException +) + from ._models import ( MetricAggregationType, LogsQueryResult, @@ -30,6 +35,8 @@ "MetricAggregationType", "LogsQueryClient", "LogsQueryResult", + "LogsQueryError", + "QueryPartialErrorException", "LogsTable", "LogsBatchQuery", "MetricsQueryClient", diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_exceptions.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_exceptions.py new file mode 100644 index 000000000000..fd37c277cf17 --- /dev/null +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_exceptions.py @@ -0,0 +1,83 @@ +# +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +from azure.core.exceptions import HttpResponseError + +class LogsQueryError(object): + """The code and message for an error. + + All required parameters must be populated in order to send to Azure. + + :ivar code: A machine readable error code. + :vartype code: str + :ivar message: A human readable error message. + :vartype message: str + :ivar details: error details. + :vartype details: list[~monitor_query_client.models.ErrorDetail] + :ivar innererror: Inner error details if they exist. + :vartype innererror: ~azure.monitor.query.LogsQueryError + :ivar additional_properties: Additional properties that can be provided on the error info + object. + :vartype additional_properties: object + :ivar bool is_error: Boolean check for error item when iterating over list of + results. Always True for an instance of a LogsQueryError. + """ + def __init__( + self, + **kwargs + ): + self.code = kwargs.get('code', None) + self.message = kwargs.get('message', None) + self.details = kwargs.get('details', None) + self.innererror = kwargs.get('innererror', None) + self.additional_properties = kwargs.get('additional_properties', None) + self.is_error = True + + @classmethod + def _from_generated(cls, generated): + if not generated: + return None + details = None + if generated.details is not None: + details=[d.serialize() for d in generated.details] + return cls( + code=generated.code, + message=generated.message, + innererror=cls._from_generated(generated.innererror) if generated.innererror else None, + additional_properties=generated.additional_properties, + details=details, + is_error=True + ) + +class QueryPartialErrorException(HttpResponseError): + """There is a partial failure in query operation. This is thrown for a single query operation + when allow_partial_errors is set to False. + + :ivar code: A machine readable error code. + :vartype code: str + :ivar message: A human readable error message. + :vartype message: str + :ivar details: error details. + :vartype details: list[~monitor_query_client.models.ErrorDetail] + :ivar innererror: Inner error details if they exist. + :vartype innererror: ~azure.monitor.query.LogsQueryError + :ivar additional_properties: Additional properties that can be provided on the error info + object. + :vartype additional_properties: object + :ivar bool is_error: Boolean check for error item when iterating over list of + results. Always True for an instance of a LogsQueryError. + """ + + def __init__(self, **kwargs): + error = kwargs.pop('error', None) + if error: + self.code = error.code + self.message = error.message + self.details = [d.serialize() for d in error.details] if error.details else None + self.innererror = LogsQueryError._from_generated(error.innererror) if error.innererror else None + self.additional_properties = error.additional_properties + self.is_error = True + super(QueryPartialErrorException, self).__init__(message=self.message) diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py index 127a37f78e7e..d19d240bb27a 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py @@ -7,7 +7,7 @@ from datetime import datetime, timedelta from typing import TYPE_CHECKING from msrest import Serializer, Deserializer -from azure.core.exceptions import HttpResponseError, ODataV4Format +from azure.core.exceptions import HttpResponseError from azure.core.pipeline.policies import BearerTokenCredentialPolicy if TYPE_CHECKING: @@ -41,10 +41,6 @@ def get_metrics_authentication_policy( raise TypeError("Unsupported credential") -def process_error(exception): - raise_error = HttpResponseError - raise raise_error(message=exception.message, response=exception.response) - def order_results(request_order, mapping, obj, err, allow_partial_errors=False): ordered = [mapping[id] for id in request_order] results = [] @@ -103,14 +99,7 @@ def native_col_type(col_type, value): def process_row(col_types, row): return [native_col_type(col_types[ind], val) for ind, val in enumerate(row)] -def process_error(error, raise_with=HttpResponseError): - if not error: - return None - formatted = ODataV4Format(error.serialize()) - return raise_with(error=formatted) - -def process_error_raise(error, raise_with=HttpResponseError): +def process_error(error): if not error: return None - formatted = ODataV4Format(error.serialize()) - raise raise_with(error=formatted) \ No newline at end of file + raise HttpResponseError(message=error.message, response=error.response) diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py index d98cd54f5752..b2c8e4aa43cb 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py @@ -12,8 +12,9 @@ from ._generated._monitor_query_client import MonitorQueryClient from ._generated.models import BatchRequest, QueryBody as LogsQueryBody -from ._helpers import get_authentication_policy, process_error, construct_iso8601, order_results, process_error_raise -from ._models import LogsBatchQuery, LogsQueryResult, LogsQueryError +from ._helpers import get_authentication_policy, construct_iso8601, order_results, process_error +from ._models import LogsBatchQuery, LogsQueryResult +from ._exceptions import LogsQueryError, QueryPartialErrorException if TYPE_CHECKING: from azure.core.credentials import TokenCredential @@ -119,18 +120,23 @@ def query(self, workspace_id, query, **kwargs): **kwargs ) - generated_response = self._query_op.execute( # pylint: disable=protected-access - workspace_id=workspace_id, - body=body, - prefer=prefer, - **kwargs - ) + try: + generated_response = self._query_op.execute( # pylint: disable=protected-access + workspace_id=workspace_id, + body=body, + prefer=prefer, + **kwargs + ) + except HttpResponseError as err: + process_error(err) response = LogsQueryResult._from_generated(generated_response) - if allow_partial_errors and generated_response.error is not None: - response.partial_error = LogsQueryError._from_generated(generated_response.error) + if not generated_response.error: return response else: - raise LogsQueryError._from_generated(generated_response.error) + if not allow_partial_errors: + raise QueryPartialErrorException(error=generated_response.error) + response.partial_error = LogsQueryError._from_generated(generated_response.error) + return response @distributed_trace def query_batch(self, queries, **kwargs): diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py index 4d38abd8059d..a7a45c6242b2 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py @@ -9,9 +9,7 @@ import uuid from typing import Any, Optional, List -from azure.core.exceptions import HttpResponseError - -from ._helpers import construct_iso8601, process_row, process_error +from ._helpers import construct_iso8601, process_row from ._generated.models import ( BatchQueryRequest as InternalLogQueryRequest, BatchQueryResponse @@ -183,9 +181,11 @@ def __init__( self.visualization = kwargs.get('visualization', None) self.is_error = False + def __iter__(self): + return iter(self.tables) + @classmethod def _from_generated(cls, generated): - if not generated: return cls() tables = None @@ -525,96 +525,3 @@ class MetricUnit(str, Enum): MILLI_CORES = "MilliCores" NANO_CORES = "NanoCores" BITS_PER_SECOND = "BitsPerSecond" - - -class LogsQueryError(object): - """The code and message for an error. - - All required parameters must be populated in order to send to Azure. - - :ivar code: A machine readable error code. - :vartype code: str - :ivar message: A human readable error message. - :vartype message: str - :ivar details: error details. - :vartype details: list[~monitor_query_client.models.ErrorDetail] - :ivar innererror: Inner error details if they exist. - :vartype innererror: ~azure.monitor.query.LogsQueryError - :ivar additional_properties: Additional properties that can be provided on the error info - object. - :vartype additional_properties: object - :ivar bool is_error: Boolean check for error item when iterating over list of - results. Always True for an instance of a LogsQueryError. - """ - def __init__( - self, - **kwargs - ): - self.code = kwargs.get('code', None) - self.message = kwargs.get('message', None) - self.details = kwargs.get('details', None) - self.innererror = kwargs.get('innererror', None) - self.additional_properties = kwargs.get('additional_properties', None) - self.is_error = True - - @classmethod - def _from_generated(cls, generated): - if not generated: - return None - details = None - if generated.details is not None: - details=[d.serialize() for d in generated.details] - return cls( - code=generated.code, - message=generated.message, - innererror=cls._from_generated(generated.innererror), - additional_properties=generated.additional_properties, - details=details, - is_error=True - ) - -class LogsQueryPartialError(HttpResponseError): - """The code and message for an error. - - All required parameters must be populated in order to send to Azure. - - :ivar code: A machine readable error code. - :vartype code: str - :ivar message: A human readable error message. - :vartype message: str - :ivar details: error details. - :vartype details: list[~monitor_query_client.models.ErrorDetail] - :ivar innererror: Inner error details if they exist. - :vartype innererror: ~azure.monitor.query.LogsQueryError - :ivar additional_properties: Additional properties that can be provided on the error info - object. - :vartype additional_properties: object - :ivar bool is_error: Boolean check for error item when iterating over list of - results. Always True for an instance of a LogsQueryError. - """ - def __init__( - self, - **kwargs - ): - self.code = kwargs.get('code', None) - self.message = kwargs.get('message', None) - self.details = kwargs.get('details', None) - self.innererror = kwargs.get('innererror', None) - self.additional_properties = kwargs.get('additional_properties', None) - self.is_error = True - - @classmethod - def _from_generated(cls, generated): - if not generated: - return None - details = None - if generated.details is not None: - details=[d.serialize() for d in generated.details] - return cls( - code=generated.code, - message=generated.message, - innererror=cls._from_generated(generated.innererror), - additional_properties=generated.additional_properties, - details=details, - is_error=True - ) diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/aio/_logs_query_client_async.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/aio/_logs_query_client_async.py index 0782a3858c08..b0ffbddb476d 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/aio/_logs_query_client_async.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/aio/_logs_query_client_async.py @@ -7,15 +7,16 @@ from datetime import datetime, timedelta from typing import Any, Tuple, Union, Sequence, Dict, List, TYPE_CHECKING -from azure.core.exceptions import HttpResponseError from azure.core.tracing.decorator_async import distributed_trace_async +from azure.core.exceptions import HttpResponseError from .._generated.aio._monitor_query_client import MonitorQueryClient from .._generated.models import BatchRequest, QueryBody as LogsQueryBody -from .._helpers import process_error, construct_iso8601, order_results -from .._models import LogsQueryResult, LogsBatchQuery, LogsQueryError +from .._helpers import construct_iso8601, order_results, process_error +from .._models import LogsQueryResult, LogsBatchQuery from ._helpers_asyc import get_authentication_policy +from .._exceptions import LogsQueryError, QueryPartialErrorException if TYPE_CHECKING: from azure.core.credentials_async import AsyncTokenCredential @@ -75,6 +76,7 @@ async def query( :rtype: ~azure.monitor.query.LogsQueryResult :raises: ~azure.core.exceptions.HttpResponseError """ + allow_partial_errors = kwargs.pop('allow_partial_errors', False) timespan = construct_iso8601(timespan) include_statistics = kwargs.pop("include_statistics", False) include_visualization = kwargs.pop("include_visualization", False) @@ -101,14 +103,22 @@ async def query( ) try: - return LogsQueryResult._from_generated(await self._query_op.execute( # pylint: disable=protected-access + generated_response = await self._query_op.execute( # pylint: disable=protected-access workspace_id=workspace_id, body=body, prefer=prefer, **kwargs - )) - except HttpResponseError as e: - process_error(e) + ) + except HttpResponseError as err: + process_error(err) + response = LogsQueryResult._from_generated(generated_response) + if not generated_response.error: + return response + else: + if not allow_partial_errors: + raise QueryPartialErrorException(error=generated_response.error) + response.partial_error = LogsQueryError._from_generated(generated_response.error) + return response @distributed_trace_async async def query_batch( diff --git a/sdk/monitor/azure-monitor-query/samples/sample_batch_query.py b/sdk/monitor/azure-monitor-query/samples/sample_batch_query.py index 51da006e3d34..43444c369c3c 100644 --- a/sdk/monitor/azure-monitor-query/samples/sample_batch_query.py +++ b/sdk/monitor/azure-monitor-query/samples/sample_batch_query.py @@ -33,7 +33,7 @@ include_statistics=True ), ] -responses = client.query_batch(requests, allow_partial_errors=True) +responses = client.query_batch(requests, allow_partial_errors=False) for response in responses: if not response.is_error: @@ -43,7 +43,7 @@ print("\n\n-------------------------\n\n") else: error = response - print(error.innererror.message) + print(error.message) # [END send_query_batch] \ No newline at end of file diff --git a/sdk/monitor/azure-monitor-query/samples/sample_log_query_client.py b/sdk/monitor/azure-monitor-query/samples/sample_log_query_client.py index 3937caeac363..5bda7d0e0f3a 100644 --- a/sdk/monitor/azure-monitor-query/samples/sample_log_query_client.py +++ b/sdk/monitor/azure-monitor-query/samples/sample_log_query_client.py @@ -4,7 +4,7 @@ import os import pandas as pd from datetime import timedelta -from azure.monitor.query import LogsQueryClient +from azure.monitor.query import LogsQueryClient, QueryPartialErrorException from azure.core.exceptions import HttpResponseError from azure.identity import DefaultAzureCredential @@ -17,22 +17,20 @@ # Response time trend # request duration over the last 12 hours. # [START send_logs_query] -query = """let Weight = 92233720368547758; -range x from 1 to 3 step 1 -| summarize percentilesw(x, Weight * 100, 50)""" +query = """AppRwequests | take 5""" # returns LogsQueryResult try: - response = client.query(os.environ['LOG_WORKSPACE_ID'], query, timespan=timedelta(days=1), allow_partial_errors=True) -except HttpResponseError as error: - print (error) + response = client.query(os.environ['LOG_WORKSPACE_ID'], query, timespan=timedelta(days=1)) + for table in response: + print(table) +except QueryPartialErrorException as err: + print("this is a partial error") + print(err.details) +except HttpResponseError as err: + print("something fatal happened") + print (err) -if response.partial_error: - e = response.partial_error - print(e) -for table in response.tables: - df = pd.DataFrame(table.rows, columns=table.columns) - print(df) # [END send_logs_query] """ TimeGenerated _ResourceId avgRequestDuration diff --git a/sdk/monitor/azure-monitor-query/tests/async/test_exceptions_async.py b/sdk/monitor/azure-monitor-query/tests/async/test_exceptions_async.py new file mode 100644 index 000000000000..cf46756a6ddf --- /dev/null +++ b/sdk/monitor/azure-monitor-query/tests/async/test_exceptions_async.py @@ -0,0 +1,171 @@ +from datetime import timedelta, datetime +import pytest +import os +from azure.identity.aio import ClientSecretCredential +from azure.core.exceptions import HttpResponseError +from azure.monitor.query import LogsBatchQuery, LogsQueryError,LogsQueryResult, QueryPartialErrorException +from azure.monitor.query.aio import LogsQueryClient + +def _credential(): + credential = ClientSecretCredential( + client_id = os.environ['AZURE_CLIENT_ID'], + client_secret = os.environ['AZURE_CLIENT_SECRET'], + tenant_id = os.environ['AZURE_TENANT_ID'] + ) + return credential + +@pytest.mark.live_test_only +@pytest.mark.asyncio +async def test_logs_single_query_fatal_exception(): + credential = _credential() + client = LogsQueryClient(credential) + with pytest.raises(HttpResponseError): + await client.query('bad_workspace_id', 'AppRequests', timespan=None) + +@pytest.mark.live_test_only +@pytest.mark.asyncio +async def test_logs_single_query_partial_exception_not_allowed(): + credential = _credential() + client = LogsQueryClient(credential) + query = """let Weight = 92233720368547758; + range x from 1 to 3 step 1 + | summarize percentilesw(x, Weight * 100, 50)""" + with pytest.raises(QueryPartialErrorException) as err: + await client.query(os.environ['LOG_WORKSPACE_ID'], query, timespan=timedelta(days=1)) + +@pytest.mark.live_test_only +@pytest.mark.asyncio +async def test_logs_single_query_partial_exception_allowed(): + credential = _credential() + client = LogsQueryClient(credential) + query = """let Weight = 92233720368547758; + range x from 1 to 3 step 1 + | summarize percentilesw(x, Weight * 100, 50)""" + response = await client.query(os.environ['LOG_WORKSPACE_ID'], query, timespan=timedelta(days=1), allow_partial_errors=True) + assert response.partial_error is not None + assert response.partial_error.code == 'PartialError' + assert response.partial_error.__class__ == LogsQueryError + +@pytest.mark.live_test_only +@pytest.mark.asyncio +async def test_logs_batch_query_fatal_exception(): + credential = ClientSecretCredential( + client_id = os.environ['AZURE_CLIENT_ID'], + client_secret = 'bad_secret', + tenant_id = os.environ['AZURE_TENANT_ID'] + ) + client = LogsQueryClient(credential) + requests = [ + LogsBatchQuery( + query="AzureActivity | summarize count()", + timespan=timedelta(hours=1), + workspace_id= os.environ['LOG_WORKSPACE_ID'] + ), + LogsBatchQuery( + query= """AppRequestsss | take 10""", + timespan=(datetime(2021, 6, 2), timedelta(days=1)), + workspace_id= os.environ['LOG_WORKSPACE_ID'] + ), + LogsBatchQuery( + query= """let Weight = 92233720368547758; + range x from 1 to 3 step 1 + | summarize percentilesw(x, Weight * 100, 50)""", + workspace_id= os.environ['LOG_WORKSPACE_ID'], + timespan=(datetime(2021, 6, 2), datetime(2021, 6, 3)), + include_statistics=True + ), + ] + with pytest.raises(HttpResponseError): + await client.query_batch(requests, allow_partial_errors=True) + +@pytest.mark.live_test_only +@pytest.mark.asyncio +async def test_logs_batch_query_partial_exception_not_allowed(): + credential = _credential() + client = LogsQueryClient(credential) + requests = [ + LogsBatchQuery( + query="AzureActivity | summarize count()", + timespan=timedelta(hours=1), + workspace_id= os.environ['LOG_WORKSPACE_ID'] + ), + LogsBatchQuery( + query= """AppRequests | take 10""", + timespan=(datetime(2021, 6, 2), timedelta(days=1)), + workspace_id= os.environ['LOG_WORKSPACE_ID'] + ), + LogsBatchQuery( + query= """let Weight = 92233720368547758; + range x from 1 to 3 step 1 + | summarize percentilesw(x, Weight * 100, 50)""", + workspace_id= os.environ['LOG_WORKSPACE_ID'], + timespan=(datetime(2021, 6, 2), datetime(2021, 6, 3)), + include_statistics=True + ), + ] + responses = await client.query_batch(requests) + r1, r2, r3 = responses[0], responses[1], responses[2] + assert r1.__class__ == LogsQueryResult + assert r2.__class__ == LogsQueryResult + assert r3.__class__ == LogsQueryError + +@pytest.mark.live_test_only +@pytest.mark.asyncio +async def test_logs_batch_query_partial_exception_allowed(): + credential = _credential() + client = LogsQueryClient(credential) + requests = [ + LogsBatchQuery( + query="AzureActivity | summarize count()", + timespan=timedelta(hours=1), + workspace_id= os.environ['LOG_WORKSPACE_ID'] + ), + LogsBatchQuery( + query= """AppRequests | take 10""", + timespan=(datetime(2021, 6, 2), timedelta(days=1)), + workspace_id= os.environ['LOG_WORKSPACE_ID'] + ), + LogsBatchQuery( + query= """let Weight = 92233720368547758; + range x from 1 to 3 step 1 + | summarize percentilesw(x, Weight * 100, 50)""", + workspace_id= os.environ['LOG_WORKSPACE_ID'], + timespan=(datetime(2021, 6, 2), datetime(2021, 6, 3)), + include_statistics=True + ), + ] + responses = await client.query_batch(requests, allow_partial_errors=True) + r1, r2, r3 = responses[0], responses[1], responses[2] + assert r1.__class__ == LogsQueryResult + assert r2.__class__ == LogsQueryResult + assert r3.__class__ == LogsQueryResult + assert r3.partial_error is not None + +@pytest.mark.live_test_only +@pytest.mark.asyncio +async def test_logs_batch_query_non_fatal_exception(): + credential = _credential() + client = LogsQueryClient(credential) + requests = [ + LogsBatchQuery( + query="AzureActivity | summarize count()", + timespan=timedelta(hours=1), + workspace_id= os.environ['LOG_WORKSPACE_ID'] + ), + LogsBatchQuery( + query= """AppRequests | take 10""", + timespan=(datetime(2021, 6, 2), timedelta(days=1)), + workspace_id= os.environ['LOG_WORKSPACE_ID'] + ), + LogsBatchQuery( + query= """Bad Query""", + workspace_id= os.environ['LOG_WORKSPACE_ID'], + timespan=(datetime(2021, 6, 2), datetime(2021, 6, 3)), + include_statistics=True + ), + ] + responses = await client.query_batch(requests) + r1, r2, r3 = responses[0], responses[1], responses[2] + assert r1.__class__ == LogsQueryResult + assert r2.__class__ == LogsQueryResult + assert r3.__class__ == LogsQueryError diff --git a/sdk/monitor/azure-monitor-query/tests/test_exceptions.py b/sdk/monitor/azure-monitor-query/tests/test_exceptions.py new file mode 100644 index 000000000000..92d28cd75dd9 --- /dev/null +++ b/sdk/monitor/azure-monitor-query/tests/test_exceptions.py @@ -0,0 +1,163 @@ +from datetime import timedelta, datetime +import pytest +import os +from azure.identity import ClientSecretCredential +from azure.core.exceptions import HttpResponseError +from azure.monitor.query import LogsQueryClient, LogsBatchQuery, LogsQueryError,LogsQueryResult, QueryPartialErrorException + +def _credential(): + credential = ClientSecretCredential( + client_id = os.environ['AZURE_CLIENT_ID'], + client_secret = os.environ['AZURE_CLIENT_SECRET'], + tenant_id = os.environ['AZURE_TENANT_ID'] + ) + return credential + +@pytest.mark.live_test_only +def test_logs_single_query_fatal_exception(): + credential = _credential() + client = LogsQueryClient(credential) + with pytest.raises(HttpResponseError): + client.query('bad_workspace_id', 'AppRequests', timespan=None) + +@pytest.mark.live_test_only +def test_logs_single_query_partial_exception_not_allowed(): + credential = _credential() + client = LogsQueryClient(credential) + query = """let Weight = 92233720368547758; + range x from 1 to 3 step 1 + | summarize percentilesw(x, Weight * 100, 50)""" + with pytest.raises(QueryPartialErrorException) as err: + client.query(os.environ['LOG_WORKSPACE_ID'], query, timespan=timedelta(days=1)) + +@pytest.mark.live_test_only +def test_logs_single_query_partial_exception_allowed(): + credential = _credential() + client = LogsQueryClient(credential) + query = """let Weight = 92233720368547758; + range x from 1 to 3 step 1 + | summarize percentilesw(x, Weight * 100, 50)""" + response = client.query(os.environ['LOG_WORKSPACE_ID'], query, timespan=timedelta(days=1), allow_partial_errors=True) + assert response.partial_error is not None + assert response.partial_error.code == 'PartialError' + assert response.partial_error.__class__ == LogsQueryError + +@pytest.mark.live_test_only +def test_logs_batch_query_fatal_exception(): + credential = ClientSecretCredential( + client_id = os.environ['AZURE_CLIENT_ID'], + client_secret = 'bad_secret', + tenant_id = os.environ['AZURE_TENANT_ID'] + ) + client = LogsQueryClient(credential) + requests = [ + LogsBatchQuery( + query="AzureActivity | summarize count()", + timespan=timedelta(hours=1), + workspace_id= os.environ['LOG_WORKSPACE_ID'] + ), + LogsBatchQuery( + query= """AppRequestsss | take 10""", + timespan=(datetime(2021, 6, 2), timedelta(days=1)), + workspace_id= os.environ['LOG_WORKSPACE_ID'] + ), + LogsBatchQuery( + query= """let Weight = 92233720368547758; + range x from 1 to 3 step 1 + | summarize percentilesw(x, Weight * 100, 50)""", + workspace_id= os.environ['LOG_WORKSPACE_ID'], + timespan=(datetime(2021, 6, 2), datetime(2021, 6, 3)), + include_statistics=True + ), + ] + with pytest.raises(HttpResponseError): + responses = client.query_batch(requests, allow_partial_errors=True) + +@pytest.mark.live_test_only +def test_logs_batch_query_partial_exception_not_allowed(): + credential = _credential() + client = LogsQueryClient(credential) + requests = [ + LogsBatchQuery( + query="AzureActivity | summarize count()", + timespan=timedelta(hours=1), + workspace_id= os.environ['LOG_WORKSPACE_ID'] + ), + LogsBatchQuery( + query= """AppRequests | take 10""", + timespan=(datetime(2021, 6, 2), timedelta(days=1)), + workspace_id= os.environ['LOG_WORKSPACE_ID'] + ), + LogsBatchQuery( + query= """let Weight = 92233720368547758; + range x from 1 to 3 step 1 + | summarize percentilesw(x, Weight * 100, 50)""", + workspace_id= os.environ['LOG_WORKSPACE_ID'], + timespan=(datetime(2021, 6, 2), datetime(2021, 6, 3)), + include_statistics=True + ), + ] + responses = client.query_batch(requests) + r1, r2, r3 = responses[0], responses[1], responses[2] + assert r1.__class__ == LogsQueryResult + assert r2.__class__ == LogsQueryResult + assert r3.__class__ == LogsQueryError + +@pytest.mark.live_test_only +def test_logs_batch_query_partial_exception_allowed(): + credential = _credential() + client = LogsQueryClient(credential) + requests = [ + LogsBatchQuery( + query="AzureActivity | summarize count()", + timespan=timedelta(hours=1), + workspace_id= os.environ['LOG_WORKSPACE_ID'] + ), + LogsBatchQuery( + query= """AppRequests | take 10""", + timespan=(datetime(2021, 6, 2), timedelta(days=1)), + workspace_id= os.environ['LOG_WORKSPACE_ID'] + ), + LogsBatchQuery( + query= """let Weight = 92233720368547758; + range x from 1 to 3 step 1 + | summarize percentilesw(x, Weight * 100, 50)""", + workspace_id= os.environ['LOG_WORKSPACE_ID'], + timespan=(datetime(2021, 6, 2), datetime(2021, 6, 3)), + include_statistics=True + ), + ] + responses = client.query_batch(requests, allow_partial_errors=True) + r1, r2, r3 = responses[0], responses[1], responses[2] + assert r1.__class__ == LogsQueryResult + assert r2.__class__ == LogsQueryResult + assert r3.__class__ == LogsQueryResult + assert r3.partial_error is not None + +@pytest.mark.live_test_only +def test_logs_batch_query_non_fatal_exception(): + credential = _credential() + client = LogsQueryClient(credential) + requests = [ + LogsBatchQuery( + query="AzureActivity | summarize count()", + timespan=timedelta(hours=1), + workspace_id= os.environ['LOG_WORKSPACE_ID'] + ), + LogsBatchQuery( + query= """AppRequests | take 10""", + timespan=(datetime(2021, 6, 2), timedelta(days=1)), + workspace_id= os.environ['LOG_WORKSPACE_ID'] + ), + LogsBatchQuery( + query= """Bad Query""", + workspace_id= os.environ['LOG_WORKSPACE_ID'], + timespan=(datetime(2021, 6, 2), datetime(2021, 6, 3)), + include_statistics=True + ), + ] + responses = client.query_batch(requests) + r1, r2, r3 = responses[0], responses[1], responses[2] + assert r1.__class__ == LogsQueryResult + assert r2.__class__ == LogsQueryResult + assert r3.__class__ == LogsQueryError From 06f5dde4a83121cd2df6fed4f15fc3ded3f5e9ca Mon Sep 17 00:00:00 2001 From: Rakshith Bhyravabhotla Date: Sun, 12 Sep 2021 15:50:01 -0700 Subject: [PATCH 05/12] more tests --- .../tests/async/test_logs_client_async.py | 28 +++++++++++++++-- .../tests/test_logs_client.py | 31 ++++++++++++++++--- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/sdk/monitor/azure-monitor-query/tests/async/test_logs_client_async.py b/sdk/monitor/azure-monitor-query/tests/async/test_logs_client_async.py index bf7ade3b9ad2..c7675081ed5a 100644 --- a/sdk/monitor/azure-monitor-query/tests/async/test_logs_client_async.py +++ b/sdk/monitor/azure-monitor-query/tests/async/test_logs_client_async.py @@ -4,7 +4,7 @@ import os from azure.identity.aio import ClientSecretCredential from azure.core.exceptions import HttpResponseError -from azure.monitor.query import LogsBatchQuery +from azure.monitor.query import LogsBatchQuery, LogsQueryError, LogsTable, LogsQueryResult from azure.monitor.query.aio import LogsQueryClient def _credential(): @@ -100,7 +100,7 @@ async def test_logs_query_batch_default(): assert r1.tables[0].columns[1] == '_ResourceId' assert r1.tables[0].columns[2] == 'avgRequestDuration' r2 = response[2] - assert r2.error is not None + assert r2.__class__ == LogsQueryError @pytest.mark.skip('https://github.com/Azure/azure-sdk-for-python/issues/19382') @pytest.mark.live_test_only @@ -178,3 +178,27 @@ async def test_logs_single_query_with_render_and_stats(): assert response.visualization is not None assert response.statistics is not None + +@pytest.mark.live_test_only +@pytest.mark.asyncio +async def test_logs_query_result_iterate_over_tables(): + client = LogsQueryClient(_credential()) + + query = "AppRequests; AppRequests | take 5" + + response = await client.query( + os.environ['LOG_WORKSPACE_ID'], + query, + timespan=None, + include_statistics=True, + include_visualization=True + ) + + ## should iterate over tables + for item in response: + assert item.__class__ == LogsTable + + assert response.statistics is not None + assert response.visualization is not None + assert len(response.tables) == 2 + assert response.__class__ == LogsQueryResult diff --git a/sdk/monitor/azure-monitor-query/tests/test_logs_client.py b/sdk/monitor/azure-monitor-query/tests/test_logs_client.py index 248f44796cbe..778fe6c15955 100644 --- a/sdk/monitor/azure-monitor-query/tests/test_logs_client.py +++ b/sdk/monitor/azure-monitor-query/tests/test_logs_client.py @@ -3,7 +3,7 @@ import os from azure.identity import ClientSecretCredential from azure.core.exceptions import HttpResponseError -from azure.monitor.query import LogsQueryClient, LogsBatchQuery +from azure.monitor.query import LogsQueryClient, LogsBatchQuery, LogsQueryError, LogsTable, LogsQueryResult def _credential(): credential = ClientSecretCredential( @@ -57,9 +57,9 @@ def test_logs_single_query_with_partial_success(): client = LogsQueryClient(credential) query = "set truncationmaxrecords=1; union * | project TimeGenerated | take 10" - response = client.query(os.environ['LOG_WORKSPACE_ID'], query, timespan=None) + response = client.query(os.environ['LOG_WORKSPACE_ID'], query, timespan=None, allow_partial_errors=True) - assert response is not None + assert response.partial_error is not None @pytest.mark.skip("https://github.com/Azure/azure-sdk-for-python/issues/19917") @pytest.mark.live_test_only @@ -108,7 +108,7 @@ def test_logs_query_batch_default(): assert r1.tables[0].columns[1] == '_ResourceId' assert r1.tables[0].columns[2] == 'avgRequestDuration' r2 = response[2] - assert r2.error is not None + assert r2.__class__ == LogsQueryError @pytest.mark.live_test_only def test_logs_single_query_with_statistics(): @@ -221,3 +221,26 @@ def test_logs_query_batch_additional_workspaces(): for resp in response: assert len(resp.tables[0].rows) == 2 + +@pytest.mark.live_test_only +def test_logs_query_result_iterate_over_tables(): + client = LogsQueryClient(_credential()) + + query = "AppRequests; AppRequests | take 5" + + response = client.query( + os.environ['LOG_WORKSPACE_ID'], + query, + timespan=None, + include_statistics=True, + include_visualization=True + ) + + ## should iterate over tables + for item in response: + assert item.__class__ == LogsTable + + assert response.statistics is not None + assert response.visualization is not None + assert len(response.tables) == 2 + assert response.__class__ == LogsQueryResult From e9f16cf4f5b3bf5acc7689b7e0908483ff0394a9 Mon Sep 17 00:00:00 2001 From: Rakshith Bhyravabhotla Date: Sun, 12 Sep 2021 16:00:28 -0700 Subject: [PATCH 06/12] lint --- .../azure/monitor/query/_exceptions.py | 2 +- .../azure/monitor/query/_helpers.py | 14 ++++++++++ .../azure/monitor/query/_logs_query_client.py | 16 ++++++----- .../query/aio/_logs_query_client_async.py | 27 +++++++------------ 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_exceptions.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_exceptions.py index fd37c277cf17..b341a9bf082c 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_exceptions.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_exceptions.py @@ -42,7 +42,7 @@ def _from_generated(cls, generated): return None details = None if generated.details is not None: - details=[d.serialize() for d in generated.details] + details = [d.serialize() for d in generated.details] return cls( code=generated.code, message=generated.message, diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py index d19d240bb27a..4124b6e3f21d 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py @@ -103,3 +103,17 @@ def process_error(error): if not error: return None raise HttpResponseError(message=error.message, response=error.response) + +def process_prefer(server_timeout, include_statistics, include_visualization): + prefer = "" + if server_timeout: + prefer += "wait=" + str(server_timeout) + if include_statistics: + if len(prefer) > 0: + prefer += "," + prefer += "include-statistics=true" + if include_visualization: + if len(prefer) > 0: + prefer += "," + prefer += "include-render=true" + return prefer diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py index b2c8e4aa43cb..0e8af480096a 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py @@ -12,7 +12,7 @@ from ._generated._monitor_query_client import MonitorQueryClient from ._generated.models import BatchRequest, QueryBody as LogsQueryBody -from ._helpers import get_authentication_policy, construct_iso8601, order_results, process_error +from ._helpers import get_authentication_policy, construct_iso8601, order_results, process_error, process_prefer from ._models import LogsBatchQuery, LogsQueryResult from ._exceptions import LogsQueryError, QueryPartialErrorException @@ -112,6 +112,7 @@ def query(self, workspace_id, query, **kwargs): if len(prefer) > 0: prefer += "," prefer += "include-render=true" + prefer = process_prefer(server_timeout, include_statistics, include_visualization) body = LogsQueryBody( query=query, @@ -129,14 +130,15 @@ def query(self, workspace_id, query, **kwargs): ) except HttpResponseError as err: process_error(err) - response = LogsQueryResult._from_generated(generated_response) + response = LogsQueryResult._from_generated(generated_response) # pylint: disable=protected-access if not generated_response.error: return response - else: - if not allow_partial_errors: - raise QueryPartialErrorException(error=generated_response.error) - response.partial_error = LogsQueryError._from_generated(generated_response.error) - return response + if not allow_partial_errors: + raise QueryPartialErrorException(error=generated_response.error) + response.partial_error = LogsQueryError._from_generated( # pylint: disable=protected-access + generated_response.error + ) + return response @distributed_trace def query_batch(self, queries, **kwargs): diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/aio/_logs_query_client_async.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/aio/_logs_query_client_async.py index b0ffbddb476d..8e1ac58dde26 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/aio/_logs_query_client_async.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/aio/_logs_query_client_async.py @@ -13,7 +13,7 @@ from .._generated.aio._monitor_query_client import MonitorQueryClient from .._generated.models import BatchRequest, QueryBody as LogsQueryBody -from .._helpers import construct_iso8601, order_results, process_error +from .._helpers import construct_iso8601, order_results, process_error, process_prefer from .._models import LogsQueryResult, LogsBatchQuery from ._helpers_asyc import get_authentication_policy from .._exceptions import LogsQueryError, QueryPartialErrorException @@ -83,17 +83,7 @@ async def query( server_timeout = kwargs.pop("server_timeout", None) additional_workspaces = kwargs.pop("additional_workspaces", None) - prefer = "" - if server_timeout: - prefer += "wait=" + str(server_timeout) - if include_statistics: - if len(prefer) > 0: - prefer += "," - prefer += "include-statistics=true" - if include_visualization: - if len(prefer) > 0: - prefer += "," - prefer += "include-render=true" + prefer = process_prefer(server_timeout, include_statistics, include_visualization) body = LogsQueryBody( query=query, @@ -111,14 +101,15 @@ async def query( ) except HttpResponseError as err: process_error(err) - response = LogsQueryResult._from_generated(generated_response) + response = LogsQueryResult._from_generated(generated_response) # pylint: disable=protected-access if not generated_response.error: return response - else: - if not allow_partial_errors: - raise QueryPartialErrorException(error=generated_response.error) - response.partial_error = LogsQueryError._from_generated(generated_response.error) - return response + if not allow_partial_errors: + raise QueryPartialErrorException(error=generated_response.error) + response.partial_error = LogsQueryError._from_generated( # pylint: disable=protected-access + generated_response.error + ) + return response @distributed_trace_async async def query_batch( From 56ecff3198d71527623ce26ad60f1da2e03e7b64 Mon Sep 17 00:00:00 2001 From: Rakshith Bhyravabhotla Date: Sun, 12 Sep 2021 16:03:34 -0700 Subject: [PATCH 07/12] changelog --- sdk/monitor/azure-monitor-query/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sdk/monitor/azure-monitor-query/CHANGELOG.md b/sdk/monitor/azure-monitor-query/CHANGELOG.md index ba96cc2ea4f4..70c9d0b31bfb 100644 --- a/sdk/monitor/azure-monitor-query/CHANGELOG.md +++ b/sdk/monitor/azure-monitor-query/CHANGELOG.md @@ -4,8 +4,14 @@ ### Features Added +- Added `QueryPartialErrorException` and `LogsQueryError` to handle errors. +- Added `partial_error` and `is_error` attributes to `LogsQueryResult`. +- Added an option `allow_partial_errors` that defaults to False, which can be set to not throw if there are any partial errors. + ### Breaking Changes +- `LogsQueryResult` now iterates over the tables directly as a convinience. + ### Bugs Fixed ### Other Changes From 46391594b1c5a32956f285211d33c4f1da07dae1 Mon Sep 17 00:00:00 2001 From: Rakshith Bhyravabhotla Date: Mon, 13 Sep 2021 10:28:35 -0700 Subject: [PATCH 08/12] Apply suggestions from code review --- .../azure/monitor/query/_generated/models/_models_py3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_generated/models/_models_py3.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_generated/models/_models_py3.py index 52cffb864a95..07aad3a671c1 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_generated/models/_models_py3.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_generated/models/_models_py3.py @@ -10,7 +10,7 @@ from typing import Dict, List, Optional, Union from azure.core.exceptions import HttpResponseError -from +import msrest.serialization from ._monitor_query_client_enums import * From 92631d438f8188820ed9f30ee80b258bae2cca6e Mon Sep 17 00:00:00 2001 From: Rakshith Bhyravabhotla Date: Mon, 13 Sep 2021 15:04:35 -0700 Subject: [PATCH 09/12] comment --- .../azure/monitor/query/_helpers.py | 11 +++++++++-- .../azure/monitor/query/_logs_query_client.py | 2 +- .../monitor/query/aio/_logs_query_client_async.py | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py index 4124b6e3f21d..13f61cf81d42 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py @@ -99,10 +99,17 @@ def native_col_type(col_type, value): def process_row(col_types, row): return [native_col_type(col_types[ind], val) for ind, val in enumerate(row)] -def process_error(error): +def process_error(error, model): if not error: return None - raise HttpResponseError(message=error.message, response=error.response) + try: + model = model._from_generated(error.model.error) + except AttributeError: # model can be none + pass + raise HttpResponseError( + message=error.message, + response=error.response, + model=model) def process_prefer(server_timeout, include_statistics, include_visualization): prefer = "" diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py index 0e8af480096a..f9cae7351fa5 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py @@ -129,7 +129,7 @@ def query(self, workspace_id, query, **kwargs): **kwargs ) except HttpResponseError as err: - process_error(err) + process_error(err, LogsQueryError) response = LogsQueryResult._from_generated(generated_response) # pylint: disable=protected-access if not generated_response.error: return response diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/aio/_logs_query_client_async.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/aio/_logs_query_client_async.py index 8e1ac58dde26..e31806a110fa 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/aio/_logs_query_client_async.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/aio/_logs_query_client_async.py @@ -100,7 +100,7 @@ async def query( **kwargs ) except HttpResponseError as err: - process_error(err) + process_error(err, LogsQueryError) response = LogsQueryResult._from_generated(generated_response) # pylint: disable=protected-access if not generated_response.error: return response From 49d66842fa8e4609b2afa2db54da04b473ff42a3 Mon Sep 17 00:00:00 2001 From: Rakshith Bhyravabhotla Date: Mon, 13 Sep 2021 15:27:19 -0700 Subject: [PATCH 10/12] lint --- sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py index 13f61cf81d42..64d64f78c728 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py @@ -103,7 +103,7 @@ def process_error(error, model): if not error: return None try: - model = model._from_generated(error.model.error) + model = model._from_generated(error.model.error) # pylint: disable=protected-access except AttributeError: # model can be none pass raise HttpResponseError( From 4c951519141496589d5b94168c36c3e7af0216a0 Mon Sep 17 00:00:00 2001 From: Rakshith Bhyravabhotla Date: Mon, 13 Sep 2021 16:00:39 -0700 Subject: [PATCH 11/12] querry --- sdk/monitor/azure-monitor-query/tests/test_logs_client.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/monitor/azure-monitor-query/tests/test_logs_client.py b/sdk/monitor/azure-monitor-query/tests/test_logs_client.py index 778fe6c15955..34b00e201dc5 100644 --- a/sdk/monitor/azure-monitor-query/tests/test_logs_client.py +++ b/sdk/monitor/azure-monitor-query/tests/test_logs_client.py @@ -55,8 +55,9 @@ def test_logs_single_query_with_non_200(): def test_logs_single_query_with_partial_success(): credential = _credential() client = LogsQueryClient(credential) - query = "set truncationmaxrecords=1; union * | project TimeGenerated | take 10" - + query = """let Weight = 92233720368547758; + range x from 1 to 3 step 1 + | summarize percentilesw(x, Weight * 100, 50)""" response = client.query(os.environ['LOG_WORKSPACE_ID'], query, timespan=None, allow_partial_errors=True) assert response.partial_error is not None From 0fabfd663b6a2c205f25f0a6ae55de5e10b204c1 Mon Sep 17 00:00:00 2001 From: Rakshith Bhyravabhotla Date: Mon, 13 Sep 2021 16:44:32 -0700 Subject: [PATCH 12/12] comments --- .../azure/monitor/query/_exceptions.py | 4 ---- .../azure/monitor/query/_helpers.py | 12 +++--------- .../azure/monitor/query/_logs_query_client.py | 11 ----------- .../azure/monitor/query/_models.py | 1 - 4 files changed, 3 insertions(+), 25 deletions(-) diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_exceptions.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_exceptions.py index b341a9bf082c..f849f93ff6ee 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_exceptions.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_exceptions.py @@ -49,7 +49,6 @@ def _from_generated(cls, generated): innererror=cls._from_generated(generated.innererror) if generated.innererror else None, additional_properties=generated.additional_properties, details=details, - is_error=True ) class QueryPartialErrorException(HttpResponseError): @@ -67,8 +66,6 @@ class QueryPartialErrorException(HttpResponseError): :ivar additional_properties: Additional properties that can be provided on the error info object. :vartype additional_properties: object - :ivar bool is_error: Boolean check for error item when iterating over list of - results. Always True for an instance of a LogsQueryError. """ def __init__(self, **kwargs): @@ -79,5 +76,4 @@ def __init__(self, **kwargs): self.details = [d.serialize() for d in error.details] if error.details else None self.innererror = LogsQueryError._from_generated(error.innererror) if error.innererror else None self.additional_properties = error.additional_properties - self.is_error = True super(QueryPartialErrorException, self).__init__(message=self.message) diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py index 64d64f78c728..5adf5bc095d9 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_helpers.py @@ -100,8 +100,6 @@ def process_row(col_types, row): return [native_col_type(col_types[ind], val) for ind, val in enumerate(row)] def process_error(error, model): - if not error: - return None try: model = model._from_generated(error.model.error) # pylint: disable=protected-access except AttributeError: # model can be none @@ -114,13 +112,9 @@ def process_error(error, model): def process_prefer(server_timeout, include_statistics, include_visualization): prefer = "" if server_timeout: - prefer += "wait=" + str(server_timeout) + prefer += "wait=" + str(server_timeout) + "," if include_statistics: - if len(prefer) > 0: - prefer += "," - prefer += "include-statistics=true" + prefer += "include-statistics=true," if include_visualization: - if len(prefer) > 0: - prefer += "," prefer += "include-render=true" - return prefer + return prefer.rstrip(",") diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py index f9cae7351fa5..ccb68fdd6414 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_logs_query_client.py @@ -101,17 +101,6 @@ def query(self, workspace_id, query, **kwargs): server_timeout = kwargs.pop("server_timeout", None) workspaces = kwargs.pop("additional_workspaces", None) - prefer = "" - if server_timeout: - prefer += "wait=" + str(server_timeout) - if include_statistics: - if len(prefer) > 0: - prefer += "," - prefer += "include-statistics=true" - if include_visualization: - if len(prefer) > 0: - prefer += "," - prefer += "include-render=true" prefer = process_prefer(server_timeout, include_statistics, include_visualization) body = LogsQueryBody( diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py index a7a45c6242b2..4532f1ab468e 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py @@ -201,7 +201,6 @@ def _from_generated(cls, generated): tables=tables, statistics=generated.statistics, visualization=generated.render, - is_error=False )