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

Extend Data Model #120

Merged
merged 12 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ Changes are grouped as follows
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.

## [0.35.0] - 2023-08-11

### Added
* Extended the `Production` data model with a relation from `Plant`to `Watercourse`.


## [0.34.1] - 2023-08-11

### Fixed
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Cognite PowerOps SDK
==========================
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
[![release](https://img.shields.io/github/actions/workflow/status/cognitedata/power-ops-sdk/release.yml?style=for-the-badge)](https://github.com/cognitedata/power-ops/actions/workflows/release.yml)
[![Documentation Status](https://readthedocs.com/projects/cognite-power-ops-sdk/badge/?version=latest&style=for-the-badge)](https://cognite-power-ops-sdk.readthedocs-hosted.com/en/latest/?badge=latest)
[![Github](https://shields.io/badge/github-cognite/power_ops_sdk-green?logo=github&style=for-the-badge)](https://github.com/cognitedata/power-ops-sdk)
[![PyPI](https://img.shields.io/pypi/v/cognite-power-ops?style=for-the-badge)](https://pypi.org/project/cognite-power-ops/)
[![Downloads](https://img.shields.io/pypi/dm/cognite-power-ops?style=for-the-badge)](https://pypistats.org/packages/cognite-power-ops)
[![GitHub](https://img.shields.io/github/license/cognitedata/power-ops-sdk?style=for-the-badge)](https://github.com/cognitedata/power-ops-sdk/blob/master/LICENSE)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge)](https://github.com/ambv/black)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json&style=for-the-badge)](https://github.com/astral-sh/ruff)
[![mypy](https://img.shields.io/badge/mypy-checked-000000.svg?style=for-the-badge&color=blue)](http://mypy-lang.org)


[Documentation](https://cognite-power-ops-sdk.readthedocs-hosted.com/en/latest/)

Expand Down
2 changes: 1 addition & 1 deletion cognite/powerops/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.34.1"
__version__ = "0.35.0"
3 changes: 1 addition & 2 deletions cognite/powerops/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,8 @@ def deploy(

model = MODEL_BY_NAME[model_name]
result = client.cdf.data_modeling.graphql.apply_dml(
# The removal of newlines is done to avoid a bug in the GraphQL API.
model.id_,
model.graphql.replace("\n", " "),
model.graphql,
model.name,
model.description,
)
Expand Down
26 changes: 19 additions & 7 deletions cognite/powerops/clients/_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,19 @@ class PowerOpsClient:
PowerOpsClient

Generated with:
pygen = 0.12.3
cognite-sdk = 6.11.1
pydantic = 2.0.3
pygen = 0.15.2
cognite-sdk = 6.13.3
pydantic = 2.1.1

"""

def __init__(self, config: ClientConfig | None = None):
client = CogniteClient(config)
def __init__(self, config_or_client: CogniteClient | ClientConfig):
if isinstance(config_or_client, CogniteClient):
client = config_or_client
elif isinstance(config_or_client, ClientConfig):
client = CogniteClient(config_or_client)
else:
raise ValueError(f"Expected CogniteClient or ClientConfig, got {type(config_or_client)}")
self.benchmark = BenchmarkAPIs(client)
self.cog_shop = CogShopAPIs(client)
self.day_ahead = DayAheadAPIs(client)
Expand All @@ -178,7 +183,14 @@ def azure_project(
return cls(config)

@classmethod
def from_toml(cls, file_path: Path | str) -> PowerOpsClient:
def from_toml(cls, file_path: Path | str, section: str | None = "cognite") -> PowerOpsClient:
import toml

return cls.azure_project(**toml.load(file_path)["cognite"])
toml_content = toml.load(file_path)
if section is not None:
try:
toml_content = toml_content[section]
except KeyError:
raise ValueError(f"Could not find section '{section}' in {file_path}")

return cls.azure_project(**toml_content)
9 changes: 5 additions & 4 deletions cognite/powerops/clients/data_classes/_benchmark_bids.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from cognite.client import data_modeling as dm
from pydantic import Field

from cognite.powerops.clients.data_classes._core import DomainModel, DomainModelApply, InstancesApply, TypeList
from cognite.powerops.clients.data_classes._core import DomainModel, DomainModelApply, TypeList

if TYPE_CHECKING:
from cognite.powerops.clients.data_classes._date_transformations import DateTransformationApply
Expand All @@ -27,9 +27,9 @@ class BenchmarkBidApply(DomainModelApply):
market: Optional[Union["NordPoolMarketApply", str]] = Field(None, repr=False)
name: Optional[str] = None

def _to_instances_apply(self, cache: set[str]) -> InstancesApply:
def _to_instances_apply(self, cache: set[str]) -> dm.InstancesApply:
if self.external_id in cache:
return InstancesApply([], [])
return dm.InstancesApply(dm.NodeApplyList([]), dm.EdgeApplyList([]))

sources = []
source = dm.NodeOrEdgeData(
Expand All @@ -52,6 +52,7 @@ def _to_instances_apply(self, cache: set[str]) -> InstancesApply:
)
nodes = [this_node]
edges = []
cache.add(self.external_id)

for date in self.date:
edge = self._create_date_edge(date)
Expand All @@ -69,7 +70,7 @@ def _to_instances_apply(self, cache: set[str]) -> InstancesApply:
nodes.extend(instances.nodes)
edges.extend(instances.edges)

return InstancesApply(nodes, edges)
return dm.InstancesApply(dm.NodeApplyList(nodes), dm.EdgeApplyList(edges))

def _create_date_edge(self, date: Union[str, "DateTransformationApply"]) -> dm.EdgeApply:
if isinstance(date, str):
Expand Down
9 changes: 5 additions & 4 deletions cognite/powerops/clients/data_classes/_benchmark_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from cognite.client import data_modeling as dm
from pydantic import Field

from cognite.powerops.clients.data_classes._core import DomainModel, DomainModelApply, InstancesApply, TypeList
from cognite.powerops.clients.data_classes._core import DomainModel, DomainModelApply, TypeList

if TYPE_CHECKING:
from cognite.powerops.clients.data_classes._benchmark_bids import BenchmarkBidApply
Expand Down Expand Up @@ -36,9 +36,9 @@ class BenchmarkProcesApply(DomainModelApply):
run_events: list[str] = []
shop: Optional[Union["ShopTransformationApply", str]] = Field(None, repr=False)

def _to_instances_apply(self, cache: set[str]) -> InstancesApply:
def _to_instances_apply(self, cache: set[str]) -> dm.InstancesApply:
if self.external_id in cache:
return InstancesApply([], [])
return dm.InstancesApply(dm.NodeApplyList([]), dm.EdgeApplyList([]))

sources = []
source = dm.NodeOrEdgeData(
Expand Down Expand Up @@ -67,6 +67,7 @@ def _to_instances_apply(self, cache: set[str]) -> InstancesApply:
)
nodes = [this_node]
edges = []
cache.add(self.external_id)

for production_plan_time_series in self.production_plan_time_series:
edge = self._create_production_plan_time_series_edge(production_plan_time_series)
Expand All @@ -89,7 +90,7 @@ def _to_instances_apply(self, cache: set[str]) -> InstancesApply:
nodes.extend(instances.nodes)
edges.extend(instances.edges)

return InstancesApply(nodes, edges)
return dm.InstancesApply(dm.NodeApplyList(nodes), dm.EdgeApplyList(edges))

def _create_production_plan_time_series_edge(
self, production_plan_time_series: Union[str, "ProductionPlanTimeSeriesApply"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from cognite.client import data_modeling as dm
from pydantic import Field

from cognite.powerops.clients.data_classes._core import DomainModel, DomainModelApply, InstancesApply, TypeList
from cognite.powerops.clients.data_classes._core import DomainModel, DomainModelApply, TypeList

__all__ = ["BidMatrixGenerator", "BidMatrixGeneratorApply", "BidMatrixGeneratorList"]

Expand All @@ -23,9 +23,9 @@ class BidMatrixGeneratorApply(DomainModelApply):
methods: Optional[str] = None
shop_plant: Optional[str] = None

def _to_instances_apply(self, cache: set[str]) -> InstancesApply:
def _to_instances_apply(self, cache: set[str]) -> dm.InstancesApply:
if self.external_id in cache:
return InstancesApply([], [])
return dm.InstancesApply(dm.NodeApplyList([]), dm.EdgeApplyList([]))

sources = []
source = dm.NodeOrEdgeData(
Expand All @@ -46,8 +46,9 @@ def _to_instances_apply(self, cache: set[str]) -> InstancesApply:
)
nodes = [this_node]
edges = []
cache.add(self.external_id)

return InstancesApply(nodes, edges)
return dm.InstancesApply(dm.NodeApplyList(nodes), dm.EdgeApplyList(edges))


class BidMatrixGeneratorList(TypeList[BidMatrixGenerator]):
Expand Down
9 changes: 5 additions & 4 deletions cognite/powerops/clients/data_classes/_command_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from cognite.client import data_modeling as dm

from cognite.powerops.clients.data_classes._core import DomainModel, DomainModelApply, InstancesApply, TypeList
from cognite.powerops.clients.data_classes._core import DomainModel, DomainModelApply, TypeList

__all__ = ["CommandConfig", "CommandConfigApply", "CommandConfigList"]

Expand All @@ -18,9 +18,9 @@ class CommandConfigApply(DomainModelApply):
space: ClassVar[str] = "power-ops"
commands: list[str] = []

def _to_instances_apply(self, cache: set[str]) -> InstancesApply:
def _to_instances_apply(self, cache: set[str]) -> dm.InstancesApply:
if self.external_id in cache:
return InstancesApply([], [])
return dm.InstancesApply(dm.NodeApplyList([]), dm.EdgeApplyList([]))

sources = []
source = dm.NodeOrEdgeData(
Expand All @@ -39,8 +39,9 @@ def _to_instances_apply(self, cache: set[str]) -> InstancesApply:
)
nodes = [this_node]
edges = []
cache.add(self.external_id)

return InstancesApply(nodes, edges)
return dm.InstancesApply(dm.NodeApplyList(nodes), dm.EdgeApplyList(edges))


class CommandConfigList(TypeList[CommandConfig]):
Expand Down
78 changes: 19 additions & 59 deletions cognite/powerops/clients/data_classes/_core.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,37 @@
from __future__ import annotations

import datetime
import types
from abc import abstractmethod
from collections import UserList
from collections.abc import Collection
from dataclasses import dataclass
from datetime import datetime
from typing import Any, ClassVar, Generic, List, Optional, TypeVar, Union
from collections.abc import Collection, Mapping
from typing import Any, ClassVar, Generic, Optional, TypeVar, Union

import pandas as pd
from cognite.client import data_modeling as dm
from cognite.client.data_classes.data_modeling.instances import Properties, PropertyValue
from pydantic import BaseModel, ConfigDict, Extra, constr

# Todo - Move into SDK


@dataclass
class InstancesApply:
"""This represents the read result of an instance query

Args:
nodes (dm.NodeApplyList): A list of nodes.
edges (dm.EdgeApply): A list of edges.

"""

nodes: list[dm.NodeApply]
edges: list[dm.EdgeApply]


ExternalId = constr(min_length=1, max_length=255)
from pydantic import BaseModel, ConfigDict, Extra, Field


class DomainModelCore(BaseModel):
space: ClassVar[str]
external_id: ExternalId
external_id: str = Field(min_length=1, max_length=255)

def id_tuple(self) -> tuple[str, str]:
return self.space, self.external_id


class DomainModel(DomainModelCore):
version: int
last_updated_time: datetime
created_time: datetime
deleted_time: Optional[datetime] = None
last_updated_time: datetime.datetime
created_time: datetime.datetime
deleted_time: Optional[datetime.datetime] = None

@classmethod
def from_node(cls, node: dm.Node) -> T_TypeNode:
def from_node(cls: type[T_TypeNode], node: dm.Node) -> T_TypeNode:
data = node.dump(camel_case=False)

return cls(**data, **unpack_properties(node.properties))
return cls(**data, **unpack_properties(node.properties)) # type: ignore[arg-type]

@classmethod
def one_to_many_fields(cls) -> list[str]:
Expand All @@ -69,19 +49,19 @@ class DomainModelApply(DomainModelCore):
model_config: ClassVar[ConfigDict] = ConfigDict(extra=Extra.forbid)
existing_version: Optional[int] = None

def to_instances_apply(self) -> InstancesApply:
def to_instances_apply(self) -> dm.InstancesApply:
return self._to_instances_apply(set())

@abstractmethod
def _to_instances_apply(self, cache: set[str]) -> InstancesApply:
def _to_instances_apply(self, cache: set[str]) -> dm.InstancesApply:
raise NotImplementedError()


class DomainModelApplyResult(DomainModelCore):
version: int
was_modified: bool
last_updated_time: datetime
created_time: datetime
last_updated_time: datetime.datetime
created_time: datetime.datetime


class DataPoint(BaseModel):
Expand All @@ -100,24 +80,20 @@ class TimeSeries(DomainModelCore):
id: Optional[int] = None
name: Optional[str] = None
is_string: bool = False
metadata: dict = {}
metadata: dict[str, str] = Field(default_factory=dict)
unit: Optional[str] = None
asset_id: Optional[int] = None
is_step: bool = False
description: Optional[str] = None
security_categories: Optional[str] = None
dataset_id: Optional[int] = None
data_points: Union[List[NumericDataPoint], List[StringDataPoint]]
data_points: Union[list[NumericDataPoint], list[StringDataPoint]]


class TypeList(UserList, Generic[T_TypeNode]):
_NODE: type[T_TypeNode]

def __init__(self, nodes: Collection[type[DomainModelCore]]):
# if any(not isinstance(node, self._NODE) for node in nodes):
# raise TypeError(
# f"All nodes for class {type(self).__name__} must be of type " f"{type(self._NODE).__name__}."
# )
super().__init__(nodes)

def dump(self) -> list[dict[str, Any]]:
Expand All @@ -127,31 +103,15 @@ def to_pandas(self) -> pd.DataFrame:
return pd.DataFrame(self.dump())

def _repr_html_(self) -> str:
return self.to_pandas()._repr_html_()
return self.to_pandas()._repr_html_() # type: ignore[operator]


T_TypeApplyNode = TypeVar("T_TypeApplyNode", bound=DomainModelApply)
T_TypeNodeList = TypeVar("T_TypeNodeList", bound=TypeList)


class Identifier(BaseModel):
_instance_type: ClassVar[str] = "node"
space: constr(min_length=1, max_length=255)
external_id: constr(min_length=1, max_length=255)

@classmethod
def from_direct_relation(cls, relation: dm.DirectRelationReference) -> T_Identifier:
return cls(space=relation.space, external_id=relation.external_id)

def __str__(self):
return f"{self.space}/{self.external_id}"


T_Identifier = TypeVar("T_Identifier", bound=Identifier)


def unpack_properties(properties: Properties) -> dict[str, PropertyValue]:
unpacked = {}
def unpack_properties(properties: Properties) -> Mapping[str, PropertyValue]:
unpacked: dict[str, PropertyValue] = {}
for view_properties in properties.values():
for prop_name, prop_value in view_properties.items():
if isinstance(prop_value, (str, int, float, bool, list)):
Expand Down
Loading