Skip to content

Commit 8b8afef

Browse files
authored
feat: Add query-tables method (#32)
1 parent 491892e commit 8b8afef

File tree

7 files changed

+129
-12
lines changed

7 files changed

+129
-12
lines changed

nisystemlink/clients/core/_uplink/_base_client.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# mypy: disable-error-code = misc
22

3+
import json
34
from typing import Dict, Optional, Type
45

56
from nisystemlink.clients import core
@@ -41,7 +42,7 @@ def _handle_http_status(response: Response) -> Optional[Response]:
4142
@dumps.to_json(JsonModel)
4243
def _deserialize_model(model_cls: Type[JsonModel], model_instance: JsonModel) -> Dict:
4344
"""Turns a :class:`.JsonModel` instance into a dictionary for serialization."""
44-
return model_instance.dict(by_alias=True, exclude_unset=True)
45+
return json.loads(model_instance.json(by_alias=True, exclude_unset=True))
4546

4647

4748
class BaseClient(Consumer):

nisystemlink/clients/core/_uplink/_paged_result.py nisystemlink/clients/core/_uplink/_with_paging.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from ._json_model import JsonModel
44

55

6-
class PagedResult(JsonModel):
7-
continuation_token: Optional[str]
6+
class WithPaging(JsonModel):
7+
continuation_token: Optional[str] = None
88
"""A token which allows the user to resume a query at the next item in the matching results.
99
1010
When querying, a token will be returned if a query may be

nisystemlink/clients/dataframe/_data_frame_client.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,26 @@ def create_table(self, table: models.CreateTableRequest) -> str:
7575
"""Create a new table with the provided metadata and column definitions.
7676
7777
Args:
78-
table: The request create the table.
78+
table: The request to create the table.
7979
8080
Returns:
8181
The ID of the newly created table.
8282
"""
8383
...
8484

85+
@json
86+
@post(_BASE_PATH + "/query-tables", args=(Body,))
87+
def query_tables(self, query: models.QueryTablesRequest) -> models.PagedTables:
88+
"""Queries available tables on the SystemLink DataFrame service and returns their metadata.
89+
90+
Args:
91+
query: The request to query tables.
92+
93+
Returns:
94+
models.PagedTables: The list of tables with a continuation token.
95+
"""
96+
...
97+
8598
@get(_BASE_PATH + "/tables/{id}")
8699
def get_table_metadata(self, id: str) -> models.TableMetadata:
87100
"""Retrieves the metadata and column information for a single table identified by its ID.

nisystemlink/clients/dataframe/models/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from ._data_type import DataType
66
from ._order_by import OrderBy
77
from ._paged_tables import PagedTables
8+
from ._query_tables_request import QueryTablesRequest
89
from ._table_metadata import TableMetadata
910

1011
# flake8: noqa

nisystemlink/clients/dataframe/models/_paged_tables.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from typing import List
22

3-
from nisystemlink.clients.core._uplink._paged_result import PagedResult
3+
from nisystemlink.clients.core._uplink._with_paging import WithPaging
44

55
from ._table_metadata import TableMetadata
66

77

8-
class PagedTables(PagedResult):
8+
class PagedTables(WithPaging):
99
"""The response for a table query containing the matched tables."""
1010

1111
tables: List[TableMetadata]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from datetime import datetime
2+
from typing import List, Optional, Union
3+
4+
from nisystemlink.clients.core._uplink._with_paging import WithPaging
5+
from pydantic import StrictBool, StrictInt
6+
7+
from ._order_by import OrderBy
8+
9+
10+
class QueryTablesRequest(WithPaging):
11+
"""Request parameters for querying tables."""
12+
13+
filter: str
14+
"""The table query filter in `Dynamic LINQ`_ format.
15+
16+
.. _Dynamic LINQ: https://github.com/ni/systemlink-OpenAPI-documents/wiki/Dynamic-Linq-Query-Language
17+
18+
Allowed properties in the filter are:
19+
20+
* ``createdAt``: DateTime the table was created
21+
* ``createdWithin``: TimeSpan in which the table was created
22+
* ``id``: String value uniquely identifying the table
23+
* ``name``: String name for the table
24+
* ``metadataModifiedAt``: DateTime the table's metadata was last modified
25+
* ``metadataModifiedWithin``: TimeSpan in which the table's metadata was
26+
last modified
27+
* ``properties``: Dictionary with string keys and values representing table
28+
metadata
29+
* ``rowsModifiedAt``: DateTime rows were last appended to the table
30+
* ``rowsModifiedWithin``: TimeSpan within rows were last appended to the
31+
table
32+
* ``rowCount``: Int32 number of rows in the table
33+
* ``supportsAppend``: Boolean indicating whether or not the table supports
34+
appending additional rows of data
35+
* ``workspace``: String value ID of the workspace the table belongs to
36+
* ``workspaceName``: String value name of the workspace the table belongs to
37+
38+
Allowed constants in the filter are:
39+
40+
* ``RelativeTime.CurrentDay``: TimeSpan representing the elapsed time
41+
between now and the start of the current day
42+
* ``RelativeTime.CurrentWeek``: TimeSpan representing the elapsed time
43+
between now and the start of the current week
44+
* ``RelativeTime.CurrentMonth``: TimeSpan representing the elapsed time
45+
between now and the start of the current month
46+
* ``RelativeTime.CurrentYear``: TimeSpan representing the elapsed time
47+
between now and the start of the current year
48+
"""
49+
50+
substitutions: Optional[List[Union[StrictInt, StrictBool, str, None]]] = None
51+
"""Make substitutions in the query filter expression.
52+
53+
Substitutions for the query expression are indicated by non-negative
54+
integers that are prefixed with the ``@`` symbol. Each substitution in the given
55+
expression will be replaced by the element at the corresponding index
56+
(zero-based) in this list. For example, ``@0`` in the filter expression will be
57+
replaced with the element at the zeroth index of the substitutions list.
58+
"""
59+
60+
reference_time: Optional[datetime] = None
61+
"""The date and time to use as the reference point for `RelativeTime` filters,
62+
including time zone information. Defaults to the time on the server in UTC."""
63+
64+
take: Optional[int] = None
65+
"""Limits the returned list to the specified number of results."""
66+
67+
order_by: Optional[OrderBy] = None
68+
"""The sort order of the returned list of tables."""
69+
70+
order_by_descending: Optional[bool] = None
71+
"""Whether to sort descending instead of ascending.
72+
73+
The elements in the list are sorted ascending by default. If the
74+
orderByDescending parameter is specified, the elements in the list are
75+
sorted based on it's value. The orderByDescending value must be a boolean
76+
string. The elements in the list are sorted ascending if false and
77+
descending if true.
78+
"""

tests/integration/dataframe/test_dataframe.py

+30-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
2-
from datetime import datetime
2+
from datetime import datetime, timezone
33
from typing import List
44

55
import pytest # type: ignore
@@ -111,24 +111,48 @@ def test__get_table_invalid_id__raises(self, client: DataFrameClient):
111111
def test__list_tables__returns(
112112
self, client: DataFrameClient, test_tables: List[str]
113113
):
114-
take = len(test_tables) - 1
115114
first_page = client.list_tables(
116-
take=take,
117-
id=test_tables,
115+
take=2,
116+
id=test_tables[:3],
118117
order_by="NAME",
119118
order_by_descending=True,
120119
)
121120

122-
assert len(first_page.tables) == take
121+
assert len(first_page.tables) == 2
123122
assert first_page.tables[0].id == test_tables[-1] # Asserts descending order
124123
assert first_page.continuation_token is not None
125124

126125
second_page = client.list_tables(
127-
id=test_tables,
126+
id=test_tables[:3],
128127
order_by="NAME",
129128
order_by_descending=True,
130129
continuation_token=first_page.continuation_token,
131130
)
132131

133132
assert len(second_page.tables) == 1
134133
assert second_page.continuation_token is None
134+
135+
def test__query_tables__returns(
136+
self, client: DataFrameClient, test_tables: List[str]
137+
):
138+
query = models.QueryTablesRequest(
139+
filter="""(id == @0 or id == @1 or id == @2)
140+
and createdWithin <= RelativeTime.CurrentWeek
141+
and supportsAppend == @3 and rowCount < @4""",
142+
substitutions=[test_tables[0], test_tables[1], test_tables[2], True, 1],
143+
reference_time=datetime.now(tz=timezone.utc),
144+
take=2,
145+
order_by="NAME",
146+
order_by_descending=True,
147+
)
148+
first_page = client.query_tables(query)
149+
150+
assert len(first_page.tables) == 2
151+
assert first_page.tables[0].id == test_tables[-1] # Asserts descending order
152+
assert first_page.continuation_token is not None
153+
154+
query.continuation_token = first_page.continuation_token
155+
156+
second_page = client.query_tables(query)
157+
assert len(second_page.tables) == 1
158+
assert second_page.continuation_token is None

0 commit comments

Comments
 (0)