From 279e7ebdb9c8a9fc30c7fa963f9a69cf339c06be Mon Sep 17 00:00:00 2001 From: Rakshith Bhyravabhotla Date: Tue, 14 Sep 2021 15:22:05 -0700 Subject: [PATCH] Add a new row type in query (#20685) * Add row type * Add a new row type * add test * lint * Apply suggestions from code review * changes --- sdk/monitor/azure-monitor-query/CHANGELOG.md | 2 + .../azure/monitor/query/__init__.py | 2 + .../monitor/query/_metrics_query_client.py | 4 +- .../azure/monitor/query/_models.py | 45 ++++++++++++++++++- .../samples/sample_log_query_client.py | 5 ++- .../tests/async/test_logs_client_async.py | 22 ++++++++- .../tests/test_logs_client.py | 21 ++++++++- 7 files changed, 93 insertions(+), 8 deletions(-) diff --git a/sdk/monitor/azure-monitor-query/CHANGELOG.md b/sdk/monitor/azure-monitor-query/CHANGELOG.md index 70c9d0b31bfb..5f2d861f2b22 100644 --- a/sdk/monitor/azure-monitor-query/CHANGELOG.md +++ b/sdk/monitor/azure-monitor-query/CHANGELOG.md @@ -7,10 +7,12 @@ - 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. +- Added a new `LogsTableRow` type that represents a single row in a table. ### Breaking Changes - `LogsQueryResult` now iterates over the tables directly as a convinience. +- `metric_namespace` is renamed to `namespace` and is a keyword-only argument in `list_metric_definitions` API. ### Bugs Fixed 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 d33a6f0a5c28..1171a0562dfc 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/__init__.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/__init__.py @@ -16,6 +16,7 @@ MetricAggregationType, LogsQueryResult, LogsTable, + LogsTableRow, MetricsResult, LogsBatchQuery, MetricNamespace, @@ -38,6 +39,7 @@ "LogsQueryError", "QueryPartialErrorException", "LogsTable", + "LogsTableRow", "LogsBatchQuery", "MetricsQueryClient", "MetricNamespace", diff --git a/sdk/monitor/azure-monitor-query/azure/monitor/query/_metrics_query_client.py b/sdk/monitor/azure-monitor-query/azure/monitor/query/_metrics_query_client.py index 30a99f6f82a0..78f168bb59de 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_metrics_query_client.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_metrics_query_client.py @@ -148,8 +148,8 @@ def list_metric_namespaces(self, resource_uri, **kwargs): **kwargs) @distributed_trace - def list_metric_definitions(self, resource_uri, metric_namespace=None, **kwargs): - # type: (str, str, Any) -> ItemPaged[MetricDefinition] + def list_metric_definitions(self, resource_uri, **kwargs): + # type: (str, Any) -> ItemPaged[MetricDefinition] """Lists the metric definitions for the resource. :param resource_uri: The identifier of the resource. 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 4532f1ab468e..daa4977803fc 100644 --- a/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py +++ b/sdk/monitor/azure-monitor-query/azure/monitor/query/_models.py @@ -28,7 +28,7 @@ class LogsTable(object): :ivar column_types: The types of columns in this table. :vartype columns: list[object] :ivar rows: Required. The resulting rows from this query. - :vartype rows: list[list[object]] + :vartype rows: list[~azure.monitor.query.LogsTableRow] """ def __init__(self, **kwargs): # type: (Any) -> None @@ -36,7 +36,14 @@ def __init__(self, **kwargs): self.columns = kwargs.pop('columns', None) # type: Optional[str] self.columns_types = kwargs.pop('column_types', None) # type: Optional[Any] _rows = kwargs.pop('rows', None) - self.rows = [process_row(self.columns_types, row) for row in _rows] + self.rows = [ + LogsTableRow( + row=row, + row_index=ind, + col_types=self.columns_types, + columns=self.columns + ) for ind, row in enumerate(_rows) + ] @classmethod def _from_generated(cls, generated): @@ -48,6 +55,40 @@ def _from_generated(cls, generated): ) +class LogsTableRow(object): + """Represents a single row in logs table. + + ivar list row: The collection of values in the row. + ivar int row_index: The index of the row in the table + """ + def __init__(self, **kwargs): + # type: (Any) -> None + _col_types = kwargs['col_types'] + row = kwargs['row'] + self.row = process_row(_col_types, row) + self.row_index = kwargs['row_index'] + _columns = kwargs['columns'] + self._row_dict = { + _columns[i]: self.row[i] for i in range(len(self.row)) + } + + def __iter__(self): + """This will iterate over the row directly. + """ + return iter(self.row) + + def __getitem__(self, column): + """This type must be subscriptable directly to row. + Must be gettableby both column name and row index + Example: row[0] -> returns the first element of row and + row[column_name] -> returns the row element against the given column name. + """ + try: + return self._row_dict[column] + except KeyError: + return self.row[column] + + class MetricsResult(object): """The response to a metrics query. 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 5bda7d0e0f3a..d16d38513f65 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 @@ -17,13 +17,14 @@ # Response time trend # request duration over the last 12 hours. # [START send_logs_query] -query = """AppRwequests | take 5""" +query = """AppRequests | take 5""" # returns LogsQueryResult try: response = client.query(os.environ['LOG_WORKSPACE_ID'], query, timespan=timedelta(days=1)) for table in response: - print(table) + df = pd.DataFrame(data=table.rows, columns=table.columns) + print(df) except QueryPartialErrorException as err: print("this is a partial error") print(err.details) 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 c7675081ed5a..1894d2de3d84 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, LogsQueryError, LogsTable, LogsQueryResult +from azure.monitor.query import LogsBatchQuery, LogsQueryError, LogsTable, LogsQueryResult, LogsTableRow from azure.monitor.query.aio import LogsQueryClient def _credential(): @@ -202,3 +202,23 @@ async def test_logs_query_result_iterate_over_tables(): assert response.visualization is not None assert len(response.tables) == 2 assert response.__class__ == LogsQueryResult + +@pytest.mark.live_test_only +@pytest.mark.asyncio +async def test_logs_query_result_row_type(): + client = LogsQueryClient(_credential()) + + query = "AppRequests | take 5" + + response = await client.query( + os.environ['LOG_WORKSPACE_ID'], + query, + timespan=None, + ) + + ## should iterate over tables + for table in response: + assert table.__class__ == LogsTable + + for row in table.rows: + assert row.__class__ == LogsTableRow 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 34b00e201dc5..a9036055eb0d 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, LogsQueryError, LogsTable, LogsQueryResult +from azure.monitor.query import LogsQueryClient, LogsBatchQuery, LogsQueryError, LogsTable, LogsQueryResult, LogsTableRow def _credential(): credential = ClientSecretCredential( @@ -245,3 +245,22 @@ def test_logs_query_result_iterate_over_tables(): assert response.visualization is not None assert len(response.tables) == 2 assert response.__class__ == LogsQueryResult + +@pytest.mark.live_test_only +def test_logs_query_result_row_type(): + client = LogsQueryClient(_credential()) + + query = "AppRequests | take 5" + + response = client.query( + os.environ['LOG_WORKSPACE_ID'], + query, + timespan=None, + ) + + ## should iterate over tables + for table in response: + assert table.__class__ == LogsTable + + for row in table.rows: + assert row.__class__ == LogsTableRow