Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(workflows): Add support for simulation integration #1999

Merged
merged 35 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
d9aa2df
feat(workflows): Add support for simulation integration
lpereiracgn Oct 31, 2024
7af7c48
Merge branch 'master' into POFSP-744-workflows
lpereiracgn Oct 31, 2024
e774b84
bump version
lpereiracgn Oct 31, 2024
4f54bbb
fix: field rename for simint tasks
lpereiracgn Nov 12, 2024
bb14a52
fix: types for simint runs
lpereiracgn Nov 12, 2024
c81726d
fix: remove old test for simint integration
lpereiracgn Nov 12, 2024
7715f46
Merge branch 'master' into POFSP-744-workflows
lpereiracgn Nov 15, 2024
fa6e0da
fix lint
lpereiracgn Nov 20, 2024
f1956ca
Merge branch 'master' into POFSP-744-workflows
polomani Nov 26, 2024
2e12eaf
add the test back
polomani Nov 26, 2024
8f0b7aa
bump the version
polomani Nov 26, 2024
9c76886
fix: accommodate API changes
polomani Nov 29, 2024
33a1229
Merge branch 'master' into POFSP-744-workflows
polomani Nov 29, 2024
5afe4e7
chore: lint
polomani Nov 29, 2024
4cd9f96
Merge branch 'POFSP-744-workflows' of github.com:cognitedata/cognite-…
polomani Nov 29, 2024
b3f4e24
Merge branch 'master' into POFSP-744-workflows
polomani Nov 29, 2024
67644aa
Update cognite/client/data_classes/workflows.py
polomani Dec 1, 2024
d3ede59
Update cognite/client/data_classes/simulators.py
polomani Dec 1, 2024
5f47f76
chore: type
polomani Dec 1, 2024
0a097d7
Merge branch 'master' into POFSP-744-workflows
polomani Dec 2, 2024
64ac4ae
fix: type according to the spec
polomani Dec 2, 2024
8a3d818
Merge branch 'POFSP-744-workflows' of github.com:cognitedata/cognite-…
polomani Dec 2, 2024
072072e
Merge branch 'master' into POFSP-744-workflows
polomani Dec 2, 2024
af161f4
Merge branch 'POFSP-744-workflows' of github.com:cognitedata/cognite-…
polomani Dec 2, 2024
79310dd
fix: changelog
polomani Dec 2, 2024
d7608c2
tests: add e2e simint workflow test
polomani Dec 2, 2024
37608a0
fix: update workflow type
polomani Dec 2, 2024
bc432db
chore: fmt
polomani Dec 2, 2024
ae1c179
fix: move fixture
polomani Dec 2, 2024
1a3eb6c
fix: small fix
polomani Dec 2, 2024
0317216
fix: move class to a folder
polomani Dec 3, 2024
61e618f
fix: put a warning
polomani Dec 3, 2024
5896faf
Merge branch 'master' into POFSP-744-workflows
polomani Dec 3, 2024
ee8a10e
tests: move files around
polomani Dec 3, 2024
4ae69a2
Merge branch 'POFSP-744-workflows' of github.com:cognitedata/cognite-…
polomani Dec 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ Changes are grouped as follows
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.

## [7.70.0] - 2024-11-29
### Added
- Workflow support for "simulation" task type.

## [7.69.2] - 2024-11-28
### Improved
- Handle conversion of instance lists like NodeList to pandas DataFrame in scenarios where: a) properties are expanded
Expand Down
2 changes: 1 addition & 1 deletion cognite/client/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from __future__ import annotations

__version__ = "7.69.2"
__version__ = "7.70.0"
__api_subversion__ = "20230101"
4 changes: 4 additions & 0 deletions cognite/client/data_classes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,8 @@
DynamicTaskParameters,
FunctionTaskOutput,
FunctionTaskParameters,
SimulationTaskOutput,
SimulationTaskParameters,
SubworkflowTaskParameters,
TransformationTaskOutput,
TransformationTaskParameters,
Expand Down Expand Up @@ -473,6 +475,8 @@
"DatapointSubscriptionWriteList",
"OidcCredentials",
"RawTable",
"SimulationTaskParameters",
"SimulationTaskOutput",
"Transformation",
"TransformationWrite",
"TransformationBlockedInfo",
Expand Down
49 changes: 49 additions & 0 deletions cognite/client/data_classes/simulators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, Any

from typing_extensions import Self

from cognite.client.data_classes._base import (
CogniteObject,
)

if TYPE_CHECKING:
from cognite.client import CogniteClient


@dataclass
class SimulationValueUnitName(CogniteObject):
name: str

@classmethod
def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self:
return cls(
name=resource["name"],
)

def dump(self, camel_case: bool = True) -> dict[str, Any]:
return super().dump(camel_case=camel_case)


@dataclass
class SimulationInputOverride(CogniteObject):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the motivation to move this to a separate .py file?

Is it expected to be used with the simulator endpoints?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exactly. there will be many more simint classes added, @lpereiracgn is working on another PR for that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to move this into /data_classes/simulators/<something>.py? See for example what we did for hosted_extractors/. The thinking is that with more resources we start to get overlapping names, so we want to separate them into "namespace" which in python would be different packages.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved

reference_id: str
value: str | int | float
polomani marked this conversation as resolved.
Show resolved Hide resolved
unit: SimulationValueUnitName | None = None
polomani marked this conversation as resolved.
Show resolved Hide resolved

@classmethod
def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self:
return cls(
reference_id=resource["referenceId"],
value=resource["value"],
unit=SimulationValueUnitName._load(resource["unit"], cognite_client) if "unit" in resource else None,
)

def dump(self, camel_case: bool = True) -> dict[str, Any]:
output = super().dump(camel_case=camel_case)
if self.unit is not None:
output["unit"] = self.unit.dump(camel_case=camel_case)

return output
90 changes: 89 additions & 1 deletion cognite/client/data_classes/workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
WriteableCogniteResourceList,
)
from cognite.client.data_classes.data_modeling.query import Query, ResultSetExpression, Select
from cognite.client.data_classes.simulators import (
SimulationInputOverride,
)
from cognite.client.utils._text import convert_all_keys_to_camel_case, to_snake_case

if TYPE_CHECKING:
Expand Down Expand Up @@ -131,7 +134,7 @@ def as_write(self) -> WorkflowUpsertList:
return WorkflowUpsertList([workflow.as_write() for workflow in self.data])


ValidTaskType = Literal["function", "transformation", "cdf", "dynamic", "subworkflow"]
ValidTaskType = Literal["function", "transformation", "cdf", "dynamic", "subworkflow", "simulation"]


class WorkflowTaskParameters(CogniteObject, ABC):
Expand Down Expand Up @@ -159,6 +162,8 @@ def load_parameters(cls, data: dict) -> WorkflowTaskParameters:
return SubworkflowTaskParameters._load(parameters)
elif type_ == "subworkflow" and "workflowExternalId" in parameters["subworkflow"]:
return SubworkflowReferenceParameters._load(parameters)
elif type_ == "simulation":
return SimulationTaskParameters._load(parameters)
else:
raise ValueError(f"Unknown task type: {type_}. Expected {ValidTaskType}")

Expand Down Expand Up @@ -238,6 +243,49 @@ def dump(self, camel_case: bool = True) -> dict[str, Any]:
return output


class SimulationTaskParameters(WorkflowTaskParameters):
"""
The simulation parameters are used to specify the simulation routine to be executed.
Args:
routine_external_id (str): The external ID of the simulation routine to be executed.
run_time (int | None): Reference timestamp used for data pre-processing and data sampling.
inputs (list[SimulationInputOverride] | None): List of input overrides.
"""

task_type = "simulation"

def __init__(
self,
routine_external_id: str,
run_time: int | None = None,
polomani marked this conversation as resolved.
Show resolved Hide resolved
inputs: list[SimulationInputOverride] | None = None,
) -> None:
self.routine_external_id = routine_external_id
self.run_time = run_time
self.inputs = inputs

@classmethod
def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> SimulationTaskParameters:
simulation: dict[str, Any] = resource["simulation"]

return cls(
routine_external_id=simulation["routineExternalId"],
run_time=simulation.get("runTime"),
inputs=[SimulationInputOverride._load(item) for item in simulation.get("inputs", [])]
if simulation.get("inputs")
else None,
)

def dump(self, camel_case: bool = True) -> dict[str, Any]:
simulation = {
"routineExternalId" if camel_case else "routine_external_id": self.routine_external_id,
"runTime" if camel_case else "run_time": self.run_time,
"inputs": [item.dump(camel_case) for item in self.inputs] if self.inputs else None,
}
polomani marked this conversation as resolved.
Show resolved Hide resolved

return {"simulation": simulation}


class TransformationTaskParameters(WorkflowTaskParameters):
"""
The transformation parameters are used to specify the transformation to be called.
Expand Down Expand Up @@ -540,6 +588,8 @@ def load_output(cls, data: dict) -> WorkflowTaskOutput:
return DynamicTaskOutput.load(data)
elif task_type == "subworkflow":
return SubworkflowTaskOutput.load(data)
elif task_type == "simulation":
return SimulationTaskOutput.load(data)
else:
raise ValueError(f"Unknown task type: {task_type}")

Expand Down Expand Up @@ -579,6 +629,44 @@ def dump(self, camel_case: bool = True) -> dict[str, Any]:
}


class SimulationTaskOutput(WorkflowTaskOutput):
"""
The class represent the output of Simulation execution.
Args:
run_id (int | None): The run ID of the simulation run.
log_id (int | None): The log ID of the simulation run.
status_message (str | None): Status message of the simulation execution.
"""

task_type: ClassVar[str] = "simulation"

def __init__(
self,
run_id: int | None,
log_id: int | None,
status_message: str | None,
polomani marked this conversation as resolved.
Show resolved Hide resolved
) -> None:
self.run_id = run_id
self.log_id = log_id
self.status_message = status_message

@classmethod
def load(cls, data: dict[str, Any]) -> SimulationTaskOutput:
output = data["output"]
return cls(
run_id=output.get("runId"),
log_id=output.get("logId"),
status_message=output.get("statusMessage"),
)

def dump(self, camel_case: bool = True) -> dict[str, Any]:
return {
"runId" if camel_case else "run_id": self.run_id,
"logId" if camel_case else "log_id": self.log_id,
"statusMessage" if camel_case else "status_message": self.status_message,
}


class TransformationTaskOutput(WorkflowTaskOutput):
"""
The transformation output is used to specify the output of a transformation task.
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
[tool.poetry]
name = "cognite-sdk"

version = "7.69.2"
version = "7.70.0"

description = "Cognite Python SDK"
readme = "README.md"
documentation = "https://cognite-sdk-python.readthedocs-hosted.com"
Expand Down
38 changes: 38 additions & 0 deletions tests/tests_integration/test_api/test_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from cognite.client.data_classes.workflows import (
CDFTaskParameters,
FunctionTaskParameters,
SimulationTaskParameters,
SubworkflowReferenceParameters,
SubworkflowTaskParameters,
TransformationTaskParameters,
Expand Down Expand Up @@ -326,6 +327,43 @@ def test_list_workflows(self, cognite_client: CogniteClient, workflow_list: Work


class TestWorkflowVersions:
def test_upsert_delete_with_simulation_task(
self,
cognite_client: CogniteClient,
):
workflow_id = "integration_test-workflow_for_simulator_integration" + random_string(5)

version = WorkflowVersionUpsert(
workflow_external_id=workflow_id,
version="1",
workflow_definition=WorkflowDefinitionUpsert(
tasks=[
WorkflowTask(
external_id=f"{workflow_id}-1-task1" + random_string(5),
parameters=SimulationTaskParameters(
routine_external_id="integration_tests_workflow",
),
)
],
description=None,
),
)

cognite_client.workflows.versions.delete(version.as_id(), ignore_unknown_ids=True)
created_version: WorkflowVersion | None = None

try:
created_version = cognite_client.workflows.versions.upsert(version)
assert created_version.workflow_external_id == workflow_id
assert created_version.workflow_definition.tasks[0].type == "simulation"
assert len(created_version.workflow_definition.tasks) > 0
finally:
if created_version is not None:
cognite_client.workflows.versions.delete(
created_version.as_id(),
)
cognite_client.workflows.delete(created_version.workflow_external_id)

def test_upsert_preexisting(self, cognite_client: CogniteClient, new_workflow_version: WorkflowVersion) -> None:
new_workflow_version.workflow_definition.description = "Updated description for testing purposes"
updated_version = cognite_client.workflows.versions.upsert(new_workflow_version.as_write())
Expand Down