Skip to content

Commit c38ea3c

Browse files
authored
API: eid sorting (#42)
- eid sorting on endpoint /entity/{etype}/get - new tests covered eid sorting and multi attr sorting
1 parent f120f29 commit c38ea3c

2 files changed

Lines changed: 79 additions & 5 deletions

File tree

dp3/api/routers/entity.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ def _validate_sort_params(etype: str, sort: list[str] | None) -> list[tuple[str,
144144
sort: list of sort specifications in format 'attribute:direction'
145145
where direction is 1 (ascending) or -1 (descending)
146146
e.g., ['hostname:-1', 'ip:1']
147+
Special attribute 'eid' is also supported for sorting by entity ID.
147148
148149
Returns:
149150
List of (attribute, direction) tuples for sorting, or None if no sorting specified
@@ -172,6 +173,11 @@ def _validate_sort_params(etype: str, sort: list[str] | None) -> list[tuple[str,
172173
attr, direction_str = match.groups()
173174
direction = int(direction_str) if direction_str else 1 # Default to ascending (1)
174175

176+
# Support sorting by entity ID using pseudo-attribute 'eid'
177+
if attr == "eid":
178+
sort_criteria.append((attr, direction))
179+
continue
180+
175181
# Check if attribute exists
176182
if attr not in entity_attribs:
177183
raise RequestValidationError(
@@ -306,11 +312,13 @@ async def get_entity_type_eids(
306312
307313
Generic and fulltext filters are merged - fulltext overrides conflicting keys.
308314
309-
Sorting is supported for plain and observations attributes with primitive data types
310-
(excluding json and multi_value observations). To sort by multiple attributes, provide
311-
multiple sort parameters in the format 'attribute:direction' where direction is 1 (ascending)
312-
or -1 (descending). Direction defaults to 1 (ascending) if not provided. Example:
313-
`?sort=hostname:-1&sort=ip:1`
315+
Sorting is supported by entity ID using the special attribute name `eid`,
316+
as well as for plain and observations attributes with primitive data types
317+
(excluding json and multi_value observations). To sort by multiple attributes,
318+
provide multiple sort parameters in the format 'attribute:direction' where
319+
direction is 1 (ascending) or -1 (descending). Direction defaults to 1 (ascending)
320+
if not provided. Examples:
321+
`?sort=eid:1`, `?sort=hostname:-1&sort=ip:1`
314322
"""
315323
fulltext_filters, generic_filter = _validate_snapshot_filters(fulltext_filters, generic_filter)
316324
sort_criteria = _validate_sort_params(etype, sort)

tests/test_api/test_get_entity_eids.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,69 @@ def test_get_entity_eids_generic_filters_eid(self):
6060
{5, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59},
6161
{x["eid"] for x in eids.data},
6262
)
63+
64+
def test_get_entity_eids_sort_by_eid_asc(self):
65+
eids = self.get_entity_data("entity/A/get", EntityEidList, sort="eid:1", limit=0)
66+
self.assertEqual(100, len(eids.data))
67+
self.assertEqual(list(range(100)), [x["eid"] for x in eids.data])
68+
69+
def test_get_entity_eids_sort_by_eid_desc(self):
70+
eids = self.get_entity_data("entity/A/get", EntityEidList, sort="eid:-1", limit=0)
71+
self.assertEqual(100, len(eids.data))
72+
self.assertEqual(list(range(99, -1, -1)), [x["eid"] for x in eids.data])
73+
74+
def test_get_entity_eids_pagination_with_sort(self):
75+
received_eids = []
76+
for i in range(0, 100, 10):
77+
eids = self.get_entity_data(
78+
"entity/A/get", EntityEidList, sort="eid:1", skip=i, limit=10
79+
)
80+
self.assertEqual(10, len(eids.data), f"Failed at {i}")
81+
received_eids.extend(x["eid"] for x in eids.data)
82+
self.assertEqual(list(range(100)), received_eids)
83+
84+
received_eids = []
85+
for i in range(0, 100, 10):
86+
eids = self.get_entity_data(
87+
"entity/A/get", EntityEidList, sort="eid:-1", skip=i, limit=10
88+
)
89+
self.assertEqual(10, len(eids.data), f"Failed at {i}")
90+
received_eids.extend(x["eid"] for x in eids.data)
91+
self.assertEqual(list(range(99, -1, -1)), received_eids)
92+
93+
def _get_sorted_eids(self, sort_params: list[str], **kwargs) -> EntityEidList:
94+
"""Fetch entity EIDs with multiple sort query parameters.
95+
96+
The shared `get_request` helper joins kwargs with '&' and cannot emit
97+
multiple values for the same key, which is required for multi-column
98+
sorting. Build the query string explicitly here.
99+
"""
100+
query_parts = [f"sort={param}" for param in sort_params]
101+
for key, value in kwargs.items():
102+
query_parts.append(f"{key}={value}")
103+
response = self.get_request(f"entity/A/get?{'&'.join(query_parts)}")
104+
self.assertEqual(response.status_code, 200)
105+
return EntityEidList.model_validate_json(response.content)
106+
107+
def test_get_entity_eids_sort_by_multiple_attrs(self):
108+
res = self.push_datapoints(
109+
[
110+
{"src": "setup@test", "attr": "data2", "type": "A", "id": i, "v": f"g{i % 5}"}
111+
for i in range(0, 100)
112+
]
113+
)
114+
self.assertEqual(res.status_code, 200)
115+
sleep(8)
116+
self.get_request("control/make_snapshots")
117+
sleep(6)
118+
119+
expected = sorted(range(100), key=lambda i: (f"g{i % 5}", i))
120+
# omit :1 if sort is ascending
121+
eids = self._get_sorted_eids(["data2", "eid"], limit=0)
122+
self.assertEqual(100, len(eids.data))
123+
self.assertEqual(expected, [x["eid"] for x in eids.data])
124+
125+
expected = sorted(range(100), key=lambda i: (f"g{i % 5}", -i))
126+
eids = self._get_sorted_eids(["data2:1", "eid:-1"], limit=0)
127+
self.assertEqual(100, len(eids.data))
128+
self.assertEqual(expected, [x["eid"] for x in eids.data])

0 commit comments

Comments
 (0)