Skip to content

Commit 7690658

Browse files
authored
[DOGE-106] Change behavior of aggregate (#2121)
1 parent 2fafb20 commit 7690658

File tree

5 files changed

+105
-17
lines changed

5 files changed

+105
-17
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ Changes are grouped as follows
2222
- Support for the `/simulators/models` and `/simulators/models/revisions` API endpoints.
2323
- Support for the `/simulators` and `/simulators/integration` API endpoints.
2424

25+
## [7.73.5] - 2025-02-26
26+
### Fixed
27+
- The `client.data_modeling.instances.aggregate/search()` methods now correctly returns maximum, 1000, results when setting
28+
the `limit` parameter to `None`, `-1`, or `math.inf`.
29+
2530
## [7.73.4] - 2025-02-24
2631
### Fixed
2732
- An issue with `DatapointsAPI.retrieve_latest` and usage of `instance_id` when using `ignore_unknown_ids=True`

cognite/client/_api/data_modeling/instances.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,15 @@
7171
)
7272
from cognite.client.data_classes.data_modeling.views import View
7373
from cognite.client.data_classes.filters import _BASIC_FILTERS, Filter, _validate_filter
74-
from cognite.client.utils._auxiliary import load_yaml_or_json
74+
from cognite.client.utils._auxiliary import is_unlimited, load_yaml_or_json
7575
from cognite.client.utils._concurrency import ConcurrencySettings
7676
from cognite.client.utils._identifier import DataModelingIdentifierSequence
7777
from cognite.client.utils._retry import Backoff
7878
from cognite.client.utils._text import random_string
7979
from cognite.client.utils.useful_types import SequenceNotStr
8080

8181
if TYPE_CHECKING:
82-
from cognite.client import CogniteClient
82+
from cognite.client import ClientConfig, CogniteClient
8383

8484
_FILTERS_SUPPORTED: frozenset[type[Filter]] = _BASIC_FILTERS.union(
8585
{filters.Nested, filters.HasData, filters.MatchAll, filters.Overlaps, filters.InstanceReferences}
@@ -164,6 +164,11 @@ def _load(data: dict, cognite_client: CogniteClient | None = None) -> NodeApply
164164
class InstancesAPI(APIClient):
165165
_RESOURCE_PATH = "/models/instances"
166166

167+
def __init__(self, config: ClientConfig, api_version: str | None, cognite_client: CogniteClient) -> None:
168+
super().__init__(config, api_version, cognite_client)
169+
self._AGGREGATE_LIMIT = 1000
170+
self._SEARCH_LIMIT = 1000
171+
167172
@overload
168173
def __call__(
169174
self,
@@ -1042,7 +1047,7 @@ def search(
10421047
space: str | SequenceNotStr[str] | None = None,
10431048
filter: Filter | dict[str, Any] | None = None,
10441049
include_typing: bool = False,
1045-
limit: int = DEFAULT_LIMIT_READ,
1050+
limit: int | None = DEFAULT_LIMIT_READ,
10461051
sort: Sequence[InstanceSort | dict] | InstanceSort | dict | None = None,
10471052
) -> NodeList[Node]: ...
10481053

@@ -1058,7 +1063,7 @@ def search(
10581063
space: str | SequenceNotStr[str] | None = None,
10591064
filter: Filter | dict[str, Any] | None = None,
10601065
include_typing: bool = False,
1061-
limit: int = DEFAULT_LIMIT_READ,
1066+
limit: int | None = DEFAULT_LIMIT_READ,
10621067
sort: Sequence[InstanceSort | dict] | InstanceSort | dict | None = None,
10631068
) -> EdgeList[Edge]: ...
10641069

@@ -1074,7 +1079,7 @@ def search(
10741079
space: str | SequenceNotStr[str] | None = None,
10751080
filter: Filter | dict[str, Any] | None = None,
10761081
include_typing: bool = False,
1077-
limit: int = DEFAULT_LIMIT_READ,
1082+
limit: int | None = DEFAULT_LIMIT_READ,
10781083
sort: Sequence[InstanceSort | dict] | InstanceSort | dict | None = None,
10791084
) -> NodeList[T_Node]: ...
10801085

@@ -1090,7 +1095,7 @@ def search(
10901095
space: str | SequenceNotStr[str] | None = None,
10911096
filter: Filter | dict[str, Any] | None = None,
10921097
include_typing: bool = False,
1093-
limit: int = DEFAULT_LIMIT_READ,
1098+
limit: int | None = DEFAULT_LIMIT_READ,
10941099
sort: Sequence[InstanceSort | dict] | InstanceSort | dict | None = None,
10951100
) -> EdgeList[T_Edge]: ...
10961101

@@ -1104,7 +1109,7 @@ def search(
11041109
space: str | SequenceNotStr[str] | None = None,
11051110
filter: Filter | dict[str, Any] | None = None,
11061111
include_typing: bool = False,
1107-
limit: int = DEFAULT_LIMIT_READ,
1112+
limit: int | None = DEFAULT_LIMIT_READ,
11081113
sort: Sequence[InstanceSort | dict] | InstanceSort | dict | None = None,
11091114
) -> NodeList[T_Node] | EdgeList[T_Edge]:
11101115
"""`Search instances <https://developer.cognite.com/api/v1/#tag/Instances/operation/searchInstances>`_
@@ -1118,7 +1123,8 @@ def search(
11181123
space (str | SequenceNotStr[str] | None): Restrict instance search to the given space (or list of spaces).
11191124
filter (Filter | dict[str, Any] | None): Advanced filtering of instances.
11201125
include_typing (bool): Whether to include typing information.
1121-
limit (int): Maximum number of instances to return. Defaults to 25.
1126+
limit (int | None): Maximum number of instances to return. Defaults to 25. Will return the maximum number
1127+
of results (1000) if set to None, -1, or math.inf.
11221128
sort (Sequence[InstanceSort | dict] | InstanceSort | dict | None): How you want the listed instances information ordered.
11231129
11241130
Returns:
@@ -1166,7 +1172,11 @@ def search(
11661172
else:
11671173
raise ValueError(f"Invalid instance type: {instance_type}")
11681174

1169-
body: dict[str, Any] = {"view": view.dump(camel_case=True), "instanceType": instance_type_str, "limit": limit}
1175+
body: dict[str, Any] = {
1176+
"view": view.dump(camel_case=True),
1177+
"instanceType": instance_type_str,
1178+
"limit": self._SEARCH_LIMIT if is_unlimited(limit) else limit,
1179+
}
11701180
if query:
11711181
body["query"] = query
11721182
if properties:
@@ -1202,7 +1212,7 @@ def aggregate(
12021212
target_units: list[TargetUnit] | None = None,
12031213
space: str | SequenceNotStr[str] | None = None,
12041214
filter: Filter | dict[str, Any] | None = None,
1205-
limit: int = DEFAULT_LIMIT_READ,
1215+
limit: int | None = DEFAULT_LIMIT_READ,
12061216
) -> AggregatedNumberedValue: ...
12071217

12081218
@overload
@@ -1217,7 +1227,7 @@ def aggregate(
12171227
target_units: list[TargetUnit] | None = None,
12181228
space: str | SequenceNotStr[str] | None = None,
12191229
filter: Filter | dict[str, Any] | None = None,
1220-
limit: int = DEFAULT_LIMIT_READ,
1230+
limit: int | None = DEFAULT_LIMIT_READ,
12211231
) -> list[AggregatedNumberedValue]: ...
12221232

12231233
@overload
@@ -1232,7 +1242,7 @@ def aggregate(
12321242
target_units: list[TargetUnit] | None = None,
12331243
space: str | SequenceNotStr[str] | None = None,
12341244
filter: Filter | dict[str, Any] | None = None,
1235-
limit: int = DEFAULT_LIMIT_READ,
1245+
limit: int | None = DEFAULT_LIMIT_READ,
12361246
) -> InstanceAggregationResultList: ...
12371247

12381248
def aggregate(
@@ -1246,7 +1256,7 @@ def aggregate(
12461256
target_units: list[TargetUnit] | None = None,
12471257
space: str | SequenceNotStr[str] | None = None,
12481258
filter: Filter | dict[str, Any] | None = None,
1249-
limit: int = DEFAULT_LIMIT_READ,
1259+
limit: int | None = DEFAULT_LIMIT_READ,
12501260
) -> AggregatedNumberedValue | list[AggregatedNumberedValue] | InstanceAggregationResultList:
12511261
"""`Aggregate data across nodes/edges <https://developer.cognite.com/api/v1/#tag/Instances/operation/aggregateInstances>`_
12521262
@@ -1260,7 +1270,8 @@ def aggregate(
12601270
target_units (list[TargetUnit] | None): Properties to convert to another unit. The API can only convert to another unit if a unit has been defined as part of the type on the underlying container being queried.
12611271
space (str | SequenceNotStr[str] | None): Restrict instance aggregate query to the given space (or list of spaces).
12621272
filter (Filter | dict[str, Any] | None): Advanced filtering of instances.
1263-
limit (int): Maximum number of instances to return. Defaults to 25.
1273+
limit (int | None): Maximum number of instances to return. Defaults to 25. Will return the maximum number
1274+
of results (1000) if set to None, -1, or math.inf.
12641275
12651276
Returns:
12661277
AggregatedNumberedValue | list[AggregatedNumberedValue] | InstanceAggregationResultList: Node or edge aggregation results.
@@ -1282,7 +1293,11 @@ def aggregate(
12821293

12831294
self._validate_filter(filter)
12841295
filter = self._merge_space_into_filter(instance_type, space, filter)
1285-
body: dict[str, Any] = {"view": view.dump(camel_case=True), "instanceType": instance_type, "limit": limit}
1296+
body: dict[str, Any] = {
1297+
"view": view.dump(camel_case=True),
1298+
"instanceType": instance_type,
1299+
"limit": self._AGGREGATE_LIMIT if is_unlimited(limit) else limit,
1300+
}
12861301
is_single = isinstance(aggregates, (dict, MetricAggregation))
12871302
aggregate_seq: Sequence[MetricAggregation | dict] = (
12881303
[aggregates] if isinstance(aggregates, (dict, MetricAggregation)) else aggregates

cognite/client/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from __future__ import annotations
22

3-
__version__ = "7.73.4"
3+
__version__ = "7.73.5"
44

55
__api_subversion__ = "20230101"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[tool.poetry]
22
name = "cognite-sdk"
33

4-
version = "7.73.4"
4+
version = "7.73.5"
55

66
description = "Cognite Python SDK"
77
readme = "README.md"

tests/tests_unit/test_api/test_data_modeling/test_instances.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
from __future__ import annotations
22

3+
import json
4+
import math
5+
import re
6+
37
import pytest
8+
from responses import RequestsMock
49

10+
from cognite.client import CogniteClient
11+
from cognite.client.data_classes.aggregations import Count
512
from cognite.client.data_classes.data_modeling.ids import ViewId
613
from cognite.client.data_classes.data_modeling.query import SourceSelector
714
from tests.tests_unit.test_api.test_data_modeling.conftest import make_test_view
@@ -36,3 +43,64 @@ def test_instances_api_dump_instance_source(self, sources, expected):
3643
# ViewIdentifier = Union[ViewId, Tuple[str, str], Tuple[str, str, str]]
3744
# ViewIdentifier | Sequence[ViewIdentifier] | View | Sequence[View]
3845
assert expected == [source.dump() for source in SourceSelector._load_list(sources)]
46+
47+
48+
class TestAggregate:
49+
@pytest.mark.usefixtures("disable_gzip")
50+
@pytest.mark.parametrize("limit", [None, -1, math.inf])
51+
def test_aggregate_maximum(
52+
self, limit: int | float | None, rsps: RequestsMock, cognite_client: CogniteClient
53+
) -> None:
54+
url = re.compile(r".*/models/instances/aggregate$")
55+
response = {
56+
"items": [
57+
{
58+
"instanceType": "node",
59+
"group": {"site": "MyLocation"},
60+
"aggregates": [
61+
{
62+
"aggregate": "count",
63+
"property": "site",
64+
"value": 42,
65+
}
66+
],
67+
},
68+
]
69+
}
70+
rsps.add(rsps.POST, url, status=200, json=response)
71+
72+
_ = cognite_client.data_modeling.instances.aggregate(
73+
ViewId("my_space", "MyView", "v1"), Count("externalId"), group_by="site", limit=limit
74+
)
75+
assert len(rsps.calls) == 1
76+
call = rsps.calls[0]
77+
body = json.loads(call.request.body)
78+
assert "limit" in body
79+
assert body["limit"] == cognite_client.data_modeling.instances._AGGREGATE_LIMIT
80+
81+
82+
class TestSearch:
83+
@pytest.mark.usefixtures("disable_gzip")
84+
@pytest.mark.parametrize("limit", [None, -1, math.inf])
85+
def test_search_maximum(self, limit: int | float | None, rsps: RequestsMock, cognite_client: CogniteClient) -> None:
86+
url = re.compile(r".*/models/instances/search$")
87+
response = {
88+
"items": [
89+
{
90+
"instanceType": "node",
91+
"version": 1,
92+
"space": "my_instance_space",
93+
"externalId": "my_instance_id",
94+
"createdTime": 0,
95+
"lastUpdatedTime": 0,
96+
},
97+
]
98+
}
99+
rsps.add(rsps.POST, url, status=200, json=response)
100+
101+
_ = cognite_client.data_modeling.instances.search(ViewId("my_space", "MyView", "v1"), "dummy text", limit=limit)
102+
assert len(rsps.calls) == 1
103+
call = rsps.calls[0]
104+
body = json.loads(call.request.body)
105+
assert "limit" in body
106+
assert body["limit"] == cognite_client.data_modeling.instances._SEARCH_LIMIT

0 commit comments

Comments
 (0)