Skip to content

Commit dafe799

Browse files
authored
[PYG-430] 😃Enable operator for search endpoint (#530)
# Description **Reviewer**: All code inside `examples/` is generated. This is just enabling the `operator` for the `/instances/search/` enpoint <img width="1181" height="386" alt="image" src="https://github.com/user-attachments/assets/133bbf3f-4a0b-451f-a894-1dff97b3b9e9" /> ## Bump - [x] Patch - [ ] Minor - [ ] Skip ## Changelog ### Fixed - [alpha] The `.search` method in the QueryExecutor now support the `operator` argument.
1 parent 584cd9d commit dafe799

File tree

9 files changed

+48
-12
lines changed

9 files changed

+48
-12
lines changed

‎cognite/pygen/_query/interface.py‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def search(
6161
filter: filters.Filter | None = None,
6262
search_properties: str | SequenceNotStr[str] | None = None,
6363
sort: Sequence[dm.InstanceSort] | dm.InstanceSort | None = None,
64+
operator: Literal["AND", "OR"] | None = None,
6465
limit: int | None = None,
6566
instance_types: list[Literal["node", "edge"]] | None = None,
6667
) -> list[dict[str, Any]]:
@@ -77,6 +78,11 @@ def search(
7778
filter: The filter to apply ahead of the search.
7879
search_properties: The properties to search. If None, all text properties are searched.
7980
sort: The sort order of the results.
81+
operator: Controls how multiple search terms are combined when matching documents.
82+
OR (default): A document matches if it contains any of the query terms in the searchable fields.
83+
This typically returns more results but with lower precision.
84+
AND: A document matches only if it contains all of the query terms across the searchable fields.
85+
This typically returns fewer results but with higher relevance.
8086
limit: The maximum number of results to return. Max 1000.
8187
instance_types: The instance types to search. If None, defaults to the view's supported types.
8288
@@ -89,7 +95,9 @@ def search(
8995
9096
"""
9197
filter = self._equals_none_to_not_exists(filter)
92-
return self._execute_search(view, properties, query, filter, search_properties, sort, limit, instance_types)
98+
return self._execute_search(
99+
view, properties, query, filter, search_properties, sort, limit, operator, instance_types
100+
)
93101

94102
def _execute_search(
95103
self,
@@ -100,8 +108,12 @@ def _execute_search(
100108
search_properties: str | SequenceNotStr[str] | None = None,
101109
sort: Sequence[dm.InstanceSort] | dm.InstanceSort | None = None,
102110
limit: int | None = None,
111+
operator: Literal["AND", "OR"] | None = None,
103112
instance_types: list[Literal["node", "edge"]] | None = None,
104113
) -> list[dict[str, Any]]:
114+
# The SDK requires the operator to be set to AND or OR, however we want to
115+
# just use the default if not specified as this is expected to change in the future.
116+
operator_arg: dict[str, Any] = {"operator": operator} if operator is not None else {}
105117
view = self._get_view(view_id)
106118
flatten_props, are_flat_properties = self._as_property_list(properties, "list") if properties else (None, False)
107119
if properties is None or are_flat_properties:
@@ -119,21 +131,23 @@ def _execute_search(
119131
filter=filter,
120132
limit=limit or SEARCH_LIMIT,
121133
sort=sort,
134+
**operator_arg,
122135
)
123136
all_results.extend(
124137
self._prepare_list_result(search_instance_result, set(flatten_props) if flatten_props else None)
125138
)
126139
return all_results
127140
elif view.used_for == "edge":
128141
raise ValueError("Nested properties are not supported for edges")
129-
search_result = self._client.data_modeling.instances.search( # type: ignore[call-overload]
142+
search_result = self._client.data_modeling.instances.search( # type: ignore[call-overload, misc]
130143
view_id,
131144
query,
132145
instance_type="node",
133146
properties=search_properties, # type: ignore[arg-type]
134147
filter=filter,
135148
limit=limit or SEARCH_LIMIT,
136149
sort=sort,
150+
**operator_arg,
137151
)
138152
# Lookup nested properties:
139153
order_by_node_ids = {node.as_id(): no for no, node in enumerate(search_result)}

‎examples/cognite_core/_api_client.py‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎examples/omni/_api_client.py‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎examples/omni_multi/_api_client.py‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎examples/omni_sub/_api_client.py‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎examples/wind_turbine/_api_client.py‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ readme="README.md"
77
requires-python = ">=3.10,<4.0"
88
license = "Apache-2.0"
99
dependencies = [
10-
"cognite-sdk>=7.75.0, <8.0.0",
10+
"cognite-sdk>=7.86.0, <8.0.0",
1111
# pydantic 2.7.0 is the latest versoion supported by pyodide '0.26.2' which is what is used in the
1212
# Cognite JupyterLab environment.
1313
# Thus, we pin it to ensure compatibility with the pyodide build.

‎tests/test_integration/test_query_execution.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,28 @@ def test_search_nested_properties(cognite_client: CogniteClient, omni_views: dic
274274
assert not ill_formed_subitems, f"Subitems with unexpected properties: {ill_formed_subitems}"
275275

276276

277+
def test_search_AND(cognite_client: CogniteClient, omni_client: OmniClient, omni_views: dict[str, dm.View]) -> None:
278+
items = omni_client.primitive_required.list(limit=1)
279+
assert len(items) > 0
280+
item = items[0]
281+
words = item.text.split(" ")
282+
# The text property should be at least 3 words. (It is generated lorem ipsum text.)
283+
assert len(words) > 2
284+
285+
view = omni_views["PrimitiveRequired"]
286+
executor = QueryExecutor(cognite_client, views=[view], unpack_edges="include")
287+
selected_properties: list[str | dict[str, Any]] = ["text", "boolean", "externalId"]
288+
result = executor.search(view.as_id(), selected_properties, query=" ".join(words), operator="AND", limit=5)
289+
290+
assert isinstance(result, list)
291+
assert len(result) > 0
292+
properties_set = set(selected_properties)
293+
ill_formed_items = [item for item in result if not (set(item.keys()) <= properties_set)]
294+
assert not ill_formed_items, f"Items with unexpected properties: {ill_formed_items}"
295+
# As we are searching with AND, we expect only one result
296+
assert len(result) == 1
297+
298+
277299
def test_query_list_root_nodes(cognite_client: CogniteClient) -> None:
278300
executor = QueryExecutor(cognite_client, unpack_edges="include")
279301
view_id = dm.ViewId("cdf_cdm", "CogniteAsset", "v1")

‎uv.lock‎

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)