From 4cff0416bf1e86dc78ce30374f63f6da5bfab460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20V=2E=20Treider?= Date: Thu, 12 Oct 2023 17:26:30 +0200 Subject: [PATCH] Fix filter dumps (#1422) --- CHANGELOG.md | 8 ++++-- .../client/_api/datapoints_subscriptions.py | 4 +-- cognite/client/_version.py | 2 +- cognite/client/data_classes/_base.py | 5 ++++ cognite/client/data_classes/assets.py | 5 ++-- .../data_classes/datapoints_subscriptions.py | 3 ++- cognite/client/data_classes/documents.py | 3 ++- cognite/client/data_classes/events.py | 5 ++-- cognite/client/data_classes/filters.py | 25 ++++++++----------- cognite/client/data_classes/sequences.py | 5 ++-- cognite/client/data_classes/time_series.py | 5 ++-- pyproject.toml | 2 +- .../test_data_models/test_filters.py | 15 +++++++++-- tests/utils.py | 2 +- 14 files changed, 55 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3851cce637..7a87db0350 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,11 @@ Changes are grouped as follows - `Fixed` for any bug fixes. - `Security` in case of vulnerabilities. -## [6.32.3] - 2023-10-06 +## [6.32.4] - 2023-10-12 +### Fixed +- Filters using e.g. metadata keys no longer dumps the key in camel case. + +## [6.32.3] - 2023-10-12 ### Added - Ability to toggle the SDK debug logging on/off by setting `config.debug` property on a CogniteClient to True (enable) or False (disable). @@ -30,7 +34,7 @@ Changes are grouped as follows ### Added - Missing `unit_external_id` and `unit_quantity` fields on `TimeSeriesProperty`. -## [6.32.0] - 2023-10-10 +## [6.32.0] - 2023-10-09 ### Fixed - Ref to openapi doc in Vision extract docstring - Parameters to Vision models can be given as Python dict (updated doc accordingly). diff --git a/cognite/client/_api/datapoints_subscriptions.py b/cognite/client/_api/datapoints_subscriptions.py index 22969c7d01..af67beff10 100644 --- a/cognite/client/_api/datapoints_subscriptions.py +++ b/cognite/client/_api/datapoints_subscriptions.py @@ -3,9 +3,7 @@ from typing import TYPE_CHECKING, Iterator, Sequence from cognite.client._api_client import APIClient -from cognite.client._constants import ( - DEFAULT_LIMIT_READ, -) +from cognite.client._constants import DEFAULT_LIMIT_READ from cognite.client.data_classes.datapoints_subscriptions import ( DatapointSubscription, DatapointSubscriptionBatch, diff --git a/cognite/client/_version.py b/cognite/client/_version.py index 1a20e11a80..f83bb88502 100644 --- a/cognite/client/_version.py +++ b/cognite/client/_version.py @@ -1,4 +1,4 @@ from __future__ import annotations -__version__ = "6.32.3" +__version__ = "6.32.4" __api_subversion__ = "V20220125" diff --git a/cognite/client/data_classes/_base.py b/cognite/client/data_classes/_base.py index 0b51b05500..459def24d2 100644 --- a/cognite/client/data_classes/_base.py +++ b/cognite/client/data_classes/_base.py @@ -512,6 +512,11 @@ def dump(self, camel_case: bool = False) -> dict[str, Any]: T_CogniteFilter = TypeVar("T_CogniteFilter", bound=CogniteFilter) +class NoCaseConversionPropertyList(list): + def as_reference(self) -> list[str]: + return list(self) + + class EnumProperty(Enum): @staticmethod def _generate_next_value_(name: str, *_: Any) -> str: diff --git a/cognite/client/data_classes/assets.py b/cognite/client/data_classes/assets.py index 82773fbc64..95cb22c2f4 100644 --- a/cognite/client/data_classes/assets.py +++ b/cognite/client/data_classes/assets.py @@ -39,6 +39,7 @@ CogniteUpdate, EnumProperty, IdTransformerMixin, + NoCaseConversionPropertyList, PropertySpec, ) from cognite.client.data_classes.labels import Label, LabelDefinition, LabelFilter @@ -889,7 +890,7 @@ class AssetProperty(EnumProperty): @staticmethod def metadata_key(key: str) -> list[str]: - return ["metadata", key] + return NoCaseConversionPropertyList(["metadata", key]) AssetPropertyLike: TypeAlias = Union[AssetProperty, str, List[str]] @@ -907,7 +908,7 @@ class SortableAssetProperty(EnumProperty): @staticmethod def metadata_key(key: str) -> list[str]: - return ["metadata", key] + return NoCaseConversionPropertyList(["metadata", key]) SortableAssetPropertyLike: TypeAlias = Union[SortableAssetProperty, str, List[str]] diff --git a/cognite/client/data_classes/datapoints_subscriptions.py b/cognite/client/data_classes/datapoints_subscriptions.py index c51fa19531..015073f2b8 100644 --- a/cognite/client/data_classes/datapoints_subscriptions.py +++ b/cognite/client/data_classes/datapoints_subscriptions.py @@ -13,6 +13,7 @@ CogniteResourceList, CogniteUpdate, EnumProperty, + NoCaseConversionPropertyList, PropertySpec, T_CogniteResource, ) @@ -361,7 +362,7 @@ class DatapointSubscriptionList(CogniteResourceList[DatapointSubscription]): def _metadata(key: str) -> list[str]: - return ["metadata", key] + return NoCaseConversionPropertyList(["metadata", key]) class DatapointSubscriptionFilterProperties(EnumProperty): diff --git a/cognite/client/data_classes/documents.py b/cognite/client/data_classes/documents.py index 82d5ebfc66..064f6de411 100644 --- a/cognite/client/data_classes/documents.py +++ b/cognite/client/data_classes/documents.py @@ -12,6 +12,7 @@ CogniteSort, EnumProperty, IdTransformerMixin, + NoCaseConversionPropertyList, ) from cognite.client.data_classes.aggregations import UniqueResult from cognite.client.data_classes.labels import Label, LabelDefinition @@ -283,7 +284,7 @@ class SourceFileProperty(EnumProperty): @staticmethod def metadata_key(key: str) -> list[str]: - return ["sourceFile", "metadata", key] + return NoCaseConversionPropertyList(["sourceFile", "metadata", key]) def as_reference(self) -> list[str]: return ["sourceFile", self.value] diff --git a/cognite/client/data_classes/events.py b/cognite/client/data_classes/events.py index 22cc51e310..eb50b9dffa 100644 --- a/cognite/client/data_classes/events.py +++ b/cognite/client/data_classes/events.py @@ -17,6 +17,7 @@ CogniteUpdate, EnumProperty, IdTransformerMixin, + NoCaseConversionPropertyList, PropertySpec, ) from cognite.client.data_classes.shared import TimestampRange @@ -271,7 +272,7 @@ class EventProperty(EnumProperty): @staticmethod def metadata_key(key: str) -> list[str]: - return ["metadata", key] + return NoCaseConversionPropertyList(["metadata", key]) EventPropertyLike: TypeAlias = Union[EventProperty, str, List[str]] @@ -292,7 +293,7 @@ class SortableEventProperty(EnumProperty): @staticmethod def metadata_key(key: str) -> list[str]: - return ["metadata", key] + return NoCaseConversionPropertyList(["metadata", key]) SortableEventPropertyLike: TypeAlias = Union[SortableEventProperty, str, List[str]] diff --git a/cognite/client/data_classes/filters.py b/cognite/client/data_classes/filters.py index 92b045c255..47ecf1af95 100644 --- a/cognite/client/data_classes/filters.py +++ b/cognite/client/data_classes/filters.py @@ -6,7 +6,7 @@ from typing_extensions import TypeAlias -from cognite.client.data_classes._base import EnumProperty, Geometry +from cognite.client.data_classes._base import EnumProperty, Geometry, NoCaseConversionPropertyList from cognite.client.data_classes.labels import Label from cognite.client.utils._text import to_camel_case @@ -35,35 +35,32 @@ class ParameterValue: def _dump_filter_value(filter_value: FilterValueList | FilterValue) -> Any: if isinstance(filter_value, PropertyReferenceValue): - return { - "property": filter_value.property.as_reference() - if isinstance(filter_value.property, EnumProperty) - else filter_value.property - } + if isinstance(filter_value.property, EnumProperty): + return {"property": filter_value.property.as_reference()} + return {"property": filter_value.property} + if isinstance(filter_value, ParameterValue): return {"parameter": filter_value.parameter} - else: - return filter_value + return filter_value def _load_filter_value(value: Any) -> FilterValue | FilterValueList: if isinstance(value, Mapping) and len(value.keys()) == 1: - (value_key,) = value + ((value_key, to_load),) = value.items() if value_key == "property": - return PropertyReferenceValue(value[value_key]) + return PropertyReferenceValue(to_load) if value_key == "parameter": - return ParameterValue(value[value_key]) + return ParameterValue(to_load) return value def _dump_property(property_: PropertyReference, camel_case: bool) -> list[str] | tuple[str, ...]: - if isinstance(property_, EnumProperty): + if isinstance(property_, (EnumProperty, NoCaseConversionPropertyList)): return property_.as_reference() elif isinstance(property_, str): return [to_camel_case(property_) if camel_case else property_] elif isinstance(property_, (list, tuple)): - output = [to_camel_case(p) if camel_case else p for p in property_] - return tuple(output) if isinstance(property_, tuple) else output + return type(property_)(map(to_camel_case, property_)) if camel_case else property_ else: raise ValueError(f"Invalid property format {property_}") diff --git a/cognite/client/data_classes/sequences.py b/cognite/client/data_classes/sequences.py index 27fad57c12..3d6f99fb7f 100644 --- a/cognite/client/data_classes/sequences.py +++ b/cognite/client/data_classes/sequences.py @@ -20,6 +20,7 @@ CogniteUpdate, EnumProperty, IdTransformerMixin, + NoCaseConversionPropertyList, PropertySpec, ) from cognite.client.data_classes.shared import TimestampRange @@ -493,7 +494,7 @@ class SequenceProperty(EnumProperty): @staticmethod def metadata_key(key: str) -> list[str]: - return ["metadata", key] + return NoCaseConversionPropertyList(["metadata", key]) class SortableSequenceProperty(EnumProperty): @@ -507,7 +508,7 @@ class SortableSequenceProperty(EnumProperty): @staticmethod def metadata_key(key: str) -> list[str]: - return ["metadata", key] + return NoCaseConversionPropertyList(["metadata", key]) SortableSequencePropertyLike: TypeAlias = Union[SortableSequenceProperty, str, List[str]] diff --git a/cognite/client/data_classes/time_series.py b/cognite/client/data_classes/time_series.py index 774600bff6..45804019e1 100644 --- a/cognite/client/data_classes/time_series.py +++ b/cognite/client/data_classes/time_series.py @@ -18,6 +18,7 @@ CogniteUpdate, EnumProperty, IdTransformerMixin, + NoCaseConversionPropertyList, PropertySpec, ) from cognite.client.data_classes.shared import TimestampRange @@ -340,7 +341,7 @@ class TimeSeriesProperty(EnumProperty): @staticmethod def metadata_key(key: str) -> list[str]: - return ["metadata", key] + return NoCaseConversionPropertyList(["metadata", key]) class SortableTimeSeriesProperty(EnumProperty): @@ -354,7 +355,7 @@ class SortableTimeSeriesProperty(EnumProperty): @staticmethod def metadata_key(key: str) -> list[str]: - return ["metadata", key] + return NoCaseConversionPropertyList(["metadata", key]) SortableTimeSeriesPropertyLike: TypeAlias = Union[SortableTimeSeriesProperty, str, List[str]] diff --git a/pyproject.toml b/pyproject.toml index 926aa56981..799946b447 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "cognite-sdk" -version = "6.32.3" +version = "6.32.4" description = "Cognite Python SDK" readme = "README.md" documentation = "https://cognite-sdk-python.readthedocs-hosted.com" diff --git a/tests/tests_unit/test_data_classes/test_data_models/test_filters.py b/tests/tests_unit/test_data_classes/test_data_models/test_filters.py index 4ba23bbc4f..a0df8601aa 100644 --- a/tests/tests_unit/test_data_classes/test_data_models/test_filters.py +++ b/tests/tests_unit/test_data_classes/test_data_models/test_filters.py @@ -4,7 +4,9 @@ from _pytest.mark import ParameterSet import cognite.client.data_classes.filters as f +from cognite.client.data_classes._base import EnumProperty from cognite.client.data_classes.filters import Filter +from tests.utils import all_subclasses def load_and_dump_equals_data() -> Iterator[ParameterSet]: @@ -97,9 +99,7 @@ def load_and_dump_equals_data() -> Iterator[ParameterSet]: @pytest.mark.parametrize("raw_data", list(load_and_dump_equals_data())) def test_load_and_dump_equals(raw_data: dict) -> None: parsed = Filter.load(raw_data) - dumped = parsed.dump() - assert dumped == raw_data @@ -163,3 +163,14 @@ def test_dump_filter(user_filter: Filter, expected: dict) -> None: def test_unknown_filter_type() -> None: with pytest.raises(ValueError, match="Unknown filter type: unknown"): Filter.load({"unknown": {}}) + + +@pytest.mark.parametrize("property_cls", filter(lambda cls: hasattr(cls, "metadata_key"), all_subclasses(EnumProperty))) +def test_user_given_metadata_keys_are_not_camel_cased(property_cls: type) -> None: + # Bug prior to 6.32.4 would dump user given keys in camelCase + flt = f.Equals(property_cls.metadata_key("key_foo_Bar_baz"), "value_foo Bar_baz") # type: ignore [attr-defined] + dumped = flt.dump(camel_case=True)["equals"] + + # property may contain more (static) values, so we just verify the end: + assert dumped["property"][-2:] == ["metadata", "key_foo_Bar_baz"] + assert dumped["value"] == "value_foo Bar_baz" diff --git a/tests/utils.py b/tests/utils.py index c505e6608f..d7a384939c 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -23,7 +23,7 @@ def all_subclasses(base: type) -> list[type]: """ return sorted( filter( - lambda sub: str(sub).startswith("