From 4826ed31debf28270f1d632c85f573b044681419 Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:05:05 +0000 Subject: [PATCH 01/20] first draft --- .../data_classes/simulators/simulators.py | 328 ++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 cognite/client/data_classes/simulators/simulators.py diff --git a/cognite/client/data_classes/simulators/simulators.py b/cognite/client/data_classes/simulators/simulators.py new file mode 100644 index 0000000000..fe17c185d2 --- /dev/null +++ b/cognite/client/data_classes/simulators/simulators.py @@ -0,0 +1,328 @@ +from __future__ import annotations + +from abc import ABC +from collections.abc import Sequence +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any + +from typing_extensions import Self + +from cognite.client.data_classes._base import ( + CogniteObject, + CogniteResourceList, + ExternalIDTransformerMixin, + IdTransformerMixin, + WriteableCogniteResource, + WriteableCogniteResourceList, +) +from cognite.client.utils.useful_types import SequenceNotStr + +if TYPE_CHECKING: + from cognite.client import CogniteClient + + +class SimulatorCore(WriteableCogniteResource["SimulatorWrite"], ABC): + """The simulator resource contains the definitions necessary for Cognite Data Fusion (CDF) to interact with a given simulator. + + It serves as a central contract that allows APIs, UIs, and integrations (connectors) to utilize the same definitions + when dealing with a specific simulator. Each simulator is uniquely identified and can be associated with various + file extension types, model types, step fields, and unit quantities. Simulators are essential for managing data + flows between CDF and external simulation tools, ensuring consistency and reliability in data handling. #### + + This is the read/response format of the simulator. + + Args: + external_id (str): External id of the simulator + name (str): Name of the simulator + file_extension_types (str | SequenceNotStr[str]): File extension types supported by the simulator + model_types (SimulatorModelType | Sequence[SimulatorModelType] | None): Model types supported by the simulator + step_fields (SimulatorStep | Sequence[SimulatorStep] | None): Step types supported by the simulator when creating routines + unit_quantities (SimulatorQuantity | Sequence[SimulatorQuantity] | None): Quantities and their units supported by the simulator + + """ + + def __init__( + self, + external_id: str, + name: str, + file_extension_types: str | SequenceNotStr[str], + model_types: SimulatorModelType | Sequence[SimulatorModelType] | None = None, + step_fields: SimulatorStep | Sequence[SimulatorStep] | None = None, + unit_quantities: SimulatorQuantity | Sequence[SimulatorQuantity] | None = None, + ) -> None: + self.external_id = external_id + self.name = name + self.file_extension_types = file_extension_types + self.model_types = model_types + self.step_fields = step_fields + self.unit_quantities = unit_quantities + + @classmethod + def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self: + return cls( + external_id=resource["externalId"], + name=resource["name"], + file_extension_types=resource["fileExtensionTypes"], + model_types=SimulatorModelType._load_list(resource["modelTypes"], cognite_client) + if "modelTypes" in resource + else None, + step_fields=SimulatorStep._load_list(resource["stepFields"], cognite_client) + if "stepFields" in resource + else None, + unit_quantities=SimulatorQuantity._load_list(resource["unitQuantities"], cognite_client) + if "unitQuantities" in resource + else None, + ) + + def dump(self, camel_case: bool = True) -> dict[str, Any]: + output = super().dump(camel_case=camel_case) + if isinstance(self.model_types, SimulatorModelType): + output["modelTypes" if camel_case else "model_types"] = self.model_types.dump(camel_case=camel_case) + if isinstance(self.step_fields, SimulatorStep): + output["stepFields" if camel_case else "step_fields"] = self.step_fields.dump(camel_case=camel_case) + if isinstance(self.unit_quantities, SimulatorQuantity): + output["unitQuantities" if camel_case else "unit_quantities"] = self.unit_quantities.dump( + camel_case=camel_case + ) + + return output + + +class SimulatorWrite(SimulatorCore): + def __init__( + self, + external_id: str, + name: str, + file_extension_types: str | SequenceNotStr[str], + model_types: SimulatorModelType | Sequence[SimulatorModelType] | None = None, + step_fields: SimulatorStep | Sequence[SimulatorStep] | None = None, + unit_quantities: SimulatorQuantity | Sequence[SimulatorQuantity] | None = None, + ) -> None: + super().__init__( + external_id=external_id, + name=name, + file_extension_types=file_extension_types, + model_types=model_types, + step_fields=step_fields, + unit_quantities=unit_quantities, + ) + + def as_write(self) -> SimulatorWrite: + """Returns a writeable version of this resource""" + return self + + +class Simulator(SimulatorCore): + def __init__( + self, + external_id: str, + name: str, + file_extension_types: str | SequenceNotStr[str], + created_time: int | None = None, + last_updated_time: int | None = None, + id: int | None = None, + model_types: SimulatorModelType | Sequence[SimulatorModelType] | None = None, + step_fields: SimulatorStep | Sequence[SimulatorStep] | None = None, + unit_quantities: SimulatorQuantity | Sequence[SimulatorQuantity] | None = None, + ) -> None: + self.external_id = external_id + self.name = name + self.file_extension_types = file_extension_types + self.model_types = model_types + self.step_fields = step_fields + self.unit_quantities = unit_quantities + # id/created_time/last_updated_time are required when using the class to read, + # but don't make sense passing in when creating a new object. So in order to make the typing + # correct here (i.e. int and not Optional[int]), we force the type to be int rather than + # Optional[int]. + self.id: int | None = id + self.created_time: int | None = created_time + self.last_updated_time: int | None = last_updated_time + + @classmethod + def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self: + load = super()._load(resource, cognite_client) + return cls( + external_id=load.external_id, + name=load.name, + file_extension_types=load.file_extension_types, + created_time=resource.get("createdTime"), + last_updated_time=resource.get("lastUpdatedTime"), + id=resource.get("id"), + model_types=load.model_types, + step_fields=load.step_fields, + unit_quantities=load.unit_quantities, + ) + + def as_write(self) -> SimulatorWrite: + """Returns a writeable version of this resource""" + return SimulatorWrite( + external_id=self.external_id, + name=self.name, + file_extension_types=self.file_extension_types, + model_types=self.model_types, + step_fields=self.step_fields, + unit_quantities=self.unit_quantities, + ) + + def __hash__(self) -> int: + return hash(self.external_id) + + +@dataclass +class SimulatorRoutineStep(CogniteObject): + step_type: str + arguments: dict[str, Any] + + @classmethod + def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self: + return cls( + step_type=resource["stepType"], + arguments=resource["arguments"], + ) + + def dump(self, camel_case: bool = True) -> dict[str, Any]: + return super().dump(camel_case=camel_case) + + +@dataclass +class SimulatorUnitEntry(CogniteObject): + label: str + name: str + + @classmethod + def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self: + return cls( + label=resource["label"], + name=resource["name"], + ) + + +@dataclass +class SimulatorStepOption(CogniteObject): + label: str + value: str + + @classmethod + def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self: + return cls( + label=resource["label"], + value=resource["value"], + ) + + +@dataclass +class SimulatorModelType(CogniteObject): + name: str + key: str + + @classmethod + def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> SimulatorModelType: + return cls( + name=resource["name"], + key=resource["key"], + ) + + @classmethod + def _load_list( + cls, resource: dict[str, Any] | list[dict[str, Any]], cognite_client: CogniteClient | None = None + ) -> SimulatorModelType | list[SimulatorModelType]: + if isinstance(resource, list): + return [cls._load(res, cognite_client) for res in resource] + + return cls._load(resource, cognite_client) + + +@dataclass +class SimulatorQuantity(CogniteObject): + name: str + label: str + units: Sequence[SimulatorUnitEntry] + + @classmethod + def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self: + return cls( + name=resource["name"], + label=resource["label"], + units=[SimulatorUnitEntry._load(unit_, cognite_client) for unit_ in resource["units"]], + ) + + @classmethod + def _load_list( + cls, resource: dict[str, Any] | list[dict[str, Any]], cognite_client: CogniteClient | None = None + ) -> SimulatorQuantity | list[SimulatorQuantity]: + if isinstance(resource, list): + return [cls._load(res, cognite_client) for res in resource] + + return cls._load(resource, cognite_client) + + def dump(self, camel_case: bool = True) -> dict[str, Any]: + output = super().dump(camel_case=camel_case) + output["units"] = [unit_.dump(camel_case=camel_case) for unit_ in self.units] + + return output + + +@dataclass +class SimulatorStepField(CogniteObject): + name: str + label: str + info: str + options: Sequence[SimulatorStepOption] | None = None + + @classmethod + def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self: + return cls( + name=resource["name"], + label=resource["label"], + info=resource["info"], + options=[SimulatorStepOption._load(option_, cognite_client) for option_ in resource["options"]] + if "options" in resource + else None, + ) + + def dump(self, camel_case: bool = True) -> dict[str, Any]: + output = super().dump(camel_case=camel_case) + if self.options is not None: + output["options"] = [option_.dump(camel_case=camel_case) for option_ in self.options] + + return output + + +@dataclass +class SimulatorStep(CogniteObject): + step_type: str + fields: Sequence[SimulatorStepField] + + @classmethod + def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self: + return cls( + step_type=resource["stepType"], + fields=[SimulatorStepField._load(field_, cognite_client) for field_ in resource["fields"]], + ) + + @classmethod + def _load_list( + cls, resource: dict[str, Any] | list[dict[str, Any]], cognite_client: CogniteClient | None = None + ) -> SimulatorStep | list[SimulatorStep]: + if isinstance(resource, list): + return [cls._load(res, cognite_client) for res in resource] + + return cls._load(resource, cognite_client) + + def dump(self, camel_case: bool = True) -> dict[str, Any]: + output = super().dump(camel_case=camel_case) + output["fields"] = [field_.dump(camel_case=camel_case) for field_ in self.fields] + + return output + + +class SimulatorWriteList(CogniteResourceList[SimulatorWrite], ExternalIDTransformerMixin): + _RESOURCE = SimulatorWrite + + +class SimulatorList(WriteableCogniteResourceList[SimulatorWrite, Simulator], IdTransformerMixin): + _RESOURCE = Simulator + + def as_write(self) -> SimulatorWriteList: + return SimulatorWriteList([a.as_write() for a in self.data], cognite_client=self._get_cognite_client()) From 5dcc0c840fe6d98a0f9642d2a0ef9e1eb3453643 Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:07:38 +0000 Subject: [PATCH 02/20] add SimulatorsApi --- cognite/client/_api/simulators/__init__.py | 45 ++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 cognite/client/_api/simulators/__init__.py diff --git a/cognite/client/_api/simulators/__init__.py b/cognite/client/_api/simulators/__init__.py new file mode 100644 index 0000000000..aa310331cb --- /dev/null +++ b/cognite/client/_api/simulators/__init__.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from cognite.client._api_client import APIClient +from cognite.client._constants import DEFAULT_LIMIT_READ +from cognite.client.data_classes.simulators.simulators import Simulator, SimulatorList +from cognite.client.utils._experimental import FeaturePreviewWarning + +if TYPE_CHECKING: + from cognite.client import CogniteClient + from cognite.client.config import ClientConfig + + +class SimulatorsAPI(APIClient): + _RESOURCE_PATH = "/simulators" + + def __init__(self, config: ClientConfig, api_version: str | None, cognite_client: CogniteClient) -> None: + super().__init__(config, api_version, cognite_client) + self._warning = FeaturePreviewWarning( + api_maturity="General Availability", sdk_maturity="alpha", feature_name="Simulators" + ) + + def list(self, limit: int = DEFAULT_LIMIT_READ) -> SimulatorList: + """`Filter simulators `_ + + List simulators + + Args: + limit (int): Maximum number of results to return. Defaults to 1000. Set to -1, float(“inf”) or None to return all items. + + Returns: + SimulatorList: List of simulators + + Examples: + + List simulators: + + >>> from cognite.client import CogniteClient + >>> client = CogniteClient() + >>> res = client.simulators.list() + + """ + self._warning.warn() + return self._list(method="POST", limit=limit, resource_cls=Simulator, list_cls=SimulatorList) From ae25ff057a875b2a17b8d91a91443b48f9d718e0 Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:23:58 +0000 Subject: [PATCH 03/20] add simulator test --- cognite/client/_cognite_client.py | 3 + .../test_api/test_simulators/seed/data.py | 201 ++++++++++++++++++ .../test_simulators/test_simulators.py | 29 +++ 3 files changed, 233 insertions(+) create mode 100644 tests/tests_integration/test_api/test_simulators/seed/data.py create mode 100644 tests/tests_integration/test_api/test_simulators/test_simulators.py diff --git a/cognite/client/_cognite_client.py b/cognite/client/_cognite_client.py index b5c0f01b46..9c9c0a4108 100644 --- a/cognite/client/_cognite_client.py +++ b/cognite/client/_cognite_client.py @@ -23,6 +23,7 @@ from cognite.client._api.raw import RawAPI from cognite.client._api.relationships import RelationshipsAPI from cognite.client._api.sequences import SequencesAPI +from cognite.client._api.simulators import SimulatorsAPI from cognite.client._api.templates import TemplatesAPI from cognite.client._api.three_d import ThreeDAPI from cognite.client._api.time_series import TimeSeriesAPI @@ -83,6 +84,8 @@ def __init__(self, config: ClientConfig | None = None) -> None: self.documents = DocumentsAPI(self._config, self._API_VERSION, self) self.workflows = WorkflowAPI(self._config, self._API_VERSION, self) self.units = UnitAPI(self._config, self._API_VERSION, self) + self.simulators = SimulatorsAPI(self._config, self._API_VERSION, self) + # APIs just using base_url: self._api_client = APIClient(self._config, api_version=None, cognite_client=self) diff --git a/tests/tests_integration/test_api/test_simulators/seed/data.py b/tests/tests_integration/test_api/test_simulators/seed/data.py new file mode 100644 index 0000000000..e376b6dac6 --- /dev/null +++ b/tests/tests_integration/test_api/test_simulators/seed/data.py @@ -0,0 +1,201 @@ +data_set_id = 97552494921583 + +resource_names = { + "simulator_external_id": "py_sdk_integration_tests", + "simulator_integration_external_id": "py_sdk_integration_tests_connector", + "simulator_model_external_id": "py_sdk_integration_tests_model", + "simulator_model_revision_external_id": "pysdk_model_revision", + "simulator_model_file_external_id": "ShowerMixer_simulator_model_file_3", + "simulator_routine_external_id": "pysdk_routine", + "simulator_routine_revision_external_id": "pysdk_routine_revision", + "simulator_test_data_set_id": data_set_id, +} + +simulator = { + "name": resource_names["simulator_external_id"], + "externalId": resource_names["simulator_external_id"], + "fileExtensionTypes": ["dwxmz"], + "modelTypes": [{"name": "Steady State", "key": "SteadyState"}], + "stepFields": [ + { + "stepType": "get/set", + "fields": [ + { + "name": "objectName", + "label": "Simulation Object Name", + "info": "Enter the name of the DWSIM object, i.e. Feed", + }, + { + "name": "objectProperty", + "label": "Simulation Object Property", + "info": "Enter the property of the DWSIM object, i.e. Temperature", + }, + ], + }, + { + "stepType": "command", + "fields": [ + { + "name": "command", + "label": "Command", + "info": "Select a command", + "options": [{"label": "Solve Flowsheet", "value": "Solve"}], + } + ], + }, + ], + "unitQuantities": [ + { + "name": "mass", + "label": "Mass", + "units": [{"label": "kg", "name": "kg"}, {"label": "g", "name": "g"}, {"label": "lb", "name": "lb"}], + }, + { + "name": "time", + "label": "Time", + "units": [{"label": "s", "name": "s"}, {"label": "min.", "name": "min."}, {"label": "h", "name": "h"}], + }, + { + "name": "accel", + "label": "Acceleration", + "units": [ + {"label": "m/s2", "name": "m/s2"}, + {"label": "cm/s2", "name": "cm/s2"}, + {"label": "ft/s2", "name": "ft/s2"}, + ], + }, + { + "name": "force", + "label": "Force", + "units": [ + {"label": "N", "name": "N"}, + {"label": "dyn", "name": "dyn"}, + {"label": "kgf", "name": "kgf"}, + {"label": "lbf", "name": "lbf"}, + ], + }, + { + "name": "volume", + "label": "Volume", + "units": [ + {"label": "m3", "name": "m3"}, + {"label": "cm3", "name": "cm3"}, + {"label": "L", "name": "L"}, + {"label": "ft3", "name": "ft3"}, + {"label": "bbl", "name": "bbl"}, + {"label": "gal[US]", "name": "gal[US]"}, + {"label": "gal[UK]", "name": "gal[UK]"}, + ], + }, + { + "name": "density", + "label": "Density", + "units": [ + {"label": "kg/m3", "name": "kg/m3"}, + {"label": "g/cm3", "name": "g/cm3"}, + {"label": "lbm/ft3", "name": "lbm/ft3"}, + ], + }, + { + "name": "diameter", + "label": "Diameter", + "units": [{"label": "mm", "name": "mm"}, {"label": "in", "name": "in"}], + }, + { + "name": "distance", + "label": "Distance", + "units": [{"label": "m", "name": "m"}, {"label": "ft", "name": "ft"}, {"label": "cm", "name": "cm"}], + }, + { + "name": "heatflow", + "label": "Heat Flow", + "units": [ + {"label": "kW", "name": "kW"}, + {"label": "kcal/h", "name": "kcal/h"}, + {"label": "BTU/h", "name": "BTU/h"}, + {"label": "BTU/s", "name": "BTU/s"}, + {"label": "cal/s", "name": "cal/s"}, + {"label": "HP", "name": "HP"}, + {"label": "kJ/h", "name": "kJ/h"}, + {"label": "kJ/d", "name": "kJ/d"}, + {"label": "MW", "name": "MW"}, + {"label": "W", "name": "W"}, + {"label": "BTU/d", "name": "BTU/d"}, + {"label": "MMBTU/d", "name": "MMBTU/d"}, + {"label": "MMBTU/h", "name": "MMBTU/h"}, + {"label": "kcal/s", "name": "kcal/s"}, + {"label": "kcal/h", "name": "kcal/h"}, + {"label": "kcal/d", "name": "kcal/d"}, + ], + }, + { + "name": "pressure", + "label": "Pressure", + "units": [ + {"label": "Pa", "name": "Pa"}, + {"label": "atm", "name": "atm"}, + {"label": "kgf/cm2", "name": "kgf/cm2"}, + {"label": "kgf/cm2g", "name": "kgf/cm2g"}, + {"label": "lbf/ft2", "name": "lbf/ft2"}, + {"label": "kPa", "name": "kPa"}, + {"label": "kPag", "name": "kPag"}, + {"label": "bar", "name": "bar"}, + {"label": "barg", "name": "barg"}, + {"label": "ftH2O", "name": "ftH2O"}, + {"label": "inH2O", "name": "inH2O"}, + {"label": "inHg", "name": "inHg"}, + {"label": "mbar", "name": "mbar"}, + {"label": "mH2O", "name": "mH2O"}, + {"label": "mmH2O", "name": "mmH2O"}, + {"label": "mmHg", "name": "mmHg"}, + {"label": "MPa", "name": "MPa"}, + {"label": "psi", "name": "psi"}, + {"label": "psig", "name": "psig"}, + ], + }, + { + "name": "velocity", + "label": "Velocity", + "units": [ + {"label": "m/s", "name": "m/s"}, + {"label": "cm/s", "name": "cm/s"}, + {"label": "mm/s", "name": "mm/s"}, + {"label": "km/h", "name": "km/h"}, + {"label": "ft/h", "name": "ft/h"}, + {"label": "ft/min", "name": "ft/min"}, + {"label": "ft/s", "name": "ft/s"}, + {"label": "in/s", "name": "in/s"}, + ], + }, + { + "name": "temperature", + "label": "Temperature", + "units": [ + {"label": "K", "name": "K"}, + {"label": "R", "name": "R"}, + {"label": "C", "name": "C"}, + {"label": "F", "name": "F"}, + ], + }, + { + "name": "volumetricFlow", + "label": "Volumetric Flow", + "units": [ + {"label": "m3/h", "name": "m3/h"}, + {"label": "cm3/s", "name": "cm3/s"}, + {"label": "L/h", "name": "L/h"}, + {"label": "L/min", "name": "L/min"}, + {"label": "L/s", "name": "L/s"}, + {"label": "ft3/h", "name": "ft3/h"}, + {"label": "ft3/min", "name": "ft3/min"}, + {"label": "ft3/s", "name": "ft3/s"}, + {"label": "gal[US]/h", "name": "gal[US]/h"}, + {"label": "gal[US]/min", "name": "gal[US]/min"}, + {"label": "gal[US]/s", "name": "gal[US]/s"}, + {"label": "gal[UK]/h", "name": "gal[UK]/h"}, + {"label": "gal[UK]/min", "name": "gal[UK]/min"}, + {"label": "gal[UK]/s", "name": "gal[UK]/s"}, + ], + }, + ], +} diff --git a/tests/tests_integration/test_api/test_simulators/test_simulators.py b/tests/tests_integration/test_api/test_simulators/test_simulators.py new file mode 100644 index 0000000000..c33b0c6f83 --- /dev/null +++ b/tests/tests_integration/test_api/test_simulators/test_simulators.py @@ -0,0 +1,29 @@ +import pytest + +from cognite.client._cognite_client import CogniteClient +from tests.tests_integration.test_api.test_simulators.seed.data import resource_names, simulator + + +@pytest.fixture(scope="class") +def seed_resource_names() -> dict[str, str]: + return resource_names + + +@pytest.fixture +def seed_simulator(cognite_client: CogniteClient, seed_resource_names) -> None: + simulator_external_id = seed_resource_names["simulator_external_id"] + simulators = cognite_client.simulators.list() + simulator_exists = len(list(filter(lambda x: x.external_id == simulator_external_id, simulators))) > 0 + if not simulator_exists: + cognite_client.post( + f"/api/v1/projects/{cognite_client.config.project}/simulators", + json={"items": [simulator]}, + ) + + +@pytest.mark.usefixtures("seed_resource_names", "seed_simulator") +class TestSimulators: + def test_list_simulators(self, cognite_client: CogniteClient) -> None: + simulators = cognite_client.simulators.list(limit=5) + + assert len(simulators) > 0 From f6beb03844c9ae4f1856f79a1f6f3b035fc14778 Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:30:15 +0000 Subject: [PATCH 04/20] add simulators api to mock --- cognite/client/testing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cognite/client/testing.py b/cognite/client/testing.py index 2a7be50abd..140256629e 100644 --- a/cognite/client/testing.py +++ b/cognite/client/testing.py @@ -43,6 +43,7 @@ from cognite.client._api.raw import RawAPI, RawDatabasesAPI, RawRowsAPI, RawTablesAPI from cognite.client._api.relationships import RelationshipsAPI from cognite.client._api.sequences import SequencesAPI, SequencesDataAPI +from cognite.client._api.simulators import SimulatorsAPI from cognite.client._api.synthetic_time_series import SyntheticDatapointsAPI from cognite.client._api.templates import ( TemplateGroupsAPI, @@ -141,6 +142,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.raw.tables = MagicMock(spec_set=RawTablesAPI) self.relationships = MagicMock(spec_set=RelationshipsAPI) + self.simulators = MagicMock(spec=SimulatorsAPI) self.sequences = MagicMock(spec=SequencesAPI) self.sequences.data = MagicMock(spec_set=SequencesDataAPI) From efa9fa02a2e6e522a155c81a213b173260eb0045 Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:36:20 +0000 Subject: [PATCH 05/20] remove simulators from retryable apis --- tests/tests_unit/test_api_client.py | 1 + tests/tests_unit/test_meta.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/tests_unit/test_api_client.py b/tests/tests_unit/test_api_client.py index c27bb4e6ea..021f2a8ef1 100644 --- a/tests/tests_unit/test_api_client.py +++ b/tests/tests_unit/test_api_client.py @@ -1183,6 +1183,7 @@ class TestRetryableEndpoints: "datasets", "relationships", "labels", + "simulators", ] for test_case in [ # Should retry POST on all _read_ endpoints diff --git a/tests/tests_unit/test_meta.py b/tests/tests_unit/test_meta.py index a066ff2379..6ff58a9f22 100644 --- a/tests/tests_unit/test_meta.py +++ b/tests/tests_unit/test_meta.py @@ -72,6 +72,7 @@ def apis_that_should_not_have_post_retry_rule(): [ "groups", # ☑️ "securitycategories", # ☑️ + "simulators", "templategroups", # Won't do: deprecated API ] ) From a22e52b9632f34693cf72e98cd8b7fc36023a4df Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:03:31 +0000 Subject: [PATCH 06/20] update tests --- cognite/client/_api_client.py | 1 + .../data_classes/simulators/__init__.py | 22 +++++++++++++++++++ cognite/client/testing.py | 2 +- tests/tests_unit/test_api_client.py | 1 - tests/tests_unit/test_meta.py | 1 - 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/cognite/client/_api_client.py b/cognite/client/_api_client.py index be93e1e8ae..578b0f9ec3 100644 --- a/cognite/client/_api_client.py +++ b/cognite/client/_api_client.py @@ -101,6 +101,7 @@ class APIClient: "geospatial/(compute|crs/byids|featuretypes/(byids|list))", "geospatial/featuretypes/[A-Za-z][A-Za-z0-9_]{0,31}/features/(aggregate|list|byids|search|search-streaming|[A-Za-z][A-Za-z0-9_]{0,255}/rasters/[A-Za-z][A-Za-z0-9_]{0,31})", "transformations/(filter|byids|jobs/byids|schedules/byids|query/run)", + "simulators/(list)", "extpipes/(list|byids|runs/list)", "workflows/.*", "hostedextractors/.*", diff --git a/cognite/client/data_classes/simulators/__init__.py b/cognite/client/data_classes/simulators/__init__.py index 9d48db4f9f..61743d4ebc 100644 --- a/cognite/client/data_classes/simulators/__init__.py +++ b/cognite/client/data_classes/simulators/__init__.py @@ -1 +1,23 @@ from __future__ import annotations + +from cognite.client.data_classes.simulators.simulators import ( + Simulator, + SimulatorList, + SimulatorStep, + SimulatorStepField, + SimulatorStepOption, + SimulatorUnitEntry, + SimulatorWrite, + SimulatorWriteList, +) + +__all__ = [ + "Simulator", + "SimulatorList", + "SimulatorStep", + "SimulatorStepField", + "SimulatorStepOption", + "SimulatorUnitEntry", + "SimulatorWrite", + "SimulatorWriteList", +] diff --git a/cognite/client/testing.py b/cognite/client/testing.py index 140256629e..4e0712a8ca 100644 --- a/cognite/client/testing.py +++ b/cognite/client/testing.py @@ -142,7 +142,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.raw.tables = MagicMock(spec_set=RawTablesAPI) self.relationships = MagicMock(spec_set=RelationshipsAPI) - self.simulators = MagicMock(spec=SimulatorsAPI) + self.simulators = MagicMock(spec_set=SimulatorsAPI) self.sequences = MagicMock(spec=SequencesAPI) self.sequences.data = MagicMock(spec_set=SequencesDataAPI) diff --git a/tests/tests_unit/test_api_client.py b/tests/tests_unit/test_api_client.py index 021f2a8ef1..c27bb4e6ea 100644 --- a/tests/tests_unit/test_api_client.py +++ b/tests/tests_unit/test_api_client.py @@ -1183,7 +1183,6 @@ class TestRetryableEndpoints: "datasets", "relationships", "labels", - "simulators", ] for test_case in [ # Should retry POST on all _read_ endpoints diff --git a/tests/tests_unit/test_meta.py b/tests/tests_unit/test_meta.py index 6ff58a9f22..a066ff2379 100644 --- a/tests/tests_unit/test_meta.py +++ b/tests/tests_unit/test_meta.py @@ -72,7 +72,6 @@ def apis_that_should_not_have_post_retry_rule(): [ "groups", # ☑️ "securitycategories", # ☑️ - "simulators", "templategroups", # Won't do: deprecated API ] ) From 6559c2dfed97e182809fc0b04ad3abc6f148267e Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:15:12 +0000 Subject: [PATCH 07/20] add retryable endpoint test --- tests/tests_unit/test_api_client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/tests_unit/test_api_client.py b/tests/tests_unit/test_api_client.py index c27bb4e6ea..fa72aa3ece 100644 --- a/tests/tests_unit/test_api_client.py +++ b/tests/tests_unit/test_api_client.py @@ -1366,6 +1366,8 @@ def test_is_retryable_resource_api_endpoints(self, api_client_with_token, method ("POST", "https://api.cognitedata.com/api/v1/projects/bla/context/diagram/detect", True), ("GET", "https://api.cognitedata.com/api/v1/projects/bla/context/diagram/convert/123", True), ("GET", "https://api.cognitedata.com/api/v1/projects/bla/context/diagram/detect/456", True), + # Simulators + ("POST", "https://api.cognitedata.com/api/v1/projects/bla/simulators/list", True), ] ), ) From 6fbc679425b880835bf7a4f4dc476abdfba1a437 Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Wed, 18 Dec 2024 12:52:03 +0000 Subject: [PATCH 08/20] move fixtures to conftest --- .../test_api/test_simulators/conftest.py | 23 +++++++++++++++++++ .../test_simulators/test_simulators.py | 20 +--------------- 2 files changed, 24 insertions(+), 19 deletions(-) create mode 100644 tests/tests_integration/test_api/test_simulators/conftest.py diff --git a/tests/tests_integration/test_api/test_simulators/conftest.py b/tests/tests_integration/test_api/test_simulators/conftest.py new file mode 100644 index 0000000000..24eee01940 --- /dev/null +++ b/tests/tests_integration/test_api/test_simulators/conftest.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +import pytest + +from cognite.client._cognite_client import CogniteClient +from tests.tests_integration.test_api.test_simulators.seed.data import resource_names, simulator + + +@pytest.fixture(scope="class") +def seed_resource_names() -> dict[str, str]: + return resource_names + + +@pytest.fixture +def seed_simulator(cognite_client: CogniteClient, seed_resource_names) -> None: + simulator_external_id = seed_resource_names["simulator_external_id"] + simulators = cognite_client.simulators.list() + simulator_exists = len(list(filter(lambda x: x.external_id == simulator_external_id, simulators))) > 0 + if not simulator_exists: + cognite_client.post( + f"/api/v1/projects/{cognite_client.config.project}/simulators", + json={"items": [simulator]}, + ) diff --git a/tests/tests_integration/test_api/test_simulators/test_simulators.py b/tests/tests_integration/test_api/test_simulators/test_simulators.py index c33b0c6f83..81b417ae84 100644 --- a/tests/tests_integration/test_api/test_simulators/test_simulators.py +++ b/tests/tests_integration/test_api/test_simulators/test_simulators.py @@ -1,27 +1,9 @@ import pytest from cognite.client._cognite_client import CogniteClient -from tests.tests_integration.test_api.test_simulators.seed.data import resource_names, simulator -@pytest.fixture(scope="class") -def seed_resource_names() -> dict[str, str]: - return resource_names - - -@pytest.fixture -def seed_simulator(cognite_client: CogniteClient, seed_resource_names) -> None: - simulator_external_id = seed_resource_names["simulator_external_id"] - simulators = cognite_client.simulators.list() - simulator_exists = len(list(filter(lambda x: x.external_id == simulator_external_id, simulators))) > 0 - if not simulator_exists: - cognite_client.post( - f"/api/v1/projects/{cognite_client.config.project}/simulators", - json={"items": [simulator]}, - ) - - -@pytest.mark.usefixtures("seed_resource_names", "seed_simulator") +@pytest.mark.usefixtures("seed_simulator") class TestSimulators: def test_list_simulators(self, cognite_client: CogniteClient) -> None: simulators = cognite_client.simulators.list(limit=5) From 98a138803fe971d0118d6653f5d883497ebac02f Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Wed, 18 Dec 2024 13:33:10 +0000 Subject: [PATCH 09/20] add integrations --- cognite/client/_api/simulators/__init__.py | 2 + .../client/_api/simulators/integrations.py | 58 ++++++ .../client/data_classes/simulators/filters.py | 14 ++ .../data_classes/simulators/simulators.py | 189 ++++++++++++++++++ cognite/client/testing.py | 2 + .../test_api/test_simulators/conftest.py | 28 ++- .../test_api/test_simulators/seed/data.py | 13 ++ .../test_simulators/test_integrations.py | 30 +++ 8 files changed, 335 insertions(+), 1 deletion(-) create mode 100644 cognite/client/_api/simulators/integrations.py create mode 100644 cognite/client/data_classes/simulators/filters.py create mode 100644 tests/tests_integration/test_api/test_simulators/test_integrations.py diff --git a/cognite/client/_api/simulators/__init__.py b/cognite/client/_api/simulators/__init__.py index aa310331cb..7d1f1115a4 100644 --- a/cognite/client/_api/simulators/__init__.py +++ b/cognite/client/_api/simulators/__init__.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING +from cognite.client._api.simulators.integrations import SimulatorIntegrationsAPI from cognite.client._api_client import APIClient from cognite.client._constants import DEFAULT_LIMIT_READ from cognite.client.data_classes.simulators.simulators import Simulator, SimulatorList @@ -17,6 +18,7 @@ class SimulatorsAPI(APIClient): def __init__(self, config: ClientConfig, api_version: str | None, cognite_client: CogniteClient) -> None: super().__init__(config, api_version, cognite_client) + self.integrations = SimulatorIntegrationsAPI(config, api_version, cognite_client) self._warning = FeaturePreviewWarning( api_maturity="General Availability", sdk_maturity="alpha", feature_name="Simulators" ) diff --git a/cognite/client/_api/simulators/integrations.py b/cognite/client/_api/simulators/integrations.py new file mode 100644 index 0000000000..2588973764 --- /dev/null +++ b/cognite/client/_api/simulators/integrations.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from cognite.client._api_client import APIClient +from cognite.client._constants import DEFAULT_LIMIT_READ +from cognite.client.data_classes._base import CogniteFilter +from cognite.client.data_classes.simulators.filters import SimulatorIntegrationFilter +from cognite.client.data_classes.simulators.simulators import ( + SimulatorIntegration, + SimulatorIntegrationList, +) +from cognite.client.utils._experimental import FeaturePreviewWarning + +if TYPE_CHECKING: + from cognite.client import CogniteClient + from cognite.client.config import ClientConfig + + +class SimulatorIntegrationsAPI(APIClient): + _RESOURCE_PATH = "/simulators/integrations" + + def __init__(self, config: ClientConfig, api_version: str | None, cognite_client: CogniteClient) -> None: + super().__init__(config, api_version, cognite_client) + self._warning = FeaturePreviewWarning( + api_maturity="General Availability", sdk_maturity="alpha", feature_name="Simulators" + ) + + def list( + self, + limit: int = DEFAULT_LIMIT_READ, + filter: SimulatorIntegrationFilter | dict[str, Any] | None = None, + ) -> SimulatorIntegrationList: + """`Filter simulator integrations `_ + Retrieves a list of simulator integrations that match the given criteria + Args: + limit (int): The maximum number of simulator integrations to return. + filter (SimulatorIntegrationFilter | dict[str, Any] | None): Filter to apply. + Returns: + SimulatorIntegrationList: List of simulator integrations + Examples: + List simulator integrations: + >>> from cognite.client import CogniteClient + >>> client = CogniteClient() + >>> res = client.simulators.integrations.list() + + Filter integrations by active status: + >>> res = client.simulators.integrations.list(filter=SimulatorIntegrationFilter(active=True)) + """ + + self._warning.warn() + return self._list( + method="POST", + limit=limit, + resource_cls=SimulatorIntegration, + list_cls=SimulatorIntegrationList, + filter=filter.dump() if isinstance(filter, CogniteFilter) else None, + ) diff --git a/cognite/client/data_classes/simulators/filters.py b/cognite/client/data_classes/simulators/filters.py new file mode 100644 index 0000000000..e912b961a6 --- /dev/null +++ b/cognite/client/data_classes/simulators/filters.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from cognite.client.data_classes._base import CogniteFilter +from cognite.client.utils.useful_types import SequenceNotStr + + +class SimulatorIntegrationFilter(CogniteFilter): + def __init__( + self, + simulator_external_ids: SequenceNotStr[str] | None = None, + active: bool | None = None, + ) -> None: + self.simulator_external_ids = simulator_external_ids + self.active = active diff --git a/cognite/client/data_classes/simulators/simulators.py b/cognite/client/data_classes/simulators/simulators.py index fe17c185d2..451b959ba4 100644 --- a/cognite/client/data_classes/simulators/simulators.py +++ b/cognite/client/data_classes/simulators/simulators.py @@ -326,3 +326,192 @@ class SimulatorList(WriteableCogniteResourceList[SimulatorWrite, Simulator], IdT def as_write(self) -> SimulatorWriteList: return SimulatorWriteList([a.as_write() for a in self.data], cognite_client=self._get_cognite_client()) + + +class SimulatorIntegrationCore(WriteableCogniteResource["SimulatorIntegrationWrite"], ABC): + """ + The simulator integration resource represents a simulator connector in Cognite Data Fusion (CDF). + It provides information about the configured connectors for a given simulator, including their status and additional + details such as dataset, name, license status, connector version, simulator version, and more. This resource is essential + for monitoring and managing the interactions between CDF and external simulators, ensuring proper data flow and integration. + This is the read/response format of the simulator integration. + Args: + external_id (str): External id of the simulator integration + simulator_external_id (str): External id of the associated simulator + heartbeat (int): The interval in seconds between the last heartbeat and the current time + data_set_id (int): The id of the dataset associated with the simulator integration + connector_version (str): The version of the connector + license_status (str | None): The status of the license + simulator_version (str | None): The version of the simulator + license_last_checked_time (int | None): The time when the license was last checked + connector_status (str | None): The status of the connector + connector_status_updated_time (int | None): The time when the connector status was last updated + """ + + def __init__( + self, + external_id: str, + simulator_external_id: str, + heartbeat: int, + data_set_id: int, + connector_version: str, + license_status: str | None = None, + simulator_version: str | None = None, + license_last_checked_time: int | None = None, + connector_status: str | None = None, + connector_status_updated_time: int | None = None, + ) -> None: + self.external_id = external_id + self.simulator_external_id = simulator_external_id + self.heartbeat = heartbeat + self.data_set_id = data_set_id + self.connector_version = connector_version + self.license_status = license_status + self.simulator_version = simulator_version + self.license_last_checked_time = license_last_checked_time + self.connector_status = connector_status + self.connector_status_updated_time = connector_status_updated_time + + @classmethod + def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self: + return cls( + external_id=resource["externalId"], + simulator_external_id=resource["simulatorExternalId"], + heartbeat=resource["heartbeat"], + data_set_id=resource["dataSetId"], + connector_version=resource["connectorVersion"], + license_status=resource.get("licenseStatus"), + simulator_version=resource.get("simulatorVersion"), + license_last_checked_time=resource.get("licenseLastCheckedTime"), + connector_status=resource.get("connectorStatus"), + connector_status_updated_time=resource.get("connectorStatusUpdatedTime"), + ) + + def dump(self, camel_case: bool = True) -> dict[str, Any]: + return super().dump(camel_case=camel_case) + + +class SimulatorIntegrationWrite(SimulatorIntegrationCore): + def __init__( + self, + external_id: str, + simulator_external_id: str, + heartbeat: int, + data_set_id: int, + connector_version: str, + license_status: str | None = None, + simulator_version: str | None = None, + license_last_checked_time: int | None = None, + connector_status: str | None = None, + connector_status_updated_time: int | None = None, + ) -> None: + super().__init__( + external_id=external_id, + simulator_external_id=simulator_external_id, + heartbeat=heartbeat, + data_set_id=data_set_id, + connector_version=connector_version, + license_status=license_status, + simulator_version=simulator_version, + license_last_checked_time=license_last_checked_time, + connector_status=connector_status, + connector_status_updated_time=connector_status_updated_time, + ) + + def as_write(self) -> SimulatorIntegrationWrite: + """Returns a writeable version of this resource""" + return self + + +class SimulatorIntegration(SimulatorIntegrationCore): + def __init__( + self, + external_id: str, + simulator_external_id: str, + heartbeat: int, + data_set_id: int, + connector_version: str, + license_status: str | None = None, + simulator_version: str | None = None, + license_last_checked_time: int | None = None, + connector_status: str | None = None, + connector_status_updated_time: int | None = None, + created_time: int | None = None, + last_updated_time: int | None = None, + id: int | None = None, + active: bool | None = None, + log_id: int | None = None, + ) -> None: + self.external_id = external_id + self.simulator_external_id = simulator_external_id + self.heartbeat = heartbeat + self.data_set_id = data_set_id + self.connector_version = connector_version + self.license_status = license_status + self.simulator_version = simulator_version + self.license_last_checked_time = license_last_checked_time + self.connector_status = connector_status + self.connector_status_updated_time = connector_status_updated_time + # id/created_time/last_updated_time are required when using the class to read, + # but don't make sense passing in when creating a new object. So in order to make the typing + # correct here (i.e. int and not Optional[int]), we force the type to be int rather than + # Optional[int]. + self.id: int | None = id + self.created_time: int | None = created_time + self.last_updated_time: int | None = last_updated_time + self.active = active + self.log_id = log_id + + @classmethod + def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self: + load = super()._load(resource, cognite_client) + return cls( + external_id=load.external_id, + simulator_external_id=load.simulator_external_id, + heartbeat=load.heartbeat, + data_set_id=load.data_set_id, + connector_version=load.connector_version, + license_status=load.license_status, + simulator_version=load.simulator_version, + license_last_checked_time=load.license_last_checked_time, + connector_status=load.connector_status, + connector_status_updated_time=load.connector_status_updated_time, + created_time=resource.get("createdTime"), + last_updated_time=resource.get("lastUpdatedTime"), + id=resource.get("id"), + active=resource.get("active"), + log_id=resource.get("logId"), + ) + + def as_write(self) -> SimulatorIntegrationWrite: + """Returns a writeable version of this resource""" + return SimulatorIntegrationWrite( + external_id=self.external_id, + simulator_external_id=self.simulator_external_id, + heartbeat=self.heartbeat, + data_set_id=self.data_set_id, + connector_version=self.connector_version, + license_status=self.license_status, + simulator_version=self.simulator_version, + license_last_checked_time=self.license_last_checked_time, + connector_status=self.connector_status, + connector_status_updated_time=self.connector_status_updated_time, + ) + + def __hash__(self) -> int: + return hash(self.external_id) + + +class SimulatorIntegrationWriteList(CogniteResourceList[SimulatorIntegrationWrite], ExternalIDTransformerMixin): + _RESOURCE = SimulatorIntegrationWrite + + +class SimulatorIntegrationList( + WriteableCogniteResourceList[SimulatorIntegrationWrite, SimulatorIntegration], IdTransformerMixin +): + _RESOURCE = SimulatorIntegration + + def as_write(self) -> SimulatorIntegrationWriteList: + return SimulatorIntegrationWriteList( + [a.as_write() for a in self.data], cognite_client=self._get_cognite_client() + ) diff --git a/cognite/client/testing.py b/cognite/client/testing.py index 4e0712a8ca..3af20248ed 100644 --- a/cognite/client/testing.py +++ b/cognite/client/testing.py @@ -44,6 +44,7 @@ from cognite.client._api.relationships import RelationshipsAPI from cognite.client._api.sequences import SequencesAPI, SequencesDataAPI from cognite.client._api.simulators import SimulatorsAPI +from cognite.client._api.simulators.integrations import SimulatorIntegrationsAPI from cognite.client._api.synthetic_time_series import SyntheticDatapointsAPI from cognite.client._api.templates import ( TemplateGroupsAPI, @@ -143,6 +144,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.relationships = MagicMock(spec_set=RelationshipsAPI) self.simulators = MagicMock(spec_set=SimulatorsAPI) + self.simulators.integrations = MagicMock(spec_set=SimulatorIntegrationsAPI) self.sequences = MagicMock(spec=SequencesAPI) self.sequences.data = MagicMock(spec_set=SequencesDataAPI) diff --git a/tests/tests_integration/test_api/test_simulators/conftest.py b/tests/tests_integration/test_api/test_simulators/conftest.py index 24eee01940..33b08487c4 100644 --- a/tests/tests_integration/test_api/test_simulators/conftest.py +++ b/tests/tests_integration/test_api/test_simulators/conftest.py @@ -1,9 +1,12 @@ from __future__ import annotations +import time + import pytest from cognite.client._cognite_client import CogniteClient -from tests.tests_integration.test_api.test_simulators.seed.data import resource_names, simulator +from cognite.client.exceptions import CogniteAPIError +from tests.tests_integration.test_api.test_simulators.seed.data import resource_names, simulator, simulator_integration @pytest.fixture(scope="class") @@ -21,3 +24,26 @@ def seed_simulator(cognite_client: CogniteClient, seed_resource_names) -> None: f"/api/v1/projects/{cognite_client.config.project}/simulators", json={"items": [simulator]}, ) + + +@pytest.fixture +def seed_simulator_integration(cognite_client: CogniteClient, seed_simulator) -> None: + try: + simulator_integration["heartbeat"] = int(time.time() * 1000) + cognite_client.post( + f"/api/v1/projects/{cognite_client.config.project}/simulators/integrations", + json={"items": [simulator_integration]}, + ) + except CogniteAPIError: + simulator_integrations = cognite_client.simulators.integrations.list() + integration_id = next( + filter( + lambda x: x.external_id == simulator_integration["externalId"], + simulator_integrations, + ) + ).id + # update hearbeat instead + cognite_client.post( + f"/api/v1/projects/{cognite_client.config.project}/simulators/integrations/update", + json={"items": [{"id": integration_id, "update": {"heartbeat": {"set": int(time.time() * 1000)}}}]}, + ) diff --git a/tests/tests_integration/test_api/test_simulators/seed/data.py b/tests/tests_integration/test_api/test_simulators/seed/data.py index e376b6dac6..297bc7c148 100644 --- a/tests/tests_integration/test_api/test_simulators/seed/data.py +++ b/tests/tests_integration/test_api/test_simulators/seed/data.py @@ -199,3 +199,16 @@ }, ], } + +simulator_integration = { + "externalId": resource_names["simulator_integration_external_id"], + "simulatorExternalId": resource_names["simulator_external_id"], + "heartbeat": 0, + "dataSetId": resource_names["simulator_test_data_set_id"], + "connectorVersion": "1.0.0", + "simulatorVersion": "1.0.0", + "licenseStatus": "AVAILABLE", + "licenseLastCheckedTime": 0, + "connectorStatus": "IDLE", + "connectorStatusUpdatedTime": 0, +} diff --git a/tests/tests_integration/test_api/test_simulators/test_integrations.py b/tests/tests_integration/test_api/test_simulators/test_integrations.py new file mode 100644 index 0000000000..e97d287d82 --- /dev/null +++ b/tests/tests_integration/test_api/test_simulators/test_integrations.py @@ -0,0 +1,30 @@ +import pytest + +from cognite.client._cognite_client import CogniteClient +from cognite.client.data_classes.simulators.filters import SimulatorIntegrationFilter + + +class TestSimulatorIntegrations: + @pytest.mark.usefixtures("seed_resource_names", "seed_simulator_integration") + def test_list_integrations(self, cognite_client: CogniteClient) -> None: + integrations = cognite_client.simulators.integrations.list(limit=5) + + assert len(integrations) > 0 + + def test_filter_integrations(self, cognite_client: CogniteClient, seed_resource_names) -> None: + all_integrations = cognite_client.simulators.integrations.list() + active_integrations = cognite_client.simulators.integrations.list( + filter=SimulatorIntegrationFilter(active=True) + ) + + filtered_integrations = cognite_client.simulators.integrations.list( + filter=SimulatorIntegrationFilter(simulator_external_ids=[seed_resource_names["simulator_external_id"]]) + ) + + assert len(all_integrations) > 0 + assert filtered_integrations[0].external_id == seed_resource_names["simulator_integration_external_id"] + # check time difference + assert filtered_integrations[0].active is True + + assert len(active_integrations) > 0 + assert len(filtered_integrations) > 0 From ddd34a4edeb181a69bdd9909e471b528d61f531d Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:01:13 +0000 Subject: [PATCH 10/20] fix MagicMock error --- cognite/client/testing.py | 3 ++- tests/tests_integration/test_api/test_simulators/seed/data.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cognite/client/testing.py b/cognite/client/testing.py index 3af20248ed..f014c1eae8 100644 --- a/cognite/client/testing.py +++ b/cognite/client/testing.py @@ -143,7 +143,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.raw.tables = MagicMock(spec_set=RawTablesAPI) self.relationships = MagicMock(spec_set=RelationshipsAPI) - self.simulators = MagicMock(spec_set=SimulatorsAPI) + + self.simulators = MagicMock(spec=SimulatorsAPI) self.simulators.integrations = MagicMock(spec_set=SimulatorIntegrationsAPI) self.sequences = MagicMock(spec=SequencesAPI) diff --git a/tests/tests_integration/test_api/test_simulators/seed/data.py b/tests/tests_integration/test_api/test_simulators/seed/data.py index 297bc7c148..e388b83912 100644 --- a/tests/tests_integration/test_api/test_simulators/seed/data.py +++ b/tests/tests_integration/test_api/test_simulators/seed/data.py @@ -1,3 +1,5 @@ +# This file contains the data used to seed the test environment for the simulator tests + data_set_id = 97552494921583 resource_names = { From f35fc502ebb3e8f13fde1aa6c02af417d2b32471 Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:23:55 +0000 Subject: [PATCH 11/20] add delete functionality to integrations --- .../client/_api/simulators/integrations.py | 28 ++++++++++++++++ .../test_simulators/test_integrations.py | 32 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/cognite/client/_api/simulators/integrations.py b/cognite/client/_api/simulators/integrations.py index 2588973764..21be77f15c 100644 --- a/cognite/client/_api/simulators/integrations.py +++ b/cognite/client/_api/simulators/integrations.py @@ -1,5 +1,6 @@ from __future__ import annotations +from collections.abc import Sequence from typing import TYPE_CHECKING, Any from cognite.client._api_client import APIClient @@ -11,6 +12,8 @@ SimulatorIntegrationList, ) from cognite.client.utils._experimental import FeaturePreviewWarning +from cognite.client.utils._identifier import IdentifierSequence +from cognite.client.utils.useful_types import SequenceNotStr if TYPE_CHECKING: from cognite.client import CogniteClient @@ -22,6 +25,7 @@ class SimulatorIntegrationsAPI(APIClient): def __init__(self, config: ClientConfig, api_version: str | None, cognite_client: CogniteClient) -> None: super().__init__(config, api_version, cognite_client) + self._DELETE_LIMIT = 1 self._warning = FeaturePreviewWarning( api_maturity="General Availability", sdk_maturity="alpha", feature_name="Simulators" ) @@ -56,3 +60,27 @@ def list( list_cls=SimulatorIntegrationList, filter=filter.dump() if isinstance(filter, CogniteFilter) else None, ) + + def delete( + self, + id: int | Sequence[int] | None = None, + external_id: str | SequenceNotStr[str] | None = None, + ) -> None: + """`Delete one or more integrations `_ + + Args: + id (int | Sequence[int] | None): Id or list of ids + external_id (str | SequenceNotStr[str] | None): External ID or list of external ids + + Examples: + + Delete assets by id or external id: + + >>> from cognite.client import CogniteClient + >>> client = CogniteClient() + >>> client.simulators.integrations.delete(id=[1,2,3], external_id="foo") + """ + self._delete_multiple( + identifiers=IdentifierSequence.load(ids=id, external_ids=external_id), + wrap_ids=True, + ) diff --git a/tests/tests_integration/test_api/test_simulators/test_integrations.py b/tests/tests_integration/test_api/test_simulators/test_integrations.py index e97d287d82..68ee9a0a67 100644 --- a/tests/tests_integration/test_api/test_simulators/test_integrations.py +++ b/tests/tests_integration/test_api/test_simulators/test_integrations.py @@ -1,7 +1,11 @@ +import time + import pytest from cognite.client._cognite_client import CogniteClient from cognite.client.data_classes.simulators.filters import SimulatorIntegrationFilter +from cognite.client.utils._text import random_string +from tests.tests_integration.test_api.test_simulators.seed.data import simulator_integration class TestSimulatorIntegrations: @@ -28,3 +32,31 @@ def test_filter_integrations(self, cognite_client: CogniteClient, seed_resource_ assert len(active_integrations) > 0 assert len(filtered_integrations) > 0 + + def test_delete_integrations(self, cognite_client: CogniteClient, seed_resource_names) -> None: + simulator_integration["heartbeat"] = int(time.time() * 1000) + simulator_integration["externalId"] = random_string(50) + + cognite_client.post( + f"/api/v1/projects/{cognite_client.config.project}/simulators/integrations", + json={"items": [simulator_integration]}, + ) + + all_integrations = cognite_client.simulators.integrations.list() + assert len(all_integrations) > 0 + assert any( + map( + lambda x: x.external_id == simulator_integration["externalId"], + all_integrations, + ) + ) + + cognite_client.simulators.integrations.delete(external_id=simulator_integration["externalId"]) + + all_integrations = cognite_client.simulators.integrations.list() + assert not any( + map( + lambda x: x.external_id == simulator_integration["externalId"], + all_integrations, + ) + ) From e6e76c01ef9a49e7b952d236397666d5d23dcedb Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:24:32 +0000 Subject: [PATCH 12/20] fix typo --- cognite/client/_api/simulators/integrations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cognite/client/_api/simulators/integrations.py b/cognite/client/_api/simulators/integrations.py index 21be77f15c..bec2a32d65 100644 --- a/cognite/client/_api/simulators/integrations.py +++ b/cognite/client/_api/simulators/integrations.py @@ -74,7 +74,7 @@ def delete( Examples: - Delete assets by id or external id: + Delete integrations by id or external id: >>> from cognite.client import CogniteClient >>> client = CogniteClient() From 00eaafdf3fc5b2252aaac51430965b9be74f4466 Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:31:27 +0000 Subject: [PATCH 13/20] update __init__ --- cognite/client/_api/simulators/__init__.py | 2 +- cognite/client/data_classes/simulators/__init__.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cognite/client/_api/simulators/__init__.py b/cognite/client/_api/simulators/__init__.py index 7d1f1115a4..c3a10896be 100644 --- a/cognite/client/_api/simulators/__init__.py +++ b/cognite/client/_api/simulators/__init__.py @@ -24,7 +24,7 @@ def __init__(self, config: ClientConfig, api_version: str | None, cognite_client ) def list(self, limit: int = DEFAULT_LIMIT_READ) -> SimulatorList: - """`Filter simulators `_ + """`List simulators `_ List simulators diff --git a/cognite/client/data_classes/simulators/__init__.py b/cognite/client/data_classes/simulators/__init__.py index 61743d4ebc..f79c243ddb 100644 --- a/cognite/client/data_classes/simulators/__init__.py +++ b/cognite/client/data_classes/simulators/__init__.py @@ -2,6 +2,8 @@ from cognite.client.data_classes.simulators.simulators import ( Simulator, + SimulatorIntegration, + SimulatorIntegrationList, SimulatorList, SimulatorStep, SimulatorStepField, @@ -13,6 +15,8 @@ __all__ = [ "Simulator", + "SimulatorIntegration", + "SimulatorIntegrationList", "SimulatorList", "SimulatorStep", "SimulatorStepField", From 1f694ccea94ef0dcbdc5c96d4226ad5e36f8df8b Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:35:35 +0000 Subject: [PATCH 14/20] update dataset id --- tests/tests_integration/test_api/test_simulators/seed/data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests_integration/test_api/test_simulators/seed/data.py b/tests/tests_integration/test_api/test_simulators/seed/data.py index e388b83912..e9c024c17c 100644 --- a/tests/tests_integration/test_api/test_simulators/seed/data.py +++ b/tests/tests_integration/test_api/test_simulators/seed/data.py @@ -1,6 +1,6 @@ # This file contains the data used to seed the test environment for the simulator tests -data_set_id = 97552494921583 +data_set_id = 1521375514069 resource_names = { "simulator_external_id": "py_sdk_integration_tests", From 27ab947589580101294b6c422369056f402d2d3e Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Sun, 5 Jan 2025 16:11:24 +0000 Subject: [PATCH 15/20] address comments --- CHANGELOG.md | 4 + cognite/client/_api/simulators/__init__.py | 2 +- .../client/_api/simulators/integrations.py | 16 +- cognite/client/_api_client.py | 2 +- .../data_classes/simulators/__init__.py | 4 - .../data_classes/simulators/simulators.py | 262 +++--------------- .../test_api/test_simulators/conftest.py | 53 ++-- .../test_api/test_simulators/seed/data.py | 9 +- .../test_simulators/test_integrations.py | 28 +- .../test_simulators/test_simulators.py | 2 + tests/tests_unit/test_api_client.py | 1 + 11 files changed, 93 insertions(+), 290 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a65057dc19..b39516f892 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ Changes are grouped as follows - `Fixed` for any bug fixes. - `Security` in case of vulnerabilities. +## [Unreleased] +### Added +- Support for the `/simulators` and `/simulators/integration` API endpoints. + ## [7.70.5] - 2024-12-12 ### Fixed - Upserting a Sequence with columns no longer silently skips the columns, but instead updates them as intended. diff --git a/cognite/client/_api/simulators/__init__.py b/cognite/client/_api/simulators/__init__.py index c3a10896be..73e83ce68a 100644 --- a/cognite/client/_api/simulators/__init__.py +++ b/cognite/client/_api/simulators/__init__.py @@ -29,7 +29,7 @@ def list(self, limit: int = DEFAULT_LIMIT_READ) -> SimulatorList: List simulators Args: - limit (int): Maximum number of results to return. Defaults to 1000. Set to -1, float(“inf”) or None to return all items. + limit (int): Maximum number of results to return. Defaults to 25. Set to -1, float(“inf”) or None to return all items. Returns: SimulatorList: List of simulators diff --git a/cognite/client/_api/simulators/integrations.py b/cognite/client/_api/simulators/integrations.py index bec2a32d65..71b34245e3 100644 --- a/cognite/client/_api/simulators/integrations.py +++ b/cognite/client/_api/simulators/integrations.py @@ -1,7 +1,7 @@ from __future__ import annotations from collections.abc import Sequence -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from cognite.client._api_client import APIClient from cognite.client._constants import DEFAULT_LIMIT_READ @@ -33,13 +33,13 @@ def __init__(self, config: ClientConfig, api_version: str | None, cognite_client def list( self, limit: int = DEFAULT_LIMIT_READ, - filter: SimulatorIntegrationFilter | dict[str, Any] | None = None, + filter: SimulatorIntegrationFilter | None = None, ) -> SimulatorIntegrationList: """`Filter simulator integrations `_ Retrieves a list of simulator integrations that match the given criteria Args: limit (int): The maximum number of simulator integrations to return. - filter (SimulatorIntegrationFilter | dict[str, Any] | None): Filter to apply. + filter (SimulatorIntegrationFilter | None): Filter to apply. Returns: SimulatorIntegrationList: List of simulator integrations Examples: @@ -49,7 +49,9 @@ def list( >>> res = client.simulators.integrations.list() Filter integrations by active status: - >>> res = client.simulators.integrations.list(filter=SimulatorIntegrationFilter(active=True)) + >>> from cognite.client.data_classes.simulators.filters import SimulatorIntegrationFilter + >>> res = client.simulators.integrations.list( + ... filter=SimulatorIntegrationFilter(active=True)) """ self._warning.warn() @@ -64,13 +66,13 @@ def list( def delete( self, id: int | Sequence[int] | None = None, - external_id: str | SequenceNotStr[str] | None = None, + external_ids: str | SequenceNotStr[str] | SequenceNotStr[str] | None = None, ) -> None: """`Delete one or more integrations `_ Args: id (int | Sequence[int] | None): Id or list of ids - external_id (str | SequenceNotStr[str] | None): External ID or list of external ids + external_ids (str | SequenceNotStr[str] | SequenceNotStr[str] | None): No description. Examples: @@ -81,6 +83,6 @@ def delete( >>> client.simulators.integrations.delete(id=[1,2,3], external_id="foo") """ self._delete_multiple( - identifiers=IdentifierSequence.load(ids=id, external_ids=external_id), + identifiers=IdentifierSequence.load(ids=id, external_ids=external_ids), wrap_ids=True, ) diff --git a/cognite/client/_api_client.py b/cognite/client/_api_client.py index 578b0f9ec3..d38027196c 100644 --- a/cognite/client/_api_client.py +++ b/cognite/client/_api_client.py @@ -101,7 +101,7 @@ class APIClient: "geospatial/(compute|crs/byids|featuretypes/(byids|list))", "geospatial/featuretypes/[A-Za-z][A-Za-z0-9_]{0,31}/features/(aggregate|list|byids|search|search-streaming|[A-Za-z][A-Za-z0-9_]{0,255}/rasters/[A-Za-z][A-Za-z0-9_]{0,31})", "transformations/(filter|byids|jobs/byids|schedules/byids|query/run)", - "simulators/(list)", + "simulators/list", "extpipes/(list|byids|runs/list)", "workflows/.*", "hostedextractors/.*", diff --git a/cognite/client/data_classes/simulators/__init__.py b/cognite/client/data_classes/simulators/__init__.py index f79c243ddb..6c3fc96dc0 100644 --- a/cognite/client/data_classes/simulators/__init__.py +++ b/cognite/client/data_classes/simulators/__init__.py @@ -9,8 +9,6 @@ SimulatorStepField, SimulatorStepOption, SimulatorUnitEntry, - SimulatorWrite, - SimulatorWriteList, ) __all__ = [ @@ -22,6 +20,4 @@ "SimulatorStepField", "SimulatorStepOption", "SimulatorUnitEntry", - "SimulatorWrite", - "SimulatorWriteList", ] diff --git a/cognite/client/data_classes/simulators/simulators.py b/cognite/client/data_classes/simulators/simulators.py index 451b959ba4..4de225f15c 100644 --- a/cognite/client/data_classes/simulators/simulators.py +++ b/cognite/client/data_classes/simulators/simulators.py @@ -1,6 +1,5 @@ from __future__ import annotations -from abc import ABC from collections.abc import Sequence from dataclasses import dataclass from typing import TYPE_CHECKING, Any @@ -9,32 +8,30 @@ from cognite.client.data_classes._base import ( CogniteObject, + CogniteResource, CogniteResourceList, - ExternalIDTransformerMixin, IdTransformerMixin, - WriteableCogniteResource, - WriteableCogniteResourceList, ) -from cognite.client.utils.useful_types import SequenceNotStr if TYPE_CHECKING: from cognite.client import CogniteClient -class SimulatorCore(WriteableCogniteResource["SimulatorWrite"], ABC): +class Simulator(CogniteResource): """The simulator resource contains the definitions necessary for Cognite Data Fusion (CDF) to interact with a given simulator. It serves as a central contract that allows APIs, UIs, and integrations (connectors) to utilize the same definitions when dealing with a specific simulator. Each simulator is uniquely identified and can be associated with various file extension types, model types, step fields, and unit quantities. Simulators are essential for managing data - flows between CDF and external simulation tools, ensuring consistency and reliability in data handling. #### + flows between CDF and external simulation tools, ensuring consistency and reliability in data handling. This is the read/response format of the simulator. Args: external_id (str): External id of the simulator + id (int): No description. name (str): Name of the simulator - file_extension_types (str | SequenceNotStr[str]): File extension types supported by the simulator + file_extension_types (str | Sequence[str]): File extension types supported by the simulator model_types (SimulatorModelType | Sequence[SimulatorModelType] | None): Model types supported by the simulator step_fields (SimulatorStep | Sequence[SimulatorStep] | None): Step types supported by the simulator when creating routines unit_quantities (SimulatorQuantity | Sequence[SimulatorQuantity] | None): Quantities and their units supported by the simulator @@ -44,8 +41,9 @@ class SimulatorCore(WriteableCogniteResource["SimulatorWrite"], ABC): def __init__( self, external_id: str, + id: int, name: str, - file_extension_types: str | SequenceNotStr[str], + file_extension_types: str | Sequence[str], model_types: SimulatorModelType | Sequence[SimulatorModelType] | None = None, step_fields: SimulatorStep | Sequence[SimulatorStep] | None = None, unit_quantities: SimulatorQuantity | Sequence[SimulatorQuantity] | None = None, @@ -56,10 +54,12 @@ def __init__( self.model_types = model_types self.step_fields = step_fields self.unit_quantities = unit_quantities + self.id = id @classmethod def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self: return cls( + id=resource["id"], external_id=resource["externalId"], name=resource["name"], file_extension_types=resource["fileExtensionTypes"], @@ -88,87 +88,6 @@ def dump(self, camel_case: bool = True) -> dict[str, Any]: return output -class SimulatorWrite(SimulatorCore): - def __init__( - self, - external_id: str, - name: str, - file_extension_types: str | SequenceNotStr[str], - model_types: SimulatorModelType | Sequence[SimulatorModelType] | None = None, - step_fields: SimulatorStep | Sequence[SimulatorStep] | None = None, - unit_quantities: SimulatorQuantity | Sequence[SimulatorQuantity] | None = None, - ) -> None: - super().__init__( - external_id=external_id, - name=name, - file_extension_types=file_extension_types, - model_types=model_types, - step_fields=step_fields, - unit_quantities=unit_quantities, - ) - - def as_write(self) -> SimulatorWrite: - """Returns a writeable version of this resource""" - return self - - -class Simulator(SimulatorCore): - def __init__( - self, - external_id: str, - name: str, - file_extension_types: str | SequenceNotStr[str], - created_time: int | None = None, - last_updated_time: int | None = None, - id: int | None = None, - model_types: SimulatorModelType | Sequence[SimulatorModelType] | None = None, - step_fields: SimulatorStep | Sequence[SimulatorStep] | None = None, - unit_quantities: SimulatorQuantity | Sequence[SimulatorQuantity] | None = None, - ) -> None: - self.external_id = external_id - self.name = name - self.file_extension_types = file_extension_types - self.model_types = model_types - self.step_fields = step_fields - self.unit_quantities = unit_quantities - # id/created_time/last_updated_time are required when using the class to read, - # but don't make sense passing in when creating a new object. So in order to make the typing - # correct here (i.e. int and not Optional[int]), we force the type to be int rather than - # Optional[int]. - self.id: int | None = id - self.created_time: int | None = created_time - self.last_updated_time: int | None = last_updated_time - - @classmethod - def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self: - load = super()._load(resource, cognite_client) - return cls( - external_id=load.external_id, - name=load.name, - file_extension_types=load.file_extension_types, - created_time=resource.get("createdTime"), - last_updated_time=resource.get("lastUpdatedTime"), - id=resource.get("id"), - model_types=load.model_types, - step_fields=load.step_fields, - unit_quantities=load.unit_quantities, - ) - - def as_write(self) -> SimulatorWrite: - """Returns a writeable version of this resource""" - return SimulatorWrite( - external_id=self.external_id, - name=self.name, - file_extension_types=self.file_extension_types, - model_types=self.model_types, - step_fields=self.step_fields, - unit_quantities=self.unit_quantities, - ) - - def __hash__(self) -> int: - return hash(self.external_id) - - @dataclass class SimulatorRoutineStep(CogniteObject): step_type: str @@ -181,9 +100,6 @@ def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = arguments=resource["arguments"], ) - def dump(self, camel_case: bool = True) -> dict[str, Any]: - return super().dump(camel_case=camel_case) - @dataclass class SimulatorUnitEntry(CogniteObject): @@ -217,7 +133,7 @@ class SimulatorModelType(CogniteObject): key: str @classmethod - def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> SimulatorModelType: + def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self: return cls( name=resource["name"], key=resource["key"], @@ -317,18 +233,11 @@ def dump(self, camel_case: bool = True) -> dict[str, Any]: return output -class SimulatorWriteList(CogniteResourceList[SimulatorWrite], ExternalIDTransformerMixin): - _RESOURCE = SimulatorWrite - - -class SimulatorList(WriteableCogniteResourceList[SimulatorWrite, Simulator], IdTransformerMixin): +class SimulatorList(CogniteResourceList[Simulator], IdTransformerMixin): _RESOURCE = Simulator - def as_write(self) -> SimulatorWriteList: - return SimulatorWriteList([a.as_write() for a in self.data], cognite_client=self._get_cognite_client()) - -class SimulatorIntegrationCore(WriteableCogniteResource["SimulatorIntegrationWrite"], ABC): +class SimulatorIntegration(CogniteResource): """ The simulator integration resource represents a simulator connector in Cognite Data Fusion (CDF). It provides information about the configured connectors for a given simulator, including their status and additional @@ -336,11 +245,16 @@ class SimulatorIntegrationCore(WriteableCogniteResource["SimulatorIntegrationWri for monitoring and managing the interactions between CDF and external simulators, ensuring proper data flow and integration. This is the read/response format of the simulator integration. Args: + id (int): No description. external_id (str): External id of the simulator integration simulator_external_id (str): External id of the associated simulator heartbeat (int): The interval in seconds between the last heartbeat and the current time data_set_id (int): The id of the dataset associated with the simulator integration connector_version (str): The version of the connector + log_id (int): No description. + active (bool): No description. + created_time (int): No description. + last_updated_time (int): No description. license_status (str | None): The status of the license simulator_version (str | None): The version of the simulator license_last_checked_time (int | None): The time when the license was last checked @@ -350,17 +264,24 @@ class SimulatorIntegrationCore(WriteableCogniteResource["SimulatorIntegrationWri def __init__( self, + id: int, external_id: str, simulator_external_id: str, heartbeat: int, data_set_id: int, connector_version: str, + log_id: int, + active: bool, + created_time: int, + last_updated_time: int, license_status: str | None = None, simulator_version: str | None = None, license_last_checked_time: int | None = None, connector_status: str | None = None, connector_status_updated_time: int | None = None, ) -> None: + self.id = id # type: ignore + self.log_id = log_id self.external_id = external_id self.simulator_external_id = simulator_external_id self.heartbeat = heartbeat @@ -371,10 +292,16 @@ def __init__( self.license_last_checked_time = license_last_checked_time self.connector_status = connector_status self.connector_status_updated_time = connector_status_updated_time + self.active = active + self.created_time = created_time + self.last_updated_time = last_updated_time @classmethod def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self: return cls( + id=resource["id"], + active=resource["active"], + log_id=resource["logId"], external_id=resource["externalId"], simulator_external_id=resource["simulatorExternalId"], heartbeat=resource["heartbeat"], @@ -385,133 +312,10 @@ def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = license_last_checked_time=resource.get("licenseLastCheckedTime"), connector_status=resource.get("connectorStatus"), connector_status_updated_time=resource.get("connectorStatusUpdatedTime"), + created_time=resource["createdTime"], + last_updated_time=resource["lastUpdatedTime"], ) - def dump(self, camel_case: bool = True) -> dict[str, Any]: - return super().dump(camel_case=camel_case) - - -class SimulatorIntegrationWrite(SimulatorIntegrationCore): - def __init__( - self, - external_id: str, - simulator_external_id: str, - heartbeat: int, - data_set_id: int, - connector_version: str, - license_status: str | None = None, - simulator_version: str | None = None, - license_last_checked_time: int | None = None, - connector_status: str | None = None, - connector_status_updated_time: int | None = None, - ) -> None: - super().__init__( - external_id=external_id, - simulator_external_id=simulator_external_id, - heartbeat=heartbeat, - data_set_id=data_set_id, - connector_version=connector_version, - license_status=license_status, - simulator_version=simulator_version, - license_last_checked_time=license_last_checked_time, - connector_status=connector_status, - connector_status_updated_time=connector_status_updated_time, - ) - - def as_write(self) -> SimulatorIntegrationWrite: - """Returns a writeable version of this resource""" - return self - -class SimulatorIntegration(SimulatorIntegrationCore): - def __init__( - self, - external_id: str, - simulator_external_id: str, - heartbeat: int, - data_set_id: int, - connector_version: str, - license_status: str | None = None, - simulator_version: str | None = None, - license_last_checked_time: int | None = None, - connector_status: str | None = None, - connector_status_updated_time: int | None = None, - created_time: int | None = None, - last_updated_time: int | None = None, - id: int | None = None, - active: bool | None = None, - log_id: int | None = None, - ) -> None: - self.external_id = external_id - self.simulator_external_id = simulator_external_id - self.heartbeat = heartbeat - self.data_set_id = data_set_id - self.connector_version = connector_version - self.license_status = license_status - self.simulator_version = simulator_version - self.license_last_checked_time = license_last_checked_time - self.connector_status = connector_status - self.connector_status_updated_time = connector_status_updated_time - # id/created_time/last_updated_time are required when using the class to read, - # but don't make sense passing in when creating a new object. So in order to make the typing - # correct here (i.e. int and not Optional[int]), we force the type to be int rather than - # Optional[int]. - self.id: int | None = id - self.created_time: int | None = created_time - self.last_updated_time: int | None = last_updated_time - self.active = active - self.log_id = log_id - - @classmethod - def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self: - load = super()._load(resource, cognite_client) - return cls( - external_id=load.external_id, - simulator_external_id=load.simulator_external_id, - heartbeat=load.heartbeat, - data_set_id=load.data_set_id, - connector_version=load.connector_version, - license_status=load.license_status, - simulator_version=load.simulator_version, - license_last_checked_time=load.license_last_checked_time, - connector_status=load.connector_status, - connector_status_updated_time=load.connector_status_updated_time, - created_time=resource.get("createdTime"), - last_updated_time=resource.get("lastUpdatedTime"), - id=resource.get("id"), - active=resource.get("active"), - log_id=resource.get("logId"), - ) - - def as_write(self) -> SimulatorIntegrationWrite: - """Returns a writeable version of this resource""" - return SimulatorIntegrationWrite( - external_id=self.external_id, - simulator_external_id=self.simulator_external_id, - heartbeat=self.heartbeat, - data_set_id=self.data_set_id, - connector_version=self.connector_version, - license_status=self.license_status, - simulator_version=self.simulator_version, - license_last_checked_time=self.license_last_checked_time, - connector_status=self.connector_status, - connector_status_updated_time=self.connector_status_updated_time, - ) - - def __hash__(self) -> int: - return hash(self.external_id) - - -class SimulatorIntegrationWriteList(CogniteResourceList[SimulatorIntegrationWrite], ExternalIDTransformerMixin): - _RESOURCE = SimulatorIntegrationWrite - - -class SimulatorIntegrationList( - WriteableCogniteResourceList[SimulatorIntegrationWrite, SimulatorIntegration], IdTransformerMixin -): +class SimulatorIntegrationList(CogniteResourceList[SimulatorIntegration], IdTransformerMixin): _RESOURCE = SimulatorIntegration - - def as_write(self) -> SimulatorIntegrationWriteList: - return SimulatorIntegrationWriteList( - [a.as_write() for a in self.data], cognite_client=self._get_cognite_client() - ) diff --git a/tests/tests_integration/test_api/test_simulators/conftest.py b/tests/tests_integration/test_api/test_simulators/conftest.py index 33b08487c4..6eef54ea45 100644 --- a/tests/tests_integration/test_api/test_simulators/conftest.py +++ b/tests/tests_integration/test_api/test_simulators/conftest.py @@ -5,45 +5,44 @@ import pytest from cognite.client._cognite_client import CogniteClient -from cognite.client.exceptions import CogniteAPIError from tests.tests_integration.test_api.test_simulators.seed.data import resource_names, simulator, simulator_integration -@pytest.fixture(scope="class") -def seed_resource_names() -> dict[str, str]: - return resource_names +@pytest.fixture(scope="session") +def seed_resource_names(cognite_client: CogniteClient) -> dict[str, str]: + seed_data_set_external_id = resource_names["simulator_test_data_set_external_id"] + data_set = cognite_client.data_sets.retrieve(external_id=seed_data_set_external_id) + if not data_set: + data_set = cognite_client.data_sets.create( + external_id=seed_data_set_external_id, name=seed_data_set_external_id + ) + + resource_names["simulator_test_data_set_id"] = data_set.id + return resource_names.copy() -@pytest.fixture +@pytest.fixture(scope="session") def seed_simulator(cognite_client: CogniteClient, seed_resource_names) -> None: simulator_external_id = seed_resource_names["simulator_external_id"] - simulators = cognite_client.simulators.list() - simulator_exists = len(list(filter(lambda x: x.external_id == simulator_external_id, simulators))) > 0 - if not simulator_exists: - cognite_client.post( - f"/api/v1/projects/{cognite_client.config.project}/simulators", - json={"items": [simulator]}, - ) + simulators = cognite_client.simulators.list(limit=None) + if not simulators.get(external_id=simulator_external_id): + cognite_client.simulators._post("/simulators", json={"items": [simulator]}) -@pytest.fixture +@pytest.fixture(scope="session") def seed_simulator_integration(cognite_client: CogniteClient, seed_simulator) -> None: - try: + # try: + simulator_integrations = cognite_client.simulators.integrations.list(limit=None) + if not simulator_integrations.get(external_id=simulator_integration["externalId"]): simulator_integration["heartbeat"] = int(time.time() * 1000) - cognite_client.post( - f"/api/v1/projects/{cognite_client.config.project}/simulators/integrations", + cognite_client.simulators._post( + "/simulators/integrations", json={"items": [simulator_integration]}, ) - except CogniteAPIError: - simulator_integrations = cognite_client.simulators.integrations.list() - integration_id = next( - filter( - lambda x: x.external_id == simulator_integration["externalId"], - simulator_integrations, - ) - ).id + else: + integration = simulator_integrations.get(external_id=simulator_integration["externalId"]) # update hearbeat instead - cognite_client.post( - f"/api/v1/projects/{cognite_client.config.project}/simulators/integrations/update", - json={"items": [{"id": integration_id, "update": {"heartbeat": {"set": int(time.time() * 1000)}}}]}, + cognite_client.simulators.integrations._post( + "/simulators/integrations/update", + json={"items": [{"id": integration.id, "update": {"heartbeat": {"set": int(time.time() * 1000)}}}]}, ) diff --git a/tests/tests_integration/test_api/test_simulators/seed/data.py b/tests/tests_integration/test_api/test_simulators/seed/data.py index e9c024c17c..717799c2a0 100644 --- a/tests/tests_integration/test_api/test_simulators/seed/data.py +++ b/tests/tests_integration/test_api/test_simulators/seed/data.py @@ -1,6 +1,8 @@ # This file contains the data used to seed the test environment for the simulator tests - -data_set_id = 1521375514069 +# 97552494921583 +# data_set_id = 1521375514069 +# data_set_id = 97552494921583 +data_set_external_id = "dwsim_test" resource_names = { "simulator_external_id": "py_sdk_integration_tests", @@ -10,7 +12,8 @@ "simulator_model_file_external_id": "ShowerMixer_simulator_model_file_3", "simulator_routine_external_id": "pysdk_routine", "simulator_routine_revision_external_id": "pysdk_routine_revision", - "simulator_test_data_set_id": data_set_id, + "simulator_test_data_set_id": None, + "simulator_test_data_set_external_id": data_set_external_id, } simulator = { diff --git a/tests/tests_integration/test_api/test_simulators/test_integrations.py b/tests/tests_integration/test_api/test_simulators/test_integrations.py index 68ee9a0a67..49f34ecf22 100644 --- a/tests/tests_integration/test_api/test_simulators/test_integrations.py +++ b/tests/tests_integration/test_api/test_simulators/test_integrations.py @@ -27,8 +27,10 @@ def test_filter_integrations(self, cognite_client: CogniteClient, seed_resource_ assert len(all_integrations) > 0 assert filtered_integrations[0].external_id == seed_resource_names["simulator_integration_external_id"] - # check time difference + assert filtered_integrations[0].data_set_id == seed_resource_names["simulator_test_data_set_id"] assert filtered_integrations[0].active is True + assert filtered_integrations[0].created_time is not None + assert filtered_integrations[0].last_updated_time is not None assert len(active_integrations) > 0 assert len(filtered_integrations) > 0 @@ -36,27 +38,17 @@ def test_filter_integrations(self, cognite_client: CogniteClient, seed_resource_ def test_delete_integrations(self, cognite_client: CogniteClient, seed_resource_names) -> None: simulator_integration["heartbeat"] = int(time.time() * 1000) simulator_integration["externalId"] = random_string(50) + simulator_integration["dataSetId"] = seed_resource_names["simulator_test_data_set_id"] - cognite_client.post( - f"/api/v1/projects/{cognite_client.config.project}/simulators/integrations", + cognite_client.simulators._post( + "/simulators/integrations", json={"items": [simulator_integration]}, ) - all_integrations = cognite_client.simulators.integrations.list() - assert len(all_integrations) > 0 - assert any( - map( - lambda x: x.external_id == simulator_integration["externalId"], - all_integrations, - ) - ) + all_integrations = cognite_client.simulators.integrations.list(limit=None) + assert all_integrations.get(external_id=simulator_integration["externalId"]) is not None cognite_client.simulators.integrations.delete(external_id=simulator_integration["externalId"]) - all_integrations = cognite_client.simulators.integrations.list() - assert not any( - map( - lambda x: x.external_id == simulator_integration["externalId"], - all_integrations, - ) - ) + all_integrations = cognite_client.simulators.integrations.list(limit=None) + assert all_integrations.get(external_id=simulator_integration["externalId"]) is None diff --git a/tests/tests_integration/test_api/test_simulators/test_simulators.py b/tests/tests_integration/test_api/test_simulators/test_simulators.py index 81b417ae84..6030c438eb 100644 --- a/tests/tests_integration/test_api/test_simulators/test_simulators.py +++ b/tests/tests_integration/test_api/test_simulators/test_simulators.py @@ -7,5 +7,7 @@ class TestSimulators: def test_list_simulators(self, cognite_client: CogniteClient) -> None: simulators = cognite_client.simulators.list(limit=5) + for simulator in simulators: + assert simulator.id is not None assert len(simulators) > 0 diff --git a/tests/tests_unit/test_api_client.py b/tests/tests_unit/test_api_client.py index fa72aa3ece..0cfbb30075 100644 --- a/tests/tests_unit/test_api_client.py +++ b/tests/tests_unit/test_api_client.py @@ -1368,6 +1368,7 @@ def test_is_retryable_resource_api_endpoints(self, api_client_with_token, method ("GET", "https://api.cognitedata.com/api/v1/projects/bla/context/diagram/detect/456", True), # Simulators ("POST", "https://api.cognitedata.com/api/v1/projects/bla/simulators/list", True), + ("POST", "https://api.cognitedata.com/api/v1/projects/bla/simulators/delete", False), ] ), ) From 4c839b3fdcaef0eadf6099eb675da42f2e2bb09e Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Sun, 5 Jan 2025 19:36:09 +0000 Subject: [PATCH 16/20] data-set creation update --- cognite/client/_api/simulators/__init__.py | 12 ++++++++++++ .../test_api/test_simulators/conftest.py | 15 ++++++++++----- .../test_api/test_simulators/seed/data.py | 2 +- .../test_api/test_simulators/test_integrations.py | 4 ++-- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/cognite/client/_api/simulators/__init__.py b/cognite/client/_api/simulators/__init__.py index 73e83ce68a..85e5891f1c 100644 --- a/cognite/client/_api/simulators/__init__.py +++ b/cognite/client/_api/simulators/__init__.py @@ -7,6 +7,8 @@ from cognite.client._constants import DEFAULT_LIMIT_READ from cognite.client.data_classes.simulators.simulators import Simulator, SimulatorList from cognite.client.utils._experimental import FeaturePreviewWarning +from cognite.client.utils._identifier import IdentifierSequence +from cognite.client.utils.useful_types import SequenceNotStr if TYPE_CHECKING: from cognite.client import CogniteClient @@ -45,3 +47,13 @@ def list(self, limit: int = DEFAULT_LIMIT_READ) -> SimulatorList: """ self._warning.warn() return self._list(method="POST", limit=limit, resource_cls=Simulator, list_cls=SimulatorList) + + # Temporary method, remove this when the API is ready + def delete( + self, + external_ids: str | SequenceNotStr[str] | SequenceNotStr[str] | None = None, + ) -> None: + self._delete_multiple( + identifiers=IdentifierSequence.load(external_ids=external_ids), + wrap_ids=True, + ) diff --git a/tests/tests_integration/test_api/test_simulators/conftest.py b/tests/tests_integration/test_api/test_simulators/conftest.py index 6eef54ea45..fa570b18bd 100644 --- a/tests/tests_integration/test_api/test_simulators/conftest.py +++ b/tests/tests_integration/test_api/test_simulators/conftest.py @@ -5,6 +5,7 @@ import pytest from cognite.client._cognite_client import CogniteClient +from cognite.client.data_classes.data_sets import DataSetWrite from tests.tests_integration.test_api.test_simulators.seed.data import resource_names, simulator, simulator_integration @@ -13,10 +14,10 @@ def seed_resource_names(cognite_client: CogniteClient) -> dict[str, str]: seed_data_set_external_id = resource_names["simulator_test_data_set_external_id"] data_set = cognite_client.data_sets.retrieve(external_id=seed_data_set_external_id) if not data_set: - data_set = cognite_client.data_sets.create( - external_id=seed_data_set_external_id, name=seed_data_set_external_id + data_sets = cognite_client.data_sets.create( + [DataSetWrite(external_id=seed_data_set_external_id, name=seed_data_set_external_id)] ) - + data_set = data_sets[0] resource_names["simulator_test_data_set_id"] = data_set.id return resource_names.copy() @@ -26,15 +27,19 @@ def seed_simulator(cognite_client: CogniteClient, seed_resource_names) -> None: simulator_external_id = seed_resource_names["simulator_external_id"] simulators = cognite_client.simulators.list(limit=None) if not simulators.get(external_id=simulator_external_id): + simulator["dataSetId"] = seed_resource_names["simulator_test_data_set_id"] cognite_client.simulators._post("/simulators", json={"items": [simulator]}) + yield + # print("Deleting simulator") + cognite_client.simulators.delete(external_ids=simulator_external_id) @pytest.fixture(scope="session") -def seed_simulator_integration(cognite_client: CogniteClient, seed_simulator) -> None: - # try: +def seed_simulator_integration(cognite_client: CogniteClient, seed_simulator, seed_resource_names) -> None: simulator_integrations = cognite_client.simulators.integrations.list(limit=None) if not simulator_integrations.get(external_id=simulator_integration["externalId"]): simulator_integration["heartbeat"] = int(time.time() * 1000) + simulator_integration["dataSetId"] = seed_resource_names["simulator_test_data_set_id"] cognite_client.simulators._post( "/simulators/integrations", json={"items": [simulator_integration]}, diff --git a/tests/tests_integration/test_api/test_simulators/seed/data.py b/tests/tests_integration/test_api/test_simulators/seed/data.py index 717799c2a0..f964d99aa3 100644 --- a/tests/tests_integration/test_api/test_simulators/seed/data.py +++ b/tests/tests_integration/test_api/test_simulators/seed/data.py @@ -2,7 +2,7 @@ # 97552494921583 # data_set_id = 1521375514069 # data_set_id = 97552494921583 -data_set_external_id = "dwsim_test" +data_set_external_id = "sdk_tests_dwsim1" resource_names = { "simulator_external_id": "py_sdk_integration_tests", diff --git a/tests/tests_integration/test_api/test_simulators/test_integrations.py b/tests/tests_integration/test_api/test_simulators/test_integrations.py index 49f34ecf22..e365f91a97 100644 --- a/tests/tests_integration/test_api/test_simulators/test_integrations.py +++ b/tests/tests_integration/test_api/test_simulators/test_integrations.py @@ -27,7 +27,7 @@ def test_filter_integrations(self, cognite_client: CogniteClient, seed_resource_ assert len(all_integrations) > 0 assert filtered_integrations[0].external_id == seed_resource_names["simulator_integration_external_id"] - assert filtered_integrations[0].data_set_id == seed_resource_names["simulator_test_data_set_id"] + # assert filtered_integrations[0].data_set_id == seed_resource_names["simulator_test_data_set_id"] assert filtered_integrations[0].active is True assert filtered_integrations[0].created_time is not None assert filtered_integrations[0].last_updated_time is not None @@ -48,7 +48,7 @@ def test_delete_integrations(self, cognite_client: CogniteClient, seed_resource_ all_integrations = cognite_client.simulators.integrations.list(limit=None) assert all_integrations.get(external_id=simulator_integration["externalId"]) is not None - cognite_client.simulators.integrations.delete(external_id=simulator_integration["externalId"]) + cognite_client.simulators.integrations.delete(external_ids=simulator_integration["externalId"]) all_integrations = cognite_client.simulators.integrations.list(limit=None) assert all_integrations.get(external_id=simulator_integration["externalId"]) is None From 7bee982ce34da80b4d761527c5ef567d0893e2f4 Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Sun, 5 Jan 2025 19:40:37 +0000 Subject: [PATCH 17/20] data-set creation update --- tests/tests_integration/test_api/test_simulators/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/tests_integration/test_api/test_simulators/conftest.py b/tests/tests_integration/test_api/test_simulators/conftest.py index fa570b18bd..2e3dd11a86 100644 --- a/tests/tests_integration/test_api/test_simulators/conftest.py +++ b/tests/tests_integration/test_api/test_simulators/conftest.py @@ -27,7 +27,6 @@ def seed_simulator(cognite_client: CogniteClient, seed_resource_names) -> None: simulator_external_id = seed_resource_names["simulator_external_id"] simulators = cognite_client.simulators.list(limit=None) if not simulators.get(external_id=simulator_external_id): - simulator["dataSetId"] = seed_resource_names["simulator_test_data_set_id"] cognite_client.simulators._post("/simulators", json={"items": [simulator]}) yield # print("Deleting simulator") From 7f6ae3fb98e8660cd9e0744ff98218f8f3d6eadb Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Sun, 5 Jan 2025 19:45:51 +0000 Subject: [PATCH 18/20] data-set creation update --- cognite/client/_api/simulators/__init__.py | 10 ---------- .../test_api/test_simulators/conftest.py | 3 --- 2 files changed, 13 deletions(-) diff --git a/cognite/client/_api/simulators/__init__.py b/cognite/client/_api/simulators/__init__.py index 85e5891f1c..c92725933b 100644 --- a/cognite/client/_api/simulators/__init__.py +++ b/cognite/client/_api/simulators/__init__.py @@ -47,13 +47,3 @@ def list(self, limit: int = DEFAULT_LIMIT_READ) -> SimulatorList: """ self._warning.warn() return self._list(method="POST", limit=limit, resource_cls=Simulator, list_cls=SimulatorList) - - # Temporary method, remove this when the API is ready - def delete( - self, - external_ids: str | SequenceNotStr[str] | SequenceNotStr[str] | None = None, - ) -> None: - self._delete_multiple( - identifiers=IdentifierSequence.load(external_ids=external_ids), - wrap_ids=True, - ) diff --git a/tests/tests_integration/test_api/test_simulators/conftest.py b/tests/tests_integration/test_api/test_simulators/conftest.py index 2e3dd11a86..cd93be94ff 100644 --- a/tests/tests_integration/test_api/test_simulators/conftest.py +++ b/tests/tests_integration/test_api/test_simulators/conftest.py @@ -28,9 +28,6 @@ def seed_simulator(cognite_client: CogniteClient, seed_resource_names) -> None: simulators = cognite_client.simulators.list(limit=None) if not simulators.get(external_id=simulator_external_id): cognite_client.simulators._post("/simulators", json={"items": [simulator]}) - yield - # print("Deleting simulator") - cognite_client.simulators.delete(external_ids=simulator_external_id) @pytest.fixture(scope="session") From 640c110529d93968f6020e3f3273f85f06757eae Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Sun, 5 Jan 2025 19:51:14 +0000 Subject: [PATCH 19/20] data-set creation update --- cognite/client/_api/simulators/__init__.py | 2 -- cognite/client/data_classes/simulators/simulators.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/cognite/client/_api/simulators/__init__.py b/cognite/client/_api/simulators/__init__.py index c92725933b..73e83ce68a 100644 --- a/cognite/client/_api/simulators/__init__.py +++ b/cognite/client/_api/simulators/__init__.py @@ -7,8 +7,6 @@ from cognite.client._constants import DEFAULT_LIMIT_READ from cognite.client.data_classes.simulators.simulators import Simulator, SimulatorList from cognite.client.utils._experimental import FeaturePreviewWarning -from cognite.client.utils._identifier import IdentifierSequence -from cognite.client.utils.useful_types import SequenceNotStr if TYPE_CHECKING: from cognite.client import CogniteClient diff --git a/cognite/client/data_classes/simulators/simulators.py b/cognite/client/data_classes/simulators/simulators.py index 4de225f15c..15fd0b2631 100644 --- a/cognite/client/data_classes/simulators/simulators.py +++ b/cognite/client/data_classes/simulators/simulators.py @@ -280,7 +280,7 @@ def __init__( connector_status: str | None = None, connector_status_updated_time: int | None = None, ) -> None: - self.id = id # type: ignore + self.id = id self.log_id = log_id self.external_id = external_id self.simulator_external_id = simulator_external_id From 1475737695cd8d081811139e6d1e37924c95ef62 Mon Sep 17 00:00:00 2001 From: abdullah-cognite <100700755+abdullah-cognite@users.noreply.github.com> Date: Sun, 5 Jan 2025 20:43:29 +0000 Subject: [PATCH 20/20] replace datasetids with externalid --- tests/tests_integration/test_api/test_simulators/seed/data.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/tests_integration/test_api/test_simulators/seed/data.py b/tests/tests_integration/test_api/test_simulators/seed/data.py index f964d99aa3..757d286444 100644 --- a/tests/tests_integration/test_api/test_simulators/seed/data.py +++ b/tests/tests_integration/test_api/test_simulators/seed/data.py @@ -1,7 +1,4 @@ # This file contains the data used to seed the test environment for the simulator tests -# 97552494921583 -# data_set_id = 1521375514069 -# data_set_id = 97552494921583 data_set_external_id = "sdk_tests_dwsim1" resource_names = {