Skip to content

Commit

Permalink
improve space filter ergonomics to more easily list global nodes (#1913)
Browse files Browse the repository at this point in the history
  • Loading branch information
haakonvt authored Sep 5, 2024
1 parent 32f224d commit 8a8ed12
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 17 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ Changes are grouped as follows
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.

## [7.58.6] - 2024-09-05
### Fixed
- Data modeling convenience filter `SpaceFilter` now allows listing of global nodes by using `equals`
(when a single space is requested (requirement)). This also affects the `space` parameter to e.g.
`client.data_modeling.instances.list(...)`

## [7.58.5] - 2024-09-04
### Added
- Data modeling filters now support properties that are lists.
Expand Down
2 changes: 1 addition & 1 deletion cognite/client/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from __future__ import annotations

__version__ = "7.58.5"
__version__ = "7.58.6"
__api_subversion__ = "20230101"
21 changes: 15 additions & 6 deletions cognite/client/data_classes/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,7 @@ class Search(FilterWithPropertyAndValue):
# ######################################################### #


class SpaceFilter(FilterWithPropertyAndValueList):
class SpaceFilter(FilterWithProperty):
"""Filters instances based on the space.
Args:
Expand All @@ -827,15 +827,24 @@ class SpaceFilter(FilterWithPropertyAndValueList):
>>> flt = SpaceFilter("space3", instance_type="edge")
"""

_filter_name = In._filter_name

def __init__(self, space: str | SequenceNotStr[str], instance_type: Literal["node", "edge"] = "node") -> None:
space_list = [space] if isinstance(space, str) else list(space)
super().__init__(property=[instance_type, "space"], values=space_list)
super().__init__(property=[instance_type, "space"])
space = [space] if isinstance(space, str) else list(space)
single = len(space) == 1
self._value = space[0] if single else space
self._value_key = "value" if single else "values"
self._filter_name = Equals._filter_name if single else In._filter_name
self._involved_filter: set[type[Filter]] = {Equals if single else In}

@classmethod
def load(cls, filter_: dict[str, Any]) -> NoReturn:
raise NotImplementedError("Custom filter 'SpaceFilter' can not be loaded")

def _filter_body(self, camel_case_property: bool) -> dict[str, Any]:
return {
"property": self._dump_property(camel_case_property),
self._value_key: _dump_filter_value(self._value),
}

def _involved_filter_types(self) -> set[type[Filter]]:
return {In}
return self._involved_filter
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[tool.poetry]
name = "cognite-sdk"

version = "7.58.5"
version = "7.58.6"
description = "Cognite Python SDK"
readme = "README.md"
documentation = "https://cognite-sdk-python.readthedocs-hosted.com"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,18 @@ def test_search_person(self, cognite_client: CogniteClient) -> None:
assert len(persons) > 0
assert all(isinstance(person, PersonRead) for person in persons)

def test_listing_global_nodes(self, cognite_client: CogniteClient) -> None:
from cognite.client.data_classes.data_modeling.cdm.v1 import CogniteUnit

# Space must be explicitly specified or nothing will be returned:
no_nodes = cognite_client.data_modeling.instances.list(sources=CogniteUnit.get_source())
assert len(no_nodes) == 0

nodes = cognite_client.data_modeling.instances.list(
space="cdf_cdm_units", sources=CogniteUnit.get_source(), limit=5
)
assert len(nodes) == 5


class TestInstancesSync:
def test_sync_movies_released_in_1994(self, cognite_client: CogniteClient, movie_view: View) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,25 +255,33 @@ def test_user_given_metadata_keys_are_not_camel_cased(property_cls: type) -> Non

class TestSpaceFilter:
@pytest.mark.parametrize(
"inst_type, space, expected_spaces",
"inst_type, space, expected",
(
("node", "myspace", ["myspace"]),
("edge", ["myspace"], ["myspace"]),
("node", ["myspace", "another"], ["myspace", "another"]),
("node", "myspace", {"equals": {"property": ["node", "space"], "value": "myspace"}}),
(None, ["myspace"], {"equals": {"property": ["node", "space"], "value": "myspace"}}),
("edge", ["myspace"], {"equals": {"property": ["edge", "space"], "value": "myspace"}}),
("node", ["myspace", "another"], {"in": {"property": ["node", "space"], "values": ["myspace", "another"]}}),
("node", ("myspace", "another"), {"in": {"property": ["node", "space"], "values": ["myspace", "another"]}}),
),
)
def test_space_filter(
self, inst_type: Literal["node", "edge"], space: str | list[str], expected_spaces: list[str]
self, inst_type: Literal["node", "edge"], space: str | list[str], expected: dict[str, Any]
) -> None:
space_filter = f.SpaceFilter(space, inst_type)
expected = {"in": {"property": [inst_type, "space"], "values": expected_spaces}}
space_filter = f.SpaceFilter(space, inst_type) if inst_type else f.SpaceFilter(space)
assert expected == space_filter.dump()

def test_space_filter_passes_isinstance_checks(self) -> None:
space_filter = f.SpaceFilter("myspace", "edge")
assert isinstance(space_filter, Filter)

def test_space_filter_passes_verification(self, cognite_client: CogniteClient) -> None:
space_filter = f.SpaceFilter("myspace", "edge")
@pytest.mark.parametrize(
"space_filter",
[
f.SpaceFilter("s1", "edge"),
f.SpaceFilter(["s1"], "edge"),
f.SpaceFilter(["s1", "s2"], "edge"),
],
)
def test_space_filter_passes_verification(self, cognite_client: CogniteClient, space_filter: f.SpaceFilter) -> None:
cognite_client.data_modeling.instances._validate_filter(space_filter)
assert True

0 comments on commit 8a8ed12

Please sign in to comment.