From ff5116e97230b95ef71eb6efb782e0f83dfccde2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20V=2E=20Treider?= Date: Fri, 22 Dec 2023 11:05:25 +0100 Subject: [PATCH] Make `AnnotationsAPI.list` support paging also for `limit=None/inf` (#1551) --- CHANGELOG.md | 6 +++++ cognite/client/_api/annotations.py | 25 +++++++++++-------- cognite/client/_api/diagrams.py | 23 +++++++++-------- cognite/client/_version.py | 2 +- .../client/data_classes/contextualization.py | 2 +- pyproject.toml | 2 +- .../test_api/test_annotations.py | 13 ++++------ 7 files changed, 41 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 426153f35d..0073c97f9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,12 @@ Changes are grouped as follows - `Fixed` for any bug fixes. - `Security` in case of vulnerabilities. +## [7.8.4] - 2023-12-22 +### Fixed +- Listing annotations now also accepts `None` and `inf` for the `limit` parameter (to return all), matching what + was already described in the documentation for the endpoint (for the parameter). +- Calling `to_pandas(...)` on an `DiagramDetectItem` no longer raises `KeyError`. + ## [7.8.3] - 2023-12-21 ### Fixed - Revert `SingleHopConnectionDefinition` from a string to child class of `ViewProperty`. diff --git a/cognite/client/_api/annotations.py b/cognite/client/_api/annotations.py index 6158662130..3df9a75b2a 100644 --- a/cognite/client/_api/annotations.py +++ b/cognite/client/_api/annotations.py @@ -10,7 +10,7 @@ from cognite.client.data_classes.annotations import AnnotationReverseLookupFilter from cognite.client.data_classes.contextualization import ResourceReference, ResourceReferenceList from cognite.client.utils._identifier import IdentifierSequence -from cognite.client.utils._text import to_camel_case +from cognite.client.utils._text import convert_all_keys_to_camel_case from cognite.client.utils._validation import assert_type @@ -29,10 +29,10 @@ def create(self, annotations: Annotation | Sequence[Annotation]) -> Annotation | """`Create annotations `_ Args: - annotations (Annotation | Sequence[Annotation]): annotation(s) to create + annotations (Annotation | Sequence[Annotation]): Annotation(s) to create Returns: - Annotation | AnnotationList: created annotation(s) + Annotation | AnnotationList: Created annotation(s) """ assert_type(annotations, "annotations", [Annotation, Sequence]) return self._create_multiple( @@ -182,19 +182,24 @@ def list(self, filter: AnnotationFilter | dict, limit: int | None = DEFAULT_LIMI Returns: AnnotationList: list of annotations + + Example: + + List all annotations for the file with id=123: + + >>> from cognite.client import CogniteClient + >>> from cognite.client.data_classes import AnnotationFilter + >>> client = CogniteClient() + >>> flt = AnnotationFilter(annotated_resource_type="file", annotated_resource_ids=[{"id": 123}]) + >>> res = client.annotations.list(flt, limit=None) """ - assert_type(limit, "limit", [int], allow_none=False) assert_type(filter, "filter", [AnnotationFilter, dict], allow_none=False) if isinstance(filter, AnnotationFilter): filter = filter.dump(camel_case=True) - elif isinstance(filter, dict): - filter = {to_camel_case(k): v for k, v in filter.items()} - + filter = convert_all_keys_to_camel_case(filter) if "annotatedResourceIds" in filter: - filter["annotatedResourceIds"] = [ - {to_camel_case(k): v for k, v in f.items()} for f in filter["annotatedResourceIds"] - ] + filter["annotatedResourceIds"] = list(map(convert_all_keys_to_camel_case, filter["annotatedResourceIds"])) return self._list(list_cls=AnnotationList, resource_cls=Annotation, method="POST", limit=limit, filter=filter) diff --git a/cognite/client/_api/diagrams.py b/cognite/client/_api/diagrams.py index c3eff49f86..77e53d0ecc 100644 --- a/cognite/client/_api/diagrams.py +++ b/cognite/client/_api/diagrams.py @@ -187,17 +187,18 @@ def detect( >>> from cognite.client.data_classes.contextualization import FileReference >>> client = CogniteClient() >>> detect_job = client.diagrams.detect( - entities=[{"userDefinedField": "21PT1017","ignoredField": "AA11"}, {"userDefinedField": "21PT1018"}], - search_field="userDefinedField", - partial_match=True, - min_tokens=2, - file_ids=[101], - file_external_ids=["Test1"], - file_references=[ - FileReference(id=20, first_page=1, last_page=10), - FileReference(external_id="ext_20", first_page=11, last_page=20) - ], - ) + ... entities=[ + ... {"userDefinedField": "21PT1017","ignoredField": "AA11"}, + ... {"userDefinedField": "21PT1018"}], + ... search_field="userDefinedField", + ... partial_match=True, + ... min_tokens=2, + ... file_ids=[101], + ... file_external_ids=["Test1"], + ... file_references=[ + ... FileReference(id=20, first_page=1, last_page=10), + ... FileReference(external_id="ext_20", first_page=11, last_page=20) + ... ]) >>> result = detect_job.result >>> print(result) diff --git a/cognite/client/_version.py b/cognite/client/_version.py index c9acbf6f7d..f4ba8beaa3 100644 --- a/cognite/client/_version.py +++ b/cognite/client/_version.py @@ -1,4 +1,4 @@ from __future__ import annotations -__version__ = "7.8.3" +__version__ = "7.8.4" __api_subversion__ = "V20220125" diff --git a/cognite/client/data_classes/contextualization.py b/cognite/client/data_classes/contextualization.py index cad3a95ea6..f323734ee4 100644 --- a/cognite/client/data_classes/contextualization.py +++ b/cognite/client/data_classes/contextualization.py @@ -489,7 +489,7 @@ def to_pandas(self, camel_case: bool = False) -> pandas.DataFrame: # type: igno pandas.DataFrame: The dataframe. """ df = super().to_pandas(camel_case=camel_case) - df.loc["annotations"] = f"{len(df['annotations'])} annotations" + df.loc["annotations"] = f"{len(self.annotations or [])} annotations" return df diff --git a/pyproject.toml b/pyproject.toml index f930eef0e9..c4f55572f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "cognite-sdk" -version = "7.8.3" +version = "7.8.4" description = "Cognite Python SDK" readme = "README.md" documentation = "https://cognite-sdk-python.readthedocs-hosted.com" diff --git a/tests/tests_integration/test_api/test_annotations.py b/tests/tests_integration/test_api/test_annotations.py index a4bc7757ed..c503f40fdd 100644 --- a/tests/tests_integration/test_api/test_annotations.py +++ b/tests/tests_integration/test_api/test_annotations.py @@ -11,6 +11,7 @@ from cognite.client.data_classes import Annotation, AnnotationFilter, AnnotationList, AnnotationUpdate, FileMetadata from cognite.client.data_classes.annotations import AnnotationReverseLookupFilter from cognite.client.exceptions import CogniteAPIError +from cognite.client.utils._auxiliary import is_unlimited def delete_with_check(cognite_client: CogniteClient, delete_ids: list[int], check_ids: list[int] | None = None) -> None: @@ -179,7 +180,7 @@ def _test_list_on_created_annotations( ) annotations_list = cognite_client.annotations.list(filter=filter, limit=limit) assert isinstance(annotations_list, AnnotationList) - if limit == -1 or limit > len(annotations): + if is_unlimited(limit) or limit > len(annotations): assert len(annotations_list) == len(annotations) else: assert len(annotations_list) == limit @@ -309,6 +310,8 @@ def test_list_limit(self, cognite_client: CogniteClient, base_annotation: Annota _test_list_on_created_annotations(cognite_client, created_annotations) _test_list_on_created_annotations(cognite_client, created_annotations, limit=30) _test_list_on_created_annotations(cognite_client, created_annotations, limit=-1) + _test_list_on_created_annotations(cognite_client, created_annotations, limit=None) + _test_list_on_created_annotations(cognite_client, created_annotations, limit=float("inf")) def test_retrieve(self, cognite_client: CogniteClient, base_annotation: Annotation) -> None: created_annotation = cognite_client.annotations.create(base_annotation) @@ -321,13 +324,7 @@ def test_retrieve_multiple(self, cognite_client: CogniteClient, base_annotation: ids = [c.id for c in created_annotations] retrieved_annotations = cognite_client.annotations.retrieve_multiple(ids) assert isinstance(retrieved_annotations, AnnotationList) - - # TODO assert the order and do without sorting - # as soon as the API is fixed - for ret, new in zip( - sorted(retrieved_annotations, key=lambda a: a.id), sorted(created_annotations, key=lambda a: a.id) - ): - assert ret.dump() == new.dump() + assert retrieved_annotations.dump() == created_annotations.dump() def test_annotations_reverse_lookup( self, asset_link_annotation: Annotation, cognite_client: CogniteClient, permanent_file_id: int