diff --git a/examples/windmill/__init__.py b/examples/windmill/__init__.py deleted file mode 100644 index 428145e51..000000000 --- a/examples/windmill/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from windmill._api_client import WindmillClient - -__all__ = ["WindmillClient"] diff --git a/examples/windmill/_api/__init__.py b/examples/windmill/_api/__init__.py deleted file mode 100644 index 1ddb05147..000000000 --- a/examples/windmill/_api/__init__.py +++ /dev/null @@ -1,129 +0,0 @@ -from windmill._api.blade import BladeAPI -from windmill._api.blade_query import BladeQueryAPI -from windmill._api.blade_sensor_positions import BladeSensorPositionsAPI -from windmill._api.gearbox import GearboxAPI -from windmill._api.gearbox_displacement_x import GearboxDisplacementXAPI -from windmill._api.gearbox_displacement_y import GearboxDisplacementYAPI -from windmill._api.gearbox_displacement_z import GearboxDisplacementZAPI -from windmill._api.gearbox_query import GearboxQueryAPI -from windmill._api.generator import GeneratorAPI -from windmill._api.generator_generator_speed_controller import GeneratorGeneratorSpeedControllerAPI -from windmill._api.generator_generator_speed_controller_reference import GeneratorGeneratorSpeedControllerReferenceAPI -from windmill._api.generator_query import GeneratorQueryAPI -from windmill._api.high_speed_shaft import HighSpeedShaftAPI -from windmill._api.high_speed_shaft_bending_moment_y import HighSpeedShaftBendingMomentYAPI -from windmill._api.high_speed_shaft_bending_monent_x import HighSpeedShaftBendingMonentXAPI -from windmill._api.high_speed_shaft_query import HighSpeedShaftQueryAPI -from windmill._api.high_speed_shaft_torque import HighSpeedShaftTorqueAPI -from windmill._api.main_shaft import MainShaftAPI -from windmill._api.main_shaft_bending_x import MainShaftBendingXAPI -from windmill._api.main_shaft_bending_y import MainShaftBendingYAPI -from windmill._api.main_shaft_calculated_tilt_moment import MainShaftCalculatedTiltMomentAPI -from windmill._api.main_shaft_calculated_yaw_moment import MainShaftCalculatedYawMomentAPI -from windmill._api.main_shaft_query import MainShaftQueryAPI -from windmill._api.main_shaft_torque import MainShaftTorqueAPI -from windmill._api.metmast import MetmastAPI -from windmill._api.metmast_query import MetmastQueryAPI -from windmill._api.metmast_temperature import MetmastTemperatureAPI -from windmill._api.metmast_tilt_angle import MetmastTiltAngleAPI -from windmill._api.metmast_wind_speed import MetmastWindSpeedAPI -from windmill._api.nacelle import NacelleAPI -from windmill._api.nacelle_acc_from_back_side_x import NacelleAccFromBackSideXAPI -from windmill._api.nacelle_acc_from_back_side_y import NacelleAccFromBackSideYAPI -from windmill._api.nacelle_acc_from_back_side_z import NacelleAccFromBackSideZAPI -from windmill._api.nacelle_query import NacelleQueryAPI -from windmill._api.nacelle_yaw_direction import NacelleYawDirectionAPI -from windmill._api.nacelle_yaw_error import NacelleYawErrorAPI -from windmill._api.power_inverter import PowerInverterAPI -from windmill._api.power_inverter_active_power_total import PowerInverterActivePowerTotalAPI -from windmill._api.power_inverter_apparent_power_total import PowerInverterApparentPowerTotalAPI -from windmill._api.power_inverter_query import PowerInverterQueryAPI -from windmill._api.power_inverter_reactive_power_total import PowerInverterReactivePowerTotalAPI -from windmill._api.rotor import RotorAPI -from windmill._api.rotor_query import RotorQueryAPI -from windmill._api.rotor_rotor_speed_controller import RotorRotorSpeedControllerAPI -from windmill._api.rotor_rpm_low_speed_shaft import RotorRpmLowSpeedShaftAPI -from windmill._api.sensor_position import SensorPositionAPI -from windmill._api.sensor_position_edgewise_bend_mom_crosstalk_corrected import ( - SensorPositionEdgewiseBendMomCrosstalkCorrectedAPI, -) -from windmill._api.sensor_position_edgewise_bend_mom_offset import SensorPositionEdgewiseBendMomOffsetAPI -from windmill._api.sensor_position_edgewise_bend_mom_offset_crosstalk_corrected import ( - SensorPositionEdgewiseBendMomOffsetCrosstalkCorrectedAPI, -) -from windmill._api.sensor_position_edgewisewise_bend_mom import SensorPositionEdgewisewiseBendMomAPI -from windmill._api.sensor_position_flapwise_bend_mom import SensorPositionFlapwiseBendMomAPI -from windmill._api.sensor_position_flapwise_bend_mom_crosstalk_corrected import ( - SensorPositionFlapwiseBendMomCrosstalkCorrectedAPI, -) -from windmill._api.sensor_position_flapwise_bend_mom_offset import SensorPositionFlapwiseBendMomOffsetAPI -from windmill._api.sensor_position_flapwise_bend_mom_offset_crosstalk_corrected import ( - SensorPositionFlapwiseBendMomOffsetCrosstalkCorrectedAPI, -) -from windmill._api.sensor_position_query import SensorPositionQueryAPI -from windmill._api.windmill import WindmillAPI -from windmill._api.windmill_blades import WindmillBladesAPI -from windmill._api.windmill_metmast import WindmillMetmastAPI -from windmill._api.windmill_query import WindmillQueryAPI - -__all__ = [ - "BladeAPI", - "BladeQueryAPI", - "BladeSensorPositionsAPI", - "GearboxAPI", - "GearboxDisplacementXAPI", - "GearboxDisplacementYAPI", - "GearboxDisplacementZAPI", - "GearboxQueryAPI", - "GeneratorAPI", - "GeneratorGeneratorSpeedControllerAPI", - "GeneratorGeneratorSpeedControllerReferenceAPI", - "GeneratorQueryAPI", - "HighSpeedShaftAPI", - "HighSpeedShaftBendingMomentYAPI", - "HighSpeedShaftBendingMonentXAPI", - "HighSpeedShaftQueryAPI", - "HighSpeedShaftTorqueAPI", - "MainShaftAPI", - "MainShaftBendingXAPI", - "MainShaftBendingYAPI", - "MainShaftCalculatedTiltMomentAPI", - "MainShaftCalculatedYawMomentAPI", - "MainShaftQueryAPI", - "MainShaftTorqueAPI", - "MetmastAPI", - "MetmastQueryAPI", - "MetmastTemperatureAPI", - "MetmastTiltAngleAPI", - "MetmastWindSpeedAPI", - "NacelleAPI", - "NacelleAccFromBackSideXAPI", - "NacelleAccFromBackSideYAPI", - "NacelleAccFromBackSideZAPI", - "NacelleQueryAPI", - "NacelleYawDirectionAPI", - "NacelleYawErrorAPI", - "PowerInverterAPI", - "PowerInverterActivePowerTotalAPI", - "PowerInverterApparentPowerTotalAPI", - "PowerInverterQueryAPI", - "PowerInverterReactivePowerTotalAPI", - "RotorAPI", - "RotorQueryAPI", - "RotorRotorSpeedControllerAPI", - "RotorRpmLowSpeedShaftAPI", - "SensorPositionAPI", - "SensorPositionEdgewiseBendMomCrosstalkCorrectedAPI", - "SensorPositionEdgewiseBendMomOffsetAPI", - "SensorPositionEdgewiseBendMomOffsetCrosstalkCorrectedAPI", - "SensorPositionEdgewisewiseBendMomAPI", - "SensorPositionFlapwiseBendMomAPI", - "SensorPositionFlapwiseBendMomCrosstalkCorrectedAPI", - "SensorPositionFlapwiseBendMomOffsetAPI", - "SensorPositionFlapwiseBendMomOffsetCrosstalkCorrectedAPI", - "SensorPositionQueryAPI", - "WindmillAPI", - "WindmillBladesAPI", - "WindmillMetmastAPI", - "WindmillQueryAPI", -] diff --git a/examples/windmill/_api/_core.py b/examples/windmill/_api/_core.py deleted file mode 100644 index ffae305f5..000000000 --- a/examples/windmill/_api/_core.py +++ /dev/null @@ -1,601 +0,0 @@ -from __future__ import annotations - -from abc import ABC -from collections import defaultdict -from collections.abc import Sequence -from itertools import groupby -from typing import ( - Generic, - Literal, - Any, - Iterator, - Protocol, - SupportsIndex, - TypeVar, - overload, - ClassVar, -) - -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import TimeSeriesList -from cognite.client.data_classes.data_modeling.instances import InstanceSort, InstanceAggregationResultList - -from windmill import data_classes -from windmill.data_classes._core import ( - DomainModel, - DomainModelWrite, - DEFAULT_INSTANCE_SPACE, - PageInfo, - GraphQLCore, - GraphQLList, - ResourcesWriteResult, - T_DomainModel, - T_DomainModelWrite, - T_DomainModelWriteList, - T_DomainModelList, - T_DomainRelation, - T_DomainRelationWrite, - T_DomainRelationList, - DataClassQueryBuilder, - NodeQueryStep, - EdgeQueryStep, -) - -DEFAULT_LIMIT_READ = 25 -DEFAULT_QUERY_LIMIT = 3 -IN_FILTER_LIMIT = 5_000 -INSTANCE_QUERY_LIMIT = 1_000 -NODE_PROPERTIES = {"externalId", "space"} - -Aggregations = Literal["avg", "count", "max", "min", "sum"] - -_METRIC_AGGREGATIONS_BY_NAME = { - "avg": dm.aggregations.Avg, - "count": dm.aggregations.Count, - "max": dm.aggregations.Max, - "min": dm.aggregations.Min, - "sum": dm.aggregations.Sum, -} - -_T_co = TypeVar("_T_co", covariant=True) - - -def _as_node_id(value: str | dm.NodeId | tuple[str, str], space: str) -> dm.NodeId: - if isinstance(value, str): - return dm.NodeId(space=space, external_id=value) - if isinstance(value, dm.NodeId): - return value - if isinstance(value, tuple): - return dm.NodeId(space=value[0], external_id=value[1]) - raise TypeError(f"Expected str, NodeId or tuple, got {type(value)}") - - -# Source from https://github.com/python/typing/issues/256#issuecomment-1442633430 -# This works because str.__contains__ does not accept an object (either in typeshed or at runtime) -class SequenceNotStr(Protocol[_T_co]): - @overload - def __getitem__(self, index: SupportsIndex, /) -> _T_co: ... - - @overload - def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ... - - def __contains__(self, value: object, /) -> bool: ... - - def __len__(self) -> int: ... - - def __iter__(self) -> Iterator[_T_co]: ... - - def index(self, value: Any, /, start: int = 0, stop: int = ...) -> int: ... - - def count(self, value: Any, /) -> int: ... - - def __reversed__(self) -> Iterator[_T_co]: ... - - -class NodeReadAPI(Generic[T_DomainModel, T_DomainModelList], ABC): - _view_id: ClassVar[dm.ViewId] - _properties_by_field: ClassVar[dict[str, str]] - _direct_children_by_external_id: ClassVar[dict[str, type[DomainModel]]] - _class_type: type[T_DomainModel] - _class_list: type[T_DomainModelList] - - def __init__(self, client: CogniteClient): - self._client = client - - def _delete(self, external_id: str | SequenceNotStr[str], space: str) -> dm.InstancesDeleteResult: - if isinstance(external_id, str): - return self._client.data_modeling.instances.delete(nodes=(space, external_id)) - else: - return self._client.data_modeling.instances.delete( - nodes=[(space, id) for id in external_id], - ) - - def _retrieve( - self, - external_id: str | dm.NodeId | tuple[str, str] | SequenceNotStr[str | dm.NodeId | tuple[str, str]], - space: str, - retrieve_edges: bool = False, - edge_api_name_type_direction_view_id_penta: ( - list[tuple[EdgeAPI, str, dm.DirectRelationReference, Literal["outwards", "inwards"], dm.ViewId]] | None - ) = None, - as_child_class: SequenceNotStr[str] | None = None, - ) -> T_DomainModel | T_DomainModelList | None: - if isinstance(external_id, str | dm.NodeId) or ( - isinstance(external_id, tuple) and len(external_id) == 2 and all(isinstance(i, str) for i in external_id) - ): - node_ids = [_as_node_id(external_id, space)] - is_multiple = False - else: - is_multiple = True - node_ids = [_as_node_id(ext_id, space) for ext_id in external_id] - - items: list[DomainModel] = [] - if as_child_class: - if not hasattr(self, "_direct_children_by_external_id"): - raise ValueError(f"{type(self).__name__} does not have any direct children") - for child_class_external_id in as_child_class: - child_cls = self._direct_children_by_external_id.get(child_class_external_id) - if child_cls is None: - raise ValueError(f"Could not find child class with external_id {child_class_external_id}") - instances = self._client.data_modeling.instances.retrieve(nodes=node_ids, sources=child_cls._view_id) - items.extend([child_cls.from_instance(node) for node in instances.nodes]) - else: - instances = self._client.data_modeling.instances.retrieve(nodes=node_ids, sources=self._view_id) - items.extend([self._class_type.from_instance(node) for node in instances.nodes]) - - nodes = self._class_list(items) - - if retrieve_edges and nodes: - self._retrieve_and_set_edge_types(nodes, edge_api_name_type_direction_view_id_penta) - - if is_multiple: - return nodes - elif not nodes: - return None - else: - return nodes[0] - - def _search( - self, - query: str, - properties: str | SequenceNotStr[str] | None = None, - filter_: dm.Filter | None = None, - limit: int = DEFAULT_LIMIT_READ, - sort_by: str | list[str] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> T_DomainModelList: - properties_input = self._to_input_properties(properties) - - sort_input = self._create_sort(sort_by, direction, sort) - nodes = self._client.data_modeling.instances.search( - view=self._view_id, - query=query, - instance_type="node", - properties=properties_input, - filter=filter_, - limit=limit, - sort=sort_input, - ) - return self._class_list([self._class_type.from_instance(node) for node in nodes]) - - def _to_input_properties(self, properties: str | SequenceNotStr[str] | None) -> list[str] | None: - properties_input: list[str] | None = None - if isinstance(properties, str): - properties_input = [properties] - elif isinstance(properties, Sequence): - properties_input = list(properties) - if properties_input: - properties_input = [self._properties_by_field.get(prop, prop) for prop in properties_input] - return properties_input - - def _aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: str | SequenceNotStr[str] | None = None, - properties: str | SequenceNotStr[str] | None = None, - query: str | None = None, - search_properties: str | SequenceNotStr[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> ( - dm.aggregations.AggregatedNumberedValue - | list[dm.aggregations.AggregatedNumberedValue] - | InstanceAggregationResultList - ): - if isinstance(group_by, str): - group_by = [group_by] - - if group_by: - group_by = [self._properties_by_field.get(prop, prop) for prop in group_by] - - search_properties_input = self._to_input_properties(search_properties) - - if isinstance(properties, str): - properties = [properties] - - if properties: - properties = [self._properties_by_field.get(prop, prop) for prop in properties] - - if isinstance(aggregate, (str, dm.aggregations.MetricAggregation)): - aggregate = [aggregate] - - if properties is None and (invalid := [agg for agg in aggregate if isinstance(agg, str) and agg != "count"]): - raise ValueError(f"Cannot aggregate on {invalid} without specifying properties") - - aggregates: list[dm.aggregations.MetricAggregation] = [] - for agg in aggregate: - if isinstance(agg, dm.aggregations.MetricAggregation): - aggregates.append(agg) - elif isinstance(agg, str): - if agg == "count" and properties is None: - aggregates.append(dm.aggregations.Count("externalId")) - elif properties is None: - raise ValueError(f"Cannot aggregate on {agg} without specifying properties") - else: - for prop in properties: - aggregates.append(_METRIC_AGGREGATIONS_BY_NAME[agg](prop)) - else: - raise TypeError(f"Expected str or MetricAggregation, got {type(agg)}") - - return self._client.data_modeling.instances.aggregate( - view=self._view_id, - aggregates=aggregates, - group_by=group_by, - instance_type="node", - query=query, - properties=search_properties_input, - filter=filter, - limit=limit, - ) - - def _histogram( - self, - property: str, - interval: float, - query: str | None = None, - search_properties: str | SequenceNotStr[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.HistogramValue: - property = self._properties_by_field.get(property, property) - - if isinstance(search_properties, str): - search_properties = [search_properties] - if search_properties: - search_properties = [self._properties_by_field.get(prop, prop) for prop in search_properties] - - return self._client.data_modeling.instances.histogram( - view=self._view_id, - histograms=dm.aggregations.Histogram(property, interval), - instance_type="node", - query=query, - properties=search_properties, - filter=filter, - limit=limit, - ) - - def _list( - self, - limit: int, - filter: dm.Filter | None, - retrieve_edges: bool = False, - edge_api_name_type_direction_view_id_penta: ( - list[tuple[EdgeAPI, str, dm.DirectRelationReference, Literal["outwards", "inwards"], dm.ViewId]] | None - ) = None, - sort_by: str | list[str] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> T_DomainModelList: - sort_input = self._create_sort(sort_by, direction, sort) - nodes = self._client.data_modeling.instances.list( - instance_type="node", - sources=self._view_id, - limit=limit, - filter=filter, - sort=sort_input, - ) - node_list = self._class_list([self._class_type.from_instance(node) for node in nodes]) - if retrieve_edges and node_list: - self._retrieve_and_set_edge_types(node_list, edge_api_name_type_direction_view_id_penta) # type: ignore[arg-type] - - return node_list - - def _create_sort( - self, - sort_by: str | list[str] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> list[InstanceSort] | None: - sort_input: list[InstanceSort] | None = None - if sort is None and isinstance(sort_by, str): - sort_input = [self._create_sort_entry(sort_by, direction)] - elif sort is None and isinstance(sort_by, list): - sort_input = [self._create_sort_entry(sort_by_, direction) for sort_by_ in sort_by] - elif sort is not None: - sort_input = sort if isinstance(sort, list) else [sort] - for sort_ in sort_input: - if isinstance(sort_.property, Sequence) and len(sort_.property) == 1: - sort_.property = self._create_property_reference(sort_.property[0]) - elif isinstance(sort_.property, str): - sort_.property = self._create_property_reference(sort_.property) - return sort_input - - def _create_sort_entry(self, sort_by: str, direction: Literal["ascending", "descending"]) -> InstanceSort: - return InstanceSort(self._create_property_reference(sort_by), direction) - - def _create_property_reference(self, property_: str) -> list[str] | tuple[str, ...]: - prop_name = self._properties_by_field.get(property_, property_) - if prop_name in NODE_PROPERTIES: - return ["node", prop_name] - else: - return self._view_id.as_property_ref(prop_name) - - def _retrieve_and_set_edge_types( - self, - nodes: T_DomainModelList, # type: ignore[misc] - edge_api_name_type_direction_view_id_penta: ( - list[tuple[EdgeAPI, str, dm.DirectRelationReference, Literal["outwards", "inwards"], dm.ViewId]] | None - ) = None, - ): - filter_: dm.Filter | None - for edge_type, values in groupby(edge_api_name_type_direction_view_id_penta or [], lambda x: x[2].as_tuple()): - edges: dict[dm.EdgeId, dm.Edge] = {} - value_list = list(values) - for edge_api, edge_name, edge_type, direction, view_id in value_list: - is_type = dm.filters.Equals( - ["edge", "type"], - {"space": edge_type.space, "externalId": edge_type.external_id}, - ) - if len(ids := nodes.as_node_ids()) > IN_FILTER_LIMIT: - filter_ = is_type - else: - is_nodes = dm.filters.In( - ["edge", "startNode"] if direction == "outwards" else ["edge", "endNode"], - values=[id_.dump(camel_case=True, include_instance_type=False) for id_ in ids], - ) - filter_ = dm.filters.And(is_type, is_nodes) - result = edge_api._list(limit=-1, filter_=filter_) - edges.update({edge.as_id(): edge for edge in result}) - edge_list = list(edges.values()) - if len(value_list) == 1: - _, edge_name, _, direction, _ = value_list[0] - self._set_edges(nodes, edge_list, edge_name, direction) - else: - # This is an 'edge' case where we have view with multiple edges of the same type. - edge_by_end_node: dict[tuple[str, str], list[dm.Edge]] = defaultdict(list) - for edge in edge_list: - node_id = edge.end_node.as_tuple() if direction == "outwards" else edge.start_node.as_tuple() - edge_by_end_node[node_id].append(edge) - - for no, (edge_api, edge_name, _, direction, view_id) in enumerate(value_list): - if not edge_by_end_node: - break - if no == len(value_list) - 1: - # Last edge, use all remaining nodes - attribute_edges = [e for e_list in edge_by_end_node.values() for e in e_list] - else: - existing = self._client.data_modeling.instances.retrieve( - nodes=list(edge_by_end_node), sources=view_id - ) - attribute_edges = [] - for node in existing.nodes: - attribute_edge = edge_by_end_node.pop(node.as_id().as_tuple(), []) - attribute_edges.extend(attribute_edge) - - self._set_edges(nodes, attribute_edges, edge_name, direction) - - @staticmethod - def _set_edges( - nodes: Sequence[DomainModel], - edges: Sequence[dm.Edge], - edge_name: str, - direction: Literal["outwards", "inwards"], - ): - edges_by_node: dict[tuple, list] = defaultdict(list) - for edge in edges: - node_id = edge.start_node.as_tuple() if direction == "outwards" else edge.end_node.as_tuple() - edges_by_node[node_id].append(edge) - - for node in nodes: - node_id = node.as_tuple_id() - if node_id in edges_by_node: - setattr( - node, - edge_name, - [ - edge.end_node.external_id if direction == "outwards" else edge.start_node.external_id - for edge in edges_by_node[node_id] - ], - ) - - -class NodeAPI( - Generic[T_DomainModel, T_DomainModelWrite, T_DomainModelList, T_DomainModelWriteList], - NodeReadAPI[T_DomainModel, T_DomainModelList], - ABC, -): - _class_write_list: type[T_DomainModelWriteList] - - def _apply( - self, item: T_DomainModelWrite | Sequence[T_DomainModelWrite], replace: bool = False, write_none: bool = False - ) -> ResourcesWriteResult: - if isinstance(item, DomainModelWrite): - instances = item.to_instances_write(write_none) - else: - instances = self._class_write_list(item).to_instances_write(write_none) - result = self._client.data_modeling.instances.apply( - nodes=instances.nodes, - edges=instances.edges, - auto_create_start_nodes=True, - auto_create_end_nodes=True, - replace=replace, - ) - time_series = TimeSeriesList([]) - if instances.time_series: - time_series = self._client.time_series.upsert(instances.time_series, mode="patch") - - return ResourcesWriteResult(result.nodes, result.edges, time_series) - - -class EdgeAPI(ABC): - def __init__(self, client: CogniteClient): - self._client = client - - def _list( - self, - limit: int = DEFAULT_LIMIT_READ, - filter_: dm.Filter | None = None, - ) -> dm.EdgeList: - return self._client.data_modeling.instances.list("edge", limit=limit, filter=filter_) - - -class EdgePropertyAPI(EdgeAPI, Generic[T_DomainRelation, T_DomainRelationWrite, T_DomainRelationList], ABC): - _view_id: ClassVar[dm.ViewId] - _class_type: type[T_DomainRelation] - _class_write_type: type[T_DomainRelationWrite] - _class_list: type[T_DomainRelationList] - - def _list( # type: ignore[override] - self, - limit: int = DEFAULT_LIMIT_READ, - filter_: dm.Filter | None = None, - ) -> T_DomainRelationList: - edges = self._client.data_modeling.instances.list("edge", limit=limit, filter=filter_, sources=[self._view_id]) - return self._class_list([self._class_type.from_instance(edge) for edge in edges]) # type: ignore[misc] - - -class QueryAPI(Generic[T_DomainModelList]): - def __init__( - self, - client: CogniteClient, - builder: DataClassQueryBuilder[T_DomainModelList], - ): - self._client = client - self._builder = builder - - def _query(self) -> T_DomainModelList: - self._builder.execute_query(self._client, remove_not_connected=True) - return self._builder.unpack() - - -def _create_edge_filter( - edge_type: dm.DirectRelationReference, - start_node: str | list[str] | dm.NodeId | list[dm.NodeId] | None = None, - start_node_space: str = DEFAULT_INSTANCE_SPACE, - end_node: str | list[str] | dm.NodeId | list[dm.NodeId] | None = None, - space_end_node: str = DEFAULT_INSTANCE_SPACE, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - filter: dm.Filter | None = None, -) -> dm.Filter: - filters: list[dm.Filter] = [ - dm.filters.Equals( - ["edge", "type"], - {"space": edge_type.space, "externalId": edge_type.external_id}, - ) - ] - if start_node and isinstance(start_node, str): - filters.append( - dm.filters.Equals(["edge", "startNode"], value={"space": start_node_space, "externalId": start_node}) - ) - if start_node and isinstance(start_node, dm.NodeId): - filters.append( - dm.filters.Equals( - ["edge", "startNode"], value=start_node.dump(camel_case=True, include_instance_type=False) - ) - ) - if start_node and isinstance(start_node, list): - filters.append( - dm.filters.In( - ["edge", "startNode"], - values=[ - ( - {"space": start_node_space, "externalId": ext_id} - if isinstance(ext_id, str) - else ext_id.dump(camel_case=True, include_instance_type=False) - ) - for ext_id in start_node - ], - ) - ) - if end_node and isinstance(end_node, str): - filters.append(dm.filters.Equals(["edge", "endNode"], value={"space": space_end_node, "externalId": end_node})) - if end_node and isinstance(end_node, dm.NodeId): - filters.append( - dm.filters.Equals(["edge", "endNode"], value=end_node.dump(camel_case=True, include_instance_type=False)) - ) - if end_node and isinstance(end_node, list): - filters.append( - dm.filters.In( - ["edge", "endNode"], - values=[ - ( - {"space": space_end_node, "externalId": ext_id} - if isinstance(ext_id, str) - else ext_id.dump(camel_case=True, include_instance_type=False) - ) - for ext_id in end_node - ], - ) - ) - if external_id_prefix: - filters.append(dm.filters.Prefix(["edge", "externalId"], value=external_id_prefix)) - if space and isinstance(space, str): - filters.append(dm.filters.Equals(["edge", "space"], value=space)) - if space and isinstance(space, list): - filters.append(dm.filters.In(["edge", "space"], values=space)) - if filter: - filters.append(filter) - return dm.filters.And(*filters) - - -class GraphQLQueryResponse: - def __init__(self, data_model_id: dm.DataModelId): - self._output = GraphQLList([]) - self._data_class_by_type = _GRAPHQL_DATA_CLASS_BY_DATA_MODEL_BY_TYPE[data_model_id] - - def parse(self, response: dict[str, Any]) -> GraphQLList: - if "errors" in response: - raise RuntimeError(response["errors"]) - _, data = list(response.items())[0] - self._parse_item(data) - if "pageInfo" in data: - self._output.page_info = PageInfo.load(data["pageInfo"]) - return self._output - - def _parse_item(self, data: dict[str, Any]) -> None: - if "items" in data: - for item in data["items"]: - self._parse_item(item) - elif "__typename" in data: - try: - item = self._data_class_by_type[data["__typename"]].model_validate(data) - except KeyError: - raise ValueError(f"Could not find class for type {data['__typename']}") - else: - self._output.append(item) - else: - raise RuntimeError("Missing '__typename' in GraphQL response. Cannot determine the type of the response.") - - -_GRAPHQL_DATA_CLASS_BY_DATA_MODEL_BY_TYPE: dict[dm.DataModelId, dict[str, type[GraphQLCore]]] = { - dm.DataModelId("power-models", "Windmill", "1"): { - "Blade": data_classes.BladeGraphQL, - "Gearbox": data_classes.GearboxGraphQL, - "Generator": data_classes.GeneratorGraphQL, - "HighSpeedShaft": data_classes.HighSpeedShaftGraphQL, - "MainShaft": data_classes.MainShaftGraphQL, - "Metmast": data_classes.MetmastGraphQL, - "Nacelle": data_classes.NacelleGraphQL, - "PowerInverter": data_classes.PowerInverterGraphQL, - "Rotor": data_classes.RotorGraphQL, - "SensorPosition": data_classes.SensorPositionGraphQL, - "Windmill": data_classes.WindmillGraphQL, - }, -} diff --git a/examples/windmill/_api/blade.py b/examples/windmill/_api/blade.py deleted file mode 100644 index ee2a538cb..000000000 --- a/examples/windmill/_api/blade.py +++ /dev/null @@ -1,573 +0,0 @@ -from __future__ import annotations - -from collections.abc import Sequence -from typing import overload, Literal -import warnings - -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes.data_modeling.instances import InstanceAggregationResultList, InstanceSort - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - NodeQueryStep, - EdgeQueryStep, - DataClassQueryBuilder, -) -from windmill.data_classes import ( - DomainModelCore, - DomainModelWrite, - ResourcesWriteResult, - Blade, - BladeWrite, - BladeFields, - BladeList, - BladeWriteList, - BladeTextFields, - SensorPosition, -) -from windmill.data_classes._blade import ( - BladeQuery, - _BLADE_PROPERTIES_BY_FIELD, - _create_blade_filter, -) -from windmill._api._core import ( - DEFAULT_LIMIT_READ, - Aggregations, - NodeAPI, - SequenceNotStr, -) -from windmill._api.blade_sensor_positions import BladeSensorPositionsAPI -from windmill._api.blade_query import BladeQueryAPI - - -class BladeAPI(NodeAPI[Blade, BladeWrite, BladeList, BladeWriteList]): - _view_id = dm.ViewId("power-models", "Blade", "1") - _properties_by_field = _BLADE_PROPERTIES_BY_FIELD - _class_type = Blade - _class_list = BladeList - _class_write_list = BladeWriteList - - def __init__(self, client: CogniteClient): - super().__init__(client=client) - - self.sensor_positions_edge = BladeSensorPositionsAPI(client) - - def __call__( - self, - is_damaged: bool | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - filter: dm.Filter | None = None, - ) -> BladeQueryAPI[BladeList]: - """Query starting at blades. - - Args: - is_damaged: The is damaged to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of blades to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query API for blades. - - """ - has_data = dm.filters.HasData(views=[self._view_id]) - filter_ = _create_blade_filter( - self._view_id, - is_damaged, - name, - name_prefix, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - builder = DataClassQueryBuilder(BladeList) - return BladeQueryAPI(self._client, builder, filter_, limit) - - def apply( - self, - blade: BladeWrite | Sequence[BladeWrite], - replace: bool = False, - write_none: bool = False, - ) -> ResourcesWriteResult: - """Add or update (upsert) blades. - - Note: This method iterates through all nodes and timeseries linked to blade and creates them including the edges - between the nodes. For example, if any of `sensor_positions` are set, then these - nodes as well as any nodes linked to them, and all the edges linking these nodes will be created. - - Args: - blade: Blade or sequence of blades to upsert. - replace (bool): How do we behave when a property value exists? Do we replace all matching and existing values with the supplied values (true)? - Or should we merge in new values for properties together with the existing values (false)? Note: This setting applies for all nodes or edges specified in the ingestion call. - write_none (bool): This method, will by default, skip properties that are set to None. However, if you want to set properties to None, - you can set this parameter to True. Note this only applies to properties that are nullable. - Returns: - Created instance(s), i.e., nodes, edges, and time series. - - Examples: - - Create a new blade: - - >>> from windmill import WindmillClient - >>> from windmill.data_classes import BladeWrite - >>> client = WindmillClient() - >>> blade = BladeWrite(external_id="my_blade", ...) - >>> result = client.blade.apply(blade) - - """ - warnings.warn( - "The .apply method is deprecated and will be removed in v1.0. " - "Please use the .upsert method on the client instead. This means instead of " - "`my_client.blade.apply(my_items)` please use `my_client.upsert(my_items)`." - "The motivation is that all apply methods are the same, and having one apply method per API " - " class encourages users to create items in small batches, which is inefficient." - "In addition, .upsert method is more descriptive of what the method does.", - UserWarning, - stacklevel=2, - ) - return self._apply(blade, replace, write_none) - - def delete( - self, external_id: str | SequenceNotStr[str], space: str = DEFAULT_INSTANCE_SPACE - ) -> dm.InstancesDeleteResult: - """Delete one or more blade. - - Args: - external_id: External id of the blade to delete. - space: The space where all the blade are located. - - Returns: - The instance(s), i.e., nodes and edges which has been deleted. Empty list if nothing was deleted. - - Examples: - - Delete blade by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> client.blade.delete("my_blade") - """ - warnings.warn( - "The .delete method is deprecated and will be removed in v1.0. " - "Please use the .delete method on the client instead. This means instead of " - "`my_client.blade.delete(my_ids)` please use `my_client.delete(my_ids)`." - "The motivation is that all delete methods are the same, and having one delete method per API " - " class encourages users to delete items in small batches, which is inefficient.", - UserWarning, - stacklevel=2, - ) - return self._delete(external_id, space) - - @overload - def retrieve( - self, external_id: str | dm.NodeId | tuple[str, str], space: str = DEFAULT_INSTANCE_SPACE - ) -> Blade | None: ... - - @overload - def retrieve( - self, external_id: SequenceNotStr[str | dm.NodeId | tuple[str, str]], space: str = DEFAULT_INSTANCE_SPACE - ) -> BladeList: ... - - def retrieve( - self, - external_id: str | dm.NodeId | tuple[str, str] | SequenceNotStr[str | dm.NodeId | tuple[str, str]], - space: str = DEFAULT_INSTANCE_SPACE, - ) -> Blade | BladeList | None: - """Retrieve one or more blades by id(s). - - Args: - external_id: External id or list of external ids of the blades. - space: The space where all the blades are located. - - Returns: - The requested blades. - - Examples: - - Retrieve blade by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> blade = client.blade.retrieve("my_blade") - - """ - return self._retrieve( - external_id, - space, - retrieve_edges=True, - edge_api_name_type_direction_view_id_penta=[ - ( - self.sensor_positions_edge, - "sensor_positions", - dm.DirectRelationReference("power-models", "Blade.sensor_positions"), - "outwards", - dm.ViewId("power-models", "SensorPosition", "1"), - ), - ], - ) - - def search( - self, - query: str, - properties: BladeTextFields | SequenceNotStr[BladeTextFields] | None = None, - is_damaged: bool | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: BladeFields | SequenceNotStr[BladeFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> BladeList: - """Search blades - - Args: - query: The search query, - properties: The property to search, if nothing is passed all text fields will be searched. - is_damaged: The is damaged to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of blades to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - Search results blades matching the query. - - Examples: - - Search for 'my_blade' in all text properties: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> blades = client.blade.search('my_blade') - - """ - filter_ = _create_blade_filter( - self._view_id, - is_damaged, - name, - name_prefix, - external_id_prefix, - space, - filter, - ) - return self._search( - query=query, - properties=properties, - filter_=filter_, - limit=limit, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) - - @overload - def aggregate( - self, - aggregate: Aggregations | dm.aggregations.MetricAggregation, - group_by: None = None, - property: BladeFields | SequenceNotStr[BladeFields] | None = None, - query: str | None = None, - search_property: BladeTextFields | SequenceNotStr[BladeTextFields] | None = None, - is_damaged: bool | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.AggregatedNumberedValue: ... - - @overload - def aggregate( - self, - aggregate: SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation], - group_by: None = None, - property: BladeFields | SequenceNotStr[BladeFields] | None = None, - query: str | None = None, - search_property: BladeTextFields | SequenceNotStr[BladeTextFields] | None = None, - is_damaged: bool | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> list[dm.aggregations.AggregatedNumberedValue]: ... - - @overload - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: BladeFields | SequenceNotStr[BladeFields], - property: BladeFields | SequenceNotStr[BladeFields] | None = None, - query: str | None = None, - search_property: BladeTextFields | SequenceNotStr[BladeTextFields] | None = None, - is_damaged: bool | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> InstanceAggregationResultList: ... - - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: BladeFields | SequenceNotStr[BladeFields] | None = None, - property: BladeFields | SequenceNotStr[BladeFields] | None = None, - query: str | None = None, - search_property: BladeTextFields | SequenceNotStr[BladeTextFields] | None = None, - is_damaged: bool | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> ( - dm.aggregations.AggregatedNumberedValue - | list[dm.aggregations.AggregatedNumberedValue] - | InstanceAggregationResultList - ): - """Aggregate data across blades - - Args: - aggregate: The aggregation to perform. - group_by: The property to group by when doing the aggregation. - property: The property to perform aggregation on. - query: The query to search for in the text field. - search_property: The text field to search in. - is_damaged: The is damaged to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of blades to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Aggregation results. - - Examples: - - Count blades in space `my_space`: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> result = client.blade.aggregate("count", space="my_space") - - """ - - filter_ = _create_blade_filter( - self._view_id, - is_damaged, - name, - name_prefix, - external_id_prefix, - space, - filter, - ) - return self._aggregate( - aggregate=aggregate, - group_by=group_by, # type: ignore[arg-type] - properties=property, # type: ignore[arg-type] - query=query, - search_properties=search_property, # type: ignore[arg-type] - limit=limit, - filter=filter_, - ) - - def histogram( - self, - property: BladeFields, - interval: float, - query: str | None = None, - search_property: BladeTextFields | SequenceNotStr[BladeTextFields] | None = None, - is_damaged: bool | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.HistogramValue: - """Produces histograms for blades - - Args: - property: The property to use as the value in the histogram. - interval: The interval to use for the histogram bins. - query: The query to search for in the text field. - search_property: The text field to search in. - is_damaged: The is damaged to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of blades to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Bucketed histogram results. - - """ - filter_ = _create_blade_filter( - self._view_id, - is_damaged, - name, - name_prefix, - external_id_prefix, - space, - filter, - ) - return self._histogram( - property, - interval, - query, - search_property, # type: ignore[arg-type] - limit, - filter_, - ) - - def query(self) -> BladeQuery: - """Start a query for blades.""" - warnings.warn("This method is renamed to .select", UserWarning, stacklevel=2) - return BladeQuery(self._client) - - def select(self) -> BladeQuery: - """Start selecting from blades.""" - warnings.warn( - "The .select is in alpha and is subject to breaking changes without notice.", UserWarning, stacklevel=2 - ) - return BladeQuery(self._client) - - def list( - self, - is_damaged: bool | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: BladeFields | Sequence[BladeFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - retrieve_connections: Literal["skip", "identifier", "full"] = "skip", - ) -> BladeList: - """List/filter blades - - Args: - is_damaged: The is damaged to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of blades to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - retrieve_connections: Whether to retrieve `sensor_positions` for the blades. Defaults to 'skip'. - 'skip' will not retrieve any connections, 'identifier' will only retrieve the identifier of the connected items, and 'full' will retrieve the full connected items. - - Returns: - List of requested blades - - Examples: - - List blades and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> blades = client.blade.list(limit=5) - - """ - filter_ = _create_blade_filter( - self._view_id, - is_damaged, - name, - name_prefix, - external_id_prefix, - space, - filter, - ) - - if retrieve_connections == "skip": - return self._list( - limit=limit, - filter=filter_, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) - - builder = DataClassQueryBuilder(BladeList) - has_data = dm.filters.HasData(views=[self._view_id]) - builder.append( - NodeQueryStep( - builder.create_name(None), - dm.query.NodeResultSetExpression( - filter=dm.filters.And(filter_, has_data) if filter_ else has_data, - sort=self._create_sort(sort_by, direction, sort), # type: ignore[arg-type] - ), - Blade, - max_retrieve_limit=limit, - raw_filter=filter_, - ) - ) - from_root = builder.get_from() - edge_sensor_positions = builder.create_name(from_root) - builder.append( - EdgeQueryStep( - edge_sensor_positions, - dm.query.EdgeResultSetExpression( - from_=from_root, - direction="outwards", - chain_to="destination", - ), - ) - ) - if retrieve_connections == "full": - builder.append( - NodeQueryStep( - builder.create_name(edge_sensor_positions), - dm.query.NodeResultSetExpression( - from_=edge_sensor_positions, - filter=dm.filters.HasData(views=[SensorPosition._view_id]), - ), - SensorPosition, - ) - ) - # We know that that all nodes are connected as it is not possible to filter on connections - builder.execute_query(self._client, remove_not_connected=False) - return builder.unpack() diff --git a/examples/windmill/_api/blade_query.py b/examples/windmill/_api/blade_query.py deleted file mode 100644 index ba5770932..000000000 --- a/examples/windmill/_api/blade_query.py +++ /dev/null @@ -1,124 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import TYPE_CHECKING, cast - -from cognite.client import data_modeling as dm, CogniteClient - -from windmill.data_classes import ( - DomainModelCore, - Blade, -) -from windmill.data_classes._sensor_position import ( - SensorPosition, - _create_sensor_position_filter, -) -from windmill._api._core import ( - DEFAULT_QUERY_LIMIT, - EdgeQueryStep, - NodeQueryStep, - DataClassQueryBuilder, - QueryAPI, - T_DomainModelList, - _create_edge_filter, -) - -if TYPE_CHECKING: - from windmill._api.sensor_position_query import SensorPositionQueryAPI - - -class BladeQueryAPI(QueryAPI[T_DomainModelList]): - _view_id = dm.ViewId("power-models", "Blade", "1") - - def __init__( - self, - client: CogniteClient, - builder: DataClassQueryBuilder[T_DomainModelList], - filter_: dm.filters.Filter | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - ): - super().__init__(client, builder) - from_ = self._builder.get_from() - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - filter=filter_, - ), - result_cls=Blade, - max_retrieve_limit=limit, - ) - ) - - def sensor_positions( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - external_id_prefix_edge: str | None = None, - space_edge: str | list[str] | None = None, - filter: dm.Filter | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - ) -> SensorPositionQueryAPI[T_DomainModelList]: - """Query along the sensor position edges of the blade. - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - external_id_prefix_edge: The prefix of the external ID to filter on. - space_edge: The space to filter on. - filter: (Advanced) Filter applied to node. If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - limit: Maximum number of sensor position edges to return. Defaults to 3. Set to -1, float("inf") or None - to return all items. - - Returns: - SensorPositionQueryAPI: The query API for the sensor position. - """ - from .sensor_position_query import SensorPositionQueryAPI - - # from is a string as we added a node query step in the __init__ method - from_ = cast(str, self._builder.get_from()) - edge_filter = _create_edge_filter( - dm.DirectRelationReference("power-models", "Blade.sensor_positions"), - external_id_prefix=external_id_prefix_edge, - space=space_edge, - ) - self._builder.append( - EdgeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.EdgeResultSetExpression( - filter=edge_filter, - from_=from_, - direction="outwards", - ), - max_retrieve_limit=limit, - ) - ) - - view_id = SensorPositionQueryAPI._view_id - has_data = dm.filters.HasData(views=[view_id]) - node_filer = _create_sensor_position_filter( - view_id, - min_position, - max_position, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - return SensorPositionQueryAPI(self._client, self._builder, node_filer, limit) - - def query( - self, - ) -> T_DomainModelList: - """Execute query and return the result. - - Returns: - The list of the source nodes of the query. - - """ - return self._query() diff --git a/examples/windmill/_api/blade_sensor_positions.py b/examples/windmill/_api/blade_sensor_positions.py deleted file mode 100644 index 0856d45c1..000000000 --- a/examples/windmill/_api/blade_sensor_positions.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import annotations - - -from cognite.client import data_modeling as dm - -from windmill._api._core import DEFAULT_LIMIT_READ, EdgeAPI, _create_edge_filter -from windmill.data_classes._core import DEFAULT_INSTANCE_SPACE - - -class BladeSensorPositionsAPI(EdgeAPI): - def list( - self, - from_blade: str | list[str] | dm.NodeId | list[dm.NodeId] | None = None, - from_blade_space: str = DEFAULT_INSTANCE_SPACE, - to_sensor_position: str | list[str] | dm.NodeId | list[dm.NodeId] | None = None, - to_sensor_position_space: str = DEFAULT_INSTANCE_SPACE, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit=DEFAULT_LIMIT_READ, - ) -> dm.EdgeList: - """List sensor position edges of a blade. - - Args: - from_blade: ID of the source blade. - from_blade_space: Location of the blades. - to_sensor_position: ID of the target sensor position. - to_sensor_position_space: Location of the sensor positions. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor position edges to return. Defaults to 25. Set to -1, float("inf") or None - to return all items. - - Returns: - The requested sensor position edges. - - Examples: - - List 5 sensor position edges connected to "my_blade": - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> blade = client.blade.sensor_positions_edge.list("my_blade", limit=5) - - """ - filter_ = _create_edge_filter( - dm.DirectRelationReference("power-models", "Blade.sensor_positions"), - from_blade, - from_blade_space, - to_sensor_position, - to_sensor_position_space, - external_id_prefix, - space, - ) - return self._list(filter_=filter_, limit=limit) diff --git a/examples/windmill/_api/gearbox.py b/examples/windmill/_api/gearbox.py deleted file mode 100644 index 07c5b5c49..000000000 --- a/examples/windmill/_api/gearbox.py +++ /dev/null @@ -1,446 +0,0 @@ -from __future__ import annotations - -from collections.abc import Sequence -from typing import overload, Literal -import warnings - -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes.data_modeling.instances import InstanceAggregationResultList, InstanceSort - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - NodeQueryStep, - EdgeQueryStep, - DataClassQueryBuilder, -) -from windmill.data_classes import ( - DomainModelCore, - DomainModelWrite, - ResourcesWriteResult, - Gearbox, - GearboxWrite, - GearboxFields, - GearboxList, - GearboxWriteList, - GearboxTextFields, -) -from windmill.data_classes._gearbox import ( - GearboxQuery, - _GEARBOX_PROPERTIES_BY_FIELD, - _create_gearbox_filter, -) -from windmill._api._core import ( - DEFAULT_LIMIT_READ, - Aggregations, - NodeAPI, - SequenceNotStr, -) -from windmill._api.gearbox_displacement_x import GearboxDisplacementXAPI -from windmill._api.gearbox_displacement_y import GearboxDisplacementYAPI -from windmill._api.gearbox_displacement_z import GearboxDisplacementZAPI -from windmill._api.gearbox_query import GearboxQueryAPI - - -class GearboxAPI(NodeAPI[Gearbox, GearboxWrite, GearboxList, GearboxWriteList]): - _view_id = dm.ViewId("power-models", "Gearbox", "1") - _properties_by_field = _GEARBOX_PROPERTIES_BY_FIELD - _class_type = Gearbox - _class_list = GearboxList - _class_write_list = GearboxWriteList - - def __init__(self, client: CogniteClient): - super().__init__(client=client) - - self.displacement_x = GearboxDisplacementXAPI(client, self._view_id) - self.displacement_y = GearboxDisplacementYAPI(client, self._view_id) - self.displacement_z = GearboxDisplacementZAPI(client, self._view_id) - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - filter: dm.Filter | None = None, - ) -> GearboxQueryAPI[GearboxList]: - """Query starting at gearboxes. - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of gearboxes to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query API for gearboxes. - - """ - has_data = dm.filters.HasData(views=[self._view_id]) - filter_ = _create_gearbox_filter( - self._view_id, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - builder = DataClassQueryBuilder(GearboxList) - return GearboxQueryAPI(self._client, builder, filter_, limit) - - def apply( - self, - gearbox: GearboxWrite | Sequence[GearboxWrite], - replace: bool = False, - write_none: bool = False, - ) -> ResourcesWriteResult: - """Add or update (upsert) gearboxes. - - Args: - gearbox: Gearbox or sequence of gearboxes to upsert. - replace (bool): How do we behave when a property value exists? Do we replace all matching and existing values with the supplied values (true)? - Or should we merge in new values for properties together with the existing values (false)? Note: This setting applies for all nodes or edges specified in the ingestion call. - write_none (bool): This method, will by default, skip properties that are set to None. However, if you want to set properties to None, - you can set this parameter to True. Note this only applies to properties that are nullable. - Returns: - Created instance(s), i.e., nodes, edges, and time series. - - Examples: - - Create a new gearbox: - - >>> from windmill import WindmillClient - >>> from windmill.data_classes import GearboxWrite - >>> client = WindmillClient() - >>> gearbox = GearboxWrite(external_id="my_gearbox", ...) - >>> result = client.gearbox.apply(gearbox) - - """ - warnings.warn( - "The .apply method is deprecated and will be removed in v1.0. " - "Please use the .upsert method on the client instead. This means instead of " - "`my_client.gearbox.apply(my_items)` please use `my_client.upsert(my_items)`." - "The motivation is that all apply methods are the same, and having one apply method per API " - " class encourages users to create items in small batches, which is inefficient." - "In addition, .upsert method is more descriptive of what the method does.", - UserWarning, - stacklevel=2, - ) - return self._apply(gearbox, replace, write_none) - - def delete( - self, external_id: str | SequenceNotStr[str], space: str = DEFAULT_INSTANCE_SPACE - ) -> dm.InstancesDeleteResult: - """Delete one or more gearbox. - - Args: - external_id: External id of the gearbox to delete. - space: The space where all the gearbox are located. - - Returns: - The instance(s), i.e., nodes and edges which has been deleted. Empty list if nothing was deleted. - - Examples: - - Delete gearbox by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> client.gearbox.delete("my_gearbox") - """ - warnings.warn( - "The .delete method is deprecated and will be removed in v1.0. " - "Please use the .delete method on the client instead. This means instead of " - "`my_client.gearbox.delete(my_ids)` please use `my_client.delete(my_ids)`." - "The motivation is that all delete methods are the same, and having one delete method per API " - " class encourages users to delete items in small batches, which is inefficient.", - UserWarning, - stacklevel=2, - ) - return self._delete(external_id, space) - - @overload - def retrieve( - self, external_id: str | dm.NodeId | tuple[str, str], space: str = DEFAULT_INSTANCE_SPACE - ) -> Gearbox | None: ... - - @overload - def retrieve( - self, external_id: SequenceNotStr[str | dm.NodeId | tuple[str, str]], space: str = DEFAULT_INSTANCE_SPACE - ) -> GearboxList: ... - - def retrieve( - self, - external_id: str | dm.NodeId | tuple[str, str] | SequenceNotStr[str | dm.NodeId | tuple[str, str]], - space: str = DEFAULT_INSTANCE_SPACE, - ) -> Gearbox | GearboxList | None: - """Retrieve one or more gearboxes by id(s). - - Args: - external_id: External id or list of external ids of the gearboxes. - space: The space where all the gearboxes are located. - - Returns: - The requested gearboxes. - - Examples: - - Retrieve gearbox by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearbox = client.gearbox.retrieve("my_gearbox") - - """ - return self._retrieve(external_id, space) - - def search( - self, - query: str, - properties: GearboxTextFields | SequenceNotStr[GearboxTextFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: GearboxFields | SequenceNotStr[GearboxFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> GearboxList: - """Search gearboxes - - Args: - query: The search query, - properties: The property to search, if nothing is passed all text fields will be searched. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of gearboxes to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - Search results gearboxes matching the query. - - Examples: - - Search for 'my_gearbox' in all text properties: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearboxes = client.gearbox.search('my_gearbox') - - """ - filter_ = _create_gearbox_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._search( - query=query, - properties=properties, - filter_=filter_, - limit=limit, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) - - @overload - def aggregate( - self, - aggregate: Aggregations | dm.aggregations.MetricAggregation, - group_by: None = None, - property: GearboxFields | SequenceNotStr[GearboxFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.AggregatedNumberedValue: ... - - @overload - def aggregate( - self, - aggregate: SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation], - group_by: None = None, - property: GearboxFields | SequenceNotStr[GearboxFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> list[dm.aggregations.AggregatedNumberedValue]: ... - - @overload - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: GearboxFields | SequenceNotStr[GearboxFields], - property: GearboxFields | SequenceNotStr[GearboxFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> InstanceAggregationResultList: ... - - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: GearboxFields | SequenceNotStr[GearboxFields] | None = None, - property: GearboxFields | SequenceNotStr[GearboxFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> ( - dm.aggregations.AggregatedNumberedValue - | list[dm.aggregations.AggregatedNumberedValue] - | InstanceAggregationResultList - ): - """Aggregate data across gearboxes - - Args: - aggregate: The aggregation to perform. - group_by: The property to group by when doing the aggregation. - property: The property to perform aggregation on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of gearboxes to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Aggregation results. - - Examples: - - Count gearboxes in space `my_space`: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> result = client.gearbox.aggregate("count", space="my_space") - - """ - - filter_ = _create_gearbox_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._aggregate( - aggregate=aggregate, - group_by=group_by, # type: ignore[arg-type] - properties=property, # type: ignore[arg-type] - query=None, - search_properties=None, - limit=limit, - filter=filter_, - ) - - def histogram( - self, - property: GearboxFields, - interval: float, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.HistogramValue: - """Produces histograms for gearboxes - - Args: - property: The property to use as the value in the histogram. - interval: The interval to use for the histogram bins. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of gearboxes to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Bucketed histogram results. - - """ - filter_ = _create_gearbox_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._histogram( - property, - interval, - None, - None, - limit, - filter_, - ) - - def query(self) -> GearboxQuery: - """Start a query for gearboxes.""" - warnings.warn("This method is renamed to .select", UserWarning, stacklevel=2) - return GearboxQuery(self._client) - - def select(self) -> GearboxQuery: - """Start selecting from gearboxes.""" - warnings.warn( - "The .select is in alpha and is subject to breaking changes without notice.", UserWarning, stacklevel=2 - ) - return GearboxQuery(self._client) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: GearboxFields | Sequence[GearboxFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> GearboxList: - """List/filter gearboxes - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of gearboxes to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - List of requested gearboxes - - Examples: - - List gearboxes and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearboxes = client.gearbox.list(limit=5) - - """ - filter_ = _create_gearbox_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return self._list( - limit=limit, - filter=filter_, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) diff --git a/examples/windmill/_api/gearbox_displacement_x.py b/examples/windmill/_api/gearbox_displacement_x.py deleted file mode 100644 index a2c725a34..000000000 --- a/examples/windmill/_api/gearbox_displacement_x.py +++ /dev/null @@ -1,493 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._gearbox import _create_gearbox_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["displacement_x", "displacement_y", "displacement_z"] - - -class GearboxDisplacementXQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `gearbox.displacement_x` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_displacement_x' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearbox_datapoints = client.gearbox.displacement_x(external_id="my_displacement_x").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `gearbox.displacement_x` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_displacement_x' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearbox_datapoints = client.gearbox.displacement_x(external_id="my_displacement_x").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "displacement_x", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `gearbox.displacement_x` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to displacement_x - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_displacement_x' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearbox_datapoints = client.gearbox.displacement_x(external_id="my_displacement_x").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "displacement_x", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `gearbox.displacement_x` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to displacement_x - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_displacement_x' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> gearbox_datapoints = client.gearbox.displacement_x( - ... external_id="my_displacement_x").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "displacement_x" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_displacement_x( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "displacement_x": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class GearboxDisplacementXAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> GearboxDisplacementXQuery: - """Query timeseries `gearbox.displacement_x` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of gearboxes to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the gearbox.displacement_x timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 gearbox.displacement_x timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearboxes = client.gearbox.displacement_x(limit=5).retrieve() - - """ - filter_ = _create_gearbox_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return GearboxDisplacementXQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `gearbox.displacement_x` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of gearboxes to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries gearbox.displacement_x. - - Examples: - - List gearbox.displacement_x and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearboxes = client.gearbox.displacement_x.list(limit=5) - - """ - filter_ = _create_gearbox_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_displacement_x( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_displacement_x( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "displacement_x", -) -> dict[str, list[str]]: - properties = {"displacement_x"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("displacement_x")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["displacement_x"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/gearbox_displacement_y.py b/examples/windmill/_api/gearbox_displacement_y.py deleted file mode 100644 index 84d9c6a6c..000000000 --- a/examples/windmill/_api/gearbox_displacement_y.py +++ /dev/null @@ -1,493 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._gearbox import _create_gearbox_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["displacement_x", "displacement_y", "displacement_z"] - - -class GearboxDisplacementYQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `gearbox.displacement_y` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_displacement_y' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearbox_datapoints = client.gearbox.displacement_y(external_id="my_displacement_y").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `gearbox.displacement_y` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_displacement_y' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearbox_datapoints = client.gearbox.displacement_y(external_id="my_displacement_y").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "displacement_y", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `gearbox.displacement_y` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to displacement_y - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_displacement_y' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearbox_datapoints = client.gearbox.displacement_y(external_id="my_displacement_y").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "displacement_y", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `gearbox.displacement_y` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to displacement_y - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_displacement_y' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> gearbox_datapoints = client.gearbox.displacement_y( - ... external_id="my_displacement_y").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "displacement_y" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_displacement_y( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "displacement_y": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class GearboxDisplacementYAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> GearboxDisplacementYQuery: - """Query timeseries `gearbox.displacement_y` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of gearboxes to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the gearbox.displacement_y timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 gearbox.displacement_y timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearboxes = client.gearbox.displacement_y(limit=5).retrieve() - - """ - filter_ = _create_gearbox_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return GearboxDisplacementYQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `gearbox.displacement_y` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of gearboxes to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries gearbox.displacement_y. - - Examples: - - List gearbox.displacement_y and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearboxes = client.gearbox.displacement_y.list(limit=5) - - """ - filter_ = _create_gearbox_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_displacement_y( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_displacement_y( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "displacement_y", -) -> dict[str, list[str]]: - properties = {"displacement_y"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("displacement_y")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["displacement_y"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/gearbox_displacement_z.py b/examples/windmill/_api/gearbox_displacement_z.py deleted file mode 100644 index 7c05b15f1..000000000 --- a/examples/windmill/_api/gearbox_displacement_z.py +++ /dev/null @@ -1,493 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._gearbox import _create_gearbox_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["displacement_x", "displacement_y", "displacement_z"] - - -class GearboxDisplacementZQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `gearbox.displacement_z` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_displacement_z' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearbox_datapoints = client.gearbox.displacement_z(external_id="my_displacement_z").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `gearbox.displacement_z` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_displacement_z' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearbox_datapoints = client.gearbox.displacement_z(external_id="my_displacement_z").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "displacement_z", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `gearbox.displacement_z` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to displacement_z - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_displacement_z' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearbox_datapoints = client.gearbox.displacement_z(external_id="my_displacement_z").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "displacement_z", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `gearbox.displacement_z` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to displacement_z - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_displacement_z' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> gearbox_datapoints = client.gearbox.displacement_z( - ... external_id="my_displacement_z").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "displacement_z" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_displacement_z( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "displacement_z": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class GearboxDisplacementZAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> GearboxDisplacementZQuery: - """Query timeseries `gearbox.displacement_z` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of gearboxes to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the gearbox.displacement_z timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 gearbox.displacement_z timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearboxes = client.gearbox.displacement_z(limit=5).retrieve() - - """ - filter_ = _create_gearbox_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return GearboxDisplacementZQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `gearbox.displacement_z` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of gearboxes to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries gearbox.displacement_z. - - Examples: - - List gearbox.displacement_z and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> gearboxes = client.gearbox.displacement_z.list(limit=5) - - """ - filter_ = _create_gearbox_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_displacement_z( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_displacement_z( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "displacement_z", -) -> dict[str, list[str]]: - properties = {"displacement_z"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("displacement_z")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["displacement_z"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/gearbox_query.py b/examples/windmill/_api/gearbox_query.py deleted file mode 100644 index 42fb4adcf..000000000 --- a/examples/windmill/_api/gearbox_query.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import TYPE_CHECKING, cast - -from cognite.client import data_modeling as dm, CogniteClient - -from windmill.data_classes import ( - DomainModelCore, - Gearbox, -) -from windmill._api._core import ( - DEFAULT_QUERY_LIMIT, - EdgeQueryStep, - NodeQueryStep, - DataClassQueryBuilder, - QueryAPI, - T_DomainModelList, - _create_edge_filter, -) - - -class GearboxQueryAPI(QueryAPI[T_DomainModelList]): - _view_id = dm.ViewId("power-models", "Gearbox", "1") - - def __init__( - self, - client: CogniteClient, - builder: DataClassQueryBuilder[T_DomainModelList], - filter_: dm.filters.Filter | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - ): - super().__init__(client, builder) - from_ = self._builder.get_from() - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - filter=filter_, - ), - result_cls=Gearbox, - max_retrieve_limit=limit, - ) - ) - - def query( - self, - ) -> T_DomainModelList: - """Execute query and return the result. - - Returns: - The list of the source nodes of the query. - - """ - return self._query() diff --git a/examples/windmill/_api/generator.py b/examples/windmill/_api/generator.py deleted file mode 100644 index d4d3ad9d7..000000000 --- a/examples/windmill/_api/generator.py +++ /dev/null @@ -1,444 +0,0 @@ -from __future__ import annotations - -from collections.abc import Sequence -from typing import overload, Literal -import warnings - -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes.data_modeling.instances import InstanceAggregationResultList, InstanceSort - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - NodeQueryStep, - EdgeQueryStep, - DataClassQueryBuilder, -) -from windmill.data_classes import ( - DomainModelCore, - DomainModelWrite, - ResourcesWriteResult, - Generator, - GeneratorWrite, - GeneratorFields, - GeneratorList, - GeneratorWriteList, - GeneratorTextFields, -) -from windmill.data_classes._generator import ( - GeneratorQuery, - _GENERATOR_PROPERTIES_BY_FIELD, - _create_generator_filter, -) -from windmill._api._core import ( - DEFAULT_LIMIT_READ, - Aggregations, - NodeAPI, - SequenceNotStr, -) -from windmill._api.generator_generator_speed_controller import GeneratorGeneratorSpeedControllerAPI -from windmill._api.generator_generator_speed_controller_reference import GeneratorGeneratorSpeedControllerReferenceAPI -from windmill._api.generator_query import GeneratorQueryAPI - - -class GeneratorAPI(NodeAPI[Generator, GeneratorWrite, GeneratorList, GeneratorWriteList]): - _view_id = dm.ViewId("power-models", "Generator", "1") - _properties_by_field = _GENERATOR_PROPERTIES_BY_FIELD - _class_type = Generator - _class_list = GeneratorList - _class_write_list = GeneratorWriteList - - def __init__(self, client: CogniteClient): - super().__init__(client=client) - - self.generator_speed_controller = GeneratorGeneratorSpeedControllerAPI(client, self._view_id) - self.generator_speed_controller_reference = GeneratorGeneratorSpeedControllerReferenceAPI(client, self._view_id) - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - filter: dm.Filter | None = None, - ) -> GeneratorQueryAPI[GeneratorList]: - """Query starting at generators. - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of generators to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query API for generators. - - """ - has_data = dm.filters.HasData(views=[self._view_id]) - filter_ = _create_generator_filter( - self._view_id, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - builder = DataClassQueryBuilder(GeneratorList) - return GeneratorQueryAPI(self._client, builder, filter_, limit) - - def apply( - self, - generator: GeneratorWrite | Sequence[GeneratorWrite], - replace: bool = False, - write_none: bool = False, - ) -> ResourcesWriteResult: - """Add or update (upsert) generators. - - Args: - generator: Generator or sequence of generators to upsert. - replace (bool): How do we behave when a property value exists? Do we replace all matching and existing values with the supplied values (true)? - Or should we merge in new values for properties together with the existing values (false)? Note: This setting applies for all nodes or edges specified in the ingestion call. - write_none (bool): This method, will by default, skip properties that are set to None. However, if you want to set properties to None, - you can set this parameter to True. Note this only applies to properties that are nullable. - Returns: - Created instance(s), i.e., nodes, edges, and time series. - - Examples: - - Create a new generator: - - >>> from windmill import WindmillClient - >>> from windmill.data_classes import GeneratorWrite - >>> client = WindmillClient() - >>> generator = GeneratorWrite(external_id="my_generator", ...) - >>> result = client.generator.apply(generator) - - """ - warnings.warn( - "The .apply method is deprecated and will be removed in v1.0. " - "Please use the .upsert method on the client instead. This means instead of " - "`my_client.generator.apply(my_items)` please use `my_client.upsert(my_items)`." - "The motivation is that all apply methods are the same, and having one apply method per API " - " class encourages users to create items in small batches, which is inefficient." - "In addition, .upsert method is more descriptive of what the method does.", - UserWarning, - stacklevel=2, - ) - return self._apply(generator, replace, write_none) - - def delete( - self, external_id: str | SequenceNotStr[str], space: str = DEFAULT_INSTANCE_SPACE - ) -> dm.InstancesDeleteResult: - """Delete one or more generator. - - Args: - external_id: External id of the generator to delete. - space: The space where all the generator are located. - - Returns: - The instance(s), i.e., nodes and edges which has been deleted. Empty list if nothing was deleted. - - Examples: - - Delete generator by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> client.generator.delete("my_generator") - """ - warnings.warn( - "The .delete method is deprecated and will be removed in v1.0. " - "Please use the .delete method on the client instead. This means instead of " - "`my_client.generator.delete(my_ids)` please use `my_client.delete(my_ids)`." - "The motivation is that all delete methods are the same, and having one delete method per API " - " class encourages users to delete items in small batches, which is inefficient.", - UserWarning, - stacklevel=2, - ) - return self._delete(external_id, space) - - @overload - def retrieve( - self, external_id: str | dm.NodeId | tuple[str, str], space: str = DEFAULT_INSTANCE_SPACE - ) -> Generator | None: ... - - @overload - def retrieve( - self, external_id: SequenceNotStr[str | dm.NodeId | tuple[str, str]], space: str = DEFAULT_INSTANCE_SPACE - ) -> GeneratorList: ... - - def retrieve( - self, - external_id: str | dm.NodeId | tuple[str, str] | SequenceNotStr[str | dm.NodeId | tuple[str, str]], - space: str = DEFAULT_INSTANCE_SPACE, - ) -> Generator | GeneratorList | None: - """Retrieve one or more generators by id(s). - - Args: - external_id: External id or list of external ids of the generators. - space: The space where all the generators are located. - - Returns: - The requested generators. - - Examples: - - Retrieve generator by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> generator = client.generator.retrieve("my_generator") - - """ - return self._retrieve(external_id, space) - - def search( - self, - query: str, - properties: GeneratorTextFields | SequenceNotStr[GeneratorTextFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: GeneratorFields | SequenceNotStr[GeneratorFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> GeneratorList: - """Search generators - - Args: - query: The search query, - properties: The property to search, if nothing is passed all text fields will be searched. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of generators to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - Search results generators matching the query. - - Examples: - - Search for 'my_generator' in all text properties: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> generators = client.generator.search('my_generator') - - """ - filter_ = _create_generator_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._search( - query=query, - properties=properties, - filter_=filter_, - limit=limit, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) - - @overload - def aggregate( - self, - aggregate: Aggregations | dm.aggregations.MetricAggregation, - group_by: None = None, - property: GeneratorFields | SequenceNotStr[GeneratorFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.AggregatedNumberedValue: ... - - @overload - def aggregate( - self, - aggregate: SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation], - group_by: None = None, - property: GeneratorFields | SequenceNotStr[GeneratorFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> list[dm.aggregations.AggregatedNumberedValue]: ... - - @overload - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: GeneratorFields | SequenceNotStr[GeneratorFields], - property: GeneratorFields | SequenceNotStr[GeneratorFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> InstanceAggregationResultList: ... - - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: GeneratorFields | SequenceNotStr[GeneratorFields] | None = None, - property: GeneratorFields | SequenceNotStr[GeneratorFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> ( - dm.aggregations.AggregatedNumberedValue - | list[dm.aggregations.AggregatedNumberedValue] - | InstanceAggregationResultList - ): - """Aggregate data across generators - - Args: - aggregate: The aggregation to perform. - group_by: The property to group by when doing the aggregation. - property: The property to perform aggregation on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of generators to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Aggregation results. - - Examples: - - Count generators in space `my_space`: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> result = client.generator.aggregate("count", space="my_space") - - """ - - filter_ = _create_generator_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._aggregate( - aggregate=aggregate, - group_by=group_by, # type: ignore[arg-type] - properties=property, # type: ignore[arg-type] - query=None, - search_properties=None, - limit=limit, - filter=filter_, - ) - - def histogram( - self, - property: GeneratorFields, - interval: float, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.HistogramValue: - """Produces histograms for generators - - Args: - property: The property to use as the value in the histogram. - interval: The interval to use for the histogram bins. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of generators to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Bucketed histogram results. - - """ - filter_ = _create_generator_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._histogram( - property, - interval, - None, - None, - limit, - filter_, - ) - - def query(self) -> GeneratorQuery: - """Start a query for generators.""" - warnings.warn("This method is renamed to .select", UserWarning, stacklevel=2) - return GeneratorQuery(self._client) - - def select(self) -> GeneratorQuery: - """Start selecting from generators.""" - warnings.warn( - "The .select is in alpha and is subject to breaking changes without notice.", UserWarning, stacklevel=2 - ) - return GeneratorQuery(self._client) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: GeneratorFields | Sequence[GeneratorFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> GeneratorList: - """List/filter generators - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of generators to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - List of requested generators - - Examples: - - List generators and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> generators = client.generator.list(limit=5) - - """ - filter_ = _create_generator_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return self._list( - limit=limit, - filter=filter_, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) diff --git a/examples/windmill/_api/generator_generator_speed_controller.py b/examples/windmill/_api/generator_generator_speed_controller.py deleted file mode 100644 index c2f8434ce..000000000 --- a/examples/windmill/_api/generator_generator_speed_controller.py +++ /dev/null @@ -1,493 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._generator import _create_generator_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["generator_speed_controller", "generator_speed_controller_reference"] - - -class GeneratorGeneratorSpeedControllerQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `generator.generator_speed_controller` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_generator_speed_controller' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> generator_datapoints = client.generator.generator_speed_controller(external_id="my_generator_speed_controller").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `generator.generator_speed_controller` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_generator_speed_controller' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> generator_datapoints = client.generator.generator_speed_controller(external_id="my_generator_speed_controller").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "generator_speed_controller", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `generator.generator_speed_controller` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to generator_speed_controller - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_generator_speed_controller' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> generator_datapoints = client.generator.generator_speed_controller(external_id="my_generator_speed_controller").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "generator_speed_controller", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `generator.generator_speed_controller` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to generator_speed_controller - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_generator_speed_controller' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> generator_datapoints = client.generator.generator_speed_controller( - ... external_id="my_generator_speed_controller").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "generator_speed_controller" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_generator_speed_controller( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "generator_speed_controller": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class GeneratorGeneratorSpeedControllerAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> GeneratorGeneratorSpeedControllerQuery: - """Query timeseries `generator.generator_speed_controller` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of generators to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the generator.generator_speed_controller timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 generator.generator_speed_controller timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> generators = client.generator.generator_speed_controller(limit=5).retrieve() - - """ - filter_ = _create_generator_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return GeneratorGeneratorSpeedControllerQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `generator.generator_speed_controller` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of generators to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries generator.generator_speed_controller. - - Examples: - - List generator.generator_speed_controller and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> generators = client.generator.generator_speed_controller.list(limit=5) - - """ - filter_ = _create_generator_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_generator_speed_controller( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_generator_speed_controller( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "generator_speed_controller", -) -> dict[str, list[str]]: - properties = {"generator_speed_controller"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("generator_speed_controller")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["generator_speed_controller"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/generator_generator_speed_controller_reference.py b/examples/windmill/_api/generator_generator_speed_controller_reference.py deleted file mode 100644 index 0f3ce12df..000000000 --- a/examples/windmill/_api/generator_generator_speed_controller_reference.py +++ /dev/null @@ -1,493 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._generator import _create_generator_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["generator_speed_controller", "generator_speed_controller_reference"] - - -class GeneratorGeneratorSpeedControllerReferenceQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `generator.generator_speed_controller_reference` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_generator_speed_controller_reference' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> generator_datapoints = client.generator.generator_speed_controller_reference(external_id="my_generator_speed_controller_reference").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `generator.generator_speed_controller_reference` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_generator_speed_controller_reference' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> generator_datapoints = client.generator.generator_speed_controller_reference(external_id="my_generator_speed_controller_reference").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "generator_speed_controller_reference", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `generator.generator_speed_controller_reference` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to generator_speed_controller_reference - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_generator_speed_controller_reference' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> generator_datapoints = client.generator.generator_speed_controller_reference(external_id="my_generator_speed_controller_reference").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "generator_speed_controller_reference", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `generator.generator_speed_controller_reference` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to generator_speed_controller_reference - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_generator_speed_controller_reference' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> generator_datapoints = client.generator.generator_speed_controller_reference( - ... external_id="my_generator_speed_controller_reference").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "generator_speed_controller_reference" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_generator_speed_controller_reference( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "generator_speed_controller_reference": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class GeneratorGeneratorSpeedControllerReferenceAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> GeneratorGeneratorSpeedControllerReferenceQuery: - """Query timeseries `generator.generator_speed_controller_reference` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of generators to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the generator.generator_speed_controller_reference timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 generator.generator_speed_controller_reference timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> generators = client.generator.generator_speed_controller_reference(limit=5).retrieve() - - """ - filter_ = _create_generator_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return GeneratorGeneratorSpeedControllerReferenceQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `generator.generator_speed_controller_reference` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of generators to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries generator.generator_speed_controller_reference. - - Examples: - - List generator.generator_speed_controller_reference and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> generators = client.generator.generator_speed_controller_reference.list(limit=5) - - """ - filter_ = _create_generator_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_generator_speed_controller_reference( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_generator_speed_controller_reference( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "generator_speed_controller_reference", -) -> dict[str, list[str]]: - properties = {"generator_speed_controller_reference"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("generator_speed_controller_reference")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["generator_speed_controller_reference"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/generator_query.py b/examples/windmill/_api/generator_query.py deleted file mode 100644 index 2b8cec24b..000000000 --- a/examples/windmill/_api/generator_query.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import TYPE_CHECKING, cast - -from cognite.client import data_modeling as dm, CogniteClient - -from windmill.data_classes import ( - DomainModelCore, - Generator, -) -from windmill._api._core import ( - DEFAULT_QUERY_LIMIT, - EdgeQueryStep, - NodeQueryStep, - DataClassQueryBuilder, - QueryAPI, - T_DomainModelList, - _create_edge_filter, -) - - -class GeneratorQueryAPI(QueryAPI[T_DomainModelList]): - _view_id = dm.ViewId("power-models", "Generator", "1") - - def __init__( - self, - client: CogniteClient, - builder: DataClassQueryBuilder[T_DomainModelList], - filter_: dm.filters.Filter | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - ): - super().__init__(client, builder) - from_ = self._builder.get_from() - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - filter=filter_, - ), - result_cls=Generator, - max_retrieve_limit=limit, - ) - ) - - def query( - self, - ) -> T_DomainModelList: - """Execute query and return the result. - - Returns: - The list of the source nodes of the query. - - """ - return self._query() diff --git a/examples/windmill/_api/high_speed_shaft.py b/examples/windmill/_api/high_speed_shaft.py deleted file mode 100644 index 50a2b72bd..000000000 --- a/examples/windmill/_api/high_speed_shaft.py +++ /dev/null @@ -1,446 +0,0 @@ -from __future__ import annotations - -from collections.abc import Sequence -from typing import overload, Literal -import warnings - -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes.data_modeling.instances import InstanceAggregationResultList, InstanceSort - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - NodeQueryStep, - EdgeQueryStep, - DataClassQueryBuilder, -) -from windmill.data_classes import ( - DomainModelCore, - DomainModelWrite, - ResourcesWriteResult, - HighSpeedShaft, - HighSpeedShaftWrite, - HighSpeedShaftFields, - HighSpeedShaftList, - HighSpeedShaftWriteList, - HighSpeedShaftTextFields, -) -from windmill.data_classes._high_speed_shaft import ( - HighSpeedShaftQuery, - _HIGHSPEEDSHAFT_PROPERTIES_BY_FIELD, - _create_high_speed_shaft_filter, -) -from windmill._api._core import ( - DEFAULT_LIMIT_READ, - Aggregations, - NodeAPI, - SequenceNotStr, -) -from windmill._api.high_speed_shaft_bending_moment_y import HighSpeedShaftBendingMomentYAPI -from windmill._api.high_speed_shaft_bending_monent_x import HighSpeedShaftBendingMonentXAPI -from windmill._api.high_speed_shaft_torque import HighSpeedShaftTorqueAPI -from windmill._api.high_speed_shaft_query import HighSpeedShaftQueryAPI - - -class HighSpeedShaftAPI(NodeAPI[HighSpeedShaft, HighSpeedShaftWrite, HighSpeedShaftList, HighSpeedShaftWriteList]): - _view_id = dm.ViewId("power-models", "HighSpeedShaft", "1") - _properties_by_field = _HIGHSPEEDSHAFT_PROPERTIES_BY_FIELD - _class_type = HighSpeedShaft - _class_list = HighSpeedShaftList - _class_write_list = HighSpeedShaftWriteList - - def __init__(self, client: CogniteClient): - super().__init__(client=client) - - self.bending_moment_y = HighSpeedShaftBendingMomentYAPI(client, self._view_id) - self.bending_monent_x = HighSpeedShaftBendingMonentXAPI(client, self._view_id) - self.torque = HighSpeedShaftTorqueAPI(client, self._view_id) - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - filter: dm.Filter | None = None, - ) -> HighSpeedShaftQueryAPI[HighSpeedShaftList]: - """Query starting at high speed shafts. - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of high speed shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query API for high speed shafts. - - """ - has_data = dm.filters.HasData(views=[self._view_id]) - filter_ = _create_high_speed_shaft_filter( - self._view_id, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - builder = DataClassQueryBuilder(HighSpeedShaftList) - return HighSpeedShaftQueryAPI(self._client, builder, filter_, limit) - - def apply( - self, - high_speed_shaft: HighSpeedShaftWrite | Sequence[HighSpeedShaftWrite], - replace: bool = False, - write_none: bool = False, - ) -> ResourcesWriteResult: - """Add or update (upsert) high speed shafts. - - Args: - high_speed_shaft: High speed shaft or sequence of high speed shafts to upsert. - replace (bool): How do we behave when a property value exists? Do we replace all matching and existing values with the supplied values (true)? - Or should we merge in new values for properties together with the existing values (false)? Note: This setting applies for all nodes or edges specified in the ingestion call. - write_none (bool): This method, will by default, skip properties that are set to None. However, if you want to set properties to None, - you can set this parameter to True. Note this only applies to properties that are nullable. - Returns: - Created instance(s), i.e., nodes, edges, and time series. - - Examples: - - Create a new high_speed_shaft: - - >>> from windmill import WindmillClient - >>> from windmill.data_classes import HighSpeedShaftWrite - >>> client = WindmillClient() - >>> high_speed_shaft = HighSpeedShaftWrite(external_id="my_high_speed_shaft", ...) - >>> result = client.high_speed_shaft.apply(high_speed_shaft) - - """ - warnings.warn( - "The .apply method is deprecated and will be removed in v1.0. " - "Please use the .upsert method on the client instead. This means instead of " - "`my_client.high_speed_shaft.apply(my_items)` please use `my_client.upsert(my_items)`." - "The motivation is that all apply methods are the same, and having one apply method per API " - " class encourages users to create items in small batches, which is inefficient." - "In addition, .upsert method is more descriptive of what the method does.", - UserWarning, - stacklevel=2, - ) - return self._apply(high_speed_shaft, replace, write_none) - - def delete( - self, external_id: str | SequenceNotStr[str], space: str = DEFAULT_INSTANCE_SPACE - ) -> dm.InstancesDeleteResult: - """Delete one or more high speed shaft. - - Args: - external_id: External id of the high speed shaft to delete. - space: The space where all the high speed shaft are located. - - Returns: - The instance(s), i.e., nodes and edges which has been deleted. Empty list if nothing was deleted. - - Examples: - - Delete high_speed_shaft by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> client.high_speed_shaft.delete("my_high_speed_shaft") - """ - warnings.warn( - "The .delete method is deprecated and will be removed in v1.0. " - "Please use the .delete method on the client instead. This means instead of " - "`my_client.high_speed_shaft.delete(my_ids)` please use `my_client.delete(my_ids)`." - "The motivation is that all delete methods are the same, and having one delete method per API " - " class encourages users to delete items in small batches, which is inefficient.", - UserWarning, - stacklevel=2, - ) - return self._delete(external_id, space) - - @overload - def retrieve( - self, external_id: str | dm.NodeId | tuple[str, str], space: str = DEFAULT_INSTANCE_SPACE - ) -> HighSpeedShaft | None: ... - - @overload - def retrieve( - self, external_id: SequenceNotStr[str | dm.NodeId | tuple[str, str]], space: str = DEFAULT_INSTANCE_SPACE - ) -> HighSpeedShaftList: ... - - def retrieve( - self, - external_id: str | dm.NodeId | tuple[str, str] | SequenceNotStr[str | dm.NodeId | tuple[str, str]], - space: str = DEFAULT_INSTANCE_SPACE, - ) -> HighSpeedShaft | HighSpeedShaftList | None: - """Retrieve one or more high speed shafts by id(s). - - Args: - external_id: External id or list of external ids of the high speed shafts. - space: The space where all the high speed shafts are located. - - Returns: - The requested high speed shafts. - - Examples: - - Retrieve high_speed_shaft by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shaft = client.high_speed_shaft.retrieve("my_high_speed_shaft") - - """ - return self._retrieve(external_id, space) - - def search( - self, - query: str, - properties: HighSpeedShaftTextFields | SequenceNotStr[HighSpeedShaftTextFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: HighSpeedShaftFields | SequenceNotStr[HighSpeedShaftFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> HighSpeedShaftList: - """Search high speed shafts - - Args: - query: The search query, - properties: The property to search, if nothing is passed all text fields will be searched. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of high speed shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - Search results high speed shafts matching the query. - - Examples: - - Search for 'my_high_speed_shaft' in all text properties: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shafts = client.high_speed_shaft.search('my_high_speed_shaft') - - """ - filter_ = _create_high_speed_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._search( - query=query, - properties=properties, - filter_=filter_, - limit=limit, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) - - @overload - def aggregate( - self, - aggregate: Aggregations | dm.aggregations.MetricAggregation, - group_by: None = None, - property: HighSpeedShaftFields | SequenceNotStr[HighSpeedShaftFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.AggregatedNumberedValue: ... - - @overload - def aggregate( - self, - aggregate: SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation], - group_by: None = None, - property: HighSpeedShaftFields | SequenceNotStr[HighSpeedShaftFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> list[dm.aggregations.AggregatedNumberedValue]: ... - - @overload - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: HighSpeedShaftFields | SequenceNotStr[HighSpeedShaftFields], - property: HighSpeedShaftFields | SequenceNotStr[HighSpeedShaftFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> InstanceAggregationResultList: ... - - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: HighSpeedShaftFields | SequenceNotStr[HighSpeedShaftFields] | None = None, - property: HighSpeedShaftFields | SequenceNotStr[HighSpeedShaftFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> ( - dm.aggregations.AggregatedNumberedValue - | list[dm.aggregations.AggregatedNumberedValue] - | InstanceAggregationResultList - ): - """Aggregate data across high speed shafts - - Args: - aggregate: The aggregation to perform. - group_by: The property to group by when doing the aggregation. - property: The property to perform aggregation on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of high speed shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Aggregation results. - - Examples: - - Count high speed shafts in space `my_space`: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> result = client.high_speed_shaft.aggregate("count", space="my_space") - - """ - - filter_ = _create_high_speed_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._aggregate( - aggregate=aggregate, - group_by=group_by, # type: ignore[arg-type] - properties=property, # type: ignore[arg-type] - query=None, - search_properties=None, - limit=limit, - filter=filter_, - ) - - def histogram( - self, - property: HighSpeedShaftFields, - interval: float, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.HistogramValue: - """Produces histograms for high speed shafts - - Args: - property: The property to use as the value in the histogram. - interval: The interval to use for the histogram bins. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of high speed shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Bucketed histogram results. - - """ - filter_ = _create_high_speed_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._histogram( - property, - interval, - None, - None, - limit, - filter_, - ) - - def query(self) -> HighSpeedShaftQuery: - """Start a query for high speed shafts.""" - warnings.warn("This method is renamed to .select", UserWarning, stacklevel=2) - return HighSpeedShaftQuery(self._client) - - def select(self) -> HighSpeedShaftQuery: - """Start selecting from high speed shafts.""" - warnings.warn( - "The .select is in alpha and is subject to breaking changes without notice.", UserWarning, stacklevel=2 - ) - return HighSpeedShaftQuery(self._client) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: HighSpeedShaftFields | Sequence[HighSpeedShaftFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> HighSpeedShaftList: - """List/filter high speed shafts - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of high speed shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - List of requested high speed shafts - - Examples: - - List high speed shafts and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shafts = client.high_speed_shaft.list(limit=5) - - """ - filter_ = _create_high_speed_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return self._list( - limit=limit, - filter=filter_, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) diff --git a/examples/windmill/_api/high_speed_shaft_bending_moment_y.py b/examples/windmill/_api/high_speed_shaft_bending_moment_y.py deleted file mode 100644 index a4cacd371..000000000 --- a/examples/windmill/_api/high_speed_shaft_bending_moment_y.py +++ /dev/null @@ -1,493 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._high_speed_shaft import _create_high_speed_shaft_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["bending_moment_y", "bending_monent_x", "torque"] - - -class HighSpeedShaftBendingMomentYQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `high_speed_shaft.bending_moment_y` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_bending_moment_y' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shaft_datapoints = client.high_speed_shaft.bending_moment_y(external_id="my_bending_moment_y").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `high_speed_shaft.bending_moment_y` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_bending_moment_y' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shaft_datapoints = client.high_speed_shaft.bending_moment_y(external_id="my_bending_moment_y").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "bending_moment_y", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `high_speed_shaft.bending_moment_y` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to bending_moment_y - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_bending_moment_y' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shaft_datapoints = client.high_speed_shaft.bending_moment_y(external_id="my_bending_moment_y").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "bending_moment_y", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `high_speed_shaft.bending_moment_y` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to bending_moment_y - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_bending_moment_y' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> high_speed_shaft_datapoints = client.high_speed_shaft.bending_moment_y( - ... external_id="my_bending_moment_y").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "bending_moment_y" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_bending_moment_y( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "bending_moment_y": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class HighSpeedShaftBendingMomentYAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> HighSpeedShaftBendingMomentYQuery: - """Query timeseries `high_speed_shaft.bending_moment_y` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of high speed shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the high_speed_shaft.bending_moment_y timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 high_speed_shaft.bending_moment_y timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shafts = client.high_speed_shaft.bending_moment_y(limit=5).retrieve() - - """ - filter_ = _create_high_speed_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return HighSpeedShaftBendingMomentYQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `high_speed_shaft.bending_moment_y` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of high speed shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries high_speed_shaft.bending_moment_y. - - Examples: - - List high_speed_shaft.bending_moment_y and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shafts = client.high_speed_shaft.bending_moment_y.list(limit=5) - - """ - filter_ = _create_high_speed_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_bending_moment_y( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_bending_moment_y( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "bending_moment_y", -) -> dict[str, list[str]]: - properties = {"bending_moment_y"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("bending_moment_y")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["bending_moment_y"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/high_speed_shaft_bending_monent_x.py b/examples/windmill/_api/high_speed_shaft_bending_monent_x.py deleted file mode 100644 index 606229d8a..000000000 --- a/examples/windmill/_api/high_speed_shaft_bending_monent_x.py +++ /dev/null @@ -1,493 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._high_speed_shaft import _create_high_speed_shaft_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["bending_moment_y", "bending_monent_x", "torque"] - - -class HighSpeedShaftBendingMonentXQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `high_speed_shaft.bending_monent_x` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_bending_monent_x' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shaft_datapoints = client.high_speed_shaft.bending_monent_x(external_id="my_bending_monent_x").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `high_speed_shaft.bending_monent_x` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_bending_monent_x' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shaft_datapoints = client.high_speed_shaft.bending_monent_x(external_id="my_bending_monent_x").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "bending_monent_x", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `high_speed_shaft.bending_monent_x` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to bending_monent_x - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_bending_monent_x' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shaft_datapoints = client.high_speed_shaft.bending_monent_x(external_id="my_bending_monent_x").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "bending_monent_x", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `high_speed_shaft.bending_monent_x` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to bending_monent_x - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_bending_monent_x' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> high_speed_shaft_datapoints = client.high_speed_shaft.bending_monent_x( - ... external_id="my_bending_monent_x").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "bending_monent_x" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_bending_monent_x( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "bending_monent_x": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class HighSpeedShaftBendingMonentXAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> HighSpeedShaftBendingMonentXQuery: - """Query timeseries `high_speed_shaft.bending_monent_x` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of high speed shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the high_speed_shaft.bending_monent_x timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 high_speed_shaft.bending_monent_x timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shafts = client.high_speed_shaft.bending_monent_x(limit=5).retrieve() - - """ - filter_ = _create_high_speed_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return HighSpeedShaftBendingMonentXQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `high_speed_shaft.bending_monent_x` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of high speed shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries high_speed_shaft.bending_monent_x. - - Examples: - - List high_speed_shaft.bending_monent_x and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shafts = client.high_speed_shaft.bending_monent_x.list(limit=5) - - """ - filter_ = _create_high_speed_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_bending_monent_x( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_bending_monent_x( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "bending_monent_x", -) -> dict[str, list[str]]: - properties = {"bending_monent_x"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("bending_monent_x")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["bending_monent_x"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/high_speed_shaft_query.py b/examples/windmill/_api/high_speed_shaft_query.py deleted file mode 100644 index 7823ff40e..000000000 --- a/examples/windmill/_api/high_speed_shaft_query.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import TYPE_CHECKING, cast - -from cognite.client import data_modeling as dm, CogniteClient - -from windmill.data_classes import ( - DomainModelCore, - HighSpeedShaft, -) -from windmill._api._core import ( - DEFAULT_QUERY_LIMIT, - EdgeQueryStep, - NodeQueryStep, - DataClassQueryBuilder, - QueryAPI, - T_DomainModelList, - _create_edge_filter, -) - - -class HighSpeedShaftQueryAPI(QueryAPI[T_DomainModelList]): - _view_id = dm.ViewId("power-models", "HighSpeedShaft", "1") - - def __init__( - self, - client: CogniteClient, - builder: DataClassQueryBuilder[T_DomainModelList], - filter_: dm.filters.Filter | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - ): - super().__init__(client, builder) - from_ = self._builder.get_from() - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - filter=filter_, - ), - result_cls=HighSpeedShaft, - max_retrieve_limit=limit, - ) - ) - - def query( - self, - ) -> T_DomainModelList: - """Execute query and return the result. - - Returns: - The list of the source nodes of the query. - - """ - return self._query() diff --git a/examples/windmill/_api/high_speed_shaft_torque.py b/examples/windmill/_api/high_speed_shaft_torque.py deleted file mode 100644 index 4ff69ff64..000000000 --- a/examples/windmill/_api/high_speed_shaft_torque.py +++ /dev/null @@ -1,491 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._high_speed_shaft import _create_high_speed_shaft_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["bending_moment_y", "bending_monent_x", "torque"] - - -class HighSpeedShaftTorqueQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `high_speed_shaft.torque` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_torque' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shaft_datapoints = client.high_speed_shaft.torque(external_id="my_torque").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `high_speed_shaft.torque` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_torque' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shaft_datapoints = client.high_speed_shaft.torque(external_id="my_torque").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "torque", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `high_speed_shaft.torque` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to torque - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_torque' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shaft_datapoints = client.high_speed_shaft.torque(external_id="my_torque").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "torque", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `high_speed_shaft.torque` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to torque - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_torque' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> high_speed_shaft_datapoints = client.high_speed_shaft.torque( - ... external_id="my_torque").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "torque" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_torque( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "torque": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class HighSpeedShaftTorqueAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> HighSpeedShaftTorqueQuery: - """Query timeseries `high_speed_shaft.torque` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of high speed shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the high_speed_shaft.torque timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 high_speed_shaft.torque timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shafts = client.high_speed_shaft.torque(limit=5).retrieve() - - """ - filter_ = _create_high_speed_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return HighSpeedShaftTorqueQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `high_speed_shaft.torque` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of high speed shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries high_speed_shaft.torque. - - Examples: - - List high_speed_shaft.torque and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> high_speed_shafts = client.high_speed_shaft.torque.list(limit=5) - - """ - filter_ = _create_high_speed_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_torque(self._client, self._view_id, filter_, limit) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_torque( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "torque", -) -> dict[str, list[str]]: - properties = {"torque"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("torque")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["torque"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/main_shaft.py b/examples/windmill/_api/main_shaft.py deleted file mode 100644 index f7d8a91d0..000000000 --- a/examples/windmill/_api/main_shaft.py +++ /dev/null @@ -1,450 +0,0 @@ -from __future__ import annotations - -from collections.abc import Sequence -from typing import overload, Literal -import warnings - -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes.data_modeling.instances import InstanceAggregationResultList, InstanceSort - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - NodeQueryStep, - EdgeQueryStep, - DataClassQueryBuilder, -) -from windmill.data_classes import ( - DomainModelCore, - DomainModelWrite, - ResourcesWriteResult, - MainShaft, - MainShaftWrite, - MainShaftFields, - MainShaftList, - MainShaftWriteList, - MainShaftTextFields, -) -from windmill.data_classes._main_shaft import ( - MainShaftQuery, - _MAINSHAFT_PROPERTIES_BY_FIELD, - _create_main_shaft_filter, -) -from windmill._api._core import ( - DEFAULT_LIMIT_READ, - Aggregations, - NodeAPI, - SequenceNotStr, -) -from windmill._api.main_shaft_bending_x import MainShaftBendingXAPI -from windmill._api.main_shaft_bending_y import MainShaftBendingYAPI -from windmill._api.main_shaft_calculated_tilt_moment import MainShaftCalculatedTiltMomentAPI -from windmill._api.main_shaft_calculated_yaw_moment import MainShaftCalculatedYawMomentAPI -from windmill._api.main_shaft_torque import MainShaftTorqueAPI -from windmill._api.main_shaft_query import MainShaftQueryAPI - - -class MainShaftAPI(NodeAPI[MainShaft, MainShaftWrite, MainShaftList, MainShaftWriteList]): - _view_id = dm.ViewId("power-models", "MainShaft", "1") - _properties_by_field = _MAINSHAFT_PROPERTIES_BY_FIELD - _class_type = MainShaft - _class_list = MainShaftList - _class_write_list = MainShaftWriteList - - def __init__(self, client: CogniteClient): - super().__init__(client=client) - - self.bending_x = MainShaftBendingXAPI(client, self._view_id) - self.bending_y = MainShaftBendingYAPI(client, self._view_id) - self.calculated_tilt_moment = MainShaftCalculatedTiltMomentAPI(client, self._view_id) - self.calculated_yaw_moment = MainShaftCalculatedYawMomentAPI(client, self._view_id) - self.torque = MainShaftTorqueAPI(client, self._view_id) - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - filter: dm.Filter | None = None, - ) -> MainShaftQueryAPI[MainShaftList]: - """Query starting at main shafts. - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of main shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query API for main shafts. - - """ - has_data = dm.filters.HasData(views=[self._view_id]) - filter_ = _create_main_shaft_filter( - self._view_id, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - builder = DataClassQueryBuilder(MainShaftList) - return MainShaftQueryAPI(self._client, builder, filter_, limit) - - def apply( - self, - main_shaft: MainShaftWrite | Sequence[MainShaftWrite], - replace: bool = False, - write_none: bool = False, - ) -> ResourcesWriteResult: - """Add or update (upsert) main shafts. - - Args: - main_shaft: Main shaft or sequence of main shafts to upsert. - replace (bool): How do we behave when a property value exists? Do we replace all matching and existing values with the supplied values (true)? - Or should we merge in new values for properties together with the existing values (false)? Note: This setting applies for all nodes or edges specified in the ingestion call. - write_none (bool): This method, will by default, skip properties that are set to None. However, if you want to set properties to None, - you can set this parameter to True. Note this only applies to properties that are nullable. - Returns: - Created instance(s), i.e., nodes, edges, and time series. - - Examples: - - Create a new main_shaft: - - >>> from windmill import WindmillClient - >>> from windmill.data_classes import MainShaftWrite - >>> client = WindmillClient() - >>> main_shaft = MainShaftWrite(external_id="my_main_shaft", ...) - >>> result = client.main_shaft.apply(main_shaft) - - """ - warnings.warn( - "The .apply method is deprecated and will be removed in v1.0. " - "Please use the .upsert method on the client instead. This means instead of " - "`my_client.main_shaft.apply(my_items)` please use `my_client.upsert(my_items)`." - "The motivation is that all apply methods are the same, and having one apply method per API " - " class encourages users to create items in small batches, which is inefficient." - "In addition, .upsert method is more descriptive of what the method does.", - UserWarning, - stacklevel=2, - ) - return self._apply(main_shaft, replace, write_none) - - def delete( - self, external_id: str | SequenceNotStr[str], space: str = DEFAULT_INSTANCE_SPACE - ) -> dm.InstancesDeleteResult: - """Delete one or more main shaft. - - Args: - external_id: External id of the main shaft to delete. - space: The space where all the main shaft are located. - - Returns: - The instance(s), i.e., nodes and edges which has been deleted. Empty list if nothing was deleted. - - Examples: - - Delete main_shaft by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> client.main_shaft.delete("my_main_shaft") - """ - warnings.warn( - "The .delete method is deprecated and will be removed in v1.0. " - "Please use the .delete method on the client instead. This means instead of " - "`my_client.main_shaft.delete(my_ids)` please use `my_client.delete(my_ids)`." - "The motivation is that all delete methods are the same, and having one delete method per API " - " class encourages users to delete items in small batches, which is inefficient.", - UserWarning, - stacklevel=2, - ) - return self._delete(external_id, space) - - @overload - def retrieve( - self, external_id: str | dm.NodeId | tuple[str, str], space: str = DEFAULT_INSTANCE_SPACE - ) -> MainShaft | None: ... - - @overload - def retrieve( - self, external_id: SequenceNotStr[str | dm.NodeId | tuple[str, str]], space: str = DEFAULT_INSTANCE_SPACE - ) -> MainShaftList: ... - - def retrieve( - self, - external_id: str | dm.NodeId | tuple[str, str] | SequenceNotStr[str | dm.NodeId | tuple[str, str]], - space: str = DEFAULT_INSTANCE_SPACE, - ) -> MainShaft | MainShaftList | None: - """Retrieve one or more main shafts by id(s). - - Args: - external_id: External id or list of external ids of the main shafts. - space: The space where all the main shafts are located. - - Returns: - The requested main shafts. - - Examples: - - Retrieve main_shaft by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shaft = client.main_shaft.retrieve("my_main_shaft") - - """ - return self._retrieve(external_id, space) - - def search( - self, - query: str, - properties: MainShaftTextFields | SequenceNotStr[MainShaftTextFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: MainShaftFields | SequenceNotStr[MainShaftFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> MainShaftList: - """Search main shafts - - Args: - query: The search query, - properties: The property to search, if nothing is passed all text fields will be searched. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of main shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - Search results main shafts matching the query. - - Examples: - - Search for 'my_main_shaft' in all text properties: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shafts = client.main_shaft.search('my_main_shaft') - - """ - filter_ = _create_main_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._search( - query=query, - properties=properties, - filter_=filter_, - limit=limit, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) - - @overload - def aggregate( - self, - aggregate: Aggregations | dm.aggregations.MetricAggregation, - group_by: None = None, - property: MainShaftFields | SequenceNotStr[MainShaftFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.AggregatedNumberedValue: ... - - @overload - def aggregate( - self, - aggregate: SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation], - group_by: None = None, - property: MainShaftFields | SequenceNotStr[MainShaftFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> list[dm.aggregations.AggregatedNumberedValue]: ... - - @overload - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: MainShaftFields | SequenceNotStr[MainShaftFields], - property: MainShaftFields | SequenceNotStr[MainShaftFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> InstanceAggregationResultList: ... - - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: MainShaftFields | SequenceNotStr[MainShaftFields] | None = None, - property: MainShaftFields | SequenceNotStr[MainShaftFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> ( - dm.aggregations.AggregatedNumberedValue - | list[dm.aggregations.AggregatedNumberedValue] - | InstanceAggregationResultList - ): - """Aggregate data across main shafts - - Args: - aggregate: The aggregation to perform. - group_by: The property to group by when doing the aggregation. - property: The property to perform aggregation on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of main shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Aggregation results. - - Examples: - - Count main shafts in space `my_space`: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> result = client.main_shaft.aggregate("count", space="my_space") - - """ - - filter_ = _create_main_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._aggregate( - aggregate=aggregate, - group_by=group_by, # type: ignore[arg-type] - properties=property, # type: ignore[arg-type] - query=None, - search_properties=None, - limit=limit, - filter=filter_, - ) - - def histogram( - self, - property: MainShaftFields, - interval: float, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.HistogramValue: - """Produces histograms for main shafts - - Args: - property: The property to use as the value in the histogram. - interval: The interval to use for the histogram bins. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of main shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Bucketed histogram results. - - """ - filter_ = _create_main_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._histogram( - property, - interval, - None, - None, - limit, - filter_, - ) - - def query(self) -> MainShaftQuery: - """Start a query for main shafts.""" - warnings.warn("This method is renamed to .select", UserWarning, stacklevel=2) - return MainShaftQuery(self._client) - - def select(self) -> MainShaftQuery: - """Start selecting from main shafts.""" - warnings.warn( - "The .select is in alpha and is subject to breaking changes without notice.", UserWarning, stacklevel=2 - ) - return MainShaftQuery(self._client) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: MainShaftFields | Sequence[MainShaftFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> MainShaftList: - """List/filter main shafts - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of main shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - List of requested main shafts - - Examples: - - List main shafts and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shafts = client.main_shaft.list(limit=5) - - """ - filter_ = _create_main_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return self._list( - limit=limit, - filter=filter_, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) diff --git a/examples/windmill/_api/main_shaft_bending_x.py b/examples/windmill/_api/main_shaft_bending_x.py deleted file mode 100644 index 869c363da..000000000 --- a/examples/windmill/_api/main_shaft_bending_x.py +++ /dev/null @@ -1,493 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._main_shaft import _create_main_shaft_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["bending_x", "bending_y", "calculated_tilt_moment", "calculated_yaw_moment", "torque"] - - -class MainShaftBendingXQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `main_shaft.bending_x` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_bending_x' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.bending_x(external_id="my_bending_x").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `main_shaft.bending_x` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_bending_x' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.bending_x(external_id="my_bending_x").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "bending_x", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `main_shaft.bending_x` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to bending_x - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_bending_x' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.bending_x(external_id="my_bending_x").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "bending_x", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `main_shaft.bending_x` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to bending_x - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_bending_x' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.bending_x( - ... external_id="my_bending_x").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "bending_x" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_bending_x( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "bending_x": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class MainShaftBendingXAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> MainShaftBendingXQuery: - """Query timeseries `main_shaft.bending_x` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of main shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the main_shaft.bending_x timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 main_shaft.bending_x timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shafts = client.main_shaft.bending_x(limit=5).retrieve() - - """ - filter_ = _create_main_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return MainShaftBendingXQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `main_shaft.bending_x` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of main shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries main_shaft.bending_x. - - Examples: - - List main_shaft.bending_x and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shafts = client.main_shaft.bending_x.list(limit=5) - - """ - filter_ = _create_main_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_bending_x( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_bending_x( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "bending_x", -) -> dict[str, list[str]]: - properties = {"bending_x"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("bending_x")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["bending_x"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/main_shaft_bending_y.py b/examples/windmill/_api/main_shaft_bending_y.py deleted file mode 100644 index c7e8eb1fe..000000000 --- a/examples/windmill/_api/main_shaft_bending_y.py +++ /dev/null @@ -1,493 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._main_shaft import _create_main_shaft_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["bending_x", "bending_y", "calculated_tilt_moment", "calculated_yaw_moment", "torque"] - - -class MainShaftBendingYQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `main_shaft.bending_y` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_bending_y' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.bending_y(external_id="my_bending_y").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `main_shaft.bending_y` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_bending_y' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.bending_y(external_id="my_bending_y").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "bending_y", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `main_shaft.bending_y` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to bending_y - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_bending_y' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.bending_y(external_id="my_bending_y").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "bending_y", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `main_shaft.bending_y` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to bending_y - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_bending_y' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.bending_y( - ... external_id="my_bending_y").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "bending_y" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_bending_y( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "bending_y": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class MainShaftBendingYAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> MainShaftBendingYQuery: - """Query timeseries `main_shaft.bending_y` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of main shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the main_shaft.bending_y timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 main_shaft.bending_y timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shafts = client.main_shaft.bending_y(limit=5).retrieve() - - """ - filter_ = _create_main_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return MainShaftBendingYQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `main_shaft.bending_y` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of main shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries main_shaft.bending_y. - - Examples: - - List main_shaft.bending_y and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shafts = client.main_shaft.bending_y.list(limit=5) - - """ - filter_ = _create_main_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_bending_y( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_bending_y( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "bending_y", -) -> dict[str, list[str]]: - properties = {"bending_y"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("bending_y")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["bending_y"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/main_shaft_calculated_tilt_moment.py b/examples/windmill/_api/main_shaft_calculated_tilt_moment.py deleted file mode 100644 index 72dde6abb..000000000 --- a/examples/windmill/_api/main_shaft_calculated_tilt_moment.py +++ /dev/null @@ -1,493 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._main_shaft import _create_main_shaft_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["bending_x", "bending_y", "calculated_tilt_moment", "calculated_yaw_moment", "torque"] - - -class MainShaftCalculatedTiltMomentQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `main_shaft.calculated_tilt_moment` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_calculated_tilt_moment' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.calculated_tilt_moment(external_id="my_calculated_tilt_moment").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `main_shaft.calculated_tilt_moment` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_calculated_tilt_moment' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.calculated_tilt_moment(external_id="my_calculated_tilt_moment").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "calculated_tilt_moment", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `main_shaft.calculated_tilt_moment` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to calculated_tilt_moment - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_calculated_tilt_moment' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.calculated_tilt_moment(external_id="my_calculated_tilt_moment").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "calculated_tilt_moment", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `main_shaft.calculated_tilt_moment` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to calculated_tilt_moment - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_calculated_tilt_moment' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.calculated_tilt_moment( - ... external_id="my_calculated_tilt_moment").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "calculated_tilt_moment" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_calculated_tilt_moment( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "calculated_tilt_moment": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class MainShaftCalculatedTiltMomentAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> MainShaftCalculatedTiltMomentQuery: - """Query timeseries `main_shaft.calculated_tilt_moment` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of main shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the main_shaft.calculated_tilt_moment timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 main_shaft.calculated_tilt_moment timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shafts = client.main_shaft.calculated_tilt_moment(limit=5).retrieve() - - """ - filter_ = _create_main_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return MainShaftCalculatedTiltMomentQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `main_shaft.calculated_tilt_moment` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of main shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries main_shaft.calculated_tilt_moment. - - Examples: - - List main_shaft.calculated_tilt_moment and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shafts = client.main_shaft.calculated_tilt_moment.list(limit=5) - - """ - filter_ = _create_main_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_calculated_tilt_moment( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_calculated_tilt_moment( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "calculated_tilt_moment", -) -> dict[str, list[str]]: - properties = {"calculated_tilt_moment"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("calculated_tilt_moment")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["calculated_tilt_moment"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/main_shaft_calculated_yaw_moment.py b/examples/windmill/_api/main_shaft_calculated_yaw_moment.py deleted file mode 100644 index b6df6d482..000000000 --- a/examples/windmill/_api/main_shaft_calculated_yaw_moment.py +++ /dev/null @@ -1,493 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._main_shaft import _create_main_shaft_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["bending_x", "bending_y", "calculated_tilt_moment", "calculated_yaw_moment", "torque"] - - -class MainShaftCalculatedYawMomentQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `main_shaft.calculated_yaw_moment` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_calculated_yaw_moment' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.calculated_yaw_moment(external_id="my_calculated_yaw_moment").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `main_shaft.calculated_yaw_moment` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_calculated_yaw_moment' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.calculated_yaw_moment(external_id="my_calculated_yaw_moment").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "calculated_yaw_moment", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `main_shaft.calculated_yaw_moment` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to calculated_yaw_moment - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_calculated_yaw_moment' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.calculated_yaw_moment(external_id="my_calculated_yaw_moment").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "calculated_yaw_moment", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `main_shaft.calculated_yaw_moment` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to calculated_yaw_moment - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_calculated_yaw_moment' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.calculated_yaw_moment( - ... external_id="my_calculated_yaw_moment").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "calculated_yaw_moment" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_calculated_yaw_moment( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "calculated_yaw_moment": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class MainShaftCalculatedYawMomentAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> MainShaftCalculatedYawMomentQuery: - """Query timeseries `main_shaft.calculated_yaw_moment` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of main shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the main_shaft.calculated_yaw_moment timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 main_shaft.calculated_yaw_moment timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shafts = client.main_shaft.calculated_yaw_moment(limit=5).retrieve() - - """ - filter_ = _create_main_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return MainShaftCalculatedYawMomentQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `main_shaft.calculated_yaw_moment` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of main shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries main_shaft.calculated_yaw_moment. - - Examples: - - List main_shaft.calculated_yaw_moment and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shafts = client.main_shaft.calculated_yaw_moment.list(limit=5) - - """ - filter_ = _create_main_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_calculated_yaw_moment( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_calculated_yaw_moment( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "calculated_yaw_moment", -) -> dict[str, list[str]]: - properties = {"calculated_yaw_moment"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("calculated_yaw_moment")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["calculated_yaw_moment"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/main_shaft_query.py b/examples/windmill/_api/main_shaft_query.py deleted file mode 100644 index e952407fe..000000000 --- a/examples/windmill/_api/main_shaft_query.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import TYPE_CHECKING, cast - -from cognite.client import data_modeling as dm, CogniteClient - -from windmill.data_classes import ( - DomainModelCore, - MainShaft, -) -from windmill._api._core import ( - DEFAULT_QUERY_LIMIT, - EdgeQueryStep, - NodeQueryStep, - DataClassQueryBuilder, - QueryAPI, - T_DomainModelList, - _create_edge_filter, -) - - -class MainShaftQueryAPI(QueryAPI[T_DomainModelList]): - _view_id = dm.ViewId("power-models", "MainShaft", "1") - - def __init__( - self, - client: CogniteClient, - builder: DataClassQueryBuilder[T_DomainModelList], - filter_: dm.filters.Filter | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - ): - super().__init__(client, builder) - from_ = self._builder.get_from() - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - filter=filter_, - ), - result_cls=MainShaft, - max_retrieve_limit=limit, - ) - ) - - def query( - self, - ) -> T_DomainModelList: - """Execute query and return the result. - - Returns: - The list of the source nodes of the query. - - """ - return self._query() diff --git a/examples/windmill/_api/main_shaft_torque.py b/examples/windmill/_api/main_shaft_torque.py deleted file mode 100644 index 30e400eb3..000000000 --- a/examples/windmill/_api/main_shaft_torque.py +++ /dev/null @@ -1,491 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._main_shaft import _create_main_shaft_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["bending_x", "bending_y", "calculated_tilt_moment", "calculated_yaw_moment", "torque"] - - -class MainShaftTorqueQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `main_shaft.torque` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_torque' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.torque(external_id="my_torque").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `main_shaft.torque` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_torque' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.torque(external_id="my_torque").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "torque", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `main_shaft.torque` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to torque - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_torque' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.torque(external_id="my_torque").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "torque", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `main_shaft.torque` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to torque - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_torque' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> main_shaft_datapoints = client.main_shaft.torque( - ... external_id="my_torque").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "torque" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_torque( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "torque": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class MainShaftTorqueAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> MainShaftTorqueQuery: - """Query timeseries `main_shaft.torque` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of main shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the main_shaft.torque timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 main_shaft.torque timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shafts = client.main_shaft.torque(limit=5).retrieve() - - """ - filter_ = _create_main_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return MainShaftTorqueQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `main_shaft.torque` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of main shafts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries main_shaft.torque. - - Examples: - - List main_shaft.torque and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> main_shafts = client.main_shaft.torque.list(limit=5) - - """ - filter_ = _create_main_shaft_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_torque(self._client, self._view_id, filter_, limit) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_torque( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "torque", -) -> dict[str, list[str]]: - properties = {"torque"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("torque")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["torque"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/metmast.py b/examples/windmill/_api/metmast.py deleted file mode 100644 index bbd9c2d97..000000000 --- a/examples/windmill/_api/metmast.py +++ /dev/null @@ -1,482 +0,0 @@ -from __future__ import annotations - -from collections.abc import Sequence -from typing import overload, Literal -import warnings - -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes.data_modeling.instances import InstanceAggregationResultList, InstanceSort - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - NodeQueryStep, - EdgeQueryStep, - DataClassQueryBuilder, -) -from windmill.data_classes import ( - DomainModelCore, - DomainModelWrite, - ResourcesWriteResult, - Metmast, - MetmastWrite, - MetmastFields, - MetmastList, - MetmastWriteList, - MetmastTextFields, -) -from windmill.data_classes._metmast import ( - MetmastQuery, - _METMAST_PROPERTIES_BY_FIELD, - _create_metmast_filter, -) -from windmill._api._core import ( - DEFAULT_LIMIT_READ, - Aggregations, - NodeAPI, - SequenceNotStr, -) -from windmill._api.metmast_temperature import MetmastTemperatureAPI -from windmill._api.metmast_tilt_angle import MetmastTiltAngleAPI -from windmill._api.metmast_wind_speed import MetmastWindSpeedAPI -from windmill._api.metmast_query import MetmastQueryAPI - - -class MetmastAPI(NodeAPI[Metmast, MetmastWrite, MetmastList, MetmastWriteList]): - _view_id = dm.ViewId("power-models", "Metmast", "1") - _properties_by_field = _METMAST_PROPERTIES_BY_FIELD - _class_type = Metmast - _class_list = MetmastList - _class_write_list = MetmastWriteList - - def __init__(self, client: CogniteClient): - super().__init__(client=client) - - self.temperature = MetmastTemperatureAPI(client, self._view_id) - self.tilt_angle = MetmastTiltAngleAPI(client, self._view_id) - self.wind_speed = MetmastWindSpeedAPI(client, self._view_id) - - def __call__( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - filter: dm.Filter | None = None, - ) -> MetmastQueryAPI[MetmastList]: - """Query starting at metmasts. - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of metmasts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query API for metmasts. - - """ - has_data = dm.filters.HasData(views=[self._view_id]) - filter_ = _create_metmast_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - builder = DataClassQueryBuilder(MetmastList) - return MetmastQueryAPI(self._client, builder, filter_, limit) - - def apply( - self, - metmast: MetmastWrite | Sequence[MetmastWrite], - replace: bool = False, - write_none: bool = False, - ) -> ResourcesWriteResult: - """Add or update (upsert) metmasts. - - Args: - metmast: Metmast or sequence of metmasts to upsert. - replace (bool): How do we behave when a property value exists? Do we replace all matching and existing values with the supplied values (true)? - Or should we merge in new values for properties together with the existing values (false)? Note: This setting applies for all nodes or edges specified in the ingestion call. - write_none (bool): This method, will by default, skip properties that are set to None. However, if you want to set properties to None, - you can set this parameter to True. Note this only applies to properties that are nullable. - Returns: - Created instance(s), i.e., nodes, edges, and time series. - - Examples: - - Create a new metmast: - - >>> from windmill import WindmillClient - >>> from windmill.data_classes import MetmastWrite - >>> client = WindmillClient() - >>> metmast = MetmastWrite(external_id="my_metmast", ...) - >>> result = client.metmast.apply(metmast) - - """ - warnings.warn( - "The .apply method is deprecated and will be removed in v1.0. " - "Please use the .upsert method on the client instead. This means instead of " - "`my_client.metmast.apply(my_items)` please use `my_client.upsert(my_items)`." - "The motivation is that all apply methods are the same, and having one apply method per API " - " class encourages users to create items in small batches, which is inefficient." - "In addition, .upsert method is more descriptive of what the method does.", - UserWarning, - stacklevel=2, - ) - return self._apply(metmast, replace, write_none) - - def delete( - self, external_id: str | SequenceNotStr[str], space: str = DEFAULT_INSTANCE_SPACE - ) -> dm.InstancesDeleteResult: - """Delete one or more metmast. - - Args: - external_id: External id of the metmast to delete. - space: The space where all the metmast are located. - - Returns: - The instance(s), i.e., nodes and edges which has been deleted. Empty list if nothing was deleted. - - Examples: - - Delete metmast by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> client.metmast.delete("my_metmast") - """ - warnings.warn( - "The .delete method is deprecated and will be removed in v1.0. " - "Please use the .delete method on the client instead. This means instead of " - "`my_client.metmast.delete(my_ids)` please use `my_client.delete(my_ids)`." - "The motivation is that all delete methods are the same, and having one delete method per API " - " class encourages users to delete items in small batches, which is inefficient.", - UserWarning, - stacklevel=2, - ) - return self._delete(external_id, space) - - @overload - def retrieve( - self, external_id: str | dm.NodeId | tuple[str, str], space: str = DEFAULT_INSTANCE_SPACE - ) -> Metmast | None: ... - - @overload - def retrieve( - self, external_id: SequenceNotStr[str | dm.NodeId | tuple[str, str]], space: str = DEFAULT_INSTANCE_SPACE - ) -> MetmastList: ... - - def retrieve( - self, - external_id: str | dm.NodeId | tuple[str, str] | SequenceNotStr[str | dm.NodeId | tuple[str, str]], - space: str = DEFAULT_INSTANCE_SPACE, - ) -> Metmast | MetmastList | None: - """Retrieve one or more metmasts by id(s). - - Args: - external_id: External id or list of external ids of the metmasts. - space: The space where all the metmasts are located. - - Returns: - The requested metmasts. - - Examples: - - Retrieve metmast by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmast = client.metmast.retrieve("my_metmast") - - """ - return self._retrieve(external_id, space) - - def search( - self, - query: str, - properties: MetmastTextFields | SequenceNotStr[MetmastTextFields] | None = None, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: MetmastFields | SequenceNotStr[MetmastFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> MetmastList: - """Search metmasts - - Args: - query: The search query, - properties: The property to search, if nothing is passed all text fields will be searched. - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of metmasts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - Search results metmasts matching the query. - - Examples: - - Search for 'my_metmast' in all text properties: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmasts = client.metmast.search('my_metmast') - - """ - filter_ = _create_metmast_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - return self._search( - query=query, - properties=properties, - filter_=filter_, - limit=limit, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) - - @overload - def aggregate( - self, - aggregate: Aggregations | dm.aggregations.MetricAggregation, - group_by: None = None, - property: MetmastFields | SequenceNotStr[MetmastFields] | None = None, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.AggregatedNumberedValue: ... - - @overload - def aggregate( - self, - aggregate: SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation], - group_by: None = None, - property: MetmastFields | SequenceNotStr[MetmastFields] | None = None, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> list[dm.aggregations.AggregatedNumberedValue]: ... - - @overload - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: MetmastFields | SequenceNotStr[MetmastFields], - property: MetmastFields | SequenceNotStr[MetmastFields] | None = None, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> InstanceAggregationResultList: ... - - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: MetmastFields | SequenceNotStr[MetmastFields] | None = None, - property: MetmastFields | SequenceNotStr[MetmastFields] | None = None, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> ( - dm.aggregations.AggregatedNumberedValue - | list[dm.aggregations.AggregatedNumberedValue] - | InstanceAggregationResultList - ): - """Aggregate data across metmasts - - Args: - aggregate: The aggregation to perform. - group_by: The property to group by when doing the aggregation. - property: The property to perform aggregation on. - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of metmasts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Aggregation results. - - Examples: - - Count metmasts in space `my_space`: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> result = client.metmast.aggregate("count", space="my_space") - - """ - - filter_ = _create_metmast_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - return self._aggregate( - aggregate=aggregate, - group_by=group_by, # type: ignore[arg-type] - properties=property, # type: ignore[arg-type] - query=None, - search_properties=None, - limit=limit, - filter=filter_, - ) - - def histogram( - self, - property: MetmastFields, - interval: float, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.HistogramValue: - """Produces histograms for metmasts - - Args: - property: The property to use as the value in the histogram. - interval: The interval to use for the histogram bins. - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of metmasts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Bucketed histogram results. - - """ - filter_ = _create_metmast_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - return self._histogram( - property, - interval, - None, - None, - limit, - filter_, - ) - - def query(self) -> MetmastQuery: - """Start a query for metmasts.""" - warnings.warn("This method is renamed to .select", UserWarning, stacklevel=2) - return MetmastQuery(self._client) - - def select(self) -> MetmastQuery: - """Start selecting from metmasts.""" - warnings.warn( - "The .select is in alpha and is subject to breaking changes without notice.", UserWarning, stacklevel=2 - ) - return MetmastQuery(self._client) - - def list( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: MetmastFields | Sequence[MetmastFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> MetmastList: - """List/filter metmasts - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of metmasts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - List of requested metmasts - - Examples: - - List metmasts and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmasts = client.metmast.list(limit=5) - - """ - filter_ = _create_metmast_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - - return self._list( - limit=limit, - filter=filter_, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) diff --git a/examples/windmill/_api/metmast_query.py b/examples/windmill/_api/metmast_query.py deleted file mode 100644 index 58e5c704a..000000000 --- a/examples/windmill/_api/metmast_query.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import TYPE_CHECKING, cast - -from cognite.client import data_modeling as dm, CogniteClient - -from windmill.data_classes import ( - DomainModelCore, - Metmast, -) -from windmill._api._core import ( - DEFAULT_QUERY_LIMIT, - EdgeQueryStep, - NodeQueryStep, - DataClassQueryBuilder, - QueryAPI, - T_DomainModelList, - _create_edge_filter, -) - - -class MetmastQueryAPI(QueryAPI[T_DomainModelList]): - _view_id = dm.ViewId("power-models", "Metmast", "1") - - def __init__( - self, - client: CogniteClient, - builder: DataClassQueryBuilder[T_DomainModelList], - filter_: dm.filters.Filter | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - ): - super().__init__(client, builder) - from_ = self._builder.get_from() - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - filter=filter_, - ), - result_cls=Metmast, - max_retrieve_limit=limit, - ) - ) - - def query( - self, - ) -> T_DomainModelList: - """Execute query and return the result. - - Returns: - The list of the source nodes of the query. - - """ - return self._query() diff --git a/examples/windmill/_api/metmast_temperature.py b/examples/windmill/_api/metmast_temperature.py deleted file mode 100644 index c68dffc64..000000000 --- a/examples/windmill/_api/metmast_temperature.py +++ /dev/null @@ -1,505 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._metmast import _create_metmast_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["position", "temperature", "tilt_angle", "wind_speed"] - - -class MetmastTemperatureQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `metmast.temperature` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_temperature' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmast_datapoints = client.metmast.temperature(external_id="my_temperature").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `metmast.temperature` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_temperature' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmast_datapoints = client.metmast.temperature(external_id="my_temperature").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "temperature", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `metmast.temperature` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to temperature - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_temperature' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmast_datapoints = client.metmast.temperature(external_id="my_temperature").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "temperature", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `metmast.temperature` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to temperature - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_temperature' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> metmast_datapoints = client.metmast.temperature( - ... external_id="my_temperature").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "temperature" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_temperature( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "temperature": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class MetmastTemperatureAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> MetmastTemperatureQuery: - """Query timeseries `metmast.temperature` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of metmasts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the metmast.temperature timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 metmast.temperature timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmasts = client.metmast.temperature(limit=5).retrieve() - - """ - filter_ = _create_metmast_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - - return MetmastTemperatureQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `metmast.temperature` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of metmasts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries metmast.temperature. - - Examples: - - List metmast.temperature and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmasts = client.metmast.temperature.list(limit=5) - - """ - filter_ = _create_metmast_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_temperature( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_temperature( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "temperature", -) -> dict[str, list[str]]: - properties = {"temperature"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("temperature")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["temperature"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/metmast_tilt_angle.py b/examples/windmill/_api/metmast_tilt_angle.py deleted file mode 100644 index ca6af338d..000000000 --- a/examples/windmill/_api/metmast_tilt_angle.py +++ /dev/null @@ -1,505 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._metmast import _create_metmast_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["position", "temperature", "tilt_angle", "wind_speed"] - - -class MetmastTiltAngleQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `metmast.tilt_angle` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_tilt_angle' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmast_datapoints = client.metmast.tilt_angle(external_id="my_tilt_angle").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `metmast.tilt_angle` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_tilt_angle' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmast_datapoints = client.metmast.tilt_angle(external_id="my_tilt_angle").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "tilt_angle", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `metmast.tilt_angle` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to tilt_angle - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_tilt_angle' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmast_datapoints = client.metmast.tilt_angle(external_id="my_tilt_angle").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "tilt_angle", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `metmast.tilt_angle` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to tilt_angle - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_tilt_angle' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> metmast_datapoints = client.metmast.tilt_angle( - ... external_id="my_tilt_angle").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "tilt_angle" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_tilt_angle( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "tilt_angle": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class MetmastTiltAngleAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> MetmastTiltAngleQuery: - """Query timeseries `metmast.tilt_angle` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of metmasts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the metmast.tilt_angle timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 metmast.tilt_angle timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmasts = client.metmast.tilt_angle(limit=5).retrieve() - - """ - filter_ = _create_metmast_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - - return MetmastTiltAngleQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `metmast.tilt_angle` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of metmasts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries metmast.tilt_angle. - - Examples: - - List metmast.tilt_angle and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmasts = client.metmast.tilt_angle.list(limit=5) - - """ - filter_ = _create_metmast_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_tilt_angle( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_tilt_angle( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "tilt_angle", -) -> dict[str, list[str]]: - properties = {"tilt_angle"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("tilt_angle")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["tilt_angle"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/metmast_wind_speed.py b/examples/windmill/_api/metmast_wind_speed.py deleted file mode 100644 index 35ece3459..000000000 --- a/examples/windmill/_api/metmast_wind_speed.py +++ /dev/null @@ -1,505 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._metmast import _create_metmast_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["position", "temperature", "tilt_angle", "wind_speed"] - - -class MetmastWindSpeedQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `metmast.wind_speed` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_wind_speed' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmast_datapoints = client.metmast.wind_speed(external_id="my_wind_speed").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `metmast.wind_speed` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_wind_speed' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmast_datapoints = client.metmast.wind_speed(external_id="my_wind_speed").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "wind_speed", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `metmast.wind_speed` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to wind_speed - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_wind_speed' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmast_datapoints = client.metmast.wind_speed(external_id="my_wind_speed").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "wind_speed", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `metmast.wind_speed` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to wind_speed - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_wind_speed' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> metmast_datapoints = client.metmast.wind_speed( - ... external_id="my_wind_speed").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "wind_speed" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_wind_speed( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "wind_speed": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class MetmastWindSpeedAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> MetmastWindSpeedQuery: - """Query timeseries `metmast.wind_speed` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of metmasts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the metmast.wind_speed timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 metmast.wind_speed timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmasts = client.metmast.wind_speed(limit=5).retrieve() - - """ - filter_ = _create_metmast_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - - return MetmastWindSpeedQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `metmast.wind_speed` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of metmasts to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries metmast.wind_speed. - - Examples: - - List metmast.wind_speed and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> metmasts = client.metmast.wind_speed.list(limit=5) - - """ - filter_ = _create_metmast_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_wind_speed( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_wind_speed( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "wind_speed", -) -> dict[str, list[str]]: - properties = {"wind_speed"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("wind_speed")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["wind_speed"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/nacelle.py b/examples/windmill/_api/nacelle.py deleted file mode 100644 index f403a97fb..000000000 --- a/examples/windmill/_api/nacelle.py +++ /dev/null @@ -1,913 +0,0 @@ -from __future__ import annotations - -from collections.abc import Sequence -from typing import overload, Literal -import warnings - -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes.data_modeling.instances import InstanceAggregationResultList, InstanceSort - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - NodeQueryStep, - EdgeQueryStep, - DataClassQueryBuilder, -) -from windmill.data_classes import ( - DomainModelCore, - DomainModelWrite, - ResourcesWriteResult, - Nacelle, - NacelleWrite, - NacelleFields, - NacelleList, - NacelleWriteList, - NacelleTextFields, - Gearbox, - Generator, - HighSpeedShaft, - MainShaft, - PowerInverter, -) -from windmill.data_classes._nacelle import ( - NacelleQuery, - _NACELLE_PROPERTIES_BY_FIELD, - _create_nacelle_filter, -) -from windmill._api._core import ( - DEFAULT_LIMIT_READ, - Aggregations, - NodeAPI, - SequenceNotStr, -) -from windmill._api.nacelle_acc_from_back_side_x import NacelleAccFromBackSideXAPI -from windmill._api.nacelle_acc_from_back_side_y import NacelleAccFromBackSideYAPI -from windmill._api.nacelle_acc_from_back_side_z import NacelleAccFromBackSideZAPI -from windmill._api.nacelle_yaw_direction import NacelleYawDirectionAPI -from windmill._api.nacelle_yaw_error import NacelleYawErrorAPI -from windmill._api.nacelle_query import NacelleQueryAPI - - -class NacelleAPI(NodeAPI[Nacelle, NacelleWrite, NacelleList, NacelleWriteList]): - _view_id = dm.ViewId("power-models", "Nacelle", "1") - _properties_by_field = _NACELLE_PROPERTIES_BY_FIELD - _class_type = Nacelle - _class_list = NacelleList - _class_write_list = NacelleWriteList - - def __init__(self, client: CogniteClient): - super().__init__(client=client) - - self.acc_from_back_side_x = NacelleAccFromBackSideXAPI(client, self._view_id) - self.acc_from_back_side_y = NacelleAccFromBackSideYAPI(client, self._view_id) - self.acc_from_back_side_z = NacelleAccFromBackSideZAPI(client, self._view_id) - self.yaw_direction = NacelleYawDirectionAPI(client, self._view_id) - self.yaw_error = NacelleYawErrorAPI(client, self._view_id) - - def __call__( - self, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - filter: dm.Filter | None = None, - ) -> NacelleQueryAPI[NacelleList]: - """Query starting at nacelles. - - Args: - gearbox: The gearbox to filter on. - generator: The generator to filter on. - high_speed_shaft: The high speed shaft to filter on. - main_shaft: The main shaft to filter on. - power_inverter: The power inverter to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of nacelles to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query API for nacelles. - - """ - has_data = dm.filters.HasData(views=[self._view_id]) - filter_ = _create_nacelle_filter( - self._view_id, - gearbox, - generator, - high_speed_shaft, - main_shaft, - power_inverter, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - builder = DataClassQueryBuilder(NacelleList) - return NacelleQueryAPI(self._client, builder, filter_, limit) - - def apply( - self, - nacelle: NacelleWrite | Sequence[NacelleWrite], - replace: bool = False, - write_none: bool = False, - ) -> ResourcesWriteResult: - """Add or update (upsert) nacelles. - - Note: This method iterates through all nodes and timeseries linked to nacelle and creates them including the edges - between the nodes. For example, if any of `gearbox`, `generator`, `high_speed_shaft`, `main_shaft` or `power_inverter` are set, then these - nodes as well as any nodes linked to them, and all the edges linking these nodes will be created. - - Args: - nacelle: Nacelle or sequence of nacelles to upsert. - replace (bool): How do we behave when a property value exists? Do we replace all matching and existing values with the supplied values (true)? - Or should we merge in new values for properties together with the existing values (false)? Note: This setting applies for all nodes or edges specified in the ingestion call. - write_none (bool): This method, will by default, skip properties that are set to None. However, if you want to set properties to None, - you can set this parameter to True. Note this only applies to properties that are nullable. - Returns: - Created instance(s), i.e., nodes, edges, and time series. - - Examples: - - Create a new nacelle: - - >>> from windmill import WindmillClient - >>> from windmill.data_classes import NacelleWrite - >>> client = WindmillClient() - >>> nacelle = NacelleWrite(external_id="my_nacelle", ...) - >>> result = client.nacelle.apply(nacelle) - - """ - warnings.warn( - "The .apply method is deprecated and will be removed in v1.0. " - "Please use the .upsert method on the client instead. This means instead of " - "`my_client.nacelle.apply(my_items)` please use `my_client.upsert(my_items)`." - "The motivation is that all apply methods are the same, and having one apply method per API " - " class encourages users to create items in small batches, which is inefficient." - "In addition, .upsert method is more descriptive of what the method does.", - UserWarning, - stacklevel=2, - ) - return self._apply(nacelle, replace, write_none) - - def delete( - self, external_id: str | SequenceNotStr[str], space: str = DEFAULT_INSTANCE_SPACE - ) -> dm.InstancesDeleteResult: - """Delete one or more nacelle. - - Args: - external_id: External id of the nacelle to delete. - space: The space where all the nacelle are located. - - Returns: - The instance(s), i.e., nodes and edges which has been deleted. Empty list if nothing was deleted. - - Examples: - - Delete nacelle by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> client.nacelle.delete("my_nacelle") - """ - warnings.warn( - "The .delete method is deprecated and will be removed in v1.0. " - "Please use the .delete method on the client instead. This means instead of " - "`my_client.nacelle.delete(my_ids)` please use `my_client.delete(my_ids)`." - "The motivation is that all delete methods are the same, and having one delete method per API " - " class encourages users to delete items in small batches, which is inefficient.", - UserWarning, - stacklevel=2, - ) - return self._delete(external_id, space) - - @overload - def retrieve( - self, external_id: str | dm.NodeId | tuple[str, str], space: str = DEFAULT_INSTANCE_SPACE - ) -> Nacelle | None: ... - - @overload - def retrieve( - self, external_id: SequenceNotStr[str | dm.NodeId | tuple[str, str]], space: str = DEFAULT_INSTANCE_SPACE - ) -> NacelleList: ... - - def retrieve( - self, - external_id: str | dm.NodeId | tuple[str, str] | SequenceNotStr[str | dm.NodeId | tuple[str, str]], - space: str = DEFAULT_INSTANCE_SPACE, - ) -> Nacelle | NacelleList | None: - """Retrieve one or more nacelles by id(s). - - Args: - external_id: External id or list of external ids of the nacelles. - space: The space where all the nacelles are located. - - Returns: - The requested nacelles. - - Examples: - - Retrieve nacelle by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelle = client.nacelle.retrieve("my_nacelle") - - """ - return self._retrieve(external_id, space) - - def search( - self, - query: str, - properties: NacelleTextFields | SequenceNotStr[NacelleTextFields] | None = None, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: NacelleFields | SequenceNotStr[NacelleFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> NacelleList: - """Search nacelles - - Args: - query: The search query, - properties: The property to search, if nothing is passed all text fields will be searched. - gearbox: The gearbox to filter on. - generator: The generator to filter on. - high_speed_shaft: The high speed shaft to filter on. - main_shaft: The main shaft to filter on. - power_inverter: The power inverter to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of nacelles to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - Search results nacelles matching the query. - - Examples: - - Search for 'my_nacelle' in all text properties: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelles = client.nacelle.search('my_nacelle') - - """ - filter_ = _create_nacelle_filter( - self._view_id, - gearbox, - generator, - high_speed_shaft, - main_shaft, - power_inverter, - external_id_prefix, - space, - filter, - ) - return self._search( - query=query, - properties=properties, - filter_=filter_, - limit=limit, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) - - @overload - def aggregate( - self, - aggregate: Aggregations | dm.aggregations.MetricAggregation, - group_by: None = None, - property: NacelleFields | SequenceNotStr[NacelleFields] | None = None, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.AggregatedNumberedValue: ... - - @overload - def aggregate( - self, - aggregate: SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation], - group_by: None = None, - property: NacelleFields | SequenceNotStr[NacelleFields] | None = None, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> list[dm.aggregations.AggregatedNumberedValue]: ... - - @overload - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: NacelleFields | SequenceNotStr[NacelleFields], - property: NacelleFields | SequenceNotStr[NacelleFields] | None = None, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> InstanceAggregationResultList: ... - - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: NacelleFields | SequenceNotStr[NacelleFields] | None = None, - property: NacelleFields | SequenceNotStr[NacelleFields] | None = None, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> ( - dm.aggregations.AggregatedNumberedValue - | list[dm.aggregations.AggregatedNumberedValue] - | InstanceAggregationResultList - ): - """Aggregate data across nacelles - - Args: - aggregate: The aggregation to perform. - group_by: The property to group by when doing the aggregation. - property: The property to perform aggregation on. - gearbox: The gearbox to filter on. - generator: The generator to filter on. - high_speed_shaft: The high speed shaft to filter on. - main_shaft: The main shaft to filter on. - power_inverter: The power inverter to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of nacelles to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Aggregation results. - - Examples: - - Count nacelles in space `my_space`: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> result = client.nacelle.aggregate("count", space="my_space") - - """ - - filter_ = _create_nacelle_filter( - self._view_id, - gearbox, - generator, - high_speed_shaft, - main_shaft, - power_inverter, - external_id_prefix, - space, - filter, - ) - return self._aggregate( - aggregate=aggregate, - group_by=group_by, # type: ignore[arg-type] - properties=property, # type: ignore[arg-type] - query=None, - search_properties=None, - limit=limit, - filter=filter_, - ) - - def histogram( - self, - property: NacelleFields, - interval: float, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.HistogramValue: - """Produces histograms for nacelles - - Args: - property: The property to use as the value in the histogram. - interval: The interval to use for the histogram bins. - gearbox: The gearbox to filter on. - generator: The generator to filter on. - high_speed_shaft: The high speed shaft to filter on. - main_shaft: The main shaft to filter on. - power_inverter: The power inverter to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of nacelles to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Bucketed histogram results. - - """ - filter_ = _create_nacelle_filter( - self._view_id, - gearbox, - generator, - high_speed_shaft, - main_shaft, - power_inverter, - external_id_prefix, - space, - filter, - ) - return self._histogram( - property, - interval, - None, - None, - limit, - filter_, - ) - - def query(self) -> NacelleQuery: - """Start a query for nacelles.""" - warnings.warn("This method is renamed to .select", UserWarning, stacklevel=2) - return NacelleQuery(self._client) - - def select(self) -> NacelleQuery: - """Start selecting from nacelles.""" - warnings.warn( - "The .select is in alpha and is subject to breaking changes without notice.", UserWarning, stacklevel=2 - ) - return NacelleQuery(self._client) - - def list( - self, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: NacelleFields | Sequence[NacelleFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - retrieve_connections: Literal["skip", "identifier", "full"] = "skip", - ) -> NacelleList: - """List/filter nacelles - - Args: - gearbox: The gearbox to filter on. - generator: The generator to filter on. - high_speed_shaft: The high speed shaft to filter on. - main_shaft: The main shaft to filter on. - power_inverter: The power inverter to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of nacelles to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - retrieve_connections: Whether to retrieve `gearbox`, `generator`, `high_speed_shaft`, `main_shaft` and `power_inverter` for the nacelles. Defaults to 'skip'. - 'skip' will not retrieve any connections, 'identifier' will only retrieve the identifier of the connected items, and 'full' will retrieve the full connected items. - - Returns: - List of requested nacelles - - Examples: - - List nacelles and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelles = client.nacelle.list(limit=5) - - """ - filter_ = _create_nacelle_filter( - self._view_id, - gearbox, - generator, - high_speed_shaft, - main_shaft, - power_inverter, - external_id_prefix, - space, - filter, - ) - - if retrieve_connections == "skip": - return self._list( - limit=limit, - filter=filter_, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) - - builder = DataClassQueryBuilder(NacelleList) - has_data = dm.filters.HasData(views=[self._view_id]) - builder.append( - NodeQueryStep( - builder.create_name(None), - dm.query.NodeResultSetExpression( - filter=dm.filters.And(filter_, has_data) if filter_ else has_data, - sort=self._create_sort(sort_by, direction, sort), # type: ignore[arg-type] - ), - Nacelle, - max_retrieve_limit=limit, - raw_filter=filter_, - ) - ) - from_root = builder.get_from() - if retrieve_connections == "full": - builder.append( - NodeQueryStep( - builder.create_name(from_root), - dm.query.NodeResultSetExpression( - from_=from_root, - filter=dm.filters.HasData(views=[Gearbox._view_id]), - direction="outwards", - through=self._view_id.as_property_ref("gearbox"), - ), - Gearbox, - ) - ) - builder.append( - NodeQueryStep( - builder.create_name(from_root), - dm.query.NodeResultSetExpression( - from_=from_root, - filter=dm.filters.HasData(views=[Generator._view_id]), - direction="outwards", - through=self._view_id.as_property_ref("generator"), - ), - Generator, - ) - ) - builder.append( - NodeQueryStep( - builder.create_name(from_root), - dm.query.NodeResultSetExpression( - from_=from_root, - filter=dm.filters.HasData(views=[HighSpeedShaft._view_id]), - direction="outwards", - through=self._view_id.as_property_ref("high_speed_shaft"), - ), - HighSpeedShaft, - ) - ) - builder.append( - NodeQueryStep( - builder.create_name(from_root), - dm.query.NodeResultSetExpression( - from_=from_root, - filter=dm.filters.HasData(views=[MainShaft._view_id]), - direction="outwards", - through=self._view_id.as_property_ref("main_shaft"), - ), - MainShaft, - ) - ) - builder.append( - NodeQueryStep( - builder.create_name(from_root), - dm.query.NodeResultSetExpression( - from_=from_root, - filter=dm.filters.HasData(views=[PowerInverter._view_id]), - direction="outwards", - through=self._view_id.as_property_ref("power_inverter"), - ), - PowerInverter, - ) - ) - # We know that that all nodes are connected as it is not possible to filter on connections - builder.execute_query(self._client, remove_not_connected=False) - return builder.unpack() diff --git a/examples/windmill/_api/nacelle_acc_from_back_side_x.py b/examples/windmill/_api/nacelle_acc_from_back_side_x.py deleted file mode 100644 index 8edbe95e3..000000000 --- a/examples/windmill/_api/nacelle_acc_from_back_side_x.py +++ /dev/null @@ -1,595 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._nacelle import _create_nacelle_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal[ - "acc_from_back_side_x", "acc_from_back_side_y", "acc_from_back_side_z", "yaw_direction", "yaw_error" -] - - -class NacelleAccFromBackSideXQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `nacelle.acc_from_back_side_x` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_acc_from_back_side_x' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.acc_from_back_side_x(external_id="my_acc_from_back_side_x").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `nacelle.acc_from_back_side_x` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_acc_from_back_side_x' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.acc_from_back_side_x(external_id="my_acc_from_back_side_x").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "acc_from_back_side_x", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `nacelle.acc_from_back_side_x` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to acc_from_back_side_x - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_acc_from_back_side_x' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.acc_from_back_side_x(external_id="my_acc_from_back_side_x").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "acc_from_back_side_x", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `nacelle.acc_from_back_side_x` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to acc_from_back_side_x - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_acc_from_back_side_x' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.acc_from_back_side_x( - ... external_id="my_acc_from_back_side_x").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "acc_from_back_side_x" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_acc_from_back_side_x( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "acc_from_back_side_x": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class NacelleAccFromBackSideXAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> NacelleAccFromBackSideXQuery: - """Query timeseries `nacelle.acc_from_back_side_x` - - Args: - gearbox: The gearbox to filter on. - generator: The generator to filter on. - high_speed_shaft: The high speed shaft to filter on. - main_shaft: The main shaft to filter on. - power_inverter: The power inverter to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of nacelles to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the nacelle.acc_from_back_side_x timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 nacelle.acc_from_back_side_x timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelles = client.nacelle.acc_from_back_side_x(limit=5).retrieve() - - """ - filter_ = _create_nacelle_filter( - self._view_id, - gearbox, - generator, - high_speed_shaft, - main_shaft, - power_inverter, - external_id_prefix, - space, - filter, - ) - - return NacelleAccFromBackSideXQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `nacelle.acc_from_back_side_x` - - Args: - gearbox: The gearbox to filter on. - generator: The generator to filter on. - high_speed_shaft: The high speed shaft to filter on. - main_shaft: The main shaft to filter on. - power_inverter: The power inverter to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of nacelles to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries nacelle.acc_from_back_side_x. - - Examples: - - List nacelle.acc_from_back_side_x and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelles = client.nacelle.acc_from_back_side_x.list(limit=5) - - """ - filter_ = _create_nacelle_filter( - self._view_id, - gearbox, - generator, - high_speed_shaft, - main_shaft, - power_inverter, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_acc_from_back_side_x( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_acc_from_back_side_x( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "acc_from_back_side_x", -) -> dict[str, list[str]]: - properties = {"acc_from_back_side_x"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("acc_from_back_side_x")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["acc_from_back_side_x"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/nacelle_acc_from_back_side_y.py b/examples/windmill/_api/nacelle_acc_from_back_side_y.py deleted file mode 100644 index e9b393b9b..000000000 --- a/examples/windmill/_api/nacelle_acc_from_back_side_y.py +++ /dev/null @@ -1,595 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._nacelle import _create_nacelle_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal[ - "acc_from_back_side_x", "acc_from_back_side_y", "acc_from_back_side_z", "yaw_direction", "yaw_error" -] - - -class NacelleAccFromBackSideYQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `nacelle.acc_from_back_side_y` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_acc_from_back_side_y' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.acc_from_back_side_y(external_id="my_acc_from_back_side_y").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `nacelle.acc_from_back_side_y` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_acc_from_back_side_y' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.acc_from_back_side_y(external_id="my_acc_from_back_side_y").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "acc_from_back_side_y", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `nacelle.acc_from_back_side_y` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to acc_from_back_side_y - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_acc_from_back_side_y' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.acc_from_back_side_y(external_id="my_acc_from_back_side_y").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "acc_from_back_side_y", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `nacelle.acc_from_back_side_y` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to acc_from_back_side_y - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_acc_from_back_side_y' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.acc_from_back_side_y( - ... external_id="my_acc_from_back_side_y").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "acc_from_back_side_y" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_acc_from_back_side_y( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "acc_from_back_side_y": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class NacelleAccFromBackSideYAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> NacelleAccFromBackSideYQuery: - """Query timeseries `nacelle.acc_from_back_side_y` - - Args: - gearbox: The gearbox to filter on. - generator: The generator to filter on. - high_speed_shaft: The high speed shaft to filter on. - main_shaft: The main shaft to filter on. - power_inverter: The power inverter to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of nacelles to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the nacelle.acc_from_back_side_y timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 nacelle.acc_from_back_side_y timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelles = client.nacelle.acc_from_back_side_y(limit=5).retrieve() - - """ - filter_ = _create_nacelle_filter( - self._view_id, - gearbox, - generator, - high_speed_shaft, - main_shaft, - power_inverter, - external_id_prefix, - space, - filter, - ) - - return NacelleAccFromBackSideYQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `nacelle.acc_from_back_side_y` - - Args: - gearbox: The gearbox to filter on. - generator: The generator to filter on. - high_speed_shaft: The high speed shaft to filter on. - main_shaft: The main shaft to filter on. - power_inverter: The power inverter to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of nacelles to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries nacelle.acc_from_back_side_y. - - Examples: - - List nacelle.acc_from_back_side_y and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelles = client.nacelle.acc_from_back_side_y.list(limit=5) - - """ - filter_ = _create_nacelle_filter( - self._view_id, - gearbox, - generator, - high_speed_shaft, - main_shaft, - power_inverter, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_acc_from_back_side_y( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_acc_from_back_side_y( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "acc_from_back_side_y", -) -> dict[str, list[str]]: - properties = {"acc_from_back_side_y"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("acc_from_back_side_y")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["acc_from_back_side_y"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/nacelle_acc_from_back_side_z.py b/examples/windmill/_api/nacelle_acc_from_back_side_z.py deleted file mode 100644 index 49cc5c46c..000000000 --- a/examples/windmill/_api/nacelle_acc_from_back_side_z.py +++ /dev/null @@ -1,595 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._nacelle import _create_nacelle_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal[ - "acc_from_back_side_x", "acc_from_back_side_y", "acc_from_back_side_z", "yaw_direction", "yaw_error" -] - - -class NacelleAccFromBackSideZQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `nacelle.acc_from_back_side_z` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_acc_from_back_side_z' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.acc_from_back_side_z(external_id="my_acc_from_back_side_z").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `nacelle.acc_from_back_side_z` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_acc_from_back_side_z' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.acc_from_back_side_z(external_id="my_acc_from_back_side_z").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "acc_from_back_side_z", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `nacelle.acc_from_back_side_z` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to acc_from_back_side_z - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_acc_from_back_side_z' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.acc_from_back_side_z(external_id="my_acc_from_back_side_z").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "acc_from_back_side_z", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `nacelle.acc_from_back_side_z` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to acc_from_back_side_z - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_acc_from_back_side_z' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.acc_from_back_side_z( - ... external_id="my_acc_from_back_side_z").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "acc_from_back_side_z" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_acc_from_back_side_z( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "acc_from_back_side_z": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class NacelleAccFromBackSideZAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> NacelleAccFromBackSideZQuery: - """Query timeseries `nacelle.acc_from_back_side_z` - - Args: - gearbox: The gearbox to filter on. - generator: The generator to filter on. - high_speed_shaft: The high speed shaft to filter on. - main_shaft: The main shaft to filter on. - power_inverter: The power inverter to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of nacelles to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the nacelle.acc_from_back_side_z timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 nacelle.acc_from_back_side_z timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelles = client.nacelle.acc_from_back_side_z(limit=5).retrieve() - - """ - filter_ = _create_nacelle_filter( - self._view_id, - gearbox, - generator, - high_speed_shaft, - main_shaft, - power_inverter, - external_id_prefix, - space, - filter, - ) - - return NacelleAccFromBackSideZQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `nacelle.acc_from_back_side_z` - - Args: - gearbox: The gearbox to filter on. - generator: The generator to filter on. - high_speed_shaft: The high speed shaft to filter on. - main_shaft: The main shaft to filter on. - power_inverter: The power inverter to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of nacelles to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries nacelle.acc_from_back_side_z. - - Examples: - - List nacelle.acc_from_back_side_z and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelles = client.nacelle.acc_from_back_side_z.list(limit=5) - - """ - filter_ = _create_nacelle_filter( - self._view_id, - gearbox, - generator, - high_speed_shaft, - main_shaft, - power_inverter, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_acc_from_back_side_z( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_acc_from_back_side_z( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "acc_from_back_side_z", -) -> dict[str, list[str]]: - properties = {"acc_from_back_side_z"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("acc_from_back_side_z")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["acc_from_back_side_z"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/nacelle_query.py b/examples/windmill/_api/nacelle_query.py deleted file mode 100644 index 09e49d26a..000000000 --- a/examples/windmill/_api/nacelle_query.py +++ /dev/null @@ -1,155 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import TYPE_CHECKING, cast - -from cognite.client import data_modeling as dm, CogniteClient - -from windmill.data_classes import ( - DomainModelCore, - Nacelle, - Gearbox, - Generator, - HighSpeedShaft, - MainShaft, - PowerInverter, -) -from windmill._api._core import ( - DEFAULT_QUERY_LIMIT, - EdgeQueryStep, - NodeQueryStep, - DataClassQueryBuilder, - QueryAPI, - T_DomainModelList, - _create_edge_filter, -) - - -class NacelleQueryAPI(QueryAPI[T_DomainModelList]): - _view_id = dm.ViewId("power-models", "Nacelle", "1") - - def __init__( - self, - client: CogniteClient, - builder: DataClassQueryBuilder[T_DomainModelList], - filter_: dm.filters.Filter | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - ): - super().__init__(client, builder) - from_ = self._builder.get_from() - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - filter=filter_, - ), - result_cls=Nacelle, - max_retrieve_limit=limit, - ) - ) - - def query( - self, - retrieve_gearbox: bool = False, - retrieve_generator: bool = False, - retrieve_high_speed_shaft: bool = False, - retrieve_main_shaft: bool = False, - retrieve_power_inverter: bool = False, - ) -> T_DomainModelList: - """Execute query and return the result. - - Args: - retrieve_gearbox: Whether to retrieve the gearbox for each nacelle or not. - retrieve_generator: Whether to retrieve the generator for each nacelle or not. - retrieve_high_speed_shaft: Whether to retrieve the high speed shaft for each nacelle or not. - retrieve_main_shaft: Whether to retrieve the main shaft for each nacelle or not. - retrieve_power_inverter: Whether to retrieve the power inverter for each nacelle or not. - - Returns: - The list of the source nodes of the query. - - """ - from_ = self._builder[-1].name - if retrieve_gearbox: - self._query_append_gearbox(from_) - if retrieve_generator: - self._query_append_generator(from_) - if retrieve_high_speed_shaft: - self._query_append_high_speed_shaft(from_) - if retrieve_main_shaft: - self._query_append_main_shaft(from_) - if retrieve_power_inverter: - self._query_append_power_inverter(from_) - return self._query() - - def _query_append_gearbox(self, from_: str) -> None: - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - through=self._view_id.as_property_ref("gearbox"), - direction="outwards", - filter=dm.filters.HasData(views=[Gearbox._view_id]), - ), - result_cls=Gearbox, - ), - ) - - def _query_append_generator(self, from_: str) -> None: - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - through=self._view_id.as_property_ref("generator"), - direction="outwards", - filter=dm.filters.HasData(views=[Generator._view_id]), - ), - result_cls=Generator, - ), - ) - - def _query_append_high_speed_shaft(self, from_: str) -> None: - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - through=self._view_id.as_property_ref("high_speed_shaft"), - direction="outwards", - filter=dm.filters.HasData(views=[HighSpeedShaft._view_id]), - ), - result_cls=HighSpeedShaft, - ), - ) - - def _query_append_main_shaft(self, from_: str) -> None: - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - through=self._view_id.as_property_ref("main_shaft"), - direction="outwards", - filter=dm.filters.HasData(views=[MainShaft._view_id]), - ), - result_cls=MainShaft, - ), - ) - - def _query_append_power_inverter(self, from_: str) -> None: - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - through=self._view_id.as_property_ref("power_inverter"), - direction="outwards", - filter=dm.filters.HasData(views=[PowerInverter._view_id]), - ), - result_cls=PowerInverter, - ), - ) diff --git a/examples/windmill/_api/nacelle_yaw_direction.py b/examples/windmill/_api/nacelle_yaw_direction.py deleted file mode 100644 index fe7d8423b..000000000 --- a/examples/windmill/_api/nacelle_yaw_direction.py +++ /dev/null @@ -1,595 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._nacelle import _create_nacelle_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal[ - "acc_from_back_side_x", "acc_from_back_side_y", "acc_from_back_side_z", "yaw_direction", "yaw_error" -] - - -class NacelleYawDirectionQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `nacelle.yaw_direction` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_yaw_direction' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.yaw_direction(external_id="my_yaw_direction").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `nacelle.yaw_direction` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_yaw_direction' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.yaw_direction(external_id="my_yaw_direction").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "yaw_direction", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `nacelle.yaw_direction` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to yaw_direction - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_yaw_direction' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.yaw_direction(external_id="my_yaw_direction").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "yaw_direction", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `nacelle.yaw_direction` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to yaw_direction - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_yaw_direction' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.yaw_direction( - ... external_id="my_yaw_direction").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "yaw_direction" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_yaw_direction( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "yaw_direction": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class NacelleYawDirectionAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> NacelleYawDirectionQuery: - """Query timeseries `nacelle.yaw_direction` - - Args: - gearbox: The gearbox to filter on. - generator: The generator to filter on. - high_speed_shaft: The high speed shaft to filter on. - main_shaft: The main shaft to filter on. - power_inverter: The power inverter to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of nacelles to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the nacelle.yaw_direction timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 nacelle.yaw_direction timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelles = client.nacelle.yaw_direction(limit=5).retrieve() - - """ - filter_ = _create_nacelle_filter( - self._view_id, - gearbox, - generator, - high_speed_shaft, - main_shaft, - power_inverter, - external_id_prefix, - space, - filter, - ) - - return NacelleYawDirectionQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `nacelle.yaw_direction` - - Args: - gearbox: The gearbox to filter on. - generator: The generator to filter on. - high_speed_shaft: The high speed shaft to filter on. - main_shaft: The main shaft to filter on. - power_inverter: The power inverter to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of nacelles to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries nacelle.yaw_direction. - - Examples: - - List nacelle.yaw_direction and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelles = client.nacelle.yaw_direction.list(limit=5) - - """ - filter_ = _create_nacelle_filter( - self._view_id, - gearbox, - generator, - high_speed_shaft, - main_shaft, - power_inverter, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_yaw_direction( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_yaw_direction( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "yaw_direction", -) -> dict[str, list[str]]: - properties = {"yaw_direction"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("yaw_direction")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["yaw_direction"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/nacelle_yaw_error.py b/examples/windmill/_api/nacelle_yaw_error.py deleted file mode 100644 index 09d021345..000000000 --- a/examples/windmill/_api/nacelle_yaw_error.py +++ /dev/null @@ -1,595 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._nacelle import _create_nacelle_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal[ - "acc_from_back_side_x", "acc_from_back_side_y", "acc_from_back_side_z", "yaw_direction", "yaw_error" -] - - -class NacelleYawErrorQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `nacelle.yaw_error` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_yaw_error' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.yaw_error(external_id="my_yaw_error").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `nacelle.yaw_error` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_yaw_error' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.yaw_error(external_id="my_yaw_error").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "yaw_error", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `nacelle.yaw_error` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to yaw_error - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_yaw_error' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.yaw_error(external_id="my_yaw_error").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "yaw_error", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `nacelle.yaw_error` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to yaw_error - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_yaw_error' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> nacelle_datapoints = client.nacelle.yaw_error( - ... external_id="my_yaw_error").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "yaw_error" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_yaw_error( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "yaw_error": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class NacelleYawErrorAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> NacelleYawErrorQuery: - """Query timeseries `nacelle.yaw_error` - - Args: - gearbox: The gearbox to filter on. - generator: The generator to filter on. - high_speed_shaft: The high speed shaft to filter on. - main_shaft: The main shaft to filter on. - power_inverter: The power inverter to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of nacelles to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the nacelle.yaw_error timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 nacelle.yaw_error timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelles = client.nacelle.yaw_error(limit=5).retrieve() - - """ - filter_ = _create_nacelle_filter( - self._view_id, - gearbox, - generator, - high_speed_shaft, - main_shaft, - power_inverter, - external_id_prefix, - space, - filter, - ) - - return NacelleYawErrorQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `nacelle.yaw_error` - - Args: - gearbox: The gearbox to filter on. - generator: The generator to filter on. - high_speed_shaft: The high speed shaft to filter on. - main_shaft: The main shaft to filter on. - power_inverter: The power inverter to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of nacelles to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries nacelle.yaw_error. - - Examples: - - List nacelle.yaw_error and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> nacelles = client.nacelle.yaw_error.list(limit=5) - - """ - filter_ = _create_nacelle_filter( - self._view_id, - gearbox, - generator, - high_speed_shaft, - main_shaft, - power_inverter, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_yaw_error( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_yaw_error( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "yaw_error", -) -> dict[str, list[str]]: - properties = {"yaw_error"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("yaw_error")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["yaw_error"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/power_inverter.py b/examples/windmill/_api/power_inverter.py deleted file mode 100644 index 19096b979..000000000 --- a/examples/windmill/_api/power_inverter.py +++ /dev/null @@ -1,446 +0,0 @@ -from __future__ import annotations - -from collections.abc import Sequence -from typing import overload, Literal -import warnings - -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes.data_modeling.instances import InstanceAggregationResultList, InstanceSort - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - NodeQueryStep, - EdgeQueryStep, - DataClassQueryBuilder, -) -from windmill.data_classes import ( - DomainModelCore, - DomainModelWrite, - ResourcesWriteResult, - PowerInverter, - PowerInverterWrite, - PowerInverterFields, - PowerInverterList, - PowerInverterWriteList, - PowerInverterTextFields, -) -from windmill.data_classes._power_inverter import ( - PowerInverterQuery, - _POWERINVERTER_PROPERTIES_BY_FIELD, - _create_power_inverter_filter, -) -from windmill._api._core import ( - DEFAULT_LIMIT_READ, - Aggregations, - NodeAPI, - SequenceNotStr, -) -from windmill._api.power_inverter_active_power_total import PowerInverterActivePowerTotalAPI -from windmill._api.power_inverter_apparent_power_total import PowerInverterApparentPowerTotalAPI -from windmill._api.power_inverter_reactive_power_total import PowerInverterReactivePowerTotalAPI -from windmill._api.power_inverter_query import PowerInverterQueryAPI - - -class PowerInverterAPI(NodeAPI[PowerInverter, PowerInverterWrite, PowerInverterList, PowerInverterWriteList]): - _view_id = dm.ViewId("power-models", "PowerInverter", "1") - _properties_by_field = _POWERINVERTER_PROPERTIES_BY_FIELD - _class_type = PowerInverter - _class_list = PowerInverterList - _class_write_list = PowerInverterWriteList - - def __init__(self, client: CogniteClient): - super().__init__(client=client) - - self.active_power_total = PowerInverterActivePowerTotalAPI(client, self._view_id) - self.apparent_power_total = PowerInverterApparentPowerTotalAPI(client, self._view_id) - self.reactive_power_total = PowerInverterReactivePowerTotalAPI(client, self._view_id) - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - filter: dm.Filter | None = None, - ) -> PowerInverterQueryAPI[PowerInverterList]: - """Query starting at power inverters. - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of power inverters to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query API for power inverters. - - """ - has_data = dm.filters.HasData(views=[self._view_id]) - filter_ = _create_power_inverter_filter( - self._view_id, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - builder = DataClassQueryBuilder(PowerInverterList) - return PowerInverterQueryAPI(self._client, builder, filter_, limit) - - def apply( - self, - power_inverter: PowerInverterWrite | Sequence[PowerInverterWrite], - replace: bool = False, - write_none: bool = False, - ) -> ResourcesWriteResult: - """Add or update (upsert) power inverters. - - Args: - power_inverter: Power inverter or sequence of power inverters to upsert. - replace (bool): How do we behave when a property value exists? Do we replace all matching and existing values with the supplied values (true)? - Or should we merge in new values for properties together with the existing values (false)? Note: This setting applies for all nodes or edges specified in the ingestion call. - write_none (bool): This method, will by default, skip properties that are set to None. However, if you want to set properties to None, - you can set this parameter to True. Note this only applies to properties that are nullable. - Returns: - Created instance(s), i.e., nodes, edges, and time series. - - Examples: - - Create a new power_inverter: - - >>> from windmill import WindmillClient - >>> from windmill.data_classes import PowerInverterWrite - >>> client = WindmillClient() - >>> power_inverter = PowerInverterWrite(external_id="my_power_inverter", ...) - >>> result = client.power_inverter.apply(power_inverter) - - """ - warnings.warn( - "The .apply method is deprecated and will be removed in v1.0. " - "Please use the .upsert method on the client instead. This means instead of " - "`my_client.power_inverter.apply(my_items)` please use `my_client.upsert(my_items)`." - "The motivation is that all apply methods are the same, and having one apply method per API " - " class encourages users to create items in small batches, which is inefficient." - "In addition, .upsert method is more descriptive of what the method does.", - UserWarning, - stacklevel=2, - ) - return self._apply(power_inverter, replace, write_none) - - def delete( - self, external_id: str | SequenceNotStr[str], space: str = DEFAULT_INSTANCE_SPACE - ) -> dm.InstancesDeleteResult: - """Delete one or more power inverter. - - Args: - external_id: External id of the power inverter to delete. - space: The space where all the power inverter are located. - - Returns: - The instance(s), i.e., nodes and edges which has been deleted. Empty list if nothing was deleted. - - Examples: - - Delete power_inverter by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> client.power_inverter.delete("my_power_inverter") - """ - warnings.warn( - "The .delete method is deprecated and will be removed in v1.0. " - "Please use the .delete method on the client instead. This means instead of " - "`my_client.power_inverter.delete(my_ids)` please use `my_client.delete(my_ids)`." - "The motivation is that all delete methods are the same, and having one delete method per API " - " class encourages users to delete items in small batches, which is inefficient.", - UserWarning, - stacklevel=2, - ) - return self._delete(external_id, space) - - @overload - def retrieve( - self, external_id: str | dm.NodeId | tuple[str, str], space: str = DEFAULT_INSTANCE_SPACE - ) -> PowerInverter | None: ... - - @overload - def retrieve( - self, external_id: SequenceNotStr[str | dm.NodeId | tuple[str, str]], space: str = DEFAULT_INSTANCE_SPACE - ) -> PowerInverterList: ... - - def retrieve( - self, - external_id: str | dm.NodeId | tuple[str, str] | SequenceNotStr[str | dm.NodeId | tuple[str, str]], - space: str = DEFAULT_INSTANCE_SPACE, - ) -> PowerInverter | PowerInverterList | None: - """Retrieve one or more power inverters by id(s). - - Args: - external_id: External id or list of external ids of the power inverters. - space: The space where all the power inverters are located. - - Returns: - The requested power inverters. - - Examples: - - Retrieve power_inverter by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverter = client.power_inverter.retrieve("my_power_inverter") - - """ - return self._retrieve(external_id, space) - - def search( - self, - query: str, - properties: PowerInverterTextFields | SequenceNotStr[PowerInverterTextFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: PowerInverterFields | SequenceNotStr[PowerInverterFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> PowerInverterList: - """Search power inverters - - Args: - query: The search query, - properties: The property to search, if nothing is passed all text fields will be searched. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of power inverters to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - Search results power inverters matching the query. - - Examples: - - Search for 'my_power_inverter' in all text properties: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverters = client.power_inverter.search('my_power_inverter') - - """ - filter_ = _create_power_inverter_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._search( - query=query, - properties=properties, - filter_=filter_, - limit=limit, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) - - @overload - def aggregate( - self, - aggregate: Aggregations | dm.aggregations.MetricAggregation, - group_by: None = None, - property: PowerInverterFields | SequenceNotStr[PowerInverterFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.AggregatedNumberedValue: ... - - @overload - def aggregate( - self, - aggregate: SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation], - group_by: None = None, - property: PowerInverterFields | SequenceNotStr[PowerInverterFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> list[dm.aggregations.AggregatedNumberedValue]: ... - - @overload - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: PowerInverterFields | SequenceNotStr[PowerInverterFields], - property: PowerInverterFields | SequenceNotStr[PowerInverterFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> InstanceAggregationResultList: ... - - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: PowerInverterFields | SequenceNotStr[PowerInverterFields] | None = None, - property: PowerInverterFields | SequenceNotStr[PowerInverterFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> ( - dm.aggregations.AggregatedNumberedValue - | list[dm.aggregations.AggregatedNumberedValue] - | InstanceAggregationResultList - ): - """Aggregate data across power inverters - - Args: - aggregate: The aggregation to perform. - group_by: The property to group by when doing the aggregation. - property: The property to perform aggregation on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of power inverters to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Aggregation results. - - Examples: - - Count power inverters in space `my_space`: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> result = client.power_inverter.aggregate("count", space="my_space") - - """ - - filter_ = _create_power_inverter_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._aggregate( - aggregate=aggregate, - group_by=group_by, # type: ignore[arg-type] - properties=property, # type: ignore[arg-type] - query=None, - search_properties=None, - limit=limit, - filter=filter_, - ) - - def histogram( - self, - property: PowerInverterFields, - interval: float, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.HistogramValue: - """Produces histograms for power inverters - - Args: - property: The property to use as the value in the histogram. - interval: The interval to use for the histogram bins. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of power inverters to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Bucketed histogram results. - - """ - filter_ = _create_power_inverter_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._histogram( - property, - interval, - None, - None, - limit, - filter_, - ) - - def query(self) -> PowerInverterQuery: - """Start a query for power inverters.""" - warnings.warn("This method is renamed to .select", UserWarning, stacklevel=2) - return PowerInverterQuery(self._client) - - def select(self) -> PowerInverterQuery: - """Start selecting from power inverters.""" - warnings.warn( - "The .select is in alpha and is subject to breaking changes without notice.", UserWarning, stacklevel=2 - ) - return PowerInverterQuery(self._client) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: PowerInverterFields | Sequence[PowerInverterFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> PowerInverterList: - """List/filter power inverters - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of power inverters to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - List of requested power inverters - - Examples: - - List power inverters and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverters = client.power_inverter.list(limit=5) - - """ - filter_ = _create_power_inverter_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return self._list( - limit=limit, - filter=filter_, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) diff --git a/examples/windmill/_api/power_inverter_active_power_total.py b/examples/windmill/_api/power_inverter_active_power_total.py deleted file mode 100644 index 90ecd9886..000000000 --- a/examples/windmill/_api/power_inverter_active_power_total.py +++ /dev/null @@ -1,493 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._power_inverter import _create_power_inverter_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["active_power_total", "apparent_power_total", "reactive_power_total"] - - -class PowerInverterActivePowerTotalQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `power_inverter.active_power_total` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_active_power_total' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverter_datapoints = client.power_inverter.active_power_total(external_id="my_active_power_total").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `power_inverter.active_power_total` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_active_power_total' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverter_datapoints = client.power_inverter.active_power_total(external_id="my_active_power_total").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "active_power_total", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `power_inverter.active_power_total` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to active_power_total - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_active_power_total' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverter_datapoints = client.power_inverter.active_power_total(external_id="my_active_power_total").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "active_power_total", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `power_inverter.active_power_total` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to active_power_total - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_active_power_total' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> power_inverter_datapoints = client.power_inverter.active_power_total( - ... external_id="my_active_power_total").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "active_power_total" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_active_power_total( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "active_power_total": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class PowerInverterActivePowerTotalAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> PowerInverterActivePowerTotalQuery: - """Query timeseries `power_inverter.active_power_total` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of power inverters to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the power_inverter.active_power_total timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 power_inverter.active_power_total timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverters = client.power_inverter.active_power_total(limit=5).retrieve() - - """ - filter_ = _create_power_inverter_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return PowerInverterActivePowerTotalQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `power_inverter.active_power_total` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of power inverters to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries power_inverter.active_power_total. - - Examples: - - List power_inverter.active_power_total and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverters = client.power_inverter.active_power_total.list(limit=5) - - """ - filter_ = _create_power_inverter_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_active_power_total( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_active_power_total( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "active_power_total", -) -> dict[str, list[str]]: - properties = {"active_power_total"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("active_power_total")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["active_power_total"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/power_inverter_apparent_power_total.py b/examples/windmill/_api/power_inverter_apparent_power_total.py deleted file mode 100644 index b6cfbdd20..000000000 --- a/examples/windmill/_api/power_inverter_apparent_power_total.py +++ /dev/null @@ -1,493 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._power_inverter import _create_power_inverter_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["active_power_total", "apparent_power_total", "reactive_power_total"] - - -class PowerInverterApparentPowerTotalQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `power_inverter.apparent_power_total` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_apparent_power_total' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverter_datapoints = client.power_inverter.apparent_power_total(external_id="my_apparent_power_total").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `power_inverter.apparent_power_total` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_apparent_power_total' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverter_datapoints = client.power_inverter.apparent_power_total(external_id="my_apparent_power_total").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "apparent_power_total", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `power_inverter.apparent_power_total` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to apparent_power_total - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_apparent_power_total' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverter_datapoints = client.power_inverter.apparent_power_total(external_id="my_apparent_power_total").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "apparent_power_total", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `power_inverter.apparent_power_total` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to apparent_power_total - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_apparent_power_total' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> power_inverter_datapoints = client.power_inverter.apparent_power_total( - ... external_id="my_apparent_power_total").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "apparent_power_total" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_apparent_power_total( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "apparent_power_total": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class PowerInverterApparentPowerTotalAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> PowerInverterApparentPowerTotalQuery: - """Query timeseries `power_inverter.apparent_power_total` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of power inverters to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the power_inverter.apparent_power_total timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 power_inverter.apparent_power_total timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverters = client.power_inverter.apparent_power_total(limit=5).retrieve() - - """ - filter_ = _create_power_inverter_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return PowerInverterApparentPowerTotalQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `power_inverter.apparent_power_total` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of power inverters to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries power_inverter.apparent_power_total. - - Examples: - - List power_inverter.apparent_power_total and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverters = client.power_inverter.apparent_power_total.list(limit=5) - - """ - filter_ = _create_power_inverter_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_apparent_power_total( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_apparent_power_total( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "apparent_power_total", -) -> dict[str, list[str]]: - properties = {"apparent_power_total"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("apparent_power_total")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["apparent_power_total"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/power_inverter_query.py b/examples/windmill/_api/power_inverter_query.py deleted file mode 100644 index 64a9b2b52..000000000 --- a/examples/windmill/_api/power_inverter_query.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import TYPE_CHECKING, cast - -from cognite.client import data_modeling as dm, CogniteClient - -from windmill.data_classes import ( - DomainModelCore, - PowerInverter, -) -from windmill._api._core import ( - DEFAULT_QUERY_LIMIT, - EdgeQueryStep, - NodeQueryStep, - DataClassQueryBuilder, - QueryAPI, - T_DomainModelList, - _create_edge_filter, -) - - -class PowerInverterQueryAPI(QueryAPI[T_DomainModelList]): - _view_id = dm.ViewId("power-models", "PowerInverter", "1") - - def __init__( - self, - client: CogniteClient, - builder: DataClassQueryBuilder[T_DomainModelList], - filter_: dm.filters.Filter | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - ): - super().__init__(client, builder) - from_ = self._builder.get_from() - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - filter=filter_, - ), - result_cls=PowerInverter, - max_retrieve_limit=limit, - ) - ) - - def query( - self, - ) -> T_DomainModelList: - """Execute query and return the result. - - Returns: - The list of the source nodes of the query. - - """ - return self._query() diff --git a/examples/windmill/_api/power_inverter_reactive_power_total.py b/examples/windmill/_api/power_inverter_reactive_power_total.py deleted file mode 100644 index 4e6eaf1f5..000000000 --- a/examples/windmill/_api/power_inverter_reactive_power_total.py +++ /dev/null @@ -1,493 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._power_inverter import _create_power_inverter_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["active_power_total", "apparent_power_total", "reactive_power_total"] - - -class PowerInverterReactivePowerTotalQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `power_inverter.reactive_power_total` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_reactive_power_total' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverter_datapoints = client.power_inverter.reactive_power_total(external_id="my_reactive_power_total").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `power_inverter.reactive_power_total` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_reactive_power_total' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverter_datapoints = client.power_inverter.reactive_power_total(external_id="my_reactive_power_total").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "reactive_power_total", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `power_inverter.reactive_power_total` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to reactive_power_total - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_reactive_power_total' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverter_datapoints = client.power_inverter.reactive_power_total(external_id="my_reactive_power_total").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "reactive_power_total", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `power_inverter.reactive_power_total` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to reactive_power_total - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_reactive_power_total' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> power_inverter_datapoints = client.power_inverter.reactive_power_total( - ... external_id="my_reactive_power_total").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "reactive_power_total" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_reactive_power_total( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "reactive_power_total": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class PowerInverterReactivePowerTotalAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> PowerInverterReactivePowerTotalQuery: - """Query timeseries `power_inverter.reactive_power_total` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of power inverters to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the power_inverter.reactive_power_total timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 power_inverter.reactive_power_total timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverters = client.power_inverter.reactive_power_total(limit=5).retrieve() - - """ - filter_ = _create_power_inverter_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return PowerInverterReactivePowerTotalQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `power_inverter.reactive_power_total` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of power inverters to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries power_inverter.reactive_power_total. - - Examples: - - List power_inverter.reactive_power_total and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> power_inverters = client.power_inverter.reactive_power_total.list(limit=5) - - """ - filter_ = _create_power_inverter_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_reactive_power_total( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_reactive_power_total( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "reactive_power_total", -) -> dict[str, list[str]]: - properties = {"reactive_power_total"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("reactive_power_total")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["reactive_power_total"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/rotor.py b/examples/windmill/_api/rotor.py deleted file mode 100644 index 0b62423b6..000000000 --- a/examples/windmill/_api/rotor.py +++ /dev/null @@ -1,444 +0,0 @@ -from __future__ import annotations - -from collections.abc import Sequence -from typing import overload, Literal -import warnings - -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes.data_modeling.instances import InstanceAggregationResultList, InstanceSort - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - NodeQueryStep, - EdgeQueryStep, - DataClassQueryBuilder, -) -from windmill.data_classes import ( - DomainModelCore, - DomainModelWrite, - ResourcesWriteResult, - Rotor, - RotorWrite, - RotorFields, - RotorList, - RotorWriteList, - RotorTextFields, -) -from windmill.data_classes._rotor import ( - RotorQuery, - _ROTOR_PROPERTIES_BY_FIELD, - _create_rotor_filter, -) -from windmill._api._core import ( - DEFAULT_LIMIT_READ, - Aggregations, - NodeAPI, - SequenceNotStr, -) -from windmill._api.rotor_rotor_speed_controller import RotorRotorSpeedControllerAPI -from windmill._api.rotor_rpm_low_speed_shaft import RotorRpmLowSpeedShaftAPI -from windmill._api.rotor_query import RotorQueryAPI - - -class RotorAPI(NodeAPI[Rotor, RotorWrite, RotorList, RotorWriteList]): - _view_id = dm.ViewId("power-models", "Rotor", "1") - _properties_by_field = _ROTOR_PROPERTIES_BY_FIELD - _class_type = Rotor - _class_list = RotorList - _class_write_list = RotorWriteList - - def __init__(self, client: CogniteClient): - super().__init__(client=client) - - self.rotor_speed_controller = RotorRotorSpeedControllerAPI(client, self._view_id) - self.rpm_low_speed_shaft = RotorRpmLowSpeedShaftAPI(client, self._view_id) - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - filter: dm.Filter | None = None, - ) -> RotorQueryAPI[RotorList]: - """Query starting at rotors. - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of rotors to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query API for rotors. - - """ - has_data = dm.filters.HasData(views=[self._view_id]) - filter_ = _create_rotor_filter( - self._view_id, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - builder = DataClassQueryBuilder(RotorList) - return RotorQueryAPI(self._client, builder, filter_, limit) - - def apply( - self, - rotor: RotorWrite | Sequence[RotorWrite], - replace: bool = False, - write_none: bool = False, - ) -> ResourcesWriteResult: - """Add or update (upsert) rotors. - - Args: - rotor: Rotor or sequence of rotors to upsert. - replace (bool): How do we behave when a property value exists? Do we replace all matching and existing values with the supplied values (true)? - Or should we merge in new values for properties together with the existing values (false)? Note: This setting applies for all nodes or edges specified in the ingestion call. - write_none (bool): This method, will by default, skip properties that are set to None. However, if you want to set properties to None, - you can set this parameter to True. Note this only applies to properties that are nullable. - Returns: - Created instance(s), i.e., nodes, edges, and time series. - - Examples: - - Create a new rotor: - - >>> from windmill import WindmillClient - >>> from windmill.data_classes import RotorWrite - >>> client = WindmillClient() - >>> rotor = RotorWrite(external_id="my_rotor", ...) - >>> result = client.rotor.apply(rotor) - - """ - warnings.warn( - "The .apply method is deprecated and will be removed in v1.0. " - "Please use the .upsert method on the client instead. This means instead of " - "`my_client.rotor.apply(my_items)` please use `my_client.upsert(my_items)`." - "The motivation is that all apply methods are the same, and having one apply method per API " - " class encourages users to create items in small batches, which is inefficient." - "In addition, .upsert method is more descriptive of what the method does.", - UserWarning, - stacklevel=2, - ) - return self._apply(rotor, replace, write_none) - - def delete( - self, external_id: str | SequenceNotStr[str], space: str = DEFAULT_INSTANCE_SPACE - ) -> dm.InstancesDeleteResult: - """Delete one or more rotor. - - Args: - external_id: External id of the rotor to delete. - space: The space where all the rotor are located. - - Returns: - The instance(s), i.e., nodes and edges which has been deleted. Empty list if nothing was deleted. - - Examples: - - Delete rotor by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> client.rotor.delete("my_rotor") - """ - warnings.warn( - "The .delete method is deprecated and will be removed in v1.0. " - "Please use the .delete method on the client instead. This means instead of " - "`my_client.rotor.delete(my_ids)` please use `my_client.delete(my_ids)`." - "The motivation is that all delete methods are the same, and having one delete method per API " - " class encourages users to delete items in small batches, which is inefficient.", - UserWarning, - stacklevel=2, - ) - return self._delete(external_id, space) - - @overload - def retrieve( - self, external_id: str | dm.NodeId | tuple[str, str], space: str = DEFAULT_INSTANCE_SPACE - ) -> Rotor | None: ... - - @overload - def retrieve( - self, external_id: SequenceNotStr[str | dm.NodeId | tuple[str, str]], space: str = DEFAULT_INSTANCE_SPACE - ) -> RotorList: ... - - def retrieve( - self, - external_id: str | dm.NodeId | tuple[str, str] | SequenceNotStr[str | dm.NodeId | tuple[str, str]], - space: str = DEFAULT_INSTANCE_SPACE, - ) -> Rotor | RotorList | None: - """Retrieve one or more rotors by id(s). - - Args: - external_id: External id or list of external ids of the rotors. - space: The space where all the rotors are located. - - Returns: - The requested rotors. - - Examples: - - Retrieve rotor by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> rotor = client.rotor.retrieve("my_rotor") - - """ - return self._retrieve(external_id, space) - - def search( - self, - query: str, - properties: RotorTextFields | SequenceNotStr[RotorTextFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: RotorFields | SequenceNotStr[RotorFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> RotorList: - """Search rotors - - Args: - query: The search query, - properties: The property to search, if nothing is passed all text fields will be searched. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of rotors to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - Search results rotors matching the query. - - Examples: - - Search for 'my_rotor' in all text properties: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> rotors = client.rotor.search('my_rotor') - - """ - filter_ = _create_rotor_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._search( - query=query, - properties=properties, - filter_=filter_, - limit=limit, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) - - @overload - def aggregate( - self, - aggregate: Aggregations | dm.aggregations.MetricAggregation, - group_by: None = None, - property: RotorFields | SequenceNotStr[RotorFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.AggregatedNumberedValue: ... - - @overload - def aggregate( - self, - aggregate: SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation], - group_by: None = None, - property: RotorFields | SequenceNotStr[RotorFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> list[dm.aggregations.AggregatedNumberedValue]: ... - - @overload - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: RotorFields | SequenceNotStr[RotorFields], - property: RotorFields | SequenceNotStr[RotorFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> InstanceAggregationResultList: ... - - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: RotorFields | SequenceNotStr[RotorFields] | None = None, - property: RotorFields | SequenceNotStr[RotorFields] | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> ( - dm.aggregations.AggregatedNumberedValue - | list[dm.aggregations.AggregatedNumberedValue] - | InstanceAggregationResultList - ): - """Aggregate data across rotors - - Args: - aggregate: The aggregation to perform. - group_by: The property to group by when doing the aggregation. - property: The property to perform aggregation on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of rotors to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Aggregation results. - - Examples: - - Count rotors in space `my_space`: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> result = client.rotor.aggregate("count", space="my_space") - - """ - - filter_ = _create_rotor_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._aggregate( - aggregate=aggregate, - group_by=group_by, # type: ignore[arg-type] - properties=property, # type: ignore[arg-type] - query=None, - search_properties=None, - limit=limit, - filter=filter_, - ) - - def histogram( - self, - property: RotorFields, - interval: float, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.HistogramValue: - """Produces histograms for rotors - - Args: - property: The property to use as the value in the histogram. - interval: The interval to use for the histogram bins. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of rotors to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Bucketed histogram results. - - """ - filter_ = _create_rotor_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - return self._histogram( - property, - interval, - None, - None, - limit, - filter_, - ) - - def query(self) -> RotorQuery: - """Start a query for rotors.""" - warnings.warn("This method is renamed to .select", UserWarning, stacklevel=2) - return RotorQuery(self._client) - - def select(self) -> RotorQuery: - """Start selecting from rotors.""" - warnings.warn( - "The .select is in alpha and is subject to breaking changes without notice.", UserWarning, stacklevel=2 - ) - return RotorQuery(self._client) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: RotorFields | Sequence[RotorFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> RotorList: - """List/filter rotors - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of rotors to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - List of requested rotors - - Examples: - - List rotors and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> rotors = client.rotor.list(limit=5) - - """ - filter_ = _create_rotor_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return self._list( - limit=limit, - filter=filter_, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) diff --git a/examples/windmill/_api/rotor_query.py b/examples/windmill/_api/rotor_query.py deleted file mode 100644 index 01cb90af5..000000000 --- a/examples/windmill/_api/rotor_query.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import TYPE_CHECKING, cast - -from cognite.client import data_modeling as dm, CogniteClient - -from windmill.data_classes import ( - DomainModelCore, - Rotor, -) -from windmill._api._core import ( - DEFAULT_QUERY_LIMIT, - EdgeQueryStep, - NodeQueryStep, - DataClassQueryBuilder, - QueryAPI, - T_DomainModelList, - _create_edge_filter, -) - - -class RotorQueryAPI(QueryAPI[T_DomainModelList]): - _view_id = dm.ViewId("power-models", "Rotor", "1") - - def __init__( - self, - client: CogniteClient, - builder: DataClassQueryBuilder[T_DomainModelList], - filter_: dm.filters.Filter | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - ): - super().__init__(client, builder) - from_ = self._builder.get_from() - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - filter=filter_, - ), - result_cls=Rotor, - max_retrieve_limit=limit, - ) - ) - - def query( - self, - ) -> T_DomainModelList: - """Execute query and return the result. - - Returns: - The list of the source nodes of the query. - - """ - return self._query() diff --git a/examples/windmill/_api/rotor_rotor_speed_controller.py b/examples/windmill/_api/rotor_rotor_speed_controller.py deleted file mode 100644 index 519d2619c..000000000 --- a/examples/windmill/_api/rotor_rotor_speed_controller.py +++ /dev/null @@ -1,493 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._rotor import _create_rotor_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["rotor_speed_controller", "rpm_low_speed_shaft"] - - -class RotorRotorSpeedControllerQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `rotor.rotor_speed_controller` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_rotor_speed_controller' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> rotor_datapoints = client.rotor.rotor_speed_controller(external_id="my_rotor_speed_controller").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `rotor.rotor_speed_controller` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_rotor_speed_controller' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> rotor_datapoints = client.rotor.rotor_speed_controller(external_id="my_rotor_speed_controller").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "rotor_speed_controller", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `rotor.rotor_speed_controller` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to rotor_speed_controller - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_rotor_speed_controller' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> rotor_datapoints = client.rotor.rotor_speed_controller(external_id="my_rotor_speed_controller").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "rotor_speed_controller", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `rotor.rotor_speed_controller` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to rotor_speed_controller - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_rotor_speed_controller' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> rotor_datapoints = client.rotor.rotor_speed_controller( - ... external_id="my_rotor_speed_controller").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "rotor_speed_controller" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_rotor_speed_controller( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "rotor_speed_controller": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class RotorRotorSpeedControllerAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> RotorRotorSpeedControllerQuery: - """Query timeseries `rotor.rotor_speed_controller` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of rotors to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the rotor.rotor_speed_controller timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 rotor.rotor_speed_controller timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> rotors = client.rotor.rotor_speed_controller(limit=5).retrieve() - - """ - filter_ = _create_rotor_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return RotorRotorSpeedControllerQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `rotor.rotor_speed_controller` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of rotors to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries rotor.rotor_speed_controller. - - Examples: - - List rotor.rotor_speed_controller and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> rotors = client.rotor.rotor_speed_controller.list(limit=5) - - """ - filter_ = _create_rotor_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_rotor_speed_controller( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_rotor_speed_controller( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "rotor_speed_controller", -) -> dict[str, list[str]]: - properties = {"rotor_speed_controller"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("rotor_speed_controller")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["rotor_speed_controller"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/rotor_rpm_low_speed_shaft.py b/examples/windmill/_api/rotor_rpm_low_speed_shaft.py deleted file mode 100644 index b8c447fe4..000000000 --- a/examples/windmill/_api/rotor_rpm_low_speed_shaft.py +++ /dev/null @@ -1,493 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._rotor import _create_rotor_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["rotor_speed_controller", "rpm_low_speed_shaft"] - - -class RotorRpmLowSpeedShaftQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `rotor.rpm_low_speed_shaft` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_rpm_low_speed_shaft' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> rotor_datapoints = client.rotor.rpm_low_speed_shaft(external_id="my_rpm_low_speed_shaft").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `rotor.rpm_low_speed_shaft` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_rpm_low_speed_shaft' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> rotor_datapoints = client.rotor.rpm_low_speed_shaft(external_id="my_rpm_low_speed_shaft").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "rpm_low_speed_shaft", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `rotor.rpm_low_speed_shaft` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to rpm_low_speed_shaft - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_rpm_low_speed_shaft' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> rotor_datapoints = client.rotor.rpm_low_speed_shaft(external_id="my_rpm_low_speed_shaft").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "rpm_low_speed_shaft", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `rotor.rpm_low_speed_shaft` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to rpm_low_speed_shaft - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_rpm_low_speed_shaft' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> rotor_datapoints = client.rotor.rpm_low_speed_shaft( - ... external_id="my_rpm_low_speed_shaft").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "rpm_low_speed_shaft" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_rpm_low_speed_shaft( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "rpm_low_speed_shaft": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class RotorRpmLowSpeedShaftAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> RotorRpmLowSpeedShaftQuery: - """Query timeseries `rotor.rpm_low_speed_shaft` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of rotors to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the rotor.rpm_low_speed_shaft timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 rotor.rpm_low_speed_shaft timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> rotors = client.rotor.rpm_low_speed_shaft(limit=5).retrieve() - - """ - filter_ = _create_rotor_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - - return RotorRpmLowSpeedShaftQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `rotor.rpm_low_speed_shaft` - - Args: - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of rotors to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries rotor.rpm_low_speed_shaft. - - Examples: - - List rotor.rpm_low_speed_shaft and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> rotors = client.rotor.rpm_low_speed_shaft.list(limit=5) - - """ - filter_ = _create_rotor_filter( - self._view_id, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_rpm_low_speed_shaft( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_rpm_low_speed_shaft( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "rpm_low_speed_shaft", -) -> dict[str, list[str]]: - properties = {"rpm_low_speed_shaft"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("rpm_low_speed_shaft")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["rpm_low_speed_shaft"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/sensor_position.py b/examples/windmill/_api/sensor_position.py deleted file mode 100644 index 41f407231..000000000 --- a/examples/windmill/_api/sensor_position.py +++ /dev/null @@ -1,508 +0,0 @@ -from __future__ import annotations - -from collections.abc import Sequence -from typing import overload, Literal -import warnings - -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes.data_modeling.instances import InstanceAggregationResultList, InstanceSort - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - NodeQueryStep, - EdgeQueryStep, - DataClassQueryBuilder, -) -from windmill.data_classes import ( - DomainModelCore, - DomainModelWrite, - ResourcesWriteResult, - SensorPosition, - SensorPositionWrite, - SensorPositionFields, - SensorPositionList, - SensorPositionWriteList, - SensorPositionTextFields, -) -from windmill.data_classes._sensor_position import ( - SensorPositionQuery, - _SENSORPOSITION_PROPERTIES_BY_FIELD, - _create_sensor_position_filter, -) -from windmill._api._core import ( - DEFAULT_LIMIT_READ, - Aggregations, - NodeAPI, - SequenceNotStr, -) -from windmill._api.sensor_position_edgewise_bend_mom_crosstalk_corrected import ( - SensorPositionEdgewiseBendMomCrosstalkCorrectedAPI, -) -from windmill._api.sensor_position_edgewise_bend_mom_offset import SensorPositionEdgewiseBendMomOffsetAPI -from windmill._api.sensor_position_edgewise_bend_mom_offset_crosstalk_corrected import ( - SensorPositionEdgewiseBendMomOffsetCrosstalkCorrectedAPI, -) -from windmill._api.sensor_position_edgewisewise_bend_mom import SensorPositionEdgewisewiseBendMomAPI -from windmill._api.sensor_position_flapwise_bend_mom import SensorPositionFlapwiseBendMomAPI -from windmill._api.sensor_position_flapwise_bend_mom_crosstalk_corrected import ( - SensorPositionFlapwiseBendMomCrosstalkCorrectedAPI, -) -from windmill._api.sensor_position_flapwise_bend_mom_offset import SensorPositionFlapwiseBendMomOffsetAPI -from windmill._api.sensor_position_flapwise_bend_mom_offset_crosstalk_corrected import ( - SensorPositionFlapwiseBendMomOffsetCrosstalkCorrectedAPI, -) -from windmill._api.sensor_position_query import SensorPositionQueryAPI - - -class SensorPositionAPI(NodeAPI[SensorPosition, SensorPositionWrite, SensorPositionList, SensorPositionWriteList]): - _view_id = dm.ViewId("power-models", "SensorPosition", "1") - _properties_by_field = _SENSORPOSITION_PROPERTIES_BY_FIELD - _class_type = SensorPosition - _class_list = SensorPositionList - _class_write_list = SensorPositionWriteList - - def __init__(self, client: CogniteClient): - super().__init__(client=client) - - self.edgewise_bend_mom_crosstalk_corrected = SensorPositionEdgewiseBendMomCrosstalkCorrectedAPI( - client, self._view_id - ) - self.edgewise_bend_mom_offset = SensorPositionEdgewiseBendMomOffsetAPI(client, self._view_id) - self.edgewise_bend_mom_offset_crosstalk_corrected = SensorPositionEdgewiseBendMomOffsetCrosstalkCorrectedAPI( - client, self._view_id - ) - self.edgewisewise_bend_mom = SensorPositionEdgewisewiseBendMomAPI(client, self._view_id) - self.flapwise_bend_mom = SensorPositionFlapwiseBendMomAPI(client, self._view_id) - self.flapwise_bend_mom_crosstalk_corrected = SensorPositionFlapwiseBendMomCrosstalkCorrectedAPI( - client, self._view_id - ) - self.flapwise_bend_mom_offset = SensorPositionFlapwiseBendMomOffsetAPI(client, self._view_id) - self.flapwise_bend_mom_offset_crosstalk_corrected = SensorPositionFlapwiseBendMomOffsetCrosstalkCorrectedAPI( - client, self._view_id - ) - - def __call__( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - filter: dm.Filter | None = None, - ) -> SensorPositionQueryAPI[SensorPositionList]: - """Query starting at sensor positions. - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query API for sensor positions. - - """ - has_data = dm.filters.HasData(views=[self._view_id]) - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - builder = DataClassQueryBuilder(SensorPositionList) - return SensorPositionQueryAPI(self._client, builder, filter_, limit) - - def apply( - self, - sensor_position: SensorPositionWrite | Sequence[SensorPositionWrite], - replace: bool = False, - write_none: bool = False, - ) -> ResourcesWriteResult: - """Add or update (upsert) sensor positions. - - Args: - sensor_position: Sensor position or sequence of sensor positions to upsert. - replace (bool): How do we behave when a property value exists? Do we replace all matching and existing values with the supplied values (true)? - Or should we merge in new values for properties together with the existing values (false)? Note: This setting applies for all nodes or edges specified in the ingestion call. - write_none (bool): This method, will by default, skip properties that are set to None. However, if you want to set properties to None, - you can set this parameter to True. Note this only applies to properties that are nullable. - Returns: - Created instance(s), i.e., nodes, edges, and time series. - - Examples: - - Create a new sensor_position: - - >>> from windmill import WindmillClient - >>> from windmill.data_classes import SensorPositionWrite - >>> client = WindmillClient() - >>> sensor_position = SensorPositionWrite(external_id="my_sensor_position", ...) - >>> result = client.sensor_position.apply(sensor_position) - - """ - warnings.warn( - "The .apply method is deprecated and will be removed in v1.0. " - "Please use the .upsert method on the client instead. This means instead of " - "`my_client.sensor_position.apply(my_items)` please use `my_client.upsert(my_items)`." - "The motivation is that all apply methods are the same, and having one apply method per API " - " class encourages users to create items in small batches, which is inefficient." - "In addition, .upsert method is more descriptive of what the method does.", - UserWarning, - stacklevel=2, - ) - return self._apply(sensor_position, replace, write_none) - - def delete( - self, external_id: str | SequenceNotStr[str], space: str = DEFAULT_INSTANCE_SPACE - ) -> dm.InstancesDeleteResult: - """Delete one or more sensor position. - - Args: - external_id: External id of the sensor position to delete. - space: The space where all the sensor position are located. - - Returns: - The instance(s), i.e., nodes and edges which has been deleted. Empty list if nothing was deleted. - - Examples: - - Delete sensor_position by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> client.sensor_position.delete("my_sensor_position") - """ - warnings.warn( - "The .delete method is deprecated and will be removed in v1.0. " - "Please use the .delete method on the client instead. This means instead of " - "`my_client.sensor_position.delete(my_ids)` please use `my_client.delete(my_ids)`." - "The motivation is that all delete methods are the same, and having one delete method per API " - " class encourages users to delete items in small batches, which is inefficient.", - UserWarning, - stacklevel=2, - ) - return self._delete(external_id, space) - - @overload - def retrieve( - self, external_id: str | dm.NodeId | tuple[str, str], space: str = DEFAULT_INSTANCE_SPACE - ) -> SensorPosition | None: ... - - @overload - def retrieve( - self, external_id: SequenceNotStr[str | dm.NodeId | tuple[str, str]], space: str = DEFAULT_INSTANCE_SPACE - ) -> SensorPositionList: ... - - def retrieve( - self, - external_id: str | dm.NodeId | tuple[str, str] | SequenceNotStr[str | dm.NodeId | tuple[str, str]], - space: str = DEFAULT_INSTANCE_SPACE, - ) -> SensorPosition | SensorPositionList | None: - """Retrieve one or more sensor positions by id(s). - - Args: - external_id: External id or list of external ids of the sensor positions. - space: The space where all the sensor positions are located. - - Returns: - The requested sensor positions. - - Examples: - - Retrieve sensor_position by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position = client.sensor_position.retrieve("my_sensor_position") - - """ - return self._retrieve(external_id, space) - - def search( - self, - query: str, - properties: SensorPositionTextFields | SequenceNotStr[SensorPositionTextFields] | None = None, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: SensorPositionFields | SequenceNotStr[SensorPositionFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> SensorPositionList: - """Search sensor positions - - Args: - query: The search query, - properties: The property to search, if nothing is passed all text fields will be searched. - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - Search results sensor positions matching the query. - - Examples: - - Search for 'my_sensor_position' in all text properties: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.search('my_sensor_position') - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - return self._search( - query=query, - properties=properties, - filter_=filter_, - limit=limit, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) - - @overload - def aggregate( - self, - aggregate: Aggregations | dm.aggregations.MetricAggregation, - group_by: None = None, - property: SensorPositionFields | SequenceNotStr[SensorPositionFields] | None = None, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.AggregatedNumberedValue: ... - - @overload - def aggregate( - self, - aggregate: SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation], - group_by: None = None, - property: SensorPositionFields | SequenceNotStr[SensorPositionFields] | None = None, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> list[dm.aggregations.AggregatedNumberedValue]: ... - - @overload - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: SensorPositionFields | SequenceNotStr[SensorPositionFields], - property: SensorPositionFields | SequenceNotStr[SensorPositionFields] | None = None, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> InstanceAggregationResultList: ... - - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: SensorPositionFields | SequenceNotStr[SensorPositionFields] | None = None, - property: SensorPositionFields | SequenceNotStr[SensorPositionFields] | None = None, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> ( - dm.aggregations.AggregatedNumberedValue - | list[dm.aggregations.AggregatedNumberedValue] - | InstanceAggregationResultList - ): - """Aggregate data across sensor positions - - Args: - aggregate: The aggregation to perform. - group_by: The property to group by when doing the aggregation. - property: The property to perform aggregation on. - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Aggregation results. - - Examples: - - Count sensor positions in space `my_space`: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> result = client.sensor_position.aggregate("count", space="my_space") - - """ - - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - return self._aggregate( - aggregate=aggregate, - group_by=group_by, # type: ignore[arg-type] - properties=property, # type: ignore[arg-type] - query=None, - search_properties=None, - limit=limit, - filter=filter_, - ) - - def histogram( - self, - property: SensorPositionFields, - interval: float, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.HistogramValue: - """Produces histograms for sensor positions - - Args: - property: The property to use as the value in the histogram. - interval: The interval to use for the histogram bins. - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Bucketed histogram results. - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - return self._histogram( - property, - interval, - None, - None, - limit, - filter_, - ) - - def query(self) -> SensorPositionQuery: - """Start a query for sensor positions.""" - warnings.warn("This method is renamed to .select", UserWarning, stacklevel=2) - return SensorPositionQuery(self._client) - - def select(self) -> SensorPositionQuery: - """Start selecting from sensor positions.""" - warnings.warn( - "The .select is in alpha and is subject to breaking changes without notice.", UserWarning, stacklevel=2 - ) - return SensorPositionQuery(self._client) - - def list( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: SensorPositionFields | Sequence[SensorPositionFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> SensorPositionList: - """List/filter sensor positions - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - List of requested sensor positions - - Examples: - - List sensor positions and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.list(limit=5) - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - - return self._list( - limit=limit, - filter=filter_, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) diff --git a/examples/windmill/_api/sensor_position_edgewise_bend_mom_crosstalk_corrected.py b/examples/windmill/_api/sensor_position_edgewise_bend_mom_crosstalk_corrected.py deleted file mode 100644 index a7bc62cc2..000000000 --- a/examples/windmill/_api/sensor_position_edgewise_bend_mom_crosstalk_corrected.py +++ /dev/null @@ -1,515 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._sensor_position import _create_sensor_position_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal[ - "edgewise_bend_mom_crosstalk_corrected", - "edgewise_bend_mom_offset", - "edgewise_bend_mom_offset_crosstalk_corrected", - "edgewisewise_bend_mom", - "flapwise_bend_mom", - "flapwise_bend_mom_crosstalk_corrected", - "flapwise_bend_mom_offset", - "flapwise_bend_mom_offset_crosstalk_corrected", - "position", -] - - -class SensorPositionEdgewiseBendMomCrosstalkCorrectedQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `sensor_position.edgewise_bend_mom_crosstalk_corrected` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_edgewise_bend_mom_crosstalk_corrected' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.edgewise_bend_mom_crosstalk_corrected(external_id="my_edgewise_bend_mom_crosstalk_corrected").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `sensor_position.edgewise_bend_mom_crosstalk_corrected` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_edgewise_bend_mom_crosstalk_corrected' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.edgewise_bend_mom_crosstalk_corrected(external_id="my_edgewise_bend_mom_crosstalk_corrected").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "edgewise_bend_mom_crosstalk_corrected", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `sensor_position.edgewise_bend_mom_crosstalk_corrected` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to edgewise_bend_mom_crosstalk_corrected - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_edgewise_bend_mom_crosstalk_corrected' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.edgewise_bend_mom_crosstalk_corrected(external_id="my_edgewise_bend_mom_crosstalk_corrected").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "edgewise_bend_mom_crosstalk_corrected", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `sensor_position.edgewise_bend_mom_crosstalk_corrected` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to edgewise_bend_mom_crosstalk_corrected - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_edgewise_bend_mom_crosstalk_corrected' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.edgewise_bend_mom_crosstalk_corrected( - ... external_id="my_edgewise_bend_mom_crosstalk_corrected").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "edgewise_bend_mom_crosstalk_corrected" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_edgewise_bend_mom_crosstalk_corrected( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "edgewise_bend_mom_crosstalk_corrected": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class SensorPositionEdgewiseBendMomCrosstalkCorrectedAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> SensorPositionEdgewiseBendMomCrosstalkCorrectedQuery: - """Query timeseries `sensor_position.edgewise_bend_mom_crosstalk_corrected` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the sensor_position.edgewise_bend_mom_crosstalk_corrected timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 sensor_position.edgewise_bend_mom_crosstalk_corrected timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.edgewise_bend_mom_crosstalk_corrected(limit=5).retrieve() - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - - return SensorPositionEdgewiseBendMomCrosstalkCorrectedQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `sensor_position.edgewise_bend_mom_crosstalk_corrected` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries sensor_position.edgewise_bend_mom_crosstalk_corrected. - - Examples: - - List sensor_position.edgewise_bend_mom_crosstalk_corrected and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.edgewise_bend_mom_crosstalk_corrected.list(limit=5) - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_edgewise_bend_mom_crosstalk_corrected( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_edgewise_bend_mom_crosstalk_corrected( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "edgewise_bend_mom_crosstalk_corrected", -) -> dict[str, list[str]]: - properties = {"edgewise_bend_mom_crosstalk_corrected"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("edgewise_bend_mom_crosstalk_corrected")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["edgewise_bend_mom_crosstalk_corrected"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/sensor_position_edgewise_bend_mom_offset.py b/examples/windmill/_api/sensor_position_edgewise_bend_mom_offset.py deleted file mode 100644 index a24e7c912..000000000 --- a/examples/windmill/_api/sensor_position_edgewise_bend_mom_offset.py +++ /dev/null @@ -1,515 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._sensor_position import _create_sensor_position_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal[ - "edgewise_bend_mom_crosstalk_corrected", - "edgewise_bend_mom_offset", - "edgewise_bend_mom_offset_crosstalk_corrected", - "edgewisewise_bend_mom", - "flapwise_bend_mom", - "flapwise_bend_mom_crosstalk_corrected", - "flapwise_bend_mom_offset", - "flapwise_bend_mom_offset_crosstalk_corrected", - "position", -] - - -class SensorPositionEdgewiseBendMomOffsetQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `sensor_position.edgewise_bend_mom_offset` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_edgewise_bend_mom_offset' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.edgewise_bend_mom_offset(external_id="my_edgewise_bend_mom_offset").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `sensor_position.edgewise_bend_mom_offset` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_edgewise_bend_mom_offset' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.edgewise_bend_mom_offset(external_id="my_edgewise_bend_mom_offset").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "edgewise_bend_mom_offset", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `sensor_position.edgewise_bend_mom_offset` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to edgewise_bend_mom_offset - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_edgewise_bend_mom_offset' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.edgewise_bend_mom_offset(external_id="my_edgewise_bend_mom_offset").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "edgewise_bend_mom_offset", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `sensor_position.edgewise_bend_mom_offset` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to edgewise_bend_mom_offset - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_edgewise_bend_mom_offset' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.edgewise_bend_mom_offset( - ... external_id="my_edgewise_bend_mom_offset").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "edgewise_bend_mom_offset" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_edgewise_bend_mom_offset( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "edgewise_bend_mom_offset": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class SensorPositionEdgewiseBendMomOffsetAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> SensorPositionEdgewiseBendMomOffsetQuery: - """Query timeseries `sensor_position.edgewise_bend_mom_offset` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the sensor_position.edgewise_bend_mom_offset timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 sensor_position.edgewise_bend_mom_offset timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.edgewise_bend_mom_offset(limit=5).retrieve() - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - - return SensorPositionEdgewiseBendMomOffsetQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `sensor_position.edgewise_bend_mom_offset` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries sensor_position.edgewise_bend_mom_offset. - - Examples: - - List sensor_position.edgewise_bend_mom_offset and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.edgewise_bend_mom_offset.list(limit=5) - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_edgewise_bend_mom_offset( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_edgewise_bend_mom_offset( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "edgewise_bend_mom_offset", -) -> dict[str, list[str]]: - properties = {"edgewise_bend_mom_offset"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("edgewise_bend_mom_offset")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["edgewise_bend_mom_offset"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/sensor_position_edgewise_bend_mom_offset_crosstalk_corrected.py b/examples/windmill/_api/sensor_position_edgewise_bend_mom_offset_crosstalk_corrected.py deleted file mode 100644 index cc7f34c8a..000000000 --- a/examples/windmill/_api/sensor_position_edgewise_bend_mom_offset_crosstalk_corrected.py +++ /dev/null @@ -1,515 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._sensor_position import _create_sensor_position_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal[ - "edgewise_bend_mom_crosstalk_corrected", - "edgewise_bend_mom_offset", - "edgewise_bend_mom_offset_crosstalk_corrected", - "edgewisewise_bend_mom", - "flapwise_bend_mom", - "flapwise_bend_mom_crosstalk_corrected", - "flapwise_bend_mom_offset", - "flapwise_bend_mom_offset_crosstalk_corrected", - "position", -] - - -class SensorPositionEdgewiseBendMomOffsetCrosstalkCorrectedQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `sensor_position.edgewise_bend_mom_offset_crosstalk_corrected` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_edgewise_bend_mom_offset_crosstalk_corrected' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.edgewise_bend_mom_offset_crosstalk_corrected(external_id="my_edgewise_bend_mom_offset_crosstalk_corrected").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `sensor_position.edgewise_bend_mom_offset_crosstalk_corrected` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_edgewise_bend_mom_offset_crosstalk_corrected' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.edgewise_bend_mom_offset_crosstalk_corrected(external_id="my_edgewise_bend_mom_offset_crosstalk_corrected").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "edgewise_bend_mom_offset_crosstalk_corrected", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `sensor_position.edgewise_bend_mom_offset_crosstalk_corrected` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to edgewise_bend_mom_offset_crosstalk_corrected - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_edgewise_bend_mom_offset_crosstalk_corrected' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.edgewise_bend_mom_offset_crosstalk_corrected(external_id="my_edgewise_bend_mom_offset_crosstalk_corrected").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "edgewise_bend_mom_offset_crosstalk_corrected", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `sensor_position.edgewise_bend_mom_offset_crosstalk_corrected` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to edgewise_bend_mom_offset_crosstalk_corrected - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_edgewise_bend_mom_offset_crosstalk_corrected' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.edgewise_bend_mom_offset_crosstalk_corrected( - ... external_id="my_edgewise_bend_mom_offset_crosstalk_corrected").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "edgewise_bend_mom_offset_crosstalk_corrected" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_edgewise_bend_mom_offset_crosstalk_corrected( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "edgewise_bend_mom_offset_crosstalk_corrected": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class SensorPositionEdgewiseBendMomOffsetCrosstalkCorrectedAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> SensorPositionEdgewiseBendMomOffsetCrosstalkCorrectedQuery: - """Query timeseries `sensor_position.edgewise_bend_mom_offset_crosstalk_corrected` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the sensor_position.edgewise_bend_mom_offset_crosstalk_corrected timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 sensor_position.edgewise_bend_mom_offset_crosstalk_corrected timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.edgewise_bend_mom_offset_crosstalk_corrected(limit=5).retrieve() - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - - return SensorPositionEdgewiseBendMomOffsetCrosstalkCorrectedQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `sensor_position.edgewise_bend_mom_offset_crosstalk_corrected` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries sensor_position.edgewise_bend_mom_offset_crosstalk_corrected. - - Examples: - - List sensor_position.edgewise_bend_mom_offset_crosstalk_corrected and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.edgewise_bend_mom_offset_crosstalk_corrected.list(limit=5) - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_edgewise_bend_mom_offset_crosstalk_corrected( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_edgewise_bend_mom_offset_crosstalk_corrected( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "edgewise_bend_mom_offset_crosstalk_corrected", -) -> dict[str, list[str]]: - properties = {"edgewise_bend_mom_offset_crosstalk_corrected"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("edgewise_bend_mom_offset_crosstalk_corrected")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["edgewise_bend_mom_offset_crosstalk_corrected"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/sensor_position_edgewisewise_bend_mom.py b/examples/windmill/_api/sensor_position_edgewisewise_bend_mom.py deleted file mode 100644 index ba5760324..000000000 --- a/examples/windmill/_api/sensor_position_edgewisewise_bend_mom.py +++ /dev/null @@ -1,515 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._sensor_position import _create_sensor_position_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal[ - "edgewise_bend_mom_crosstalk_corrected", - "edgewise_bend_mom_offset", - "edgewise_bend_mom_offset_crosstalk_corrected", - "edgewisewise_bend_mom", - "flapwise_bend_mom", - "flapwise_bend_mom_crosstalk_corrected", - "flapwise_bend_mom_offset", - "flapwise_bend_mom_offset_crosstalk_corrected", - "position", -] - - -class SensorPositionEdgewisewiseBendMomQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `sensor_position.edgewisewise_bend_mom` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_edgewisewise_bend_mom' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.edgewisewise_bend_mom(external_id="my_edgewisewise_bend_mom").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `sensor_position.edgewisewise_bend_mom` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_edgewisewise_bend_mom' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.edgewisewise_bend_mom(external_id="my_edgewisewise_bend_mom").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "edgewisewise_bend_mom", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `sensor_position.edgewisewise_bend_mom` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to edgewisewise_bend_mom - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_edgewisewise_bend_mom' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.edgewisewise_bend_mom(external_id="my_edgewisewise_bend_mom").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "edgewisewise_bend_mom", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `sensor_position.edgewisewise_bend_mom` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to edgewisewise_bend_mom - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_edgewisewise_bend_mom' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.edgewisewise_bend_mom( - ... external_id="my_edgewisewise_bend_mom").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "edgewisewise_bend_mom" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_edgewisewise_bend_mom( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "edgewisewise_bend_mom": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class SensorPositionEdgewisewiseBendMomAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> SensorPositionEdgewisewiseBendMomQuery: - """Query timeseries `sensor_position.edgewisewise_bend_mom` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the sensor_position.edgewisewise_bend_mom timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 sensor_position.edgewisewise_bend_mom timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.edgewisewise_bend_mom(limit=5).retrieve() - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - - return SensorPositionEdgewisewiseBendMomQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `sensor_position.edgewisewise_bend_mom` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries sensor_position.edgewisewise_bend_mom. - - Examples: - - List sensor_position.edgewisewise_bend_mom and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.edgewisewise_bend_mom.list(limit=5) - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_edgewisewise_bend_mom( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_edgewisewise_bend_mom( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "edgewisewise_bend_mom", -) -> dict[str, list[str]]: - properties = {"edgewisewise_bend_mom"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("edgewisewise_bend_mom")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["edgewisewise_bend_mom"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/sensor_position_flapwise_bend_mom.py b/examples/windmill/_api/sensor_position_flapwise_bend_mom.py deleted file mode 100644 index ecad9a06f..000000000 --- a/examples/windmill/_api/sensor_position_flapwise_bend_mom.py +++ /dev/null @@ -1,515 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._sensor_position import _create_sensor_position_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal[ - "edgewise_bend_mom_crosstalk_corrected", - "edgewise_bend_mom_offset", - "edgewise_bend_mom_offset_crosstalk_corrected", - "edgewisewise_bend_mom", - "flapwise_bend_mom", - "flapwise_bend_mom_crosstalk_corrected", - "flapwise_bend_mom_offset", - "flapwise_bend_mom_offset_crosstalk_corrected", - "position", -] - - -class SensorPositionFlapwiseBendMomQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `sensor_position.flapwise_bend_mom` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_flapwise_bend_mom' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.flapwise_bend_mom(external_id="my_flapwise_bend_mom").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `sensor_position.flapwise_bend_mom` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_flapwise_bend_mom' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.flapwise_bend_mom(external_id="my_flapwise_bend_mom").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "flapwise_bend_mom", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `sensor_position.flapwise_bend_mom` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to flapwise_bend_mom - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_flapwise_bend_mom' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.flapwise_bend_mom(external_id="my_flapwise_bend_mom").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "flapwise_bend_mom", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `sensor_position.flapwise_bend_mom` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to flapwise_bend_mom - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_flapwise_bend_mom' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.flapwise_bend_mom( - ... external_id="my_flapwise_bend_mom").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "flapwise_bend_mom" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_flapwise_bend_mom( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "flapwise_bend_mom": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class SensorPositionFlapwiseBendMomAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> SensorPositionFlapwiseBendMomQuery: - """Query timeseries `sensor_position.flapwise_bend_mom` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the sensor_position.flapwise_bend_mom timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 sensor_position.flapwise_bend_mom timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.flapwise_bend_mom(limit=5).retrieve() - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - - return SensorPositionFlapwiseBendMomQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `sensor_position.flapwise_bend_mom` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries sensor_position.flapwise_bend_mom. - - Examples: - - List sensor_position.flapwise_bend_mom and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.flapwise_bend_mom.list(limit=5) - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_flapwise_bend_mom( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_flapwise_bend_mom( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "flapwise_bend_mom", -) -> dict[str, list[str]]: - properties = {"flapwise_bend_mom"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("flapwise_bend_mom")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["flapwise_bend_mom"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/sensor_position_flapwise_bend_mom_crosstalk_corrected.py b/examples/windmill/_api/sensor_position_flapwise_bend_mom_crosstalk_corrected.py deleted file mode 100644 index 13fd1f935..000000000 --- a/examples/windmill/_api/sensor_position_flapwise_bend_mom_crosstalk_corrected.py +++ /dev/null @@ -1,515 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._sensor_position import _create_sensor_position_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal[ - "edgewise_bend_mom_crosstalk_corrected", - "edgewise_bend_mom_offset", - "edgewise_bend_mom_offset_crosstalk_corrected", - "edgewisewise_bend_mom", - "flapwise_bend_mom", - "flapwise_bend_mom_crosstalk_corrected", - "flapwise_bend_mom_offset", - "flapwise_bend_mom_offset_crosstalk_corrected", - "position", -] - - -class SensorPositionFlapwiseBendMomCrosstalkCorrectedQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `sensor_position.flapwise_bend_mom_crosstalk_corrected` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_flapwise_bend_mom_crosstalk_corrected' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.flapwise_bend_mom_crosstalk_corrected(external_id="my_flapwise_bend_mom_crosstalk_corrected").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `sensor_position.flapwise_bend_mom_crosstalk_corrected` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_flapwise_bend_mom_crosstalk_corrected' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.flapwise_bend_mom_crosstalk_corrected(external_id="my_flapwise_bend_mom_crosstalk_corrected").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "flapwise_bend_mom_crosstalk_corrected", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `sensor_position.flapwise_bend_mom_crosstalk_corrected` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to flapwise_bend_mom_crosstalk_corrected - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_flapwise_bend_mom_crosstalk_corrected' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.flapwise_bend_mom_crosstalk_corrected(external_id="my_flapwise_bend_mom_crosstalk_corrected").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "flapwise_bend_mom_crosstalk_corrected", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `sensor_position.flapwise_bend_mom_crosstalk_corrected` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to flapwise_bend_mom_crosstalk_corrected - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_flapwise_bend_mom_crosstalk_corrected' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.flapwise_bend_mom_crosstalk_corrected( - ... external_id="my_flapwise_bend_mom_crosstalk_corrected").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "flapwise_bend_mom_crosstalk_corrected" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_flapwise_bend_mom_crosstalk_corrected( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "flapwise_bend_mom_crosstalk_corrected": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class SensorPositionFlapwiseBendMomCrosstalkCorrectedAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> SensorPositionFlapwiseBendMomCrosstalkCorrectedQuery: - """Query timeseries `sensor_position.flapwise_bend_mom_crosstalk_corrected` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the sensor_position.flapwise_bend_mom_crosstalk_corrected timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 sensor_position.flapwise_bend_mom_crosstalk_corrected timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.flapwise_bend_mom_crosstalk_corrected(limit=5).retrieve() - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - - return SensorPositionFlapwiseBendMomCrosstalkCorrectedQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `sensor_position.flapwise_bend_mom_crosstalk_corrected` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries sensor_position.flapwise_bend_mom_crosstalk_corrected. - - Examples: - - List sensor_position.flapwise_bend_mom_crosstalk_corrected and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.flapwise_bend_mom_crosstalk_corrected.list(limit=5) - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_flapwise_bend_mom_crosstalk_corrected( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_flapwise_bend_mom_crosstalk_corrected( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "flapwise_bend_mom_crosstalk_corrected", -) -> dict[str, list[str]]: - properties = {"flapwise_bend_mom_crosstalk_corrected"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("flapwise_bend_mom_crosstalk_corrected")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["flapwise_bend_mom_crosstalk_corrected"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/sensor_position_flapwise_bend_mom_offset.py b/examples/windmill/_api/sensor_position_flapwise_bend_mom_offset.py deleted file mode 100644 index 68eb401c2..000000000 --- a/examples/windmill/_api/sensor_position_flapwise_bend_mom_offset.py +++ /dev/null @@ -1,515 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._sensor_position import _create_sensor_position_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal[ - "edgewise_bend_mom_crosstalk_corrected", - "edgewise_bend_mom_offset", - "edgewise_bend_mom_offset_crosstalk_corrected", - "edgewisewise_bend_mom", - "flapwise_bend_mom", - "flapwise_bend_mom_crosstalk_corrected", - "flapwise_bend_mom_offset", - "flapwise_bend_mom_offset_crosstalk_corrected", - "position", -] - - -class SensorPositionFlapwiseBendMomOffsetQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `sensor_position.flapwise_bend_mom_offset` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_flapwise_bend_mom_offset' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.flapwise_bend_mom_offset(external_id="my_flapwise_bend_mom_offset").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `sensor_position.flapwise_bend_mom_offset` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_flapwise_bend_mom_offset' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.flapwise_bend_mom_offset(external_id="my_flapwise_bend_mom_offset").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "flapwise_bend_mom_offset", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `sensor_position.flapwise_bend_mom_offset` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to flapwise_bend_mom_offset - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_flapwise_bend_mom_offset' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.flapwise_bend_mom_offset(external_id="my_flapwise_bend_mom_offset").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "flapwise_bend_mom_offset", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `sensor_position.flapwise_bend_mom_offset` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to flapwise_bend_mom_offset - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_flapwise_bend_mom_offset' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.flapwise_bend_mom_offset( - ... external_id="my_flapwise_bend_mom_offset").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "flapwise_bend_mom_offset" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_flapwise_bend_mom_offset( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "flapwise_bend_mom_offset": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class SensorPositionFlapwiseBendMomOffsetAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> SensorPositionFlapwiseBendMomOffsetQuery: - """Query timeseries `sensor_position.flapwise_bend_mom_offset` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the sensor_position.flapwise_bend_mom_offset timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 sensor_position.flapwise_bend_mom_offset timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.flapwise_bend_mom_offset(limit=5).retrieve() - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - - return SensorPositionFlapwiseBendMomOffsetQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `sensor_position.flapwise_bend_mom_offset` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries sensor_position.flapwise_bend_mom_offset. - - Examples: - - List sensor_position.flapwise_bend_mom_offset and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.flapwise_bend_mom_offset.list(limit=5) - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_flapwise_bend_mom_offset( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_flapwise_bend_mom_offset( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "flapwise_bend_mom_offset", -) -> dict[str, list[str]]: - properties = {"flapwise_bend_mom_offset"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("flapwise_bend_mom_offset")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["flapwise_bend_mom_offset"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/sensor_position_flapwise_bend_mom_offset_crosstalk_corrected.py b/examples/windmill/_api/sensor_position_flapwise_bend_mom_offset_crosstalk_corrected.py deleted file mode 100644 index 12d6bc026..000000000 --- a/examples/windmill/_api/sensor_position_flapwise_bend_mom_offset_crosstalk_corrected.py +++ /dev/null @@ -1,515 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import Literal, cast - -import pandas as pd -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, DatapointsArrayList, DatapointsList, TimeSeriesList -from cognite.client.data_classes.datapoints import Aggregate -from windmill.data_classes._sensor_position import _create_sensor_position_filter -from windmill.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from windmill._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal[ - "edgewise_bend_mom_crosstalk_corrected", - "edgewise_bend_mom_offset", - "edgewise_bend_mom_offset_crosstalk_corrected", - "edgewisewise_bend_mom", - "flapwise_bend_mom", - "flapwise_bend_mom_crosstalk_corrected", - "flapwise_bend_mom_offset", - "flapwise_bend_mom_offset_crosstalk_corrected", - "position", -] - - -class SensorPositionFlapwiseBendMomOffsetCrosstalkCorrectedQuery: - def __init__( - self, - client: CogniteClient, - view_id: dm.ViewId, - timeseries_limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ): - self._client = client - self._view_id = view_id - self._timeseries_limit = timeseries_limit - self._filter = filter - - def retrieve( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsList: - """`Retrieve datapoints for the `sensor_position.flapwise_bend_mom_offset_crosstalk_corrected` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_flapwise_bend_mom_offset_crosstalk_corrected' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.flapwise_bend_mom_offset_crosstalk_corrected(external_id="my_flapwise_bend_mom_offset_crosstalk_corrected").retrieve(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsList([]) - - def retrieve_arrays( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - ) -> DatapointsArrayList: - """`Retrieve numpy arrays for the `sensor_position.flapwise_bend_mom_offset_crosstalk_corrected` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - - Returns: - A ``DatapointsArrayList`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_flapwise_bend_mom_offset_crosstalk_corrected' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.flapwise_bend_mom_offset_crosstalk_corrected(external_id="my_flapwise_bend_mom_offset_crosstalk_corrected").retrieve_array(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - # Missing overload in SDK - return self._client.time_series.data.retrieve_arrays( # type: ignore[return-value] - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ) - else: - return DatapointsArrayList([]) - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - *, - aggregates: Aggregate | list[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - include_outside_points: bool = False, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "flapwise_bend_mom_offset_crosstalk_corrected", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `sensor_position.flapwise_bend_mom_offset_crosstalk_corrected` timeseries. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. Default: 1970-01-01 UTC. - end: Exclusive end. Default: "now" - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to flapwise_bend_mom_offset_crosstalk_corrected - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - we are using the time-ago format to get raw data for the 'my_flapwise_bend_mom_offset_crosstalk_corrected' from 2 weeks ago up until now:: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.flapwise_bend_mom_offset_crosstalk_corrected(external_id="my_flapwise_bend_mom_offset_crosstalk_corrected").retrieve_dataframe(start="2w-ago") - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_dataframe_in_tz( - self, - start: datetime.datetime, - end: datetime.datetime, - *, - aggregates: Aggregate | Sequence[Aggregate] | None = None, - granularity: str | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - column_names: ColumnNames | list[ColumnNames] = "flapwise_bend_mom_offset_crosstalk_corrected", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `sensor_position.flapwise_bend_mom_offset_crosstalk_corrected` timeseries in Timezone. - - **Performance guide**: - In order to retrieve millions of datapoints as efficiently as possible, here are a few guidelines: - - 1. For the best speed, and significantly lower memory usage, consider using ``retrieve_arrays(...)`` which uses ``numpy.ndarrays`` for data storage. - 2. Only unlimited queries with (``limit=None``) are fetched in parallel, so specifying a large finite ``limit`` like 1 million, comes with severe performance penalty as data is fetched serially. - 3. Try to avoid specifying `start` and `end` to be very far from the actual data: If you had data from 2000 to 2015, don't set start=0 (1970). - - Args: - start: Inclusive start. - end: Exclusive end - aggregates: Single aggregate or list of aggregates to retrieve. Default: None (raw datapoints returned) - granularity The granularity to fetch aggregates at. e.g. '15s', '2h', '10d'. Default: None. - target_unit: The unit_external_id of the data points returned. If the time series does not have an unit_external_id that can be converted to the target_unit, an error will be returned. Cannot be used with target_unit_system. - target_unit_system: The unit system of the data points returned. Cannot be used with target_unit. - limit: Maximum number of datapoints to return for each time series. Default: None (no limit) - include_outside_points: Whether to include outside points. Not allowed when fetching aggregates. Default: False - uniform_index: If only querying aggregates AND a single granularity is used, AND no limit is used, specifying `uniform_index=True` will return a dataframe with an equidistant datetime index from the earliest `start` to the latest `end` (missing values will be NaNs). If these requirements are not met, a ValueError is raised. Default: False - include_aggregate_name: Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name: Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - column_names: Which property to use for column names. Defauts to flapwise_bend_mom_offset_crosstalk_corrected - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_flapwise_bend_mom_offset_crosstalk_corrected' for the first month of 2023 in Oslo time: - - >>> from windmill import WindmillClient - >>> from datetime import datetime, timezone - >>> client = WindmillClient() - >>> sensor_position_datapoints = client.sensor_position.flapwise_bend_mom_offset_crosstalk_corrected( - ... external_id="my_flapwise_bend_mom_offset_crosstalk_corrected").retrieve_dataframe_in_timezone( - ... datetime(2023, 1, 1, tzinfo=ZoneInfo("Europe/Oslo")), - ... datetime(2023, 1, 2, tzinfo=ZoneInfo("Europe/Oslo")), - ... aggregates="average", - ... granularity="1week", - ... ) - """ - external_ids = self._retrieve_timeseries_external_ids_with_extra(column_names) - if external_ids: - df = self._client.time_series.data.retrieve_dataframe_in_tz( - external_id=list(external_ids), - start=start, - end=end, - aggregates=aggregates, # type: ignore[arg-type] - granularity=granularity, - target_unit=target_unit, - target_unit_system=target_unit_system, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - ) - is_aggregate = aggregates is not None - return self._rename_columns( - external_ids, - df, - column_names, - is_aggregate and include_aggregate_name, - is_aggregate and include_granularity_name, - ) - else: - return pd.DataFrame() - - def retrieve_latest( - self, - before: None | int | str | datetime.datetime = None, - ) -> Datapoints | DatapointsList | None: - external_ids = self._retrieve_timeseries_external_ids_with_extra() - if external_ids: - return self._client.time_series.data.retrieve_latest( - external_id=list(external_ids), - before=before, - ) - else: - return None - - def _retrieve_timeseries_external_ids_with_extra( - self, extra_properties: ColumnNames | list[ColumnNames] = "flapwise_bend_mom_offset_crosstalk_corrected" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_flapwise_bend_mom_offset_crosstalk_corrected( - self._client, - self._view_id, - self._filter, - self._timeseries_limit, - extra_properties, - ) - - @staticmethod - def _rename_columns( - external_ids: dict[str, list[str]], - df: pd.DataFrame, - column_names: ColumnNames | list[ColumnNames], - include_aggregate_name: bool, - include_granularity_name: bool, - ) -> pd.DataFrame: - if isinstance(column_names, str) and column_names == "flapwise_bend_mom_offset_crosstalk_corrected": - return df - splits = sum(included for included in [include_aggregate_name, include_granularity_name]) - if splits == 0: - df.columns = ["-".join(external_ids[external_id]) for external_id in df.columns] # type: ignore[assignment] - else: - column_parts = (col.rsplit("|", maxsplit=splits) for col in df.columns) - df.columns = [ # type: ignore[assignment] - "-".join(external_ids[external_id]) + "|" + "|".join(parts) for external_id, *parts in column_parts - ] - return df - - -class SensorPositionFlapwiseBendMomOffsetCrosstalkCorrectedAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> SensorPositionFlapwiseBendMomOffsetCrosstalkCorrectedQuery: - """Query timeseries `sensor_position.flapwise_bend_mom_offset_crosstalk_corrected` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query object that can be used to retrieve datapoins for the sensor_position.flapwise_bend_mom_offset_crosstalk_corrected timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 sensor_position.flapwise_bend_mom_offset_crosstalk_corrected timeseries: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.flapwise_bend_mom_offset_crosstalk_corrected(limit=5).retrieve() - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - - return SensorPositionFlapwiseBendMomOffsetCrosstalkCorrectedQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> TimeSeriesList: - """List timeseries `sensor_position.flapwise_bend_mom_offset_crosstalk_corrected` - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of sensor positions to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - List of Timeseries sensor_position.flapwise_bend_mom_offset_crosstalk_corrected. - - Examples: - - List sensor_position.flapwise_bend_mom_offset_crosstalk_corrected and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> sensor_positions = client.sensor_position.flapwise_bend_mom_offset_crosstalk_corrected.list(limit=5) - - """ - filter_ = _create_sensor_position_filter( - self._view_id, - min_position, - max_position, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_flapwise_bend_mom_offset_crosstalk_corrected( - self._client, self._view_id, filter_, limit - ) - if external_ids: - return self._client.time_series.retrieve_multiple(external_ids=list(external_ids)) - else: - return TimeSeriesList([]) - - -def _retrieve_timeseries_external_ids_with_extra_flapwise_bend_mom_offset_crosstalk_corrected( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "flapwise_bend_mom_offset_crosstalk_corrected", -) -> dict[str, list[str]]: - properties = {"flapwise_bend_mom_offset_crosstalk_corrected"} - if isinstance(extra_properties, str): - properties.add(extra_properties) - extra_properties_list = [extra_properties] - elif isinstance(extra_properties, list): - properties.update(extra_properties) - extra_properties_list = extra_properties - else: - raise ValueError(f"Invalid value for extra_properties: {extra_properties}") - - has_data = dm.filters.HasData(views=[view_id]) - has_property = dm.filters.Exists(property=view_id.as_property_ref("flapwise_bend_mom_offset_crosstalk_corrected")) - filter_ = dm.filters.And(filter_, has_data, has_property) if filter_ else dm.filters.And(has_data, has_property) - - builder = DataClassQueryBuilder[DomainModelList](None) - builder.append( - QueryStep( - name="nodes", - expression=dm.query.NodeResultSetExpression(filter=filter_), - max_retrieve_limit=limit, - select=dm.query.Select([dm.query.SourceSelector(view_id, list(properties))]), - ) - ) - builder.execute_query(client) - - output: dict[str, list[str]] = {} - for node in builder[0].results: - if node.properties is None: - continue - view_prop = node.properties[view_id] - key = view_prop["flapwise_bend_mom_offset_crosstalk_corrected"] - values = [prop_ for prop in extra_properties_list if isinstance(prop_ := view_prop.get(prop, "MISSING"), str)] - if isinstance(key, str): - output[key] = values - elif isinstance(key, list): - for k in key: - if isinstance(k, str): - output[k] = values - return output diff --git a/examples/windmill/_api/sensor_position_query.py b/examples/windmill/_api/sensor_position_query.py deleted file mode 100644 index f5c04d93e..000000000 --- a/examples/windmill/_api/sensor_position_query.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import TYPE_CHECKING, cast - -from cognite.client import data_modeling as dm, CogniteClient - -from windmill.data_classes import ( - DomainModelCore, - SensorPosition, -) -from windmill._api._core import ( - DEFAULT_QUERY_LIMIT, - EdgeQueryStep, - NodeQueryStep, - DataClassQueryBuilder, - QueryAPI, - T_DomainModelList, - _create_edge_filter, -) - - -class SensorPositionQueryAPI(QueryAPI[T_DomainModelList]): - _view_id = dm.ViewId("power-models", "SensorPosition", "1") - - def __init__( - self, - client: CogniteClient, - builder: DataClassQueryBuilder[T_DomainModelList], - filter_: dm.filters.Filter | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - ): - super().__init__(client, builder) - from_ = self._builder.get_from() - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - filter=filter_, - ), - result_cls=SensorPosition, - max_retrieve_limit=limit, - ) - ) - - def query( - self, - ) -> T_DomainModelList: - """Execute query and return the result. - - Returns: - The list of the source nodes of the query. - - """ - return self._query() diff --git a/examples/windmill/_api/windmill.py b/examples/windmill/_api/windmill.py deleted file mode 100644 index 03f6653b8..000000000 --- a/examples/windmill/_api/windmill.py +++ /dev/null @@ -1,832 +0,0 @@ -from __future__ import annotations - -from collections.abc import Sequence -from typing import overload, Literal -import warnings - -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes.data_modeling.instances import InstanceAggregationResultList, InstanceSort - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - NodeQueryStep, - EdgeQueryStep, - DataClassQueryBuilder, -) -from windmill.data_classes import ( - DomainModelCore, - DomainModelWrite, - ResourcesWriteResult, - Windmill, - WindmillWrite, - WindmillFields, - WindmillList, - WindmillWriteList, - WindmillTextFields, - Blade, - Metmast, - Nacelle, - Rotor, -) -from windmill.data_classes._windmill import ( - WindmillQuery, - _WINDMILL_PROPERTIES_BY_FIELD, - _create_windmill_filter, -) -from windmill._api._core import ( - DEFAULT_LIMIT_READ, - Aggregations, - NodeAPI, - SequenceNotStr, -) -from windmill._api.windmill_blades import WindmillBladesAPI -from windmill._api.windmill_metmast import WindmillMetmastAPI -from windmill._api.windmill_query import WindmillQueryAPI - - -class WindmillAPI(NodeAPI[Windmill, WindmillWrite, WindmillList, WindmillWriteList]): - _view_id = dm.ViewId("power-models", "Windmill", "1") - _properties_by_field = _WINDMILL_PROPERTIES_BY_FIELD - _class_type = Windmill - _class_list = WindmillList - _class_write_list = WindmillWriteList - - def __init__(self, client: CogniteClient): - super().__init__(client=client) - - self.blades_edge = WindmillBladesAPI(client) - self.metmast_edge = WindmillMetmastAPI(client) - - def __call__( - self, - min_capacity: float | None = None, - max_capacity: float | None = None, - nacelle: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - rotor: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - windfarm: str | list[str] | None = None, - windfarm_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - filter: dm.Filter | None = None, - ) -> WindmillQueryAPI[WindmillList]: - """Query starting at windmills. - - Args: - min_capacity: The minimum value of the capacity to filter on. - max_capacity: The maximum value of the capacity to filter on. - nacelle: The nacelle to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - rotor: The rotor to filter on. - windfarm: The windfarm to filter on. - windfarm_prefix: The prefix of the windfarm to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of windmills to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - A query API for windmills. - - """ - has_data = dm.filters.HasData(views=[self._view_id]) - filter_ = _create_windmill_filter( - self._view_id, - min_capacity, - max_capacity, - nacelle, - name, - name_prefix, - rotor, - windfarm, - windfarm_prefix, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - builder = DataClassQueryBuilder(WindmillList) - return WindmillQueryAPI(self._client, builder, filter_, limit) - - def apply( - self, - windmill: WindmillWrite | Sequence[WindmillWrite], - replace: bool = False, - write_none: bool = False, - ) -> ResourcesWriteResult: - """Add or update (upsert) windmills. - - Note: This method iterates through all nodes and timeseries linked to windmill and creates them including the edges - between the nodes. For example, if any of `blades`, `metmast`, `nacelle` or `rotor` are set, then these - nodes as well as any nodes linked to them, and all the edges linking these nodes will be created. - - Args: - windmill: Windmill or sequence of windmills to upsert. - replace (bool): How do we behave when a property value exists? Do we replace all matching and existing values with the supplied values (true)? - Or should we merge in new values for properties together with the existing values (false)? Note: This setting applies for all nodes or edges specified in the ingestion call. - write_none (bool): This method, will by default, skip properties that are set to None. However, if you want to set properties to None, - you can set this parameter to True. Note this only applies to properties that are nullable. - Returns: - Created instance(s), i.e., nodes, edges, and time series. - - Examples: - - Create a new windmill: - - >>> from windmill import WindmillClient - >>> from windmill.data_classes import WindmillWrite - >>> client = WindmillClient() - >>> windmill = WindmillWrite(external_id="my_windmill", ...) - >>> result = client.windmill.apply(windmill) - - """ - warnings.warn( - "The .apply method is deprecated and will be removed in v1.0. " - "Please use the .upsert method on the client instead. This means instead of " - "`my_client.windmill.apply(my_items)` please use `my_client.upsert(my_items)`." - "The motivation is that all apply methods are the same, and having one apply method per API " - " class encourages users to create items in small batches, which is inefficient." - "In addition, .upsert method is more descriptive of what the method does.", - UserWarning, - stacklevel=2, - ) - return self._apply(windmill, replace, write_none) - - def delete( - self, external_id: str | SequenceNotStr[str], space: str = DEFAULT_INSTANCE_SPACE - ) -> dm.InstancesDeleteResult: - """Delete one or more windmill. - - Args: - external_id: External id of the windmill to delete. - space: The space where all the windmill are located. - - Returns: - The instance(s), i.e., nodes and edges which has been deleted. Empty list if nothing was deleted. - - Examples: - - Delete windmill by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> client.windmill.delete("my_windmill") - """ - warnings.warn( - "The .delete method is deprecated and will be removed in v1.0. " - "Please use the .delete method on the client instead. This means instead of " - "`my_client.windmill.delete(my_ids)` please use `my_client.delete(my_ids)`." - "The motivation is that all delete methods are the same, and having one delete method per API " - " class encourages users to delete items in small batches, which is inefficient.", - UserWarning, - stacklevel=2, - ) - return self._delete(external_id, space) - - @overload - def retrieve( - self, external_id: str | dm.NodeId | tuple[str, str], space: str = DEFAULT_INSTANCE_SPACE - ) -> Windmill | None: ... - - @overload - def retrieve( - self, external_id: SequenceNotStr[str | dm.NodeId | tuple[str, str]], space: str = DEFAULT_INSTANCE_SPACE - ) -> WindmillList: ... - - def retrieve( - self, - external_id: str | dm.NodeId | tuple[str, str] | SequenceNotStr[str | dm.NodeId | tuple[str, str]], - space: str = DEFAULT_INSTANCE_SPACE, - ) -> Windmill | WindmillList | None: - """Retrieve one or more windmills by id(s). - - Args: - external_id: External id or list of external ids of the windmills. - space: The space where all the windmills are located. - - Returns: - The requested windmills. - - Examples: - - Retrieve windmill by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> windmill = client.windmill.retrieve("my_windmill") - - """ - return self._retrieve( - external_id, - space, - retrieve_edges=True, - edge_api_name_type_direction_view_id_penta=[ - ( - self.blades_edge, - "blades", - dm.DirectRelationReference("power-models", "Windmill.blades"), - "outwards", - dm.ViewId("power-models", "Blade", "1"), - ), - ( - self.metmast_edge, - "metmast", - dm.DirectRelationReference("power-models", "Windmill.metmast"), - "outwards", - dm.ViewId("power-models", "Metmast", "1"), - ), - ], - ) - - def search( - self, - query: str, - properties: WindmillTextFields | SequenceNotStr[WindmillTextFields] | None = None, - min_capacity: float | None = None, - max_capacity: float | None = None, - nacelle: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - rotor: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - windfarm: str | list[str] | None = None, - windfarm_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: WindmillFields | SequenceNotStr[WindmillFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> WindmillList: - """Search windmills - - Args: - query: The search query, - properties: The property to search, if nothing is passed all text fields will be searched. - min_capacity: The minimum value of the capacity to filter on. - max_capacity: The maximum value of the capacity to filter on. - nacelle: The nacelle to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - rotor: The rotor to filter on. - windfarm: The windfarm to filter on. - windfarm_prefix: The prefix of the windfarm to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of windmills to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - - Returns: - Search results windmills matching the query. - - Examples: - - Search for 'my_windmill' in all text properties: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> windmills = client.windmill.search('my_windmill') - - """ - filter_ = _create_windmill_filter( - self._view_id, - min_capacity, - max_capacity, - nacelle, - name, - name_prefix, - rotor, - windfarm, - windfarm_prefix, - external_id_prefix, - space, - filter, - ) - return self._search( - query=query, - properties=properties, - filter_=filter_, - limit=limit, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) - - @overload - def aggregate( - self, - aggregate: Aggregations | dm.aggregations.MetricAggregation, - group_by: None = None, - property: WindmillFields | SequenceNotStr[WindmillFields] | None = None, - query: str | None = None, - search_property: WindmillTextFields | SequenceNotStr[WindmillTextFields] | None = None, - min_capacity: float | None = None, - max_capacity: float | None = None, - nacelle: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - rotor: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - windfarm: str | list[str] | None = None, - windfarm_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.AggregatedNumberedValue: ... - - @overload - def aggregate( - self, - aggregate: SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation], - group_by: None = None, - property: WindmillFields | SequenceNotStr[WindmillFields] | None = None, - query: str | None = None, - search_property: WindmillTextFields | SequenceNotStr[WindmillTextFields] | None = None, - min_capacity: float | None = None, - max_capacity: float | None = None, - nacelle: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - rotor: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - windfarm: str | list[str] | None = None, - windfarm_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> list[dm.aggregations.AggregatedNumberedValue]: ... - - @overload - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: WindmillFields | SequenceNotStr[WindmillFields], - property: WindmillFields | SequenceNotStr[WindmillFields] | None = None, - query: str | None = None, - search_property: WindmillTextFields | SequenceNotStr[WindmillTextFields] | None = None, - min_capacity: float | None = None, - max_capacity: float | None = None, - nacelle: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - rotor: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - windfarm: str | list[str] | None = None, - windfarm_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> InstanceAggregationResultList: ... - - def aggregate( - self, - aggregate: ( - Aggregations - | dm.aggregations.MetricAggregation - | SequenceNotStr[Aggregations | dm.aggregations.MetricAggregation] - ), - group_by: WindmillFields | SequenceNotStr[WindmillFields] | None = None, - property: WindmillFields | SequenceNotStr[WindmillFields] | None = None, - query: str | None = None, - search_property: WindmillTextFields | SequenceNotStr[WindmillTextFields] | None = None, - min_capacity: float | None = None, - max_capacity: float | None = None, - nacelle: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - rotor: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - windfarm: str | list[str] | None = None, - windfarm_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> ( - dm.aggregations.AggregatedNumberedValue - | list[dm.aggregations.AggregatedNumberedValue] - | InstanceAggregationResultList - ): - """Aggregate data across windmills - - Args: - aggregate: The aggregation to perform. - group_by: The property to group by when doing the aggregation. - property: The property to perform aggregation on. - query: The query to search for in the text field. - search_property: The text field to search in. - min_capacity: The minimum value of the capacity to filter on. - max_capacity: The maximum value of the capacity to filter on. - nacelle: The nacelle to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - rotor: The rotor to filter on. - windfarm: The windfarm to filter on. - windfarm_prefix: The prefix of the windfarm to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of windmills to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Aggregation results. - - Examples: - - Count windmills in space `my_space`: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> result = client.windmill.aggregate("count", space="my_space") - - """ - - filter_ = _create_windmill_filter( - self._view_id, - min_capacity, - max_capacity, - nacelle, - name, - name_prefix, - rotor, - windfarm, - windfarm_prefix, - external_id_prefix, - space, - filter, - ) - return self._aggregate( - aggregate=aggregate, - group_by=group_by, # type: ignore[arg-type] - properties=property, # type: ignore[arg-type] - query=query, - search_properties=search_property, # type: ignore[arg-type] - limit=limit, - filter=filter_, - ) - - def histogram( - self, - property: WindmillFields, - interval: float, - query: str | None = None, - search_property: WindmillTextFields | SequenceNotStr[WindmillTextFields] | None = None, - min_capacity: float | None = None, - max_capacity: float | None = None, - nacelle: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - rotor: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - windfarm: str | list[str] | None = None, - windfarm_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - ) -> dm.aggregations.HistogramValue: - """Produces histograms for windmills - - Args: - property: The property to use as the value in the histogram. - interval: The interval to use for the histogram bins. - query: The query to search for in the text field. - search_property: The text field to search in. - min_capacity: The minimum value of the capacity to filter on. - max_capacity: The maximum value of the capacity to filter on. - nacelle: The nacelle to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - rotor: The rotor to filter on. - windfarm: The windfarm to filter on. - windfarm_prefix: The prefix of the windfarm to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of windmills to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - - Returns: - Bucketed histogram results. - - """ - filter_ = _create_windmill_filter( - self._view_id, - min_capacity, - max_capacity, - nacelle, - name, - name_prefix, - rotor, - windfarm, - windfarm_prefix, - external_id_prefix, - space, - filter, - ) - return self._histogram( - property, - interval, - query, - search_property, # type: ignore[arg-type] - limit, - filter_, - ) - - def query(self) -> WindmillQuery: - """Start a query for windmills.""" - warnings.warn("This method is renamed to .select", UserWarning, stacklevel=2) - return WindmillQuery(self._client) - - def select(self) -> WindmillQuery: - """Start selecting from windmills.""" - warnings.warn( - "The .select is in alpha and is subject to breaking changes without notice.", UserWarning, stacklevel=2 - ) - return WindmillQuery(self._client) - - def list( - self, - min_capacity: float | None = None, - max_capacity: float | None = None, - nacelle: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - rotor: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - windfarm: str | list[str] | None = None, - windfarm_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit: int = DEFAULT_LIMIT_READ, - filter: dm.Filter | None = None, - sort_by: WindmillFields | Sequence[WindmillFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - retrieve_connections: Literal["skip", "identifier", "full"] = "skip", - ) -> WindmillList: - """List/filter windmills - - Args: - min_capacity: The minimum value of the capacity to filter on. - max_capacity: The maximum value of the capacity to filter on. - nacelle: The nacelle to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - rotor: The rotor to filter on. - windfarm: The windfarm to filter on. - windfarm_prefix: The prefix of the windfarm to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of windmills to return. Defaults to 25. Set to -1, float("inf") or None to return all items. - filter: (Advanced) If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - sort_by: The property to sort by. - direction: The direction to sort by, either 'ascending' or 'descending'. - sort: (Advanced) If sort_by and direction are not sufficient, you can write your own sorting. - This will override the sort_by and direction. This allowos you to sort by multiple fields and - specify the direction for each field as well as how to handle null values. - retrieve_connections: Whether to retrieve `blades`, `metmast`, `nacelle` and `rotor` for the windmills. Defaults to 'skip'. - 'skip' will not retrieve any connections, 'identifier' will only retrieve the identifier of the connected items, and 'full' will retrieve the full connected items. - - Returns: - List of requested windmills - - Examples: - - List windmills and limit to 5: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> windmills = client.windmill.list(limit=5) - - """ - filter_ = _create_windmill_filter( - self._view_id, - min_capacity, - max_capacity, - nacelle, - name, - name_prefix, - rotor, - windfarm, - windfarm_prefix, - external_id_prefix, - space, - filter, - ) - - if retrieve_connections == "skip": - return self._list( - limit=limit, - filter=filter_, - sort_by=sort_by, # type: ignore[arg-type] - direction=direction, - sort=sort, - ) - - builder = DataClassQueryBuilder(WindmillList) - has_data = dm.filters.HasData(views=[self._view_id]) - builder.append( - NodeQueryStep( - builder.create_name(None), - dm.query.NodeResultSetExpression( - filter=dm.filters.And(filter_, has_data) if filter_ else has_data, - sort=self._create_sort(sort_by, direction, sort), # type: ignore[arg-type] - ), - Windmill, - max_retrieve_limit=limit, - raw_filter=filter_, - ) - ) - from_root = builder.get_from() - edge_blades = builder.create_name(from_root) - builder.append( - EdgeQueryStep( - edge_blades, - dm.query.EdgeResultSetExpression( - from_=from_root, - direction="outwards", - chain_to="destination", - ), - ) - ) - edge_metmast = builder.create_name(from_root) - builder.append( - EdgeQueryStep( - edge_metmast, - dm.query.EdgeResultSetExpression( - from_=from_root, - direction="outwards", - chain_to="destination", - ), - ) - ) - if retrieve_connections == "full": - builder.append( - NodeQueryStep( - builder.create_name(edge_blades), - dm.query.NodeResultSetExpression( - from_=edge_blades, - filter=dm.filters.HasData(views=[Blade._view_id]), - ), - Blade, - ) - ) - builder.append( - NodeQueryStep( - builder.create_name(edge_metmast), - dm.query.NodeResultSetExpression( - from_=edge_metmast, - filter=dm.filters.HasData(views=[Metmast._view_id]), - ), - Metmast, - ) - ) - builder.append( - NodeQueryStep( - builder.create_name(from_root), - dm.query.NodeResultSetExpression( - from_=from_root, - filter=dm.filters.HasData(views=[Nacelle._view_id]), - direction="outwards", - through=self._view_id.as_property_ref("nacelle"), - ), - Nacelle, - ) - ) - builder.append( - NodeQueryStep( - builder.create_name(from_root), - dm.query.NodeResultSetExpression( - from_=from_root, - filter=dm.filters.HasData(views=[Rotor._view_id]), - direction="outwards", - through=self._view_id.as_property_ref("rotor"), - ), - Rotor, - ) - ) - # We know that that all nodes are connected as it is not possible to filter on connections - builder.execute_query(self._client, remove_not_connected=False) - return builder.unpack() diff --git a/examples/windmill/_api/windmill_blades.py b/examples/windmill/_api/windmill_blades.py deleted file mode 100644 index 5e1b8cea7..000000000 --- a/examples/windmill/_api/windmill_blades.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import annotations - - -from cognite.client import data_modeling as dm - -from windmill._api._core import DEFAULT_LIMIT_READ, EdgeAPI, _create_edge_filter -from windmill.data_classes._core import DEFAULT_INSTANCE_SPACE - - -class WindmillBladesAPI(EdgeAPI): - def list( - self, - from_windmill: str | list[str] | dm.NodeId | list[dm.NodeId] | None = None, - from_windmill_space: str = DEFAULT_INSTANCE_SPACE, - to_blade: str | list[str] | dm.NodeId | list[dm.NodeId] | None = None, - to_blade_space: str = DEFAULT_INSTANCE_SPACE, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit=DEFAULT_LIMIT_READ, - ) -> dm.EdgeList: - """List blade edges of a windmill. - - Args: - from_windmill: ID of the source windmill. - from_windmill_space: Location of the windmills. - to_blade: ID of the target blade. - to_blade_space: Location of the blades. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of blade edges to return. Defaults to 25. Set to -1, float("inf") or None - to return all items. - - Returns: - The requested blade edges. - - Examples: - - List 5 blade edges connected to "my_windmill": - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> windmill = client.windmill.blades_edge.list("my_windmill", limit=5) - - """ - filter_ = _create_edge_filter( - dm.DirectRelationReference("power-models", "Windmill.blades"), - from_windmill, - from_windmill_space, - to_blade, - to_blade_space, - external_id_prefix, - space, - ) - return self._list(filter_=filter_, limit=limit) diff --git a/examples/windmill/_api/windmill_metmast.py b/examples/windmill/_api/windmill_metmast.py deleted file mode 100644 index 4f4a8593a..000000000 --- a/examples/windmill/_api/windmill_metmast.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import annotations - - -from cognite.client import data_modeling as dm - -from windmill._api._core import DEFAULT_LIMIT_READ, EdgeAPI, _create_edge_filter -from windmill.data_classes._core import DEFAULT_INSTANCE_SPACE - - -class WindmillMetmastAPI(EdgeAPI): - def list( - self, - from_windmill: str | list[str] | dm.NodeId | list[dm.NodeId] | None = None, - from_windmill_space: str = DEFAULT_INSTANCE_SPACE, - to_metmast: str | list[str] | dm.NodeId | list[dm.NodeId] | None = None, - to_metmast_space: str = DEFAULT_INSTANCE_SPACE, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit=DEFAULT_LIMIT_READ, - ) -> dm.EdgeList: - """List metmast edges of a windmill. - - Args: - from_windmill: ID of the source windmill. - from_windmill_space: Location of the windmills. - to_metmast: ID of the target metmast. - to_metmast_space: Location of the metmasts. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - limit: Maximum number of metmast edges to return. Defaults to 25. Set to -1, float("inf") or None - to return all items. - - Returns: - The requested metmast edges. - - Examples: - - List 5 metmast edges connected to "my_windmill": - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> windmill = client.windmill.metmast_edge.list("my_windmill", limit=5) - - """ - filter_ = _create_edge_filter( - dm.DirectRelationReference("power-models", "Windmill.metmast"), - from_windmill, - from_windmill_space, - to_metmast, - to_metmast_space, - external_id_prefix, - space, - ) - return self._list(filter_=filter_, limit=limit) diff --git a/examples/windmill/_api/windmill_query.py b/examples/windmill/_api/windmill_query.py deleted file mode 100644 index 6e4bc84ec..000000000 --- a/examples/windmill/_api/windmill_query.py +++ /dev/null @@ -1,249 +0,0 @@ -from __future__ import annotations - -import datetime -from collections.abc import Sequence -from typing import TYPE_CHECKING, cast - -from cognite.client import data_modeling as dm, CogniteClient - -from windmill.data_classes import ( - DomainModelCore, - Windmill, - Nacelle, - Rotor, -) -from windmill.data_classes._blade import ( - Blade, - _create_blade_filter, -) -from windmill.data_classes._metmast import ( - Metmast, - _create_metmast_filter, -) -from windmill._api._core import ( - DEFAULT_QUERY_LIMIT, - EdgeQueryStep, - NodeQueryStep, - DataClassQueryBuilder, - QueryAPI, - T_DomainModelList, - _create_edge_filter, -) - -if TYPE_CHECKING: - from windmill._api.blade_query import BladeQueryAPI - from windmill._api.metmast_query import MetmastQueryAPI - - -class WindmillQueryAPI(QueryAPI[T_DomainModelList]): - _view_id = dm.ViewId("power-models", "Windmill", "1") - - def __init__( - self, - client: CogniteClient, - builder: DataClassQueryBuilder[T_DomainModelList], - filter_: dm.filters.Filter | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - ): - super().__init__(client, builder) - from_ = self._builder.get_from() - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - filter=filter_, - ), - result_cls=Windmill, - max_retrieve_limit=limit, - ) - ) - - def blades( - self, - is_damaged: bool | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - external_id_prefix_edge: str | None = None, - space_edge: str | list[str] | None = None, - filter: dm.Filter | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - retrieve_nacelle: bool = False, - retrieve_rotor: bool = False, - ) -> BladeQueryAPI[T_DomainModelList]: - """Query along the blade edges of the windmill. - - Args: - is_damaged: The is damaged to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - external_id_prefix_edge: The prefix of the external ID to filter on. - space_edge: The space to filter on. - filter: (Advanced) Filter applied to node. If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - limit: Maximum number of blade edges to return. Defaults to 3. Set to -1, float("inf") or None - to return all items. - retrieve_nacelle: Whether to retrieve the nacelle for each windmill or not. - retrieve_rotor: Whether to retrieve the rotor for each windmill or not. - - Returns: - BladeQueryAPI: The query API for the blade. - """ - from .blade_query import BladeQueryAPI - - # from is a string as we added a node query step in the __init__ method - from_ = cast(str, self._builder.get_from()) - edge_filter = _create_edge_filter( - dm.DirectRelationReference("power-models", "Windmill.blades"), - external_id_prefix=external_id_prefix_edge, - space=space_edge, - ) - self._builder.append( - EdgeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.EdgeResultSetExpression( - filter=edge_filter, - from_=from_, - direction="outwards", - ), - max_retrieve_limit=limit, - ) - ) - - view_id = BladeQueryAPI._view_id - has_data = dm.filters.HasData(views=[view_id]) - node_filer = _create_blade_filter( - view_id, - is_damaged, - name, - name_prefix, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - if retrieve_nacelle: - self._query_append_nacelle(from_) - if retrieve_rotor: - self._query_append_rotor(from_) - return BladeQueryAPI(self._client, self._builder, node_filer, limit) - - def metmast( - self, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - external_id_prefix_edge: str | None = None, - space_edge: str | list[str] | None = None, - filter: dm.Filter | None = None, - limit: int = DEFAULT_QUERY_LIMIT, - retrieve_nacelle: bool = False, - retrieve_rotor: bool = False, - ) -> MetmastQueryAPI[T_DomainModelList]: - """Query along the metmast edges of the windmill. - - Args: - min_position: The minimum value of the position to filter on. - max_position: The maximum value of the position to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - external_id_prefix_edge: The prefix of the external ID to filter on. - space_edge: The space to filter on. - filter: (Advanced) Filter applied to node. If the filtering available in the above is not sufficient, you can write your own filtering which will be ANDed with the filter above. - limit: Maximum number of metmast edges to return. Defaults to 3. Set to -1, float("inf") or None - to return all items. - retrieve_nacelle: Whether to retrieve the nacelle for each windmill or not. - retrieve_rotor: Whether to retrieve the rotor for each windmill or not. - - Returns: - MetmastQueryAPI: The query API for the metmast. - """ - from .metmast_query import MetmastQueryAPI - - # from is a string as we added a node query step in the __init__ method - from_ = cast(str, self._builder.get_from()) - edge_filter = _create_edge_filter( - dm.DirectRelationReference("power-models", "Windmill.metmast"), - external_id_prefix=external_id_prefix_edge, - space=space_edge, - ) - self._builder.append( - EdgeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.EdgeResultSetExpression( - filter=edge_filter, - from_=from_, - direction="outwards", - ), - max_retrieve_limit=limit, - ) - ) - - view_id = MetmastQueryAPI._view_id - has_data = dm.filters.HasData(views=[view_id]) - node_filer = _create_metmast_filter( - view_id, - min_position, - max_position, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - if retrieve_nacelle: - self._query_append_nacelle(from_) - if retrieve_rotor: - self._query_append_rotor(from_) - return MetmastQueryAPI(self._client, self._builder, node_filer, limit) - - def query( - self, - retrieve_nacelle: bool = False, - retrieve_rotor: bool = False, - ) -> T_DomainModelList: - """Execute query and return the result. - - Args: - retrieve_nacelle: Whether to retrieve the nacelle for each windmill or not. - retrieve_rotor: Whether to retrieve the rotor for each windmill or not. - - Returns: - The list of the source nodes of the query. - - """ - from_ = self._builder[-1].name - if retrieve_nacelle: - self._query_append_nacelle(from_) - if retrieve_rotor: - self._query_append_rotor(from_) - return self._query() - - def _query_append_nacelle(self, from_: str) -> None: - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - through=self._view_id.as_property_ref("nacelle"), - direction="outwards", - filter=dm.filters.HasData(views=[Nacelle._view_id]), - ), - result_cls=Nacelle, - ), - ) - - def _query_append_rotor(self, from_: str) -> None: - self._builder.append( - NodeQueryStep( - name=self._builder.create_name(from_), - expression=dm.query.NodeResultSetExpression( - from_=from_, - through=self._view_id.as_property_ref("rotor"), - direction="outwards", - filter=dm.filters.HasData(views=[Rotor._view_id]), - ), - result_cls=Rotor, - ), - ) diff --git a/examples/windmill/_api_client.py b/examples/windmill/_api_client.py deleted file mode 100644 index 350a22866..000000000 --- a/examples/windmill/_api_client.py +++ /dev/null @@ -1,276 +0,0 @@ -from __future__ import annotations - -import warnings -from pathlib import Path -from typing import Any, Sequence - -from cognite.client import ClientConfig, CogniteClient, data_modeling as dm -from cognite.client.data_classes import TimeSeriesList, FileMetadataList, SequenceList -from cognite.client.credentials import OAuthClientCredentials - -from windmill._api import ( - BladeAPI, - GearboxAPI, - GeneratorAPI, - HighSpeedShaftAPI, - MainShaftAPI, - MetmastAPI, - NacelleAPI, - PowerInverterAPI, - RotorAPI, - SensorPositionAPI, - WindmillAPI, -) -from windmill._api._core import SequenceNotStr, GraphQLQueryResponse -from windmill.data_classes._core import DEFAULT_INSTANCE_SPACE, GraphQLList -from windmill import data_classes - - -class WindmillClient: - """ - WindmillClient - - Generated with: - pygen = 0.99.49 - cognite-sdk = 7.66.0 - pydantic = 2.9.2 - - Data Model: - space: power-models - externalId: Windmill - version: 1 - """ - - 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)}") - # The client name is used for aggregated logging of Pygen Usage - client.config.client_name = "CognitePygen:0.99.49" - - self._client = client - - self.blade = BladeAPI(client) - self.gearbox = GearboxAPI(client) - self.generator = GeneratorAPI(client) - self.high_speed_shaft = HighSpeedShaftAPI(client) - self.main_shaft = MainShaftAPI(client) - self.metmast = MetmastAPI(client) - self.nacelle = NacelleAPI(client) - self.power_inverter = PowerInverterAPI(client) - self.rotor = RotorAPI(client) - self.sensor_position = SensorPositionAPI(client) - self.windmill = WindmillAPI(client) - - def upsert( - self, - items: data_classes.DomainModelWrite | Sequence[data_classes.DomainModelWrite], - replace: bool = False, - write_none: bool = False, - allow_version_increase: bool = False, - ) -> data_classes.ResourcesWriteResult: - """Add or update (upsert) items. - - This method will create the nodes, edges, timeseries, files and sequences of the supplied items. - - Args: - items: One or more instances of the pygen generated data classes. - replace (bool): How do we behave when a property value exists? Do we replace all matching and existing values with the supplied values (true)? - Or should we merge in new values for properties together with the existing values (false)? Note: This setting applies for all nodes or edges specified in the ingestion call. - write_none (bool): This method will, by default, skip properties that are set to None. However, if you want to set properties to None, - you can set this parameter to True. Note this only applies to properties that are nullable. - allow_version_increase (bool): If set to true, the version of the instance will be increased if the instance already exists. - If you get an error: 'A version conflict caused the ingest to fail', you can set this to true to allow - the version to increase. - Returns: - Created instance(s), i.e., nodes, edges, and time series. - - """ - instances = self._create_instances(items, write_none, allow_version_increase) - result = self._client.data_modeling.instances.apply( - nodes=instances.nodes, - edges=instances.edges, - auto_create_start_nodes=True, - auto_create_end_nodes=True, - replace=replace, - ) - time_series = TimeSeriesList([]) - if instances.time_series: - time_series = self._client.time_series.upsert(instances.time_series, mode="patch") - files = FileMetadataList([]) - if instances.files: - for file in instances.files: - created, _ = self._client.files.create(file, overwrite=True) - files.append(created) - - sequences = SequenceList([]) - if instances.sequences: - sequences = self._client.sequences.upsert(instances.sequences, mode="patch") - - return data_classes.ResourcesWriteResult(result.nodes, result.edges, time_series, files, sequences) - - def _create_instances( - self, - items: data_classes.DomainModelWrite | Sequence[data_classes.DomainModelWrite], - write_none: bool, - allow_version_increase: bool, - ) -> data_classes.ResourcesWrite: - if isinstance(items, data_classes.DomainModelWrite): - instances = items.to_instances_write(write_none, allow_version_increase) - else: - instances = data_classes.ResourcesWrite() - cache: set[tuple[str, str]] = set() - for item in items: - instances.extend( - item._to_instances_write( - cache, - write_none, - allow_version_increase, - ) - ) - return instances - - def apply( - self, - items: data_classes.DomainModelWrite | Sequence[data_classes.DomainModelWrite], - replace: bool = False, - write_none: bool = False, - ) -> data_classes.ResourcesWriteResult: - """[DEPRECATED] Add or update (upsert) items. - - Args: - items: One or more instances of the pygen generated data classes. - replace (bool): How do we behave when a property value exists? Do we replace all matching and existing values with the supplied values (true)? - Or should we merge in new values for properties together with the existing values (false)? Note: This setting applies for all nodes or edges specified in the ingestion call. - write_none (bool): This method will, by default, skip properties that are set to None. However, if you want to set properties to None, - you can set this parameter to True. Note this only applies to properties that are nullable. - Returns: - Created instance(s), i.e., nodes, edges, and time series. - - """ - warnings.warn( - "The .apply method is deprecated and will be removed in v1.0. " - "Please use the .upsert method on the instead." - "The motivation is that .upsert is a more descriptive name for the operation.", - UserWarning, - stacklevel=2, - ) - return self.upsert(items, replace, write_none) - - def delete( - self, - external_id: ( - str - | dm.NodeId - | data_classes.DomainModelWrite - | SequenceNotStr[str | dm.NodeId | data_classes.DomainModelWrite] - ), - space: str = DEFAULT_INSTANCE_SPACE, - ) -> dm.InstancesDeleteResult: - """Delete one or more items. - - If you pass in an item, it will be deleted recursively, i.e., all connected nodes and edges - will be deleted as well. - - Args: - external_id: The external id or items(s) to delete. Can also be a list of NodeId(s) or DomainModelWrite(s). - space: The space where all the item(s) are located. - - Returns: - The instance(s), i.e., nodes and edges which has been deleted. Empty list if nothing was deleted. - - Examples: - - Delete item by id: - - >>> from windmill import WindmillClient - >>> client = WindmillClient() - >>> client.delete("my_node_external_id") - """ - if isinstance(external_id, str): - return self._client.data_modeling.instances.delete(nodes=(space, external_id)) - elif isinstance(external_id, dm.NodeId): - return self._client.data_modeling.instances.delete(nodes=external_id) - elif isinstance(external_id, data_classes.DomainModelWrite): - resources = self._create_instances(external_id, False, False) - return self._client.data_modeling.instances.delete( - nodes=resources.nodes.as_ids(), - edges=resources.edges.as_ids(), - ) - elif isinstance(external_id, Sequence): - node_ids: list[dm.NodeId] = [] - edge_ids: list[dm.EdgeId] = [] - for item in external_id: - if isinstance(item, str): - node_ids.append(dm.NodeId(space, item)) - elif isinstance(item, dm.NodeId): - node_ids.append(item) - elif isinstance(item, data_classes.DomainModelWrite): - resources = self._create_instances(item, False, False) - node_ids.extend(resources.nodes.as_ids()) - edge_ids.extend(resources.edges.as_ids()) - else: - raise ValueError( - f"Expected str, NodeId, or DomainModelWrite, Sequence of these types. Got {type(external_id)}" - ) - return self._client.data_modeling.instances.delete(nodes=node_ids, edges=edge_ids) - else: - raise ValueError( - f"Expected str, NodeId, or DomainModelWrite, Sequence of these types. Got {type(external_id)}" - ) - - def graphql_query(self, query: str, variables: dict[str, Any] | None = None) -> GraphQLList: - """Execute a GraphQl query against the Windmill data model. - - Args: - query (str): The GraphQL query to issue. - variables (dict[str, Any] | None): An optional dict of variables to pass to the query. - """ - data_model_id = dm.DataModelId("power-models", "Windmill", "1") - result = self._client.data_modeling.graphql.query(data_model_id, query, variables) - return GraphQLQueryResponse(data_model_id).parse(result) - - @classmethod - def azure_project( - cls, tenant_id: str, client_id: str, client_secret: str, cdf_cluster: str, project: str - ) -> WindmillClient: - credentials = OAuthClientCredentials.default_for_azure_ad(tenant_id, client_id, client_secret, cdf_cluster) - config = ClientConfig.default(project, cdf_cluster, credentials) - - return cls(config) - - @classmethod - def from_toml(cls, file_path: Path | str, section: str | None = "cognite") -> WindmillClient: - import toml - - toml_content = toml.load(file_path) - if section is not None: - try: - toml_content = toml_content[section] - except KeyError as e: - raise ValueError(f"Could not find section '{section}' in {file_path}") from e - - return cls.azure_project(**toml_content) - - def _repr_html_(self) -> str: - return """WindmillClient generated from data model ("power-models", "Windmill", "1")
-with the following APIs available
-    .blade
-    .gearbox
-    .generator
-    .high_speed_shaft
-    .main_shaft
-    .metmast
-    .nacelle
-    .power_inverter
-    .rotor
-    .sensor_position
-    .windmill
-
-and with the methods:
-    .upsert - Create or update any instance.
-    .delete - Delete instances.
-""" diff --git a/examples/windmill/data_classes/__init__.py b/examples/windmill/data_classes/__init__.py deleted file mode 100644 index b477eaefc..000000000 --- a/examples/windmill/data_classes/__init__.py +++ /dev/null @@ -1,305 +0,0 @@ -from windmill.data_classes._core import ( - DataRecord, - DataRecordGraphQL, - DataRecordWrite, - DomainModel, - DomainModelCore, - DomainModelWrite, - DomainModelList, - DomainRelationWrite, - GraphQLCore, - GraphQLList, - ResourcesWrite, - ResourcesWriteResult, - PageInfo, - TimeSeriesGraphQL, - FileMetadataGraphQL, - SequenceColumnGraphQL, - SequenceGraphQL, -) -from ._blade import ( - Blade, - BladeApply, - BladeApplyList, - BladeFields, - BladeGraphQL, - BladeList, - BladeTextFields, - BladeWrite, - BladeWriteList, -) -from ._gearbox import ( - Gearbox, - GearboxApply, - GearboxApplyList, - GearboxFields, - GearboxGraphQL, - GearboxList, - GearboxTextFields, - GearboxWrite, - GearboxWriteList, -) -from ._generator import ( - Generator, - GeneratorApply, - GeneratorApplyList, - GeneratorFields, - GeneratorGraphQL, - GeneratorList, - GeneratorTextFields, - GeneratorWrite, - GeneratorWriteList, -) -from ._high_speed_shaft import ( - HighSpeedShaft, - HighSpeedShaftApply, - HighSpeedShaftApplyList, - HighSpeedShaftFields, - HighSpeedShaftGraphQL, - HighSpeedShaftList, - HighSpeedShaftTextFields, - HighSpeedShaftWrite, - HighSpeedShaftWriteList, -) -from ._main_shaft import ( - MainShaft, - MainShaftApply, - MainShaftApplyList, - MainShaftFields, - MainShaftGraphQL, - MainShaftList, - MainShaftTextFields, - MainShaftWrite, - MainShaftWriteList, -) -from ._metmast import ( - Metmast, - MetmastApply, - MetmastApplyList, - MetmastFields, - MetmastGraphQL, - MetmastList, - MetmastTextFields, - MetmastWrite, - MetmastWriteList, -) -from ._nacelle import ( - Nacelle, - NacelleApply, - NacelleApplyList, - NacelleFields, - NacelleGraphQL, - NacelleList, - NacelleTextFields, - NacelleWrite, - NacelleWriteList, -) -from ._power_inverter import ( - PowerInverter, - PowerInverterApply, - PowerInverterApplyList, - PowerInverterFields, - PowerInverterGraphQL, - PowerInverterList, - PowerInverterTextFields, - PowerInverterWrite, - PowerInverterWriteList, -) -from ._rotor import ( - Rotor, - RotorApply, - RotorApplyList, - RotorFields, - RotorGraphQL, - RotorList, - RotorTextFields, - RotorWrite, - RotorWriteList, -) -from ._sensor_position import ( - SensorPosition, - SensorPositionApply, - SensorPositionApplyList, - SensorPositionFields, - SensorPositionGraphQL, - SensorPositionList, - SensorPositionTextFields, - SensorPositionWrite, - SensorPositionWriteList, -) -from ._windmill import ( - Windmill, - WindmillApply, - WindmillApplyList, - WindmillFields, - WindmillGraphQL, - WindmillList, - WindmillTextFields, - WindmillWrite, - WindmillWriteList, -) - -Blade.model_rebuild() -BladeGraphQL.model_rebuild() -BladeWrite.model_rebuild() -BladeApply.model_rebuild() -Gearbox.model_rebuild() -GearboxGraphQL.model_rebuild() -GearboxWrite.model_rebuild() -GearboxApply.model_rebuild() -Generator.model_rebuild() -GeneratorGraphQL.model_rebuild() -GeneratorWrite.model_rebuild() -GeneratorApply.model_rebuild() -HighSpeedShaft.model_rebuild() -HighSpeedShaftGraphQL.model_rebuild() -HighSpeedShaftWrite.model_rebuild() -HighSpeedShaftApply.model_rebuild() -MainShaft.model_rebuild() -MainShaftGraphQL.model_rebuild() -MainShaftWrite.model_rebuild() -MainShaftApply.model_rebuild() -Metmast.model_rebuild() -MetmastGraphQL.model_rebuild() -MetmastWrite.model_rebuild() -MetmastApply.model_rebuild() -Nacelle.model_rebuild() -NacelleGraphQL.model_rebuild() -NacelleWrite.model_rebuild() -NacelleApply.model_rebuild() -PowerInverter.model_rebuild() -PowerInverterGraphQL.model_rebuild() -PowerInverterWrite.model_rebuild() -PowerInverterApply.model_rebuild() -Rotor.model_rebuild() -RotorGraphQL.model_rebuild() -RotorWrite.model_rebuild() -RotorApply.model_rebuild() -SensorPosition.model_rebuild() -SensorPositionGraphQL.model_rebuild() -SensorPositionWrite.model_rebuild() -SensorPositionApply.model_rebuild() -Windmill.model_rebuild() -WindmillGraphQL.model_rebuild() -WindmillWrite.model_rebuild() -WindmillApply.model_rebuild() - - -__all__ = [ - "DataRecord", - "DataRecordGraphQL", - "DataRecordWrite", - "ResourcesWrite", - "DomainModel", - "DomainModelCore", - "DomainModelWrite", - "DomainModelList", - "DomainRelationWrite", - "GraphQLCore", - "GraphQLList", - "ResourcesWriteResult", - "PageInfo", - "TimeSeriesGraphQL", - "FileMetadataGraphQL", - "SequenceColumnGraphQL", - "SequenceGraphQL", - "Blade", - "BladeGraphQL", - "BladeWrite", - "BladeApply", - "BladeList", - "BladeWriteList", - "BladeApplyList", - "BladeFields", - "BladeTextFields", - "Gearbox", - "GearboxGraphQL", - "GearboxWrite", - "GearboxApply", - "GearboxList", - "GearboxWriteList", - "GearboxApplyList", - "GearboxFields", - "GearboxTextFields", - "Generator", - "GeneratorGraphQL", - "GeneratorWrite", - "GeneratorApply", - "GeneratorList", - "GeneratorWriteList", - "GeneratorApplyList", - "GeneratorFields", - "GeneratorTextFields", - "HighSpeedShaft", - "HighSpeedShaftGraphQL", - "HighSpeedShaftWrite", - "HighSpeedShaftApply", - "HighSpeedShaftList", - "HighSpeedShaftWriteList", - "HighSpeedShaftApplyList", - "HighSpeedShaftFields", - "HighSpeedShaftTextFields", - "MainShaft", - "MainShaftGraphQL", - "MainShaftWrite", - "MainShaftApply", - "MainShaftList", - "MainShaftWriteList", - "MainShaftApplyList", - "MainShaftFields", - "MainShaftTextFields", - "Metmast", - "MetmastGraphQL", - "MetmastWrite", - "MetmastApply", - "MetmastList", - "MetmastWriteList", - "MetmastApplyList", - "MetmastFields", - "MetmastTextFields", - "Nacelle", - "NacelleGraphQL", - "NacelleWrite", - "NacelleApply", - "NacelleList", - "NacelleWriteList", - "NacelleApplyList", - "NacelleFields", - "NacelleTextFields", - "PowerInverter", - "PowerInverterGraphQL", - "PowerInverterWrite", - "PowerInverterApply", - "PowerInverterList", - "PowerInverterWriteList", - "PowerInverterApplyList", - "PowerInverterFields", - "PowerInverterTextFields", - "Rotor", - "RotorGraphQL", - "RotorWrite", - "RotorApply", - "RotorList", - "RotorWriteList", - "RotorApplyList", - "RotorFields", - "RotorTextFields", - "SensorPosition", - "SensorPositionGraphQL", - "SensorPositionWrite", - "SensorPositionApply", - "SensorPositionList", - "SensorPositionWriteList", - "SensorPositionApplyList", - "SensorPositionFields", - "SensorPositionTextFields", - "Windmill", - "WindmillGraphQL", - "WindmillWrite", - "WindmillApply", - "WindmillList", - "WindmillWriteList", - "WindmillApplyList", - "WindmillFields", - "WindmillTextFields", -] diff --git a/examples/windmill/data_classes/_blade.py b/examples/windmill/data_classes/_blade.py deleted file mode 100644 index c3b5aa821..000000000 --- a/examples/windmill/data_classes/_blade.py +++ /dev/null @@ -1,464 +0,0 @@ -from __future__ import annotations - -import warnings -from collections.abc import Sequence -from typing import TYPE_CHECKING, Any, ClassVar, Literal, no_type_check, Optional, Union - -from cognite.client import data_modeling as dm, CogniteClient -from pydantic import Field -from pydantic import field_validator, model_validator - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - DataRecord, - DataRecordGraphQL, - DataRecordWrite, - DomainModel, - DomainModelWrite, - DomainModelWriteList, - DomainModelList, - DomainRelation, - DomainRelationWrite, - GraphQLCore, - ResourcesWrite, - T_DomainModelList, - as_direct_relation_reference, - as_instance_dict_id, - as_node_id, - as_pygen_node_id, - are_nodes_equal, - is_tuple_id, - select_best_node, - QueryCore, - NodeQueryCore, - StringFilter, - BooleanFilter, -) - -if TYPE_CHECKING: - from windmill.data_classes._sensor_position import ( - SensorPosition, - SensorPositionList, - SensorPositionGraphQL, - SensorPositionWrite, - SensorPositionWriteList, - ) - - -__all__ = [ - "Blade", - "BladeWrite", - "BladeApply", - "BladeList", - "BladeWriteList", - "BladeApplyList", - "BladeFields", - "BladeTextFields", - "BladeGraphQL", -] - - -BladeTextFields = Literal["external_id", "name"] -BladeFields = Literal["external_id", "is_damaged", "name"] - -_BLADE_PROPERTIES_BY_FIELD = { - "external_id": "externalId", - "is_damaged": "is_damaged", - "name": "name", -} - - -class BladeGraphQL(GraphQLCore): - """This represents the reading version of blade, used - when data is retrieved from CDF using GraphQL. - - It is used when retrieving data from CDF using GraphQL. - - Args: - space: The space where the node is located. - external_id: The external id of the blade. - data_record: The data record of the blade node. - is_damaged: The is damaged field. - name: The name field. - sensor_positions: The sensor position field. - """ - - view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Blade", "1") - is_damaged: Optional[bool] = None - name: Optional[str] = None - sensor_positions: Optional[list[SensorPositionGraphQL]] = Field(default=None, repr=False) - - @model_validator(mode="before") - def parse_data_record(cls, values: Any) -> Any: - if not isinstance(values, dict): - return values - if "lastUpdatedTime" in values or "createdTime" in values: - values["dataRecord"] = DataRecordGraphQL( - created_time=values.pop("createdTime", None), - last_updated_time=values.pop("lastUpdatedTime", None), - ) - return values - - @field_validator("sensor_positions", mode="before") - def parse_graphql(cls, value: Any) -> Any: - if not isinstance(value, dict): - return value - if "items" in value: - return value["items"] - return value - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_read(self) -> Blade: - """Convert this GraphQL format of blade to the reading format.""" - if self.data_record is None: - raise ValueError("This object cannot be converted to a read format because it lacks a data record.") - return Blade( - space=self.space, - external_id=self.external_id, - data_record=DataRecord( - version=0, - last_updated_time=self.data_record.last_updated_time, - created_time=self.data_record.created_time, - ), - is_damaged=self.is_damaged, - name=self.name, - sensor_positions=[sensor_position.as_read() for sensor_position in self.sensor_positions or []], - ) - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_write(self) -> BladeWrite: - """Convert this GraphQL format of blade to the writing format.""" - return BladeWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=0), - is_damaged=self.is_damaged, - name=self.name, - sensor_positions=[sensor_position.as_write() for sensor_position in self.sensor_positions or []], - ) - - -class Blade(DomainModel): - """This represents the reading version of blade. - - It is used to when data is retrieved from CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the blade. - data_record: The data record of the blade node. - is_damaged: The is damaged field. - name: The name field. - sensor_positions: The sensor position field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Blade", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, None] = None - is_damaged: Optional[bool] = None - name: Optional[str] = None - sensor_positions: Optional[list[Union[SensorPosition, str, dm.NodeId]]] = Field(default=None, repr=False) - - def as_write(self) -> BladeWrite: - """Convert this read version of blade to the writing version.""" - return BladeWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=self.data_record.version), - is_damaged=self.is_damaged, - name=self.name, - sensor_positions=[ - sensor_position.as_write() if isinstance(sensor_position, DomainModel) else sensor_position - for sensor_position in self.sensor_positions or [] - ], - ) - - def as_apply(self) -> BladeWrite: - """Convert this read version of blade to the writing version.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - @classmethod - def _update_connections( - cls, - instances: dict[dm.NodeId | str, Blade], # type: ignore[override] - nodes_by_id: dict[dm.NodeId | str, DomainModel], - edges_by_source_node: dict[dm.NodeId, list[dm.Edge | DomainRelation]], - ) -> None: - from ._sensor_position import SensorPosition - - for instance in instances.values(): - if edges := edges_by_source_node.get(instance.as_id()): - sensor_positions: list[SensorPosition | str | dm.NodeId] = [] - for edge in edges: - value: DomainModel | DomainRelation | str | dm.NodeId - if isinstance(edge, DomainRelation): - value = edge - else: - other_end: dm.DirectRelationReference = ( - edge.end_node - if edge.start_node.space == instance.space - and edge.start_node.external_id == instance.external_id - else edge.start_node - ) - destination: dm.NodeId | str = ( - as_node_id(other_end) - if other_end.space != DEFAULT_INSTANCE_SPACE - else other_end.external_id - ) - if destination in nodes_by_id: - value = nodes_by_id[destination] - else: - value = destination - edge_type = edge.edge_type if isinstance(edge, DomainRelation) else edge.type - - if edge_type == dm.DirectRelationReference("power-models", "Blade.sensor_positions") and isinstance( - value, (SensorPosition, str, dm.NodeId) - ): - sensor_positions.append(value) - - instance.sensor_positions = sensor_positions or None - - -class BladeWrite(DomainModelWrite): - """This represents the writing version of blade. - - It is used to when data is sent to CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the blade. - data_record: The data record of the blade node. - is_damaged: The is damaged field. - name: The name field. - sensor_positions: The sensor position field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Blade", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, dm.NodeId, tuple[str, str], None] = None - is_damaged: Optional[bool] = None - name: Optional[str] = None - sensor_positions: Optional[list[Union[SensorPositionWrite, str, dm.NodeId]]] = Field(default=None, repr=False) - - @field_validator("sensor_positions", mode="before") - def as_node_id(cls, value: Any) -> Any: - if isinstance(value, dm.DirectRelationReference): - return dm.NodeId(value.space, value.external_id) - elif isinstance(value, tuple) and len(value) == 2 and all(isinstance(item, str) for item in value): - return dm.NodeId(value[0], value[1]) - elif isinstance(value, list): - return [cls.as_node_id(item) for item in value] - return value - - def _to_instances_write( - self, - cache: set[tuple[str, str]], - write_none: bool = False, - allow_version_increase: bool = False, - ) -> ResourcesWrite: - resources = ResourcesWrite() - if self.as_tuple_id() in cache: - return resources - - properties: dict[str, Any] = {} - - if self.is_damaged is not None or write_none: - properties["is_damaged"] = self.is_damaged - - if self.name is not None or write_none: - properties["name"] = self.name - - if properties: - this_node = dm.NodeApply( - space=self.space, - external_id=self.external_id, - existing_version=None if allow_version_increase else self.data_record.existing_version, - type=as_direct_relation_reference(self.node_type), - sources=[ - dm.NodeOrEdgeData( - source=self._view_id, - properties=properties, - ) - ], - ) - resources.nodes.append(this_node) - cache.add(self.as_tuple_id()) - - edge_type = dm.DirectRelationReference("power-models", "Blade.sensor_positions") - for sensor_position in self.sensor_positions or []: - other_resources = DomainRelationWrite.from_edge_to_resources( - cache, - start_node=self, - end_node=sensor_position, - edge_type=edge_type, - write_none=write_none, - allow_version_increase=allow_version_increase, - ) - resources.extend(other_resources) - - return resources - - -class BladeApply(BladeWrite): - def __new__(cls, *args, **kwargs) -> BladeApply: - warnings.warn( - "BladeApply is deprecated and will be removed in v1.0. Use BladeWrite instead." - "The motivation for this change is that Write is a more descriptive name for the writing version of the" - "Blade.", - UserWarning, - stacklevel=2, - ) - return super().__new__(cls) - - -class BladeList(DomainModelList[Blade]): - """List of blades in the read version.""" - - _INSTANCE = Blade - - def as_write(self) -> BladeWriteList: - """Convert these read versions of blade to the writing versions.""" - return BladeWriteList([node.as_write() for node in self.data]) - - def as_apply(self) -> BladeWriteList: - """Convert these read versions of primitive nullable to the writing versions.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - @property - def sensor_positions(self) -> SensorPositionList: - from ._sensor_position import SensorPosition, SensorPositionList - - return SensorPositionList( - [item for items in self.data for item in items.sensor_positions or [] if isinstance(item, SensorPosition)] - ) - - -class BladeWriteList(DomainModelWriteList[BladeWrite]): - """List of blades in the writing version.""" - - _INSTANCE = BladeWrite - - @property - def sensor_positions(self) -> SensorPositionWriteList: - from ._sensor_position import SensorPositionWrite, SensorPositionWriteList - - return SensorPositionWriteList( - [ - item - for items in self.data - for item in items.sensor_positions or [] - if isinstance(item, SensorPositionWrite) - ] - ) - - -class BladeApplyList(BladeWriteList): ... - - -def _create_blade_filter( - view_id: dm.ViewId, - is_damaged: bool | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - filter: dm.Filter | None = None, -) -> dm.Filter | None: - filters: list[dm.Filter] = [] - if isinstance(is_damaged, bool): - filters.append(dm.filters.Equals(view_id.as_property_ref("is_damaged"), value=is_damaged)) - if isinstance(name, str): - filters.append(dm.filters.Equals(view_id.as_property_ref("name"), value=name)) - if name and isinstance(name, list): - filters.append(dm.filters.In(view_id.as_property_ref("name"), values=name)) - if name_prefix is not None: - filters.append(dm.filters.Prefix(view_id.as_property_ref("name"), value=name_prefix)) - if external_id_prefix is not None: - filters.append(dm.filters.Prefix(["node", "externalId"], value=external_id_prefix)) - if isinstance(space, str): - filters.append(dm.filters.Equals(["node", "space"], value=space)) - if space and isinstance(space, list): - filters.append(dm.filters.In(["node", "space"], values=space)) - if filter: - filters.append(filter) - return dm.filters.And(*filters) if filters else None - - -class _BladeQuery(NodeQueryCore[T_DomainModelList, BladeList]): - _view_id = Blade._view_id - _result_cls = Blade - _result_list_cls_end = BladeList - - def __init__( - self, - created_types: set[type], - creation_path: list[QueryCore], - client: CogniteClient, - result_list_cls: type[T_DomainModelList], - expression: dm.query.ResultSetExpression | None = None, - connection_name: str | None = None, - connection_type: Literal["reverse-list"] | None = None, - reverse_expression: dm.query.ResultSetExpression | None = None, - ): - from ._sensor_position import _SensorPositionQuery - - super().__init__( - created_types, - creation_path, - client, - result_list_cls, - expression, - dm.filters.HasData(views=[self._view_id]), - connection_name, - connection_type, - reverse_expression, - ) - - if _SensorPositionQuery not in created_types: - self.sensor_positions = _SensorPositionQuery( - created_types.copy(), - self._creation_path, - client, - result_list_cls, - dm.query.EdgeResultSetExpression( - direction="outwards", - chain_to="destination", - ), - connection_name="sensor_positions", - ) - - self.space = StringFilter(self, ["node", "space"]) - self.external_id = StringFilter(self, ["node", "externalId"]) - self.is_damaged = BooleanFilter(self, self._view_id.as_property_ref("is_damaged")) - self.name = StringFilter(self, self._view_id.as_property_ref("name")) - self._filter_classes.extend( - [ - self.space, - self.external_id, - self.is_damaged, - self.name, - ] - ) - - def list_blade(self, limit: int = DEFAULT_QUERY_LIMIT) -> BladeList: - return self._list(limit=limit) - - -class BladeQuery(_BladeQuery[BladeList]): - def __init__(self, client: CogniteClient): - super().__init__(set(), [], client, BladeList) diff --git a/examples/windmill/data_classes/_core/__init__.py b/examples/windmill/data_classes/_core/__init__.py deleted file mode 100644 index ebd27f19c..000000000 --- a/examples/windmill/data_classes/_core/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from windmill.data_classes._core.constants import * # noqa -from windmill.data_classes._core.base import * # noqa -from windmill.data_classes._core.cdf_external import * # noqa -from windmill.data_classes._core.helpers import * # noqa -from windmill.data_classes._core.query import * # noqa diff --git a/examples/windmill/data_classes/_core/base.py b/examples/windmill/data_classes/_core/base.py deleted file mode 100644 index 137e3dcef..000000000 --- a/examples/windmill/data_classes/_core/base.py +++ /dev/null @@ -1,685 +0,0 @@ -from __future__ import annotations - -import datetime -import sys -import warnings -from abc import ABC, abstractmethod -from collections import UserList -from collections.abc import Collection, Mapping -from dataclasses import dataclass, field -from typing import ( - Callable, - cast, - ClassVar, - Generic, - Optional, - Any, - Iterator, - TypeVar, - overload, - Union, - SupportsIndex, -) - -import pandas as pd -from cognite.client import data_modeling as dm -from cognite.client.data_classes import ( - TimeSeriesWriteList, - FileMetadataWriteList, - SequenceWriteList, - TimeSeriesList, - FileMetadataList, - SequenceList, -) -from cognite.client.data_classes.data_modeling.instances import ( - Instance, - InstanceApply, - Properties, - PropertyValue, -) -from pydantic import BaseModel, Field, model_validator - -from windmill.data_classes._core.constants import DEFAULT_INSTANCE_SPACE - -if sys.version_info >= (3, 11): - from typing import Self -else: - from typing_extensions import Self - - -@dataclass -class ResourcesWrite: - nodes: dm.NodeApplyList = field(default_factory=lambda: dm.NodeApplyList([])) - edges: dm.EdgeApplyList = field(default_factory=lambda: dm.EdgeApplyList([])) - time_series: TimeSeriesWriteList = field(default_factory=lambda: TimeSeriesWriteList([])) - files: FileMetadataWriteList = field(default_factory=lambda: FileMetadataWriteList([])) - sequences: SequenceWriteList = field(default_factory=lambda: SequenceWriteList([])) - - def extend(self, other: ResourcesWrite) -> None: - self.nodes.extend(other.nodes) - self.edges.extend(other.edges) - self.time_series.extend(other.time_series) - self.files.extend(other.files) - self.sequences.extend(other.sequences) - - -@dataclass -class ResourcesWriteResult: - nodes: dm.NodeApplyResultList = field(default_factory=lambda: dm.NodeApplyResultList([])) - edges: dm.EdgeApplyResultList = field(default_factory=lambda: dm.EdgeApplyResultList([])) - time_series: TimeSeriesList = field(default_factory=lambda: TimeSeriesList([])) - files: FileMetadataList = field(default_factory=lambda: FileMetadataList([])) - sequences: SequenceList = field(default_factory=lambda: SequenceList([])) - - -# Arbitrary types are allowed to be able to use the TimeSeries class -class Core(BaseModel, arbitrary_types_allowed=True, populate_by_name=True): - def to_pandas(self) -> pd.Series: - return pd.Series(self.model_dump()) - - def _repr_html_(self) -> str: - """Returns HTML representation of DomainModel.""" - return self.to_pandas().to_frame("value")._repr_html_() # type: ignore[operator] - - def dump(self, by_alias: bool = True) -> dict[str, Any]: - """Returns the item as a dictionary. - - Args: - by_alias: Whether to use the alias names in the dictionary. - - Returns: - The item as a dictionary. - - """ - return self.model_dump(by_alias=by_alias) - - -T_Core = TypeVar("T_Core", bound=Core) - - -class DataRecordGraphQL(Core): - last_updated_time: Optional[datetime.datetime] = Field(None, alias="lastUpdatedTime") - created_time: Optional[datetime.datetime] = Field(None, alias="createdTime") - - -class GraphQLCore(Core, ABC): - view_id: ClassVar[dm.ViewId] - space: Optional[str] = None - external_id: Optional[str] = Field(None, alias="externalId") - data_record: Optional[DataRecordGraphQL] = Field(None, alias="dataRecord") - - -class PageInfo(BaseModel): - has_next_page: Optional[bool] = Field(None, alias="hasNextPage") - has_previous_page: Optional[bool] = Field(None, alias="hasPreviousPage") - start_cursor: Optional[str] = Field(None, alias="startCursor") - end_cursor: Optional[str] = Field(None, alias="endCursor") - - @classmethod - def load(cls, data: dict[str, Any]) -> PageInfo: - return cls.model_validate(data) - - -class GraphQLList(UserList): - def __init__(self, nodes: Collection[GraphQLCore] | None = None): - super().__init__(nodes or []) - self.page_info: PageInfo | None = None - - # The dunder implementations are to get proper type hints - def __iter__(self) -> Iterator[GraphQLCore]: - return super().__iter__() - - @overload - def __getitem__(self, item: SupportsIndex) -> GraphQLCore: ... - - @overload - def __getitem__(self, item: slice) -> GraphQLList: ... - - def __getitem__(self, item: SupportsIndex | slice) -> GraphQLCore | GraphQLList: - value = self.data[item] - if isinstance(item, slice): - return type(self)(value) - return cast(GraphQLCore, value) - - def dump(self) -> list[dict[str, Any]]: - return [node.model_dump() for node in self.data] - - def to_pandas(self, dropna_columns: bool = False) -> pd.DataFrame: - """ - Convert the list of nodes to a pandas.DataFrame. - - Args: - dropna_columns: Whether to drop columns that are all NaN. - - Returns: - A pandas.DataFrame with the nodes as rows. - """ - df = pd.DataFrame(self.dump()) - if df.empty: - df = pd.DataFrame(columns=GraphQLCore.model_fields) - # Reorder columns to have the most relevant first - id_columns = ["space", "external_id"] - end_columns = ["data_record"] - fixed_columns = set(id_columns + end_columns) - columns = ( - id_columns + [col for col in df if col not in fixed_columns] + [col for col in end_columns if col in df] - ) - df = df[columns] - if df.empty: - return df - if dropna_columns: - df.dropna(how="all", axis=1, inplace=True) - return df - - def _repr_html_(self) -> str: - return self.to_pandas(dropna_columns=True)._repr_html_() # type: ignore[operator] - - -class DomainModelCore(Core, ABC): - _view_id: ClassVar[dm.ViewId] - - space: str - external_id: str = Field(min_length=1, max_length=255, alias="externalId") - - def as_tuple_id(self) -> tuple[str, str]: - return self.space, self.external_id - - def as_direct_reference(self) -> dm.DirectRelationReference: - return dm.DirectRelationReference(space=self.space, external_id=self.external_id) - - @classmethod - def _update_connections( - cls, - instances: dict[dm.NodeId | dm.EdgeId | str, Self], - nodes_by_id: dict[dm.NodeId | str, DomainModel], - edges_by_source_node: dict[dm.NodeId, list[dm.Edge | DomainRelation]], - ) -> None: - # This is used when unpacking a query result and should be overridden in the subclasses - return None - - -T_DomainModelCore = TypeVar("T_DomainModelCore", bound=DomainModelCore) - - -class DataRecord(BaseModel): - """The data record represents the metadata of a node. - - Args: - created_time: The created time of the node. - last_updated_time: The last updated time of the node. - deleted_time: If present, the deleted time of the node. - version: The version of the node. - """ - - version: int - last_updated_time: datetime.datetime - created_time: datetime.datetime - deleted_time: Optional[datetime.datetime] = None - - -class DomainModel(DomainModelCore, ABC): - data_record: DataRecord - node_type: Optional[dm.DirectRelationReference] = None - - def as_id(self) -> dm.NodeId: - return dm.NodeId(space=self.space, external_id=self.external_id) - - @classmethod - def from_instance(cls, instance: Instance) -> Self: - data = instance.dump(camel_case=False) - node_type = data.pop("type", None) - space = data.pop("space") - external_id = data.pop("external_id") - return cls( - space=space, - external_id=external_id, - data_record=DataRecord(**data), - node_type=node_type, - **unpack_properties(instance.properties), - ) - - -T_DomainModel = TypeVar("T_DomainModel", bound=DomainModel) - - -class DataRecordWrite(BaseModel): - """The data record represents the metadata of a node. - - Args: - existing_version: Fail the ingestion request if the node version is greater than or equal to this value. - If no existingVersion is specified, the ingestion will always overwrite any existing data for the edge (for the specified container or instance). - If existingVersion is set to 0, the upsert will behave as an insert, so it will fail the bulk if the item already exists. - If skipOnVersionConflict is set on the ingestion request, then the item will be skipped instead of failing the ingestion request. - """ - - existing_version: Optional[int] = None - - -T_DataRecord = TypeVar("T_DataRecord", bound=Union[DataRecord, DataRecordWrite]) - - -class _DataRecordListCore(UserList, Generic[T_DataRecord]): - _INSTANCE: type[T_DataRecord] - - def __init__(self, nodes: Collection[T_DataRecord] | None = None): - super().__init__(nodes or []) - - # The dunder implementations are to get proper type hints - def __iter__(self) -> Iterator[T_DataRecord]: - return super().__iter__() - - @overload - def __getitem__(self, item: SupportsIndex) -> T_DataRecord: ... - - @overload - def __getitem__(self, item: slice) -> _DataRecordListCore[T_DataRecord]: ... - - def __getitem__(self, item: SupportsIndex | slice) -> T_DataRecord | _DataRecordListCore[T_DataRecord]: - value = self.data[item] - if isinstance(item, slice): - return type(self)(value) - return cast(T_DataRecord, value) - - def to_pandas(self) -> pd.DataFrame: - """ - Convert the list of nodes to a pandas.DataFrame. - - Returns: - A pandas.DataFrame with the nodes as rows. - """ - df = pd.DataFrame([item.model_dump() for item in self]) - if df.empty: - df = pd.DataFrame(columns=self._INSTANCE.model_fields) - return df - - def _repr_html_(self) -> str: - return self.to_pandas()._repr_html_() # type: ignore[operator] - - -class DataRecordList(_DataRecordListCore[DataRecord]): - _INSTANCE = DataRecord - - -class DataRecordWriteList(_DataRecordListCore[DataRecordWrite]): - _INSTANCE = DataRecordWrite - - -class DomainModelWrite(DomainModelCore, extra="ignore", populate_by_name=True): - external_id_factory: ClassVar[Optional[Callable[[type[DomainModelWrite], dict], str]]] = None - data_record: DataRecordWrite = Field(default_factory=DataRecordWrite) - node_type: Union[dm.DirectRelationReference, dm.NodeId, tuple[str, str], None] = None - - def as_id(self) -> dm.NodeId: - return dm.NodeId(space=self.space, external_id=self.external_id) - - def to_instances_write( - self, - write_none: bool = False, - allow_version_increase: bool = False, - ) -> ResourcesWrite: - return self._to_instances_write(set(), write_none, allow_version_increase) - - def to_instances_apply(self, write_none: bool = False) -> ResourcesWrite: - warnings.warn( - "to_instances_apply is deprecated and will be removed in v1.0. Use to_instances_write instead.", - UserWarning, - stacklevel=2, - ) - return self.to_instances_write(write_none) - - @abstractmethod - def _to_instances_write( - self, - cache: set[tuple[str, str]], - write_none: bool = False, - allow_version_increase: bool = False, - ) -> ResourcesWrite: - raise NotImplementedError() - - @model_validator(mode="before") - def create_external_id_if_factory(cls, data: Any) -> Any: - if ( - isinstance(data, dict) - and cls.external_id_factory is not None - and data.get("external_id") is None - and data.get("externalId") is None - ): - data["external_id"] = cls.external_id_factory(cls, data) - return data - - @classmethod - def from_instance(cls: type[T_DomainModelWrite], instance: InstanceApply) -> T_DomainModelWrite: - data = instance.dump(camel_case=False) - data.pop("instance_type", None) - node_type = data.pop("type", None) - space = data.pop("space") - external_id = data.pop("external_id") - sources = data.pop("sources", []) - properties = {} - for source in sources: - for prop_name, prop_value in source["properties"].items(): - if isinstance(prop_value, dict) and "externalId" in prop_value and "space" in prop_value: - if prop_value["space"] == DEFAULT_INSTANCE_SPACE: - properties[prop_name] = prop_value["externalId"] - else: - properties[prop_name] = dm.NodeId( - space=prop_value["space"], external_id=prop_value["externalId"] - ) - else: - properties[prop_name] = prop_value - return cls( - space=space, external_id=external_id, node_type=node_type, data_record=DataRecordWrite(**data), **properties - ) - - -T_DomainModelWrite = TypeVar("T_DomainModelWrite", bound=DomainModelWrite) - - -class CoreList(UserList, Generic[T_Core]): - _INSTANCE: type[T_Core] - _PARENT_CLASS: type[Core] - - def __init__(self, nodes: Collection[T_Core] | None = None): - super().__init__(nodes or []) - - # The dunder implementations are to get proper type hints - def __iter__(self) -> Iterator[T_Core]: - return super().__iter__() - - @overload - def __getitem__(self, item: SupportsIndex) -> T_Core: ... - - @overload - def __getitem__(self, item: slice) -> Self: ... - - def __getitem__(self, item: SupportsIndex | slice) -> T_Core | Self: - value = self.data[item] - if isinstance(item, slice): - return type(self)(value) - return cast(T_Core, value) - - def dump(self) -> list[dict[str, Any]]: - return [node.model_dump() for node in self.data] - - def as_external_ids(self) -> list[str]: - return [node.external_id for node in self.data] - - def to_pandas(self, dropna_columns: bool = False) -> pd.DataFrame: - """ - Convert the list of nodes to a pandas.DataFrame. - - Args: - dropna_columns: Whether to drop columns that are all NaN. - - Returns: - A pandas.DataFrame with the nodes as rows. - """ - df = pd.DataFrame(self.dump()) - if df.empty: - df = pd.DataFrame(columns=self._INSTANCE.model_fields) - # Reorder columns to have the most relevant first - id_columns = ["space", "external_id"] - end_columns = ["node_type", "data_record"] - fixed_columns = set(id_columns + end_columns) - columns = ( - id_columns + [col for col in df if col not in fixed_columns] + [col for col in end_columns if col in df] - ) - df = df[columns] - if df.empty: - return df - if dropna_columns: - df.dropna(how="all", axis=1, inplace=True) - return df - - def _repr_html_(self) -> str: - return self.to_pandas(dropna_columns=True)._repr_html_() # type: ignore[operator] - - -class DomainModelList(CoreList[T_DomainModel]): - _PARENT_CLASS = DomainModel - - @property - def data_records(self) -> DataRecordList: - return DataRecordList([node.data_record for node in self.data]) - - def as_node_ids(self) -> list[dm.NodeId]: - return [dm.NodeId(space=node.space, external_id=node.external_id) for node in self.data] - - -T_DomainModelList = TypeVar("T_DomainModelList", bound=DomainModelList, covariant=True) - - -class DomainModelWriteList(CoreList[T_DomainModelWrite]): - _PARENT_CLASS = DomainModelWrite - - @property - def data_records(self) -> DataRecordWriteList: - return DataRecordWriteList([node.data_record for node in self]) - - def as_node_ids(self) -> list[dm.NodeId]: - return [dm.NodeId(space=node.space, external_id=node.external_id) for node in self] - - def to_instances_write( - self, - write_none: bool = False, - allow_version_increase: bool = False, - ) -> ResourcesWrite: - cache: set[tuple[str, str]] = set() - domains = ResourcesWrite() - for node in self: - result = node._to_instances_write(cache, write_none, allow_version_increase) - domains.extend(result) - return domains - - def to_instances_apply(self, write_none: bool = False) -> ResourcesWrite: - warnings.warn( - "to_instances_apply is deprecated and will be removed in v1.0. Use to_instances_write instead.", - UserWarning, - stacklevel=2, - ) - return self.to_instances_write(write_none) - - -T_DomainModelWriteList = TypeVar("T_DomainModelWriteList", bound=DomainModelWriteList, covariant=True) - - -class DomainRelation(DomainModelCore): - edge_type: dm.DirectRelationReference - start_node: dm.DirectRelationReference - end_node: Any - data_record: DataRecord - - def as_id(self) -> dm.EdgeId: - return dm.EdgeId(space=self.space, external_id=self.external_id) - - @classmethod - def from_instance(cls, instance: Instance) -> Self: - data = instance.dump(camel_case=False) - data.pop("instance_type", None) - edge_type = data.pop("type", None) - start_node = data.pop("start_node") - end_node = data.pop("end_node") - space = data.pop("space") - external_id = data.pop("external_id") - return cls( - space=space, - external_id=external_id, - data_record=DataRecord(**data), - edge_type=edge_type, - start_node=start_node, - end_node=end_node, - **unpack_properties(instance.properties), - ) - - -T_DomainRelation = TypeVar("T_DomainRelation", bound=DomainRelation) - - -def default_edge_external_id_factory( - start_node: DomainModelWrite | str | dm.NodeId, - end_node: DomainModelWrite | str | dm.NodeId, - edge_type: dm.DirectRelationReference, -) -> str: - start = start_node if isinstance(start_node, str) else start_node.external_id - end = end_node if isinstance(end_node, str) else end_node.external_id - return f"{start}:{end}" - - -class DomainRelationWrite(Core, extra="forbid", populate_by_name=True): - external_id_factory: ClassVar[ - Callable[ - [ - Union[DomainModelWrite, str, dm.NodeId], - Union[DomainModelWrite, str, dm.NodeId], - dm.DirectRelationReference, - ], - str, - ] - ] = default_edge_external_id_factory - data_record: DataRecordWrite = Field(default_factory=DataRecordWrite) - external_id: Optional[str] = Field(None, min_length=1, max_length=255) - - @abstractmethod - def _to_instances_write( - self, - cache: set[tuple[str, str]], - start_node: DomainModelWrite, - edge_type: dm.DirectRelationReference, - write_none: bool = False, - allow_version_increase: bool = False, - ) -> ResourcesWrite: - raise NotImplementedError() - - @classmethod - def create_edge( - cls, - start_node: DomainModelWrite | str | dm.NodeId, - end_node: DomainModelWrite | str | dm.NodeId, - edge_type: dm.DirectRelationReference, - ) -> dm.EdgeApply: - if isinstance(start_node, (DomainModelWrite, dm.NodeId)): - space = start_node.space - elif isinstance(end_node, (DomainModelWrite, dm.NodeId)): - space = end_node.space - else: - space = DEFAULT_INSTANCE_SPACE - - if isinstance(end_node, str): - end_ref = dm.DirectRelationReference(space, end_node) - elif isinstance(end_node, DomainModelWrite): - end_ref = end_node.as_direct_reference() - elif isinstance(end_node, dm.NodeId): - end_ref = dm.DirectRelationReference(end_node.space, end_node.external_id) - else: - raise TypeError(f"Expected str or subclass of {DomainRelationWrite.__name__}, got {type(end_node)}") - - if isinstance(start_node, str): - start_ref = dm.DirectRelationReference(space, start_node) - elif isinstance(start_node, DomainModelWrite): - start_ref = start_node.as_direct_reference() - elif isinstance(start_node, dm.NodeId): - start_ref = dm.DirectRelationReference(start_node.space, start_node.external_id) - else: - raise TypeError(f"Expected str or subclass of {DomainRelationWrite.__name__}, got {type(start_node)}") - - return dm.EdgeApply( - space=space, - external_id=cls.external_id_factory(start_node, end_node, edge_type), - type=edge_type, - start_node=start_ref, - end_node=end_ref, - ) - - @classmethod - def from_edge_to_resources( - cls, - cache: set[tuple[str, str]], - start_node: DomainModelWrite | str | dm.NodeId, - end_node: DomainModelWrite | str | dm.NodeId, - edge_type: dm.DirectRelationReference, - write_none: bool = False, - allow_version_increase: bool = False, - ) -> ResourcesWrite: - resources = ResourcesWrite() - edge = DomainRelationWrite.create_edge(start_node, end_node, edge_type) - if (edge.space, edge.external_id) in cache: - return resources - resources.edges.append(edge) - cache.add((edge.space, edge.external_id)) - - if isinstance(end_node, DomainModelWrite): - other_resources = end_node._to_instances_write( - cache, - write_none, - allow_version_increase, - ) - resources.extend(other_resources) - if isinstance(start_node, DomainModelWrite): - other_resources = start_node._to_instances_write( - cache, - write_none, - allow_version_increase, - ) - resources.extend(other_resources) - - return resources - - @classmethod - def reset_external_id_factory(cls) -> None: - cls.external_id_factory = default_edge_external_id_factory - - -T_DomainRelationWrite = TypeVar("T_DomainRelationWrite", bound=DomainRelationWrite) - - -class DomainRelationList(CoreList[T_DomainRelation]): - _PARENT_CLASS = DomainRelation - - def as_edge_ids(self) -> list[dm.EdgeId]: - return [edge.as_id() for edge in self.data] - - @property - def data_records(self) -> DataRecordList: - return DataRecordList([connection.data_record for connection in self.data]) - - -class DomainRelationWriteList(CoreList[T_DomainRelationWrite]): - _PARENT_CLASS = DomainRelationWrite - - @property - def data_records(self) -> DataRecordWriteList: - return DataRecordWriteList([connection.data_record for connection in self.data]) - - def as_edge_ids(self) -> list[dm.EdgeId]: - return [edge.as_id() for edge in self.data] - - -T_DomainRelationList = TypeVar("T_DomainRelationList", bound=DomainRelationList) - - -def unpack_properties(properties: Properties) -> Mapping[str, PropertyValue | dm.NodeId]: - unpacked: dict[str, PropertyValue | dm.NodeId] = {} - for view_properties in properties.values(): - for prop_name, prop_value in view_properties.items(): - if isinstance(prop_value, dict) and "externalId" in prop_value and "space" in prop_value: - if prop_value["space"] == DEFAULT_INSTANCE_SPACE: - unpacked[prop_name] = prop_value["externalId"] - else: - unpacked[prop_name] = dm.NodeId(space=prop_value["space"], external_id=prop_value["externalId"]) - elif isinstance(prop_value, list): - values: list[Any] = [] - for value in prop_value: - if isinstance(value, dict) and "externalId" in value and "space" in value: - if value["space"] == DEFAULT_INSTANCE_SPACE: - values.append(value["externalId"]) - else: - values.append(dm.NodeId(space=value["space"], external_id=value["externalId"])) - else: - values.append(value) - unpacked[prop_name] = values - else: - unpacked[prop_name] = prop_value - return unpacked - - -T_DomainList = TypeVar("T_DomainList", bound=Union[DomainModelList, DomainRelationList], covariant=True) diff --git a/examples/windmill/data_classes/_core/cdf_external.py b/examples/windmill/data_classes/_core/cdf_external.py deleted file mode 100644 index 6712f57eb..000000000 --- a/examples/windmill/data_classes/_core/cdf_external.py +++ /dev/null @@ -1,299 +0,0 @@ -from __future__ import annotations - -import datetime -from typing import ( - Annotated, - Optional, - Any, - no_type_check, -) - -from cognite.client import data_modeling as dm -from cognite.client.data_classes import Datapoints, SequenceColumnWrite, SequenceColumn -from cognite.client.data_classes import ( - TimeSeries as CogniteTimeSeries, - Sequence as CogniteSequence, - FileMetadata as CogniteFileMetadata, - TimeSeriesWrite as CogniteTimeSeriesWrite, - SequenceWrite as CogniteSequenceWrite, - FileMetadataWrite as CogniteFileMetadataWrite, -) -from cognite.client.data_classes.sequences import ValueType -from cognite.client.utils import datetime_to_ms -from pydantic import BaseModel, BeforeValidator, model_validator, field_validator -from pydantic.alias_generators import to_camel -from pydantic.functional_serializers import PlainSerializer - - -TimeSeries = Annotated[ - CogniteTimeSeries, - PlainSerializer( - lambda v: v.dump(camel_case=True) if isinstance(v, CogniteTimeSeries) else v, - return_type=dict, - when_used="unless-none", - ), - BeforeValidator(lambda v: CogniteTimeSeries.load(v) if isinstance(v, dict) else v), -] - - -SequenceRead = Annotated[ - CogniteSequence, - PlainSerializer( - lambda v: v.dump(camel_case=True) if isinstance(v, CogniteSequence) else v, - return_type=dict, - when_used="unless-none", - ), - BeforeValidator(lambda v: CogniteSequence.load(v) if isinstance(v, dict) else v), -] - - -FileMetadata = Annotated[ - CogniteFileMetadata, - PlainSerializer( - lambda v: v.dump(camel_case=True) if isinstance(v, CogniteFileMetadata) else v, - return_type=dict, - when_used="unless-none", - ), - BeforeValidator(lambda v: CogniteFileMetadata.load(v) if isinstance(v, dict) else v), -] - - -TimeSeriesWrite = Annotated[ - CogniteTimeSeriesWrite, - PlainSerializer( - lambda v: v.dump(camel_case=True) if isinstance(v, CogniteTimeSeriesWrite) else v, - return_type=dict, - when_used="unless-none", - ), - BeforeValidator(lambda v: CogniteTimeSeriesWrite.load(v) if isinstance(v, dict) else v), -] - - -SequenceWrite = Annotated[ - CogniteSequenceWrite, - PlainSerializer( - lambda v: v.dump(camel_case=True) if isinstance(v, CogniteSequenceWrite) else v, - return_type=dict, - when_used="unless-none", - ), - BeforeValidator(lambda v: CogniteSequenceWrite.load(v) if isinstance(v, dict) else v), -] - -FileMetadataWrite = Annotated[ - CogniteFileMetadataWrite, - PlainSerializer( - lambda v: v.dump(camel_case=True) if isinstance(v, CogniteFileMetadataWrite) else v, - return_type=dict, - when_used="unless-none", - ), - BeforeValidator(lambda v: CogniteFileMetadataWrite.load(v) if isinstance(v, dict) else v), -] - - -class GraphQLExternal(BaseModel, arbitrary_types_allowed=True, populate_by_name=True, alias_generator=to_camel): ... - - -class TimeSeriesGraphQL(GraphQLExternal): - id: Optional[int] = None - external_id: Optional[str] = None - instance_id: Optional[dm.NodeId] = None - name: Optional[str] = None - is_string: Optional[bool] = None - metadata: Optional[dict[str, str]] = None - unit: Optional[str] = None - unit_external_id: Optional[str] = None - asset_id: Optional[int] = None - is_step: Optional[bool] = None - description: Optional[str] = None - security_categories: Optional[list[int]] = None - data_set_id: Optional[int] = None - created_time: Optional[int] = None - last_updated_time: Optional[int] = None - data: Optional[Datapoints] = None - - @model_validator(mode="before") - def parse_datapoints(cls, data: Any) -> Any: - if isinstance(data, dict) and "getDataPoints" in data: - datapoints = data.pop("getDataPoints") - if "items" in datapoints: - for item in datapoints["items"]: - # The Datapoints expects the timestamp to be in milliseconds - item["timestamp"] = datetime_to_ms( - datetime.datetime.fromisoformat(item["timestamp"].replace("Z", "+00:00")) - ) - data["datapoints"] = datapoints["items"] - data["data"] = Datapoints.load(data) - return data - - def as_write(self) -> CogniteTimeSeriesWrite: - return CogniteTimeSeriesWrite( - external_id=self.external_id, - name=self.name, - is_string=self.is_string, - metadata=self.metadata, - unit=self.unit, - unit_external_id=self.unit_external_id, - asset_id=self.asset_id, - is_step=self.is_step, - description=self.description, - ) - - def as_read(self) -> CogniteTimeSeries: - return CogniteTimeSeries( - id=self.id, - external_id=self.external_id, - instance_id=self.instance_id, - name=self.name, - is_string=self.is_string, - metadata=self.metadata, - unit=self.unit, - unit_external_id=self.unit_external_id, - asset_id=self.asset_id, - is_step=self.is_step, - description=self.description, - security_categories=self.security_categories, - ) - - -class FileMetadataGraphQL(GraphQLExternal): - external_id: Optional[str] = None - name: Optional[str] = None - source: Optional[str] = None - mime_type: Optional[str] = None - metadata: Optional[dict[str, str]] = None - directory: Optional[str] = None - asset_ids: Optional[list[int]] = None - data_set_id: Optional[int] = None - labels: Optional[list[dict]] = None - geo_location: Optional[dict] = None - source_created_time: Optional[int] = None - source_modified_time: Optional[int] = None - security_categories: Optional[list[int]] = None - id: Optional[int] = None - uploaded: Optional[bool] = None - uploaded_time: Optional[int] = None - created_time: Optional[int] = None - last_updated_time: Optional[int] = None - - @no_type_check - def as_write(self) -> CogniteFileMetadataWrite: - return CogniteFileMetadataWrite( - external_id=self.external_id, - name=self.name, - source=self.source, - mime_type=self.mime_type, - metadata=self.metadata, - directory=self.directory, - asset_ids=self.asset_ids, - data_set_id=self.data_set_id, - labels=self.labels, - geo_location=self.geo_location, - source_created_time=self.source_created_time, - source_modified_time=self.source_modified_time, - security_categories=self.security_categories, - ) - - @no_type_check - def as_read(self) -> CogniteFileMetadata: - return CogniteFileMetadata( - external_id=self.external_id, - name=self.name, - source=self.source, - mime_type=self.mime_type, - metadata=self.metadata, - directory=self.directory, - asset_ids=self.asset_ids, - data_set_id=self.data_set_id, - labels=self.labels, - geo_location=self.geo_location, - source_created_time=self.source_created_time, - source_modified_time=self.source_modified_time, - security_categories=self.security_categories, - id=self.id, - uploaded=self.uploaded, - uploaded_time=self.uploaded_time, - created_time=self.created_time, - last_updated_time=self.last_updated_time, - ) - - -class SequenceColumnGraphQL(GraphQLExternal): - external_id: Optional[str] = None - name: Optional[str] = None - description: Optional[str] = None - value_type: Optional[ValueType] = None - metadata: Optional[dict[str, str]] = None - created_time: Optional[int] = None - last_updated_time: Optional[int] = None - - @field_validator("value_type", mode="before") - def title_value_type(cls, value: Any) -> Any: - if isinstance(value, str): - return value.title() - return value - - @no_type_check - def as_write(self) -> SequenceColumnWrite: - if self.value_type is None: - raise ValueError("value_type is required") - return SequenceColumnWrite( - external_id=self.external_id, - name=self.name, - description=self.description, - value_type=self.value_type, - metadata=self.metadata, - ) - - @no_type_check - def as_read(self) -> SequenceColumn: - if self.value_type is None: - raise ValueError("value_type is required") - return SequenceColumn( - external_id=self.external_id, - name=self.name, - description=self.description, - value_type=self.value_type, - metadata=self.metadata, - created_time=self.created_time, - last_updated_time=self.last_updated_time, - ) - - -class SequenceGraphQL(GraphQLExternal): - id: Optional[int] = None - name: Optional[str] = None - description: Optional[str] = None - asset_id: Optional[int] = None - external_id: Optional[str] = None - metadata: Optional[dict[str, str]] = None - columns: Optional[list[SequenceColumnGraphQL]] = None - created_time: Optional[int] = None - last_updated_time: Optional[int] = None - data_set_id: Optional[int] = None - - @no_type_check - def as_write(self) -> CogniteSequenceWrite: - return CogniteSequenceWrite( - name=self.name, - description=self.description, - asset_id=self.asset_id, - external_id=self.external_id, - metadata=self.metadata, - columns=[col.as_write() for col in self.columns or []] if self.columns else None, - data_set_id=self.data_set_id, - ) - - @no_type_check - def as_read(self) -> CogniteSequence: - return CogniteSequence( - id=self.id, - name=self.name, - description=self.description, - asset_id=self.asset_id, - external_id=self.external_id, - metadata=self.metadata, - columns=[col.as_read() for col in self.columns or []] if self.columns else None, - created_time=self.created_time, - last_updated_time=self.last_updated_time, - data_set_id=self.data_set_id, - ) diff --git a/examples/windmill/data_classes/_core/constants.py b/examples/windmill/data_classes/_core/constants.py deleted file mode 100644 index 2cca8f6f1..000000000 --- a/examples/windmill/data_classes/_core/constants.py +++ /dev/null @@ -1,20 +0,0 @@ -DEFAULT_QUERY_LIMIT = 5 -INSTANCE_QUERY_LIMIT = 1_000 -# The limit used for the In filter in /search -IN_FILTER_CHUNK_SIZE = 100 -# This is the actual limit of the API, we typically set it to a lower value to avoid hitting the limit. -# The actual instance query limit is 10_000, but we set it to 5_000 such that is matches the In filter -# which we use in /search for reverse of list direct relations. -ACTUAL_INSTANCE_QUERY_LIMIT = 5_000 -DEFAULT_INSTANCE_SPACE = "windmill-instances" -# The minimum estimated seconds before print progress on a query -MINIMUM_ESTIMATED_SECONDS_BEFORE_PRINT_PROGRESS = 30 -PRINT_PROGRESS_PER_N_NODES = 10_000 -SEARCH_LIMIT = 1_000 - - -class _NotSetSentinel: - """This is a special class that indicates that a value has not been set. - It is used when we need to distinguish between not set and None.""" - - ... diff --git a/examples/windmill/data_classes/_core/helpers.py b/examples/windmill/data_classes/_core/helpers.py deleted file mode 100644 index 66082a97a..000000000 --- a/examples/windmill/data_classes/_core/helpers.py +++ /dev/null @@ -1,73 +0,0 @@ -from __future__ import annotations - -from typing import Any - -from cognite.client import data_modeling as dm -from windmill.data_classes._core.base import DomainModel, T_DomainModel -from windmill.data_classes._core.constants import DEFAULT_INSTANCE_SPACE - - -def as_node_id(value: dm.DirectRelationReference) -> dm.NodeId: - return dm.NodeId(space=value.space, external_id=value.external_id) - - -def as_direct_relation_reference( - value: dm.DirectRelationReference | dm.NodeId | tuple[str, str] | None -) -> dm.DirectRelationReference | None: - if value is None or isinstance(value, dm.DirectRelationReference): - return value - if isinstance(value, dm.NodeId): - return dm.DirectRelationReference(space=value.space, external_id=value.external_id) - if isinstance(value, tuple): - return dm.DirectRelationReference(space=value[0], external_id=value[1]) - raise TypeError(f"Expected DirectRelationReference, NodeId or tuple, got {type(value)}") - - -# Any is to make mypy happy, while the rest is a hint of what the function expects -def as_instance_dict_id(value: str | dm.NodeId | tuple[str, str] | dm.DirectRelationReference | Any) -> dict[str, str]: - if isinstance(value, str): - return {"space": DEFAULT_INSTANCE_SPACE, "externalId": value} - if isinstance(value, dm.NodeId): - return {"space": value.space, "externalId": value.external_id} - if isinstance(value, tuple) and is_tuple_id(value): - return {"space": value[0], "externalId": value[1]} - if isinstance(value, dm.DirectRelationReference): - return {"space": value.space, "externalId": value.external_id} - raise TypeError(f"Expected str, NodeId, tuple or DirectRelationReference, got {type(value)}") - - -def is_tuple_id(value: Any) -> bool: - return isinstance(value, tuple) and len(value) == 2 and isinstance(value[0], str) and isinstance(value[1], str) - - -def as_pygen_node_id(value: DomainModel | dm.NodeId | str) -> dm.NodeId | str: - if isinstance(value, str): - return value - elif value.space == DEFAULT_INSTANCE_SPACE: - return value.external_id - elif isinstance(value, dm.NodeId): - return value - return value.as_id() - - -def are_nodes_equal(node1: DomainModel | str | dm.NodeId, node2: DomainModel | str | dm.NodeId) -> bool: - if isinstance(node1, (str, dm.NodeId)): - node1_id = node1 - else: - node1_id = node1.as_id() if node1.space != DEFAULT_INSTANCE_SPACE else node1.external_id - if isinstance(node2, (str, dm.NodeId)): - node2_id = node2 - else: - node2_id = node2.as_id() if node2.space != DEFAULT_INSTANCE_SPACE else node2.external_id - return node1_id == node2_id - - -def select_best_node( - node1: T_DomainModel | str | dm.NodeId, node2: T_DomainModel | str | dm.NodeId -) -> T_DomainModel | str | dm.NodeId: - if isinstance(node1, DomainModel): - return node1 # type: ignore[return-value] - elif isinstance(node2, DomainModel): - return node2 # type: ignore[return-value] - else: - return node1 diff --git a/examples/windmill/data_classes/_core/query.py b/examples/windmill/data_classes/_core/query.py deleted file mode 100644 index 5de2853d8..000000000 --- a/examples/windmill/data_classes/_core/query.py +++ /dev/null @@ -1,963 +0,0 @@ -from __future__ import annotations - -import datetime -import math -import time -import warnings -from abc import ABC -from collections import defaultdict -from collections.abc import Collection, MutableSequence, Iterable, Sequence -from contextlib import suppress -from dataclasses import dataclass -from typing import ( - cast, - ClassVar, - Generic, - Any, - Iterator, - TypeVar, - overload, - Union, - SupportsIndex, - Literal, -) - -from cognite.client import CogniteClient -from cognite.client import data_modeling as dm -from cognite.client.data_classes._base import CogniteObject -from cognite.client.data_classes.aggregations import Count -from cognite.client.data_classes.data_modeling.instances import Instance -from cognite.client.exceptions import CogniteAPIError - -from windmill.data_classes._core.base import ( - DomainModelList, - T_DomainList, - DomainRelationList, - DomainModelCore, - T_DomainModelList, - DomainRelation, - DomainModel, -) -from windmill.data_classes._core.constants import ( - _NotSetSentinel, - DEFAULT_QUERY_LIMIT, - DEFAULT_INSTANCE_SPACE, - ACTUAL_INSTANCE_QUERY_LIMIT, - INSTANCE_QUERY_LIMIT, - IN_FILTER_CHUNK_SIZE, - MINIMUM_ESTIMATED_SECONDS_BEFORE_PRINT_PROGRESS, - PRINT_PROGRESS_PER_N_NODES, - SEARCH_LIMIT, -) -from windmill.data_classes._core.helpers import as_node_id - - -T_DomainListEnd = TypeVar("T_DomainListEnd", bound=Union[DomainModelList, DomainRelationList], covariant=True) - - -class QueryCore(Generic[T_DomainList, T_DomainListEnd]): - _view_id: ClassVar[dm.ViewId] - _result_list_cls_end: type[T_DomainListEnd] - _result_cls: ClassVar[type[DomainModelCore]] - - def __init__( - self, - created_types: set[type], - creation_path: "list[QueryCore]", - client: CogniteClient, - result_list_cls: type[T_DomainList], - expression: dm.query.ResultSetExpression | None = None, - view_filter: dm.filters.Filter | None = None, - connection_name: str | None = None, - connection_type: Literal["reverse-list"] | None = None, - reverse_expression: dm.query.ResultSetExpression | None = None, - ): - created_types.add(type(self)) - self._creation_path = creation_path[:] + [self] - self._client = client - self._result_list_cls = result_list_cls - self._view_filter = view_filter - self._expression = expression or dm.query.NodeResultSetExpression() - self._reverse_expression = reverse_expression - self._connection_name = connection_name - self._connection_type = connection_type - self._filter_classes: list[Filtering] = [] - - @property - def _connection_names(self) -> set[str]: - return {step._connection_name for step in self._creation_path if step._connection_name} - - @property - def _is_reverseable(self) -> bool: - return self._reverse_expression is not None - - def __getattr__(self, item: str) -> Any: - if item in self._connection_names: - nodes = [step._result_cls.__name__ for step in self._creation_path] - raise ValueError(f"Circular reference detected. Cannot query a circular reference: {nodes}") - elif self._connection_type == "reverse-list": - raise ValueError(f"Cannot query across a reverse-list connection.") - raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{item}'") - - def _assemble_filter(self) -> dm.filters.Filter | None: - filters: list[dm.filters.Filter] = [self._view_filter] if self._view_filter else [] - for filter_cls in self._filter_classes: - if item := filter_cls._as_filter(): - filters.append(item) - return dm.filters.And(*filters) if filters else None - - def _repr_html_(self) -> str: - nodes = [step._result_cls.__name__ for step in self._creation_path] - edges = [step._connection_name or "missing" for step in self._creation_path[1:]] - last_connection_name = self._connection_name or "missing" - w = 120 - h = 40 - circles = " \n".join(f'' for i in range(len(nodes))) - circle_text = " \n".join( - f'{node}' for i, node in enumerate(nodes) - ) - arrows = " \n".join( - f'' - for i in range(len(edges)) - ) - arrow_text = " \n".join( - f'{edge}' for i, edge in enumerate(edges) - ) - - return f"""
Query
-
- - - - - - - - {arrows} - - - {circles} - - - {arrow_text} - - - {circle_text} - - -
-

Call .list_full() to return a list of {nodes[0].title()} and -.list_{last_connection_name}() to return a list of {nodes[-1].title()}.

-""" - - -class NodeQueryCore(QueryCore[T_DomainModelList, T_DomainListEnd]): - _result_cls: ClassVar[type[DomainModel]] - - def list_full(self, limit: int = DEFAULT_QUERY_LIMIT) -> T_DomainModelList: - builder = self._create_query(limit, self._result_list_cls, return_step="first", try_reverse=True) - builder.execute_query(self._client, remove_not_connected=True) - return builder.unpack() - - def _list(self, limit: int = DEFAULT_QUERY_LIMIT) -> T_DomainListEnd: - builder = self._create_query(limit, cast(type[DomainModelList], self._result_list_cls_end), return_step="last") - for step in builder[:-1]: - step.select = None - builder.execute_query(self._client, remove_not_connected=False) - return builder.unpack() - - def _dump_yaml(self) -> str: - return self._create_query(DEFAULT_QUERY_LIMIT, self._result_list_cls)._dump_yaml() - - def _create_query( - self, - limit: int, - result_list_cls: type[DomainModelList], - return_step: Literal["first", "last"] | None = None, - try_reverse: bool = False, - ) -> DataClassQueryBuilder: - builder = DataClassQueryBuilder(result_list_cls, return_step=return_step) - from_: str | None = None - first: bool = True - is_last_reverse_list = False - for item in self._creation_path: - if is_last_reverse_list: - raise ValueError( - "Cannot traverse past reverse direct relation of list. " - "This is a limitation of the modeling implementation in your data model." - "To do this query, you need to reimplement the data model and use an edge to " - "implement this connection instead of a reverse direct relation" - ) - name = builder.create_name(from_) - max_retrieve_limit = limit if first else -1 - step: QueryStep - if isinstance(item, NodeQueryCore) and isinstance(item._expression, dm.query.NodeResultSetExpression): - step = NodeQueryStep( - name=name, - expression=item._expression, - result_cls=item._result_cls, - max_retrieve_limit=max_retrieve_limit, - connection_type=item._connection_type, - ) - step.expression.from_ = from_ - step.expression.filter = item._assemble_filter() - builder.append(step) - elif isinstance(item, NodeQueryCore) and isinstance(item._expression, dm.query.EdgeResultSetExpression): - edge_name = name - step = EdgeQueryStep(name=edge_name, expression=item._expression, max_retrieve_limit=max_retrieve_limit) - step.expression.from_ = from_ - builder.append(step) - - name = builder.create_name(edge_name) - node_step = NodeQueryStep( - name=name, - expression=dm.query.NodeResultSetExpression( - from_=edge_name, - filter=item._assemble_filter(), - ), - result_cls=item._result_cls, - ) - builder.append(node_step) - elif isinstance(item, EdgeQueryCore): - step = EdgeQueryStep( - name=name, - expression=cast(dm.query.EdgeResultSetExpression, item._expression), - result_cls=item._result_cls, - ) - step.expression.from_ = from_ - step.expression.filter = item._assemble_filter() - builder.append(step) - else: - raise TypeError(f"Unsupported query step type: {type(item._expression)}") - - is_last_reverse_list = item._connection_type == "reverse-list" - first = False - from_ = name - return builder - - -class EdgeQueryCore(QueryCore[T_DomainList, T_DomainListEnd]): - _result_cls: ClassVar[type[DomainRelation]] - - -@dataclass(frozen=True) -class ViewPropertyId(CogniteObject): - view: dm.ViewId - property: str - - @classmethod - def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> "ViewPropertyId": - return cls( - view=dm.ViewId.load(resource["view"]), - property=resource["identifier"], - ) - - def dump(self, camel_case: bool = True) -> dict[str, Any]: - return { - "view": self.view.dump(camel_case=camel_case, include_type=False), - "identifier": self.property, - } - - -class QueryReducingBatchSize(UserWarning): - """Raised when a query is too large and the batch size must be reduced.""" - - ... - - -def chunker(sequence: Sequence, chunk_size: int) -> Iterator[Sequence]: - """ - Split a sequence into chunks of size chunk_size. - - Args: - sequence: The sequence to split. - chunk_size: The size of each chunk. - - Returns: - An iterator over the chunks. - - """ - for i in range(0, len(sequence), chunk_size): - yield sequence[i : i + chunk_size] - - -class QueryStep: - def __init__( - self, - name: str, - expression: dm.query.ResultSetExpression, - view_id: dm.ViewId | None = None, - max_retrieve_limit: int = -1, - select: dm.query.Select | None | type[_NotSetSentinel] = _NotSetSentinel, - raw_filter: dm.Filter | None = None, - connection_type: Literal["reverse-list"] | None = None, - view_property: ViewPropertyId | None = None, - selected_properties: list[str] | None = None, - ): - self.name = name - self.expression = expression - self.view_id = view_id - self.max_retrieve_limit = max_retrieve_limit - self.select: dm.query.Select | None - if select is _NotSetSentinel: - try: - self.select = self._default_select() - except NotImplementedError: - raise ValueError(f"You need to provide a select to instantiate a {type(self).__name__}") from None - else: - self.select = select # type: ignore[assignment] - self.raw_filter = raw_filter - self.connection_type = connection_type - self.view_property = view_property - self.selected_properties = selected_properties - self._max_retrieve_batch_limit = ACTUAL_INSTANCE_QUERY_LIMIT - self.cursor: str | None = None - self.total_retrieved: int = 0 - self.last_batch_count: int = 0 - self.results: list[Instance] = [] - - def _default_select(self) -> dm.query.Select: - if self.view_id is None: - return dm.query.Select() - else: - return dm.query.Select([dm.query.SourceSelector(self.view_id, ["*"])]) - - @property - def is_queryable(self) -> bool: - # We cannot query across reverse-list connections - return self.connection_type != "reverse-list" - - @property - def from_(self) -> str | None: - return self.expression.from_ - - @property - def is_single_direct_relation(self) -> bool: - return isinstance(self.expression, dm.query.NodeResultSetExpression) and self.expression.through is not None - - @property - def node_expression(self) -> dm.query.NodeResultSetExpression | None: - if isinstance(self.expression, dm.query.NodeResultSetExpression): - return self.expression - return None - - @property - def edge_expression(self) -> dm.query.EdgeResultSetExpression | None: - if isinstance(self.expression, dm.query.EdgeResultSetExpression): - return self.expression - return None - - @property - def node_results(self) -> Iterable[dm.Node]: - return (item for item in self.results if isinstance(item, dm.Node)) - - @property - def edge_results(self) -> Iterable[dm.Edge]: - return (item for item in self.results if isinstance(item, dm.Edge)) - - def update_expression_limit(self) -> None: - if self.is_unlimited: - self.expression.limit = self._max_retrieve_batch_limit - else: - self.expression.limit = max(min(INSTANCE_QUERY_LIMIT, self.max_retrieve_limit - self.total_retrieved), 0) - - def reduce_max_batch_limit(self) -> bool: - self._max_retrieve_batch_limit = max(1, self._max_retrieve_batch_limit // 2) - return self._max_retrieve_batch_limit > 1 - - @property - def is_unlimited(self) -> bool: - return self.max_retrieve_limit in {None, -1, math.inf} - - @property - def is_finished(self) -> bool: - return ( - (not self.is_unlimited and self.total_retrieved >= self.max_retrieve_limit) - or self.cursor is None - or self.last_batch_count == 0 - # Single direct relations are dependent on the parent node, - # so we assume that the parent node is the limiting factor. - or self.is_single_direct_relation - ) - - def count_total(self, cognite_client: CogniteClient) -> float: - if self.view_id is None: - raise ValueError("Cannot count total if select is not set") - - return cognite_client.data_modeling.instances.aggregate( - self.view_id, Count("externalId"), filter=self.raw_filter - ).value - - def __repr__(self) -> str: - return f"{self.__class__.__name__}(name={self.name!r}, from={self.from_!r}, results={len(self.results)})" - - -class QueryBuilder(list, MutableSequence[QueryStep]): - """This is a helper class to build and execute a query. It is responsible for - doing the paging of the query and keeping track of the results.""" - - def __init__(self, steps: Collection[QueryStep] | None = None): - super().__init__(steps or []) - - def _reset(self): - for expression in self: - expression.total_retrieved = 0 - expression.cursor = None - expression.results = [] - - def _update_expression_limits(self) -> None: - for expression in self: - expression.update_expression_limit() - - def _build(self) -> tuple[dm.query.Query, list[QueryStep], set[str]]: - with_ = {step.name: step.expression for step in self if step.is_queryable} - select = {step.name: step.select for step in self if step.select is not None and step.is_queryable} - cursors = self._cursors - - step_by_name = {step.name: step for step in self} - search: list[QueryStep] = [] - temporary_select: set[str] = set() - for step in self: - if step.is_queryable: - continue - if step.node_expression is not None: - search.append(step) - # Ensure that select is set for the parent - if step.from_ in select or step.from_ is None: - continue - view_id = step_by_name[step.from_].view_id - if view_id is None: - continue - select[step.from_] = dm.query.Select([dm.query.SourceSelector(view_id, ["*"])]) - temporary_select.add(step.from_) - return dm.query.Query(with_=with_, select=select, cursors=cursors), search, temporary_select - - def _dump_yaml(self) -> str: - return self._build()[0].dump_yaml() - - @property - def _cursors(self) -> dict[str, str | None]: - return {expression.name: expression.cursor for expression in self if expression.is_queryable} - - def _update(self, batch: dm.query.QueryResult): - for expression in self: - if expression.name not in batch: - continue - expression.last_batch_count = len(batch[expression.name]) - expression.total_retrieved += expression.last_batch_count - expression.cursor = batch.cursors.get(expression.name) - expression.results.extend(batch[expression.name].data) - - @property - def _is_finished(self) -> bool: - return self[0].is_finished - - def _reduce_max_batch_limit(self) -> bool: - for expression in self: - if not expression.reduce_max_batch_limit(): - return False - return True - - def execute_query(self, client: CogniteClient, remove_not_connected: bool = False) -> dict[str, list[Instance]]: - self._reset() - query, to_search, temp_select = self._build() - - if not self: - raise ValueError("No query steps to execute") - - count: float | None = None - with suppress(ValueError, CogniteAPIError): - count = self[0].count_total(client) - - is_large_query = False - last_progress_print = 0 - nodes_per_second = 0.0 - while True: - self._update_expression_limits() - query.cursors = self._cursors - t0 = time.time() - try: - batch = client.data_modeling.instances.query(query) - except CogniteAPIError as e: - if e.code == 408: - # Too big query, try to reduce the limit - if self._reduce_max_batch_limit(): - continue - new_limit = self[0]._max_retrieve_batch_limit - warnings.warn( - f"Query is too large, reducing batch size to {new_limit:,}, and trying again", - QueryReducingBatchSize, - stacklevel=2, - ) - - raise e - - self._fetch_reverse_direct_relation_of_lists(client, to_search, batch) - - for name in temp_select: - batch.pop(name, None) - - last_execution_time = time.time() - t0 - - self._update(batch) - if self._is_finished: - break - - if count is None: - continue - # Estimate the number of nodes per second using exponential moving average - last_batch_nodes_per_second = len(batch[self[0].name]) / last_execution_time - if nodes_per_second == 0.0: - nodes_per_second = last_batch_nodes_per_second - else: - nodes_per_second = 0.1 * last_batch_nodes_per_second + 0.9 * nodes_per_second - # Estimate the time to completion - remaining_nodes = count - self[0].total_retrieved - remaining_time = remaining_nodes / nodes_per_second - - if is_large_query and (self[0].total_retrieved - last_progress_print) > PRINT_PROGRESS_PER_N_NODES: - estimate = datetime.timedelta(seconds=round(remaining_time, 0)) - print( - f"Progress: {self[0].total_retrieved:,}/{count:,} nodes retrieved. " - f"Estimated time to completion: {estimate}" - ) - last_progress_print = self[0].total_retrieved - - if is_large_query is False and remaining_time > MINIMUM_ESTIMATED_SECONDS_BEFORE_PRINT_PROGRESS: - is_large_query = True - print("Large query detected. Will print progress.") - - if remove_not_connected and len(self) > 1: - _QueryResultCleaner(self).clean() - - return {step.name: step.results for step in self} - - @staticmethod - def _fetch_reverse_direct_relation_of_lists( - client: CogniteClient, to_search: list[QueryStep], batch: dm.query.QueryResult - ) -> None: - """Reverse direct relations for lists are not supported by the query API. - This method fetches them separately.""" - for step in to_search: - if step.from_ is None or step.from_ not in batch: - continue - item_ids = [node.as_id() for node in batch[step.from_].data] - if not item_ids: - continue - - view_id = step.view_id - expression = step.node_expression - if view_id is None or expression is None: - raise ValueError( - "Invalid state of the query. Search should always be a node expression with view properties" - ) - if expression.through is None: - raise ValueError("Missing through set in a reverse-list query") - limit = SEARCH_LIMIT if step.is_unlimited else min(step.max_retrieve_limit, SEARCH_LIMIT) - - step_result = dm.NodeList[dm.Node]([]) - for item_ids_chunk in chunker(item_ids, IN_FILTER_CHUNK_SIZE): - is_items = dm.filters.In(view_id.as_property_ref(expression.through.property), item_ids_chunk) - is_selected = is_items if step.raw_filter is None else dm.filters.And(is_items, step.raw_filter) - - chunk_result = client.data_modeling.instances.search( - view_id, properties=None, filter=is_selected, limit=limit - ) - step_result.extend(chunk_result) - - batch[step.name] = dm.NodeListWithCursor(step_result, None) - return None - - def get_from(self) -> str | None: - if len(self) == 0: - return None - return self[-1].name - - def create_name(self, from_: str | None) -> str: - if from_ is None: - return "0" - return f"{from_}_{len(self)}" - - def append(self, __object: QueryStep, /) -> None: - # Extra validation to ensure all assumptions are met - if len(self) == 0: - if __object.from_ is not None: - raise ValueError("The first step should not have a 'from_' value") - else: - if __object.from_ is None: - raise ValueError("The 'from_' value should be set") - super().append(__object) - - def extend(self, __iterable: Iterable[QueryStep], /) -> None: - for item in __iterable: - self.append(item) - - # The implementations below are to get proper type hints - def __iter__(self) -> Iterator[QueryStep]: - return super().__iter__() - - @overload - def __getitem__(self, item: SupportsIndex) -> QueryStep: ... - - @overload - def __getitem__(self, item: slice) -> "QueryBuilder": ... - - def __getitem__(self, item: SupportsIndex | slice) -> "QueryStep | QueryBuilder": - value = super().__getitem__(item) - if isinstance(item, slice): - return QueryBuilder(value) # type: ignore[arg-type] - return cast(QueryStep, value) - - -class _QueryResultCleaner: - """Remove nodes and edges that are not connected through the entire query""" - - def __init__(self, steps: list[QueryStep]): - self._tree = self._create_tree(steps) - self._root = steps[0] - - @classmethod - def _create_tree(cls, steps: list[QueryStep]) -> dict[str, list[QueryStep]]: - tree: dict[str, list[QueryStep]] = defaultdict(list) - for step in steps: - if step.from_ is None: - continue - tree[step.from_].append(step) - return dict(tree) - - def clean(self) -> None: - self._clean(self._root) - - @staticmethod - def as_node_id(direct_relation: dm.DirectRelationReference | dict[str, str]) -> dm.NodeId: - if isinstance(direct_relation, dict): - return dm.NodeId(direct_relation["space"], direct_relation["externalId"]) - - return dm.NodeId(direct_relation.space, direct_relation.external_id) - - def _clean(self, step: QueryStep) -> tuple[set[dm.NodeId], str | None]: - if step.name not in self._tree: - # Leaf Node - direct_relation: str | None = None - if step.node_expression and (through := step.node_expression.through) is not None: - direct_relation = through.property - if step.node_expression.direction == "inwards": - return { - node_id for item in step.node_results for node_id in self._get_relations(item, direct_relation) - }, None - - return {item.as_id() for item in step.results}, direct_relation # type: ignore[attr-defined] - - expected_ids_by_property: dict[str | None, set[dm.NodeId]] = {} - for child in self._tree[step.name]: - child_ids, property_id = self._clean(child) - if property_id not in expected_ids_by_property: - expected_ids_by_property[property_id] = child_ids - else: - expected_ids_by_property[property_id] |= child_ids - - if step.node_expression is not None: - filtered_results: list[Instance] = [] - for node in step.node_results: - if self._is_connected_node(node, expected_ids_by_property): - filtered_results.append(node) - step.results = filtered_results - direct_relation = None if step.node_expression.through is None else step.node_expression.through.property - return {node.as_id() for node in step.node_results}, direct_relation - - if step.edge_expression: - if len(expected_ids_by_property) > 1 or None not in expected_ids_by_property: - raise RuntimeError(f"Invalid state of {type(self).__name__}") - expected_ids = expected_ids_by_property[None] - if step.edge_expression.direction == "outwards": - step.results = [edge for edge in step.edge_results if self.as_node_id(edge.end_node) in expected_ids] - return {self.as_node_id(edge.start_node) for edge in step.edge_results}, None - else: # inwards - step.results = [edge for edge in step.edge_results if self.as_node_id(edge.start_node) in expected_ids] - return {self.as_node_id(edge.end_node) for edge in step.edge_results}, None - - raise TypeError(f"Unsupported query step type: {type(step)}") - - @classmethod - def _is_connected_node(cls, node: dm.Node, expected_ids_by_property: dict[str | None, set[dm.NodeId]]) -> bool: - if not expected_ids_by_property: - return True - if None in expected_ids_by_property: - if node.as_id() in expected_ids_by_property[None]: - return True - if len(expected_ids_by_property) == 1: - return False - node_properties = next(iter(node.properties.values())) - for property_id, expected_ids in expected_ids_by_property.items(): - if property_id is None: - continue - value = node_properties.get(property_id) - if value is None: - continue - elif isinstance(value, list): - if {cls.as_node_id(item) for item in value if isinstance(item, dict)} & expected_ids: - return True - elif isinstance(value, dict) and cls.as_node_id(value) in expected_ids: - return True - return False - - @classmethod - def _get_relations(cls, node: dm.Node, property_id: str) -> Iterable[dm.NodeId]: - if property_id is None: - return {node.as_id()} - value = next(iter(node.properties.values())).get(property_id) - if isinstance(value, list): - return [cls.as_node_id(item) for item in value if isinstance(item, dict)] - elif isinstance(value, dict): - return [cls.as_node_id(value)] - return [] - - -class NodeQueryStep(QueryStep): - def __init__( - self, - name: str, - expression: dm.query.NodeResultSetExpression, - result_cls: type[DomainModel], - max_retrieve_limit: int = -1, - select: dm.query.Select | None | type[_NotSetSentinel] = _NotSetSentinel, - raw_filter: dm.Filter | None = None, - connection_type: Literal["reverse-list"] | None = None, - ): - self.result_cls = result_cls - super().__init__(name, expression, result_cls._view_id, max_retrieve_limit, select, raw_filter, connection_type) - - def unpack(self) -> dict[dm.NodeId | str, DomainModel]: - return { - ( - instance.as_id() if instance.space != DEFAULT_INSTANCE_SPACE else instance.external_id - ): self.result_cls.from_instance(instance) - for instance in cast(list[dm.Node], self.results) - } - - @property - def node_results(self) -> list[dm.Node]: - return cast(list[dm.Node], self.results) - - @property - def node_expression(self) -> dm.query.NodeResultSetExpression: - return cast(dm.query.NodeResultSetExpression, self.expression) - - -class EdgeQueryStep(QueryStep): - def __init__( - self, - name: str, - expression: dm.query.EdgeResultSetExpression, - result_cls: type[DomainRelation] | None = None, - max_retrieve_limit: int = -1, - select: dm.query.Select | None | type[_NotSetSentinel] = _NotSetSentinel, - raw_filter: dm.Filter | None = None, - ): - self.result_cls = result_cls - view_id = result_cls._view_id if result_cls is not None else None - super().__init__(name, expression, view_id, max_retrieve_limit, select, raw_filter, None) - - def unpack(self) -> dict[dm.NodeId, list[dm.Edge | DomainRelation]]: - output: dict[dm.NodeId, list[dm.Edge | DomainRelation]] = defaultdict(list) - for edge in cast(list[dm.Edge], self.results): - edge_source = edge.start_node if self.expression.direction == "outwards" else edge.end_node - value = self.result_cls.from_instance(edge) if self.result_cls is not None else edge - output[as_node_id(edge_source)].append(value) # type: ignore[arg-type] - return output - - @property - def edge_results(self) -> list[dm.Edge]: - return cast(list[dm.Edge], self.results) - - @property - def edge_expression(self) -> dm.query.EdgeResultSetExpression: - return cast(dm.query.EdgeResultSetExpression, self.expression) - - -class DataClassQueryBuilder(QueryBuilder, Generic[T_DomainModelList]): - """This is a helper class to build and execute a query. It is responsible for - doing the paging of the query and keeping track of the results.""" - - def __init__( - self, - result_cls: type[T_DomainModelList] | None, - steps: Collection[QueryStep] | None = None, - return_step: Literal["first", "last"] | None = None, - ): - super().__init__(steps or []) - self._result_list_cls = result_cls - self._return_step: Literal["first", "last"] | None = return_step - - def unpack(self) -> T_DomainModelList: - if self._result_list_cls is None: - raise ValueError("No result class set, unable to unpack results") - selected = [step for step in self if step.select is not None] - if len(selected) == 0: - return self._result_list_cls([]) - elif len(selected) == 1: - # Validated in the append method - if self._return_step == "first": - selected_step = cast(NodeQueryStep, self[0]) - elif self._return_step == "last": - selected_step = cast(NodeQueryStep, self[-1]) - else: - raise ValueError(f"Invalid return_step: {self._return_step}") - return self._result_list_cls(selected_step.unpack().values()) - # More than one step, we need to unpack the nodes and edges - nodes_by_from: dict[str | None, dict[dm.NodeId | str, DomainModel]] = defaultdict(dict) - edges_by_from: dict[str, dict[dm.NodeId, list[dm.Edge | DomainRelation]]] = defaultdict(dict) - for step in reversed(self): - # Validated in the append method - from_ = cast(str, step.from_) - if isinstance(step, EdgeQueryStep): - edges_by_from[from_].update(step.unpack()) - if step.name in nodes_by_from: - nodes_by_from[from_].update(nodes_by_from[step.name]) - del nodes_by_from[step.name] - elif isinstance(step, NodeQueryStep): - unpacked = step.unpack() - nodes_by_from[from_].update(unpacked) # type: ignore[arg-type] - if step.name in nodes_by_from or step.name in edges_by_from: - step.result_cls._update_connections( - unpacked, # type: ignore[arg-type] - nodes_by_from.get(step.name, {}), # type: ignore[arg-type] - edges_by_from.get(step.name, {}), - ) - if self._return_step == "first": - return self._result_list_cls(nodes_by_from[None].values()) - elif self._return_step == "last" and self[-1].from_ in nodes_by_from: - return self._result_list_cls(nodes_by_from[self[-1].from_].values()) - elif self._return_step == "last": - raise ValueError("Cannot return the last step when the last step is an edge query") - else: - raise ValueError(f"Invalid return_step: {self._return_step}") - - def append(self, __object: QueryStep, /) -> None: - # Extra validation to ensure all assumptions are met - if len(self) == 0: - if __object.from_ is not None: - raise ValueError("The first step should not have a 'from_' value") - if self._result_list_cls is None: - if self._return_step is None: - self._return_step = "first" - else: - if not isinstance(__object, NodeQueryStep): - raise ValueError("The first step should be a NodeQueryStep") - # If the first step is a NodeQueryStep, and matches the instance - # in the result_list_cls we can return the result from the first step - # Alternative is result_cls is not set, then we also assume that the first step - if self._return_step is None: - if __object.result_cls is self._result_list_cls._INSTANCE: - self._return_step = "first" - else: - # If not, we assume that the last step is the one we want to return - self._return_step = "last" - else: - if __object.from_ is None: - raise ValueError("The 'from_' value should be set") - super().append(__object) - - def extend(self, __iterable: Iterable[QueryStep], /) -> None: - for item in __iterable: - self.append(item) - - # The implementations below are to get proper type hints - def __iter__(self) -> Iterator[QueryStep]: - return super().__iter__() - - @overload - def __getitem__(self, item: SupportsIndex) -> QueryStep: ... - - @overload - def __getitem__(self, item: slice) -> DataClassQueryBuilder[T_DomainModelList]: ... - - def __getitem__(self, item: SupportsIndex | slice) -> QueryStep | DataClassQueryBuilder[T_DomainModelList]: - value = super().__getitem__(item) - if isinstance(item, slice): - return DataClassQueryBuilder(self._result_list_cls, value) # type: ignore[arg-type] - return cast(QueryStep, value) - - -T_QueryCore = TypeVar("T_QueryCore") - - -class Filtering(Generic[T_QueryCore], ABC): - def __init__(self, query: T_QueryCore, prop_path: list[str] | tuple[str, ...]): - self._query = query - self._prop_path = prop_path - self._filter: dm.Filter | None = None - - def _raise_if_filter_set(self): - if self._filter is not None: - raise ValueError("Filter has already been set") - - def _as_filter(self) -> dm.Filter | None: - return self._filter - - -class StringFilter(Filtering[T_QueryCore]): - def equals(self, value: str) -> T_QueryCore: - self._raise_if_filter_set() - self._filter = dm.filters.Equals(self._prop_path, value) - return self._query - - def prefix(self, prefix: str) -> T_QueryCore: - self._raise_if_filter_set() - self._filter = dm.filters.Prefix(self._prop_path, prefix) - return self._query - - def in_(self, values: list[str]) -> T_QueryCore: - self._raise_if_filter_set() - self._filter = dm.filters.In(self._prop_path, values) - return self._query - - -class BooleanFilter(Filtering[T_QueryCore]): - def equals(self, value: bool) -> T_QueryCore: - self._raise_if_filter_set() - self._filter = dm.filters.Equals(self._prop_path, value) - return self._query - - -class IntFilter(Filtering[T_QueryCore]): - def range(self, gte: int | None, lte: int | None) -> T_QueryCore: - self._raise_if_filter_set() - self._filter = dm.filters.Range(self._prop_path, gte=gte, lte=lte) - return self._query - - -class FloatFilter(Filtering[T_QueryCore]): - def range(self, gte: float | None, lte: float | None) -> T_QueryCore: - self._raise_if_filter_set() - self._filter = dm.filters.Range(self._prop_path, gte=gte, lte=lte) - return self._query - - -class TimestampFilter(Filtering[T_QueryCore]): - def range(self, gte: datetime.datetime | None, lte: datetime.datetime | None) -> T_QueryCore: - self._raise_if_filter_set() - self._filter = dm.filters.Range( - self._prop_path, - gte=gte.isoformat(timespec="milliseconds") if gte else None, - lte=lte.isoformat(timespec="milliseconds") if lte else None, - ) - return self._query - - -class DateFilter(Filtering[T_QueryCore]): - def range(self, gte: datetime.date | None, lte: datetime.date | None) -> T_QueryCore: - self._raise_if_filter_set() - self._filter = dm.filters.Range( - self._prop_path, - gte=gte.isoformat() if gte else None, - lte=lte.isoformat() if lte else None, - ) - return self._query diff --git a/examples/windmill/data_classes/_gearbox.py b/examples/windmill/data_classes/_gearbox.py deleted file mode 100644 index 1c00b2925..000000000 --- a/examples/windmill/data_classes/_gearbox.py +++ /dev/null @@ -1,370 +0,0 @@ -from __future__ import annotations - -import warnings -from collections.abc import Sequence -from typing import Any, ClassVar, Literal, no_type_check, Optional, Union - -from cognite.client import data_modeling as dm, CogniteClient -from cognite.client.data_classes import ( - TimeSeries as CogniteTimeSeries, - TimeSeriesWrite as CogniteTimeSeriesWrite, -) -from pydantic import field_validator, model_validator - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - DataRecord, - DataRecordGraphQL, - DataRecordWrite, - DomainModel, - DomainModelWrite, - DomainModelWriteList, - DomainModelList, - DomainRelation, - DomainRelationWrite, - GraphQLCore, - ResourcesWrite, - FileMetadata, - FileMetadataWrite, - FileMetadataGraphQL, - TimeSeries, - TimeSeriesWrite, - TimeSeriesGraphQL, - T_DomainModelList, - as_direct_relation_reference, - as_instance_dict_id, - as_node_id, - as_pygen_node_id, - are_nodes_equal, - is_tuple_id, - select_best_node, - QueryCore, - NodeQueryCore, - StringFilter, -) - - -__all__ = [ - "Gearbox", - "GearboxWrite", - "GearboxApply", - "GearboxList", - "GearboxWriteList", - "GearboxApplyList", - "GearboxFields", - "GearboxTextFields", - "GearboxGraphQL", -] - - -GearboxTextFields = Literal["external_id", "displacement_x", "displacement_y", "displacement_z"] -GearboxFields = Literal["external_id", "displacement_x", "displacement_y", "displacement_z"] - -_GEARBOX_PROPERTIES_BY_FIELD = { - "external_id": "externalId", - "displacement_x": "displacement_x", - "displacement_y": "displacement_y", - "displacement_z": "displacement_z", -} - - -class GearboxGraphQL(GraphQLCore): - """This represents the reading version of gearbox, used - when data is retrieved from CDF using GraphQL. - - It is used when retrieving data from CDF using GraphQL. - - Args: - space: The space where the node is located. - external_id: The external id of the gearbox. - data_record: The data record of the gearbox node. - displacement_x: The displacement x field. - displacement_y: The displacement y field. - displacement_z: The displacement z field. - """ - - view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Gearbox", "1") - displacement_x: Optional[TimeSeriesGraphQL] = None - displacement_y: Optional[TimeSeriesGraphQL] = None - displacement_z: Optional[TimeSeriesGraphQL] = None - - @model_validator(mode="before") - def parse_data_record(cls, values: Any) -> Any: - if not isinstance(values, dict): - return values - if "lastUpdatedTime" in values or "createdTime" in values: - values["dataRecord"] = DataRecordGraphQL( - created_time=values.pop("createdTime", None), - last_updated_time=values.pop("lastUpdatedTime", None), - ) - return values - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_read(self) -> Gearbox: - """Convert this GraphQL format of gearbox to the reading format.""" - if self.data_record is None: - raise ValueError("This object cannot be converted to a read format because it lacks a data record.") - return Gearbox( - space=self.space, - external_id=self.external_id, - data_record=DataRecord( - version=0, - last_updated_time=self.data_record.last_updated_time, - created_time=self.data_record.created_time, - ), - displacement_x=self.displacement_x.as_read() if self.displacement_x else None, - displacement_y=self.displacement_y.as_read() if self.displacement_y else None, - displacement_z=self.displacement_z.as_read() if self.displacement_z else None, - ) - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_write(self) -> GearboxWrite: - """Convert this GraphQL format of gearbox to the writing format.""" - return GearboxWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=0), - displacement_x=self.displacement_x.as_write() if self.displacement_x else None, - displacement_y=self.displacement_y.as_write() if self.displacement_y else None, - displacement_z=self.displacement_z.as_write() if self.displacement_z else None, - ) - - -class Gearbox(DomainModel): - """This represents the reading version of gearbox. - - It is used to when data is retrieved from CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the gearbox. - data_record: The data record of the gearbox node. - displacement_x: The displacement x field. - displacement_y: The displacement y field. - displacement_z: The displacement z field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Gearbox", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, None] = None - displacement_x: Union[TimeSeries, str, None] = None - displacement_y: Union[TimeSeries, str, None] = None - displacement_z: Union[TimeSeries, str, None] = None - - def as_write(self) -> GearboxWrite: - """Convert this read version of gearbox to the writing version.""" - return GearboxWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=self.data_record.version), - displacement_x=( - self.displacement_x.as_write() - if isinstance(self.displacement_x, CogniteTimeSeries) - else self.displacement_x - ), - displacement_y=( - self.displacement_y.as_write() - if isinstance(self.displacement_y, CogniteTimeSeries) - else self.displacement_y - ), - displacement_z=( - self.displacement_z.as_write() - if isinstance(self.displacement_z, CogniteTimeSeries) - else self.displacement_z - ), - ) - - def as_apply(self) -> GearboxWrite: - """Convert this read version of gearbox to the writing version.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - -class GearboxWrite(DomainModelWrite): - """This represents the writing version of gearbox. - - It is used to when data is sent to CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the gearbox. - data_record: The data record of the gearbox node. - displacement_x: The displacement x field. - displacement_y: The displacement y field. - displacement_z: The displacement z field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Gearbox", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, dm.NodeId, tuple[str, str], None] = None - displacement_x: Union[TimeSeriesWrite, str, None] = None - displacement_y: Union[TimeSeriesWrite, str, None] = None - displacement_z: Union[TimeSeriesWrite, str, None] = None - - def _to_instances_write( - self, - cache: set[tuple[str, str]], - write_none: bool = False, - allow_version_increase: bool = False, - ) -> ResourcesWrite: - resources = ResourcesWrite() - if self.as_tuple_id() in cache: - return resources - - properties: dict[str, Any] = {} - - if self.displacement_x is not None or write_none: - properties["displacement_x"] = ( - self.displacement_x - if isinstance(self.displacement_x, str) or self.displacement_x is None - else self.displacement_x.external_id - ) - - if self.displacement_y is not None or write_none: - properties["displacement_y"] = ( - self.displacement_y - if isinstance(self.displacement_y, str) or self.displacement_y is None - else self.displacement_y.external_id - ) - - if self.displacement_z is not None or write_none: - properties["displacement_z"] = ( - self.displacement_z - if isinstance(self.displacement_z, str) or self.displacement_z is None - else self.displacement_z.external_id - ) - - if properties: - this_node = dm.NodeApply( - space=self.space, - external_id=self.external_id, - existing_version=None if allow_version_increase else self.data_record.existing_version, - type=as_direct_relation_reference(self.node_type), - sources=[ - dm.NodeOrEdgeData( - source=self._view_id, - properties=properties, - ) - ], - ) - resources.nodes.append(this_node) - cache.add(self.as_tuple_id()) - - if isinstance(self.displacement_x, CogniteTimeSeriesWrite): - resources.time_series.append(self.displacement_x) - - if isinstance(self.displacement_y, CogniteTimeSeriesWrite): - resources.time_series.append(self.displacement_y) - - if isinstance(self.displacement_z, CogniteTimeSeriesWrite): - resources.time_series.append(self.displacement_z) - - return resources - - -class GearboxApply(GearboxWrite): - def __new__(cls, *args, **kwargs) -> GearboxApply: - warnings.warn( - "GearboxApply is deprecated and will be removed in v1.0. Use GearboxWrite instead." - "The motivation for this change is that Write is a more descriptive name for the writing version of the" - "Gearbox.", - UserWarning, - stacklevel=2, - ) - return super().__new__(cls) - - -class GearboxList(DomainModelList[Gearbox]): - """List of gearboxes in the read version.""" - - _INSTANCE = Gearbox - - def as_write(self) -> GearboxWriteList: - """Convert these read versions of gearbox to the writing versions.""" - return GearboxWriteList([node.as_write() for node in self.data]) - - def as_apply(self) -> GearboxWriteList: - """Convert these read versions of primitive nullable to the writing versions.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - -class GearboxWriteList(DomainModelWriteList[GearboxWrite]): - """List of gearboxes in the writing version.""" - - _INSTANCE = GearboxWrite - - -class GearboxApplyList(GearboxWriteList): ... - - -def _create_gearbox_filter( - view_id: dm.ViewId, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - filter: dm.Filter | None = None, -) -> dm.Filter | None: - filters: list[dm.Filter] = [] - if external_id_prefix is not None: - filters.append(dm.filters.Prefix(["node", "externalId"], value=external_id_prefix)) - if isinstance(space, str): - filters.append(dm.filters.Equals(["node", "space"], value=space)) - if space and isinstance(space, list): - filters.append(dm.filters.In(["node", "space"], values=space)) - if filter: - filters.append(filter) - return dm.filters.And(*filters) if filters else None - - -class _GearboxQuery(NodeQueryCore[T_DomainModelList, GearboxList]): - _view_id = Gearbox._view_id - _result_cls = Gearbox - _result_list_cls_end = GearboxList - - def __init__( - self, - created_types: set[type], - creation_path: list[QueryCore], - client: CogniteClient, - result_list_cls: type[T_DomainModelList], - expression: dm.query.ResultSetExpression | None = None, - connection_name: str | None = None, - connection_type: Literal["reverse-list"] | None = None, - reverse_expression: dm.query.ResultSetExpression | None = None, - ): - - super().__init__( - created_types, - creation_path, - client, - result_list_cls, - expression, - dm.filters.HasData(views=[self._view_id]), - connection_name, - connection_type, - reverse_expression, - ) - - self.space = StringFilter(self, ["node", "space"]) - self.external_id = StringFilter(self, ["node", "externalId"]) - - def list_gearbox(self, limit: int = DEFAULT_QUERY_LIMIT) -> GearboxList: - return self._list(limit=limit) - - -class GearboxQuery(_GearboxQuery[GearboxList]): - def __init__(self, client: CogniteClient): - super().__init__(set(), [], client, GearboxList) diff --git a/examples/windmill/data_classes/_generator.py b/examples/windmill/data_classes/_generator.py deleted file mode 100644 index 70e7351b1..000000000 --- a/examples/windmill/data_classes/_generator.py +++ /dev/null @@ -1,359 +0,0 @@ -from __future__ import annotations - -import warnings -from collections.abc import Sequence -from typing import Any, ClassVar, Literal, no_type_check, Optional, Union - -from cognite.client import data_modeling as dm, CogniteClient -from cognite.client.data_classes import ( - TimeSeries as CogniteTimeSeries, - TimeSeriesWrite as CogniteTimeSeriesWrite, -) -from pydantic import field_validator, model_validator - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - DataRecord, - DataRecordGraphQL, - DataRecordWrite, - DomainModel, - DomainModelWrite, - DomainModelWriteList, - DomainModelList, - DomainRelation, - DomainRelationWrite, - GraphQLCore, - ResourcesWrite, - FileMetadata, - FileMetadataWrite, - FileMetadataGraphQL, - TimeSeries, - TimeSeriesWrite, - TimeSeriesGraphQL, - T_DomainModelList, - as_direct_relation_reference, - as_instance_dict_id, - as_node_id, - as_pygen_node_id, - are_nodes_equal, - is_tuple_id, - select_best_node, - QueryCore, - NodeQueryCore, - StringFilter, -) - - -__all__ = [ - "Generator", - "GeneratorWrite", - "GeneratorApply", - "GeneratorList", - "GeneratorWriteList", - "GeneratorApplyList", - "GeneratorFields", - "GeneratorTextFields", - "GeneratorGraphQL", -] - - -GeneratorTextFields = Literal["external_id", "generator_speed_controller", "generator_speed_controller_reference"] -GeneratorFields = Literal["external_id", "generator_speed_controller", "generator_speed_controller_reference"] - -_GENERATOR_PROPERTIES_BY_FIELD = { - "external_id": "externalId", - "generator_speed_controller": "generator_speed_controller", - "generator_speed_controller_reference": "generator_speed_controller_reference", -} - - -class GeneratorGraphQL(GraphQLCore): - """This represents the reading version of generator, used - when data is retrieved from CDF using GraphQL. - - It is used when retrieving data from CDF using GraphQL. - - Args: - space: The space where the node is located. - external_id: The external id of the generator. - data_record: The data record of the generator node. - generator_speed_controller: The generator speed controller field. - generator_speed_controller_reference: The generator speed controller reference field. - """ - - view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Generator", "1") - generator_speed_controller: Optional[TimeSeriesGraphQL] = None - generator_speed_controller_reference: Optional[TimeSeriesGraphQL] = None - - @model_validator(mode="before") - def parse_data_record(cls, values: Any) -> Any: - if not isinstance(values, dict): - return values - if "lastUpdatedTime" in values or "createdTime" in values: - values["dataRecord"] = DataRecordGraphQL( - created_time=values.pop("createdTime", None), - last_updated_time=values.pop("lastUpdatedTime", None), - ) - return values - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_read(self) -> Generator: - """Convert this GraphQL format of generator to the reading format.""" - if self.data_record is None: - raise ValueError("This object cannot be converted to a read format because it lacks a data record.") - return Generator( - space=self.space, - external_id=self.external_id, - data_record=DataRecord( - version=0, - last_updated_time=self.data_record.last_updated_time, - created_time=self.data_record.created_time, - ), - generator_speed_controller=( - self.generator_speed_controller.as_read() if self.generator_speed_controller else None - ), - generator_speed_controller_reference=( - self.generator_speed_controller_reference.as_read() - if self.generator_speed_controller_reference - else None - ), - ) - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_write(self) -> GeneratorWrite: - """Convert this GraphQL format of generator to the writing format.""" - return GeneratorWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=0), - generator_speed_controller=( - self.generator_speed_controller.as_write() if self.generator_speed_controller else None - ), - generator_speed_controller_reference=( - self.generator_speed_controller_reference.as_write() - if self.generator_speed_controller_reference - else None - ), - ) - - -class Generator(DomainModel): - """This represents the reading version of generator. - - It is used to when data is retrieved from CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the generator. - data_record: The data record of the generator node. - generator_speed_controller: The generator speed controller field. - generator_speed_controller_reference: The generator speed controller reference field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Generator", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, None] = None - generator_speed_controller: Union[TimeSeries, str, None] = None - generator_speed_controller_reference: Union[TimeSeries, str, None] = None - - def as_write(self) -> GeneratorWrite: - """Convert this read version of generator to the writing version.""" - return GeneratorWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=self.data_record.version), - generator_speed_controller=( - self.generator_speed_controller.as_write() - if isinstance(self.generator_speed_controller, CogniteTimeSeries) - else self.generator_speed_controller - ), - generator_speed_controller_reference=( - self.generator_speed_controller_reference.as_write() - if isinstance(self.generator_speed_controller_reference, CogniteTimeSeries) - else self.generator_speed_controller_reference - ), - ) - - def as_apply(self) -> GeneratorWrite: - """Convert this read version of generator to the writing version.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - -class GeneratorWrite(DomainModelWrite): - """This represents the writing version of generator. - - It is used to when data is sent to CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the generator. - data_record: The data record of the generator node. - generator_speed_controller: The generator speed controller field. - generator_speed_controller_reference: The generator speed controller reference field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Generator", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, dm.NodeId, tuple[str, str], None] = None - generator_speed_controller: Union[TimeSeriesWrite, str, None] = None - generator_speed_controller_reference: Union[TimeSeriesWrite, str, None] = None - - def _to_instances_write( - self, - cache: set[tuple[str, str]], - write_none: bool = False, - allow_version_increase: bool = False, - ) -> ResourcesWrite: - resources = ResourcesWrite() - if self.as_tuple_id() in cache: - return resources - - properties: dict[str, Any] = {} - - if self.generator_speed_controller is not None or write_none: - properties["generator_speed_controller"] = ( - self.generator_speed_controller - if isinstance(self.generator_speed_controller, str) or self.generator_speed_controller is None - else self.generator_speed_controller.external_id - ) - - if self.generator_speed_controller_reference is not None or write_none: - properties["generator_speed_controller_reference"] = ( - self.generator_speed_controller_reference - if isinstance(self.generator_speed_controller_reference, str) - or self.generator_speed_controller_reference is None - else self.generator_speed_controller_reference.external_id - ) - - if properties: - this_node = dm.NodeApply( - space=self.space, - external_id=self.external_id, - existing_version=None if allow_version_increase else self.data_record.existing_version, - type=as_direct_relation_reference(self.node_type), - sources=[ - dm.NodeOrEdgeData( - source=self._view_id, - properties=properties, - ) - ], - ) - resources.nodes.append(this_node) - cache.add(self.as_tuple_id()) - - if isinstance(self.generator_speed_controller, CogniteTimeSeriesWrite): - resources.time_series.append(self.generator_speed_controller) - - if isinstance(self.generator_speed_controller_reference, CogniteTimeSeriesWrite): - resources.time_series.append(self.generator_speed_controller_reference) - - return resources - - -class GeneratorApply(GeneratorWrite): - def __new__(cls, *args, **kwargs) -> GeneratorApply: - warnings.warn( - "GeneratorApply is deprecated and will be removed in v1.0. Use GeneratorWrite instead." - "The motivation for this change is that Write is a more descriptive name for the writing version of the" - "Generator.", - UserWarning, - stacklevel=2, - ) - return super().__new__(cls) - - -class GeneratorList(DomainModelList[Generator]): - """List of generators in the read version.""" - - _INSTANCE = Generator - - def as_write(self) -> GeneratorWriteList: - """Convert these read versions of generator to the writing versions.""" - return GeneratorWriteList([node.as_write() for node in self.data]) - - def as_apply(self) -> GeneratorWriteList: - """Convert these read versions of primitive nullable to the writing versions.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - -class GeneratorWriteList(DomainModelWriteList[GeneratorWrite]): - """List of generators in the writing version.""" - - _INSTANCE = GeneratorWrite - - -class GeneratorApplyList(GeneratorWriteList): ... - - -def _create_generator_filter( - view_id: dm.ViewId, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - filter: dm.Filter | None = None, -) -> dm.Filter | None: - filters: list[dm.Filter] = [] - if external_id_prefix is not None: - filters.append(dm.filters.Prefix(["node", "externalId"], value=external_id_prefix)) - if isinstance(space, str): - filters.append(dm.filters.Equals(["node", "space"], value=space)) - if space and isinstance(space, list): - filters.append(dm.filters.In(["node", "space"], values=space)) - if filter: - filters.append(filter) - return dm.filters.And(*filters) if filters else None - - -class _GeneratorQuery(NodeQueryCore[T_DomainModelList, GeneratorList]): - _view_id = Generator._view_id - _result_cls = Generator - _result_list_cls_end = GeneratorList - - def __init__( - self, - created_types: set[type], - creation_path: list[QueryCore], - client: CogniteClient, - result_list_cls: type[T_DomainModelList], - expression: dm.query.ResultSetExpression | None = None, - connection_name: str | None = None, - connection_type: Literal["reverse-list"] | None = None, - reverse_expression: dm.query.ResultSetExpression | None = None, - ): - - super().__init__( - created_types, - creation_path, - client, - result_list_cls, - expression, - dm.filters.HasData(views=[self._view_id]), - connection_name, - connection_type, - reverse_expression, - ) - - self.space = StringFilter(self, ["node", "space"]) - self.external_id = StringFilter(self, ["node", "externalId"]) - - def list_generator(self, limit: int = DEFAULT_QUERY_LIMIT) -> GeneratorList: - return self._list(limit=limit) - - -class GeneratorQuery(_GeneratorQuery[GeneratorList]): - def __init__(self, client: CogniteClient): - super().__init__(set(), [], client, GeneratorList) diff --git a/examples/windmill/data_classes/_high_speed_shaft.py b/examples/windmill/data_classes/_high_speed_shaft.py deleted file mode 100644 index 3842c1809..000000000 --- a/examples/windmill/data_classes/_high_speed_shaft.py +++ /dev/null @@ -1,364 +0,0 @@ -from __future__ import annotations - -import warnings -from collections.abc import Sequence -from typing import Any, ClassVar, Literal, no_type_check, Optional, Union - -from cognite.client import data_modeling as dm, CogniteClient -from cognite.client.data_classes import ( - TimeSeries as CogniteTimeSeries, - TimeSeriesWrite as CogniteTimeSeriesWrite, -) -from pydantic import field_validator, model_validator - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - DataRecord, - DataRecordGraphQL, - DataRecordWrite, - DomainModel, - DomainModelWrite, - DomainModelWriteList, - DomainModelList, - DomainRelation, - DomainRelationWrite, - GraphQLCore, - ResourcesWrite, - FileMetadata, - FileMetadataWrite, - FileMetadataGraphQL, - TimeSeries, - TimeSeriesWrite, - TimeSeriesGraphQL, - T_DomainModelList, - as_direct_relation_reference, - as_instance_dict_id, - as_node_id, - as_pygen_node_id, - are_nodes_equal, - is_tuple_id, - select_best_node, - QueryCore, - NodeQueryCore, - StringFilter, -) - - -__all__ = [ - "HighSpeedShaft", - "HighSpeedShaftWrite", - "HighSpeedShaftApply", - "HighSpeedShaftList", - "HighSpeedShaftWriteList", - "HighSpeedShaftApplyList", - "HighSpeedShaftFields", - "HighSpeedShaftTextFields", - "HighSpeedShaftGraphQL", -] - - -HighSpeedShaftTextFields = Literal["external_id", "bending_moment_y", "bending_monent_x", "torque"] -HighSpeedShaftFields = Literal["external_id", "bending_moment_y", "bending_monent_x", "torque"] - -_HIGHSPEEDSHAFT_PROPERTIES_BY_FIELD = { - "external_id": "externalId", - "bending_moment_y": "bending_moment_y", - "bending_monent_x": "bending_monent_x", - "torque": "torque", -} - - -class HighSpeedShaftGraphQL(GraphQLCore): - """This represents the reading version of high speed shaft, used - when data is retrieved from CDF using GraphQL. - - It is used when retrieving data from CDF using GraphQL. - - Args: - space: The space where the node is located. - external_id: The external id of the high speed shaft. - data_record: The data record of the high speed shaft node. - bending_moment_y: The bending moment y field. - bending_monent_x: The bending monent x field. - torque: The torque field. - """ - - view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "HighSpeedShaft", "1") - bending_moment_y: Optional[TimeSeriesGraphQL] = None - bending_monent_x: Optional[TimeSeriesGraphQL] = None - torque: Optional[TimeSeriesGraphQL] = None - - @model_validator(mode="before") - def parse_data_record(cls, values: Any) -> Any: - if not isinstance(values, dict): - return values - if "lastUpdatedTime" in values or "createdTime" in values: - values["dataRecord"] = DataRecordGraphQL( - created_time=values.pop("createdTime", None), - last_updated_time=values.pop("lastUpdatedTime", None), - ) - return values - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_read(self) -> HighSpeedShaft: - """Convert this GraphQL format of high speed shaft to the reading format.""" - if self.data_record is None: - raise ValueError("This object cannot be converted to a read format because it lacks a data record.") - return HighSpeedShaft( - space=self.space, - external_id=self.external_id, - data_record=DataRecord( - version=0, - last_updated_time=self.data_record.last_updated_time, - created_time=self.data_record.created_time, - ), - bending_moment_y=self.bending_moment_y.as_read() if self.bending_moment_y else None, - bending_monent_x=self.bending_monent_x.as_read() if self.bending_monent_x else None, - torque=self.torque.as_read() if self.torque else None, - ) - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_write(self) -> HighSpeedShaftWrite: - """Convert this GraphQL format of high speed shaft to the writing format.""" - return HighSpeedShaftWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=0), - bending_moment_y=self.bending_moment_y.as_write() if self.bending_moment_y else None, - bending_monent_x=self.bending_monent_x.as_write() if self.bending_monent_x else None, - torque=self.torque.as_write() if self.torque else None, - ) - - -class HighSpeedShaft(DomainModel): - """This represents the reading version of high speed shaft. - - It is used to when data is retrieved from CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the high speed shaft. - data_record: The data record of the high speed shaft node. - bending_moment_y: The bending moment y field. - bending_monent_x: The bending monent x field. - torque: The torque field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "HighSpeedShaft", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, None] = None - bending_moment_y: Union[TimeSeries, str, None] = None - bending_monent_x: Union[TimeSeries, str, None] = None - torque: Union[TimeSeries, str, None] = None - - def as_write(self) -> HighSpeedShaftWrite: - """Convert this read version of high speed shaft to the writing version.""" - return HighSpeedShaftWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=self.data_record.version), - bending_moment_y=( - self.bending_moment_y.as_write() - if isinstance(self.bending_moment_y, CogniteTimeSeries) - else self.bending_moment_y - ), - bending_monent_x=( - self.bending_monent_x.as_write() - if isinstance(self.bending_monent_x, CogniteTimeSeries) - else self.bending_monent_x - ), - torque=self.torque.as_write() if isinstance(self.torque, CogniteTimeSeries) else self.torque, - ) - - def as_apply(self) -> HighSpeedShaftWrite: - """Convert this read version of high speed shaft to the writing version.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - -class HighSpeedShaftWrite(DomainModelWrite): - """This represents the writing version of high speed shaft. - - It is used to when data is sent to CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the high speed shaft. - data_record: The data record of the high speed shaft node. - bending_moment_y: The bending moment y field. - bending_monent_x: The bending monent x field. - torque: The torque field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "HighSpeedShaft", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, dm.NodeId, tuple[str, str], None] = None - bending_moment_y: Union[TimeSeriesWrite, str, None] = None - bending_monent_x: Union[TimeSeriesWrite, str, None] = None - torque: Union[TimeSeriesWrite, str, None] = None - - def _to_instances_write( - self, - cache: set[tuple[str, str]], - write_none: bool = False, - allow_version_increase: bool = False, - ) -> ResourcesWrite: - resources = ResourcesWrite() - if self.as_tuple_id() in cache: - return resources - - properties: dict[str, Any] = {} - - if self.bending_moment_y is not None or write_none: - properties["bending_moment_y"] = ( - self.bending_moment_y - if isinstance(self.bending_moment_y, str) or self.bending_moment_y is None - else self.bending_moment_y.external_id - ) - - if self.bending_monent_x is not None or write_none: - properties["bending_monent_x"] = ( - self.bending_monent_x - if isinstance(self.bending_monent_x, str) or self.bending_monent_x is None - else self.bending_monent_x.external_id - ) - - if self.torque is not None or write_none: - properties["torque"] = ( - self.torque if isinstance(self.torque, str) or self.torque is None else self.torque.external_id - ) - - if properties: - this_node = dm.NodeApply( - space=self.space, - external_id=self.external_id, - existing_version=None if allow_version_increase else self.data_record.existing_version, - type=as_direct_relation_reference(self.node_type), - sources=[ - dm.NodeOrEdgeData( - source=self._view_id, - properties=properties, - ) - ], - ) - resources.nodes.append(this_node) - cache.add(self.as_tuple_id()) - - if isinstance(self.bending_moment_y, CogniteTimeSeriesWrite): - resources.time_series.append(self.bending_moment_y) - - if isinstance(self.bending_monent_x, CogniteTimeSeriesWrite): - resources.time_series.append(self.bending_monent_x) - - if isinstance(self.torque, CogniteTimeSeriesWrite): - resources.time_series.append(self.torque) - - return resources - - -class HighSpeedShaftApply(HighSpeedShaftWrite): - def __new__(cls, *args, **kwargs) -> HighSpeedShaftApply: - warnings.warn( - "HighSpeedShaftApply is deprecated and will be removed in v1.0. Use HighSpeedShaftWrite instead." - "The motivation for this change is that Write is a more descriptive name for the writing version of the" - "HighSpeedShaft.", - UserWarning, - stacklevel=2, - ) - return super().__new__(cls) - - -class HighSpeedShaftList(DomainModelList[HighSpeedShaft]): - """List of high speed shafts in the read version.""" - - _INSTANCE = HighSpeedShaft - - def as_write(self) -> HighSpeedShaftWriteList: - """Convert these read versions of high speed shaft to the writing versions.""" - return HighSpeedShaftWriteList([node.as_write() for node in self.data]) - - def as_apply(self) -> HighSpeedShaftWriteList: - """Convert these read versions of primitive nullable to the writing versions.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - -class HighSpeedShaftWriteList(DomainModelWriteList[HighSpeedShaftWrite]): - """List of high speed shafts in the writing version.""" - - _INSTANCE = HighSpeedShaftWrite - - -class HighSpeedShaftApplyList(HighSpeedShaftWriteList): ... - - -def _create_high_speed_shaft_filter( - view_id: dm.ViewId, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - filter: dm.Filter | None = None, -) -> dm.Filter | None: - filters: list[dm.Filter] = [] - if external_id_prefix is not None: - filters.append(dm.filters.Prefix(["node", "externalId"], value=external_id_prefix)) - if isinstance(space, str): - filters.append(dm.filters.Equals(["node", "space"], value=space)) - if space and isinstance(space, list): - filters.append(dm.filters.In(["node", "space"], values=space)) - if filter: - filters.append(filter) - return dm.filters.And(*filters) if filters else None - - -class _HighSpeedShaftQuery(NodeQueryCore[T_DomainModelList, HighSpeedShaftList]): - _view_id = HighSpeedShaft._view_id - _result_cls = HighSpeedShaft - _result_list_cls_end = HighSpeedShaftList - - def __init__( - self, - created_types: set[type], - creation_path: list[QueryCore], - client: CogniteClient, - result_list_cls: type[T_DomainModelList], - expression: dm.query.ResultSetExpression | None = None, - connection_name: str | None = None, - connection_type: Literal["reverse-list"] | None = None, - reverse_expression: dm.query.ResultSetExpression | None = None, - ): - - super().__init__( - created_types, - creation_path, - client, - result_list_cls, - expression, - dm.filters.HasData(views=[self._view_id]), - connection_name, - connection_type, - reverse_expression, - ) - - self.space = StringFilter(self, ["node", "space"]) - self.external_id = StringFilter(self, ["node", "externalId"]) - - def list_high_speed_shaft(self, limit: int = DEFAULT_QUERY_LIMIT) -> HighSpeedShaftList: - return self._list(limit=limit) - - -class HighSpeedShaftQuery(_HighSpeedShaftQuery[HighSpeedShaftList]): - def __init__(self, client: CogniteClient): - super().__init__(set(), [], client, HighSpeedShaftList) diff --git a/examples/windmill/data_classes/_main_shaft.py b/examples/windmill/data_classes/_main_shaft.py deleted file mode 100644 index 6dd757f8f..000000000 --- a/examples/windmill/data_classes/_main_shaft.py +++ /dev/null @@ -1,408 +0,0 @@ -from __future__ import annotations - -import warnings -from collections.abc import Sequence -from typing import Any, ClassVar, Literal, no_type_check, Optional, Union - -from cognite.client import data_modeling as dm, CogniteClient -from cognite.client.data_classes import ( - TimeSeries as CogniteTimeSeries, - TimeSeriesWrite as CogniteTimeSeriesWrite, -) -from pydantic import field_validator, model_validator - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - DataRecord, - DataRecordGraphQL, - DataRecordWrite, - DomainModel, - DomainModelWrite, - DomainModelWriteList, - DomainModelList, - DomainRelation, - DomainRelationWrite, - GraphQLCore, - ResourcesWrite, - FileMetadata, - FileMetadataWrite, - FileMetadataGraphQL, - TimeSeries, - TimeSeriesWrite, - TimeSeriesGraphQL, - T_DomainModelList, - as_direct_relation_reference, - as_instance_dict_id, - as_node_id, - as_pygen_node_id, - are_nodes_equal, - is_tuple_id, - select_best_node, - QueryCore, - NodeQueryCore, - StringFilter, -) - - -__all__ = [ - "MainShaft", - "MainShaftWrite", - "MainShaftApply", - "MainShaftList", - "MainShaftWriteList", - "MainShaftApplyList", - "MainShaftFields", - "MainShaftTextFields", - "MainShaftGraphQL", -] - - -MainShaftTextFields = Literal[ - "external_id", "bending_x", "bending_y", "calculated_tilt_moment", "calculated_yaw_moment", "torque" -] -MainShaftFields = Literal[ - "external_id", "bending_x", "bending_y", "calculated_tilt_moment", "calculated_yaw_moment", "torque" -] - -_MAINSHAFT_PROPERTIES_BY_FIELD = { - "external_id": "externalId", - "bending_x": "bending_x", - "bending_y": "bending_y", - "calculated_tilt_moment": "calculated_tilt_moment", - "calculated_yaw_moment": "calculated_yaw_moment", - "torque": "torque", -} - - -class MainShaftGraphQL(GraphQLCore): - """This represents the reading version of main shaft, used - when data is retrieved from CDF using GraphQL. - - It is used when retrieving data from CDF using GraphQL. - - Args: - space: The space where the node is located. - external_id: The external id of the main shaft. - data_record: The data record of the main shaft node. - bending_x: The bending x field. - bending_y: The bending y field. - calculated_tilt_moment: The calculated tilt moment field. - calculated_yaw_moment: The calculated yaw moment field. - torque: The torque field. - """ - - view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "MainShaft", "1") - bending_x: Optional[TimeSeriesGraphQL] = None - bending_y: Optional[TimeSeriesGraphQL] = None - calculated_tilt_moment: Optional[TimeSeriesGraphQL] = None - calculated_yaw_moment: Optional[TimeSeriesGraphQL] = None - torque: Optional[TimeSeriesGraphQL] = None - - @model_validator(mode="before") - def parse_data_record(cls, values: Any) -> Any: - if not isinstance(values, dict): - return values - if "lastUpdatedTime" in values or "createdTime" in values: - values["dataRecord"] = DataRecordGraphQL( - created_time=values.pop("createdTime", None), - last_updated_time=values.pop("lastUpdatedTime", None), - ) - return values - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_read(self) -> MainShaft: - """Convert this GraphQL format of main shaft to the reading format.""" - if self.data_record is None: - raise ValueError("This object cannot be converted to a read format because it lacks a data record.") - return MainShaft( - space=self.space, - external_id=self.external_id, - data_record=DataRecord( - version=0, - last_updated_time=self.data_record.last_updated_time, - created_time=self.data_record.created_time, - ), - bending_x=self.bending_x.as_read() if self.bending_x else None, - bending_y=self.bending_y.as_read() if self.bending_y else None, - calculated_tilt_moment=self.calculated_tilt_moment.as_read() if self.calculated_tilt_moment else None, - calculated_yaw_moment=self.calculated_yaw_moment.as_read() if self.calculated_yaw_moment else None, - torque=self.torque.as_read() if self.torque else None, - ) - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_write(self) -> MainShaftWrite: - """Convert this GraphQL format of main shaft to the writing format.""" - return MainShaftWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=0), - bending_x=self.bending_x.as_write() if self.bending_x else None, - bending_y=self.bending_y.as_write() if self.bending_y else None, - calculated_tilt_moment=self.calculated_tilt_moment.as_write() if self.calculated_tilt_moment else None, - calculated_yaw_moment=self.calculated_yaw_moment.as_write() if self.calculated_yaw_moment else None, - torque=self.torque.as_write() if self.torque else None, - ) - - -class MainShaft(DomainModel): - """This represents the reading version of main shaft. - - It is used to when data is retrieved from CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the main shaft. - data_record: The data record of the main shaft node. - bending_x: The bending x field. - bending_y: The bending y field. - calculated_tilt_moment: The calculated tilt moment field. - calculated_yaw_moment: The calculated yaw moment field. - torque: The torque field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "MainShaft", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, None] = None - bending_x: Union[TimeSeries, str, None] = None - bending_y: Union[TimeSeries, str, None] = None - calculated_tilt_moment: Union[TimeSeries, str, None] = None - calculated_yaw_moment: Union[TimeSeries, str, None] = None - torque: Union[TimeSeries, str, None] = None - - def as_write(self) -> MainShaftWrite: - """Convert this read version of main shaft to the writing version.""" - return MainShaftWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=self.data_record.version), - bending_x=self.bending_x.as_write() if isinstance(self.bending_x, CogniteTimeSeries) else self.bending_x, - bending_y=self.bending_y.as_write() if isinstance(self.bending_y, CogniteTimeSeries) else self.bending_y, - calculated_tilt_moment=( - self.calculated_tilt_moment.as_write() - if isinstance(self.calculated_tilt_moment, CogniteTimeSeries) - else self.calculated_tilt_moment - ), - calculated_yaw_moment=( - self.calculated_yaw_moment.as_write() - if isinstance(self.calculated_yaw_moment, CogniteTimeSeries) - else self.calculated_yaw_moment - ), - torque=self.torque.as_write() if isinstance(self.torque, CogniteTimeSeries) else self.torque, - ) - - def as_apply(self) -> MainShaftWrite: - """Convert this read version of main shaft to the writing version.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - -class MainShaftWrite(DomainModelWrite): - """This represents the writing version of main shaft. - - It is used to when data is sent to CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the main shaft. - data_record: The data record of the main shaft node. - bending_x: The bending x field. - bending_y: The bending y field. - calculated_tilt_moment: The calculated tilt moment field. - calculated_yaw_moment: The calculated yaw moment field. - torque: The torque field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "MainShaft", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, dm.NodeId, tuple[str, str], None] = None - bending_x: Union[TimeSeriesWrite, str, None] = None - bending_y: Union[TimeSeriesWrite, str, None] = None - calculated_tilt_moment: Union[TimeSeriesWrite, str, None] = None - calculated_yaw_moment: Union[TimeSeriesWrite, str, None] = None - torque: Union[TimeSeriesWrite, str, None] = None - - def _to_instances_write( - self, - cache: set[tuple[str, str]], - write_none: bool = False, - allow_version_increase: bool = False, - ) -> ResourcesWrite: - resources = ResourcesWrite() - if self.as_tuple_id() in cache: - return resources - - properties: dict[str, Any] = {} - - if self.bending_x is not None or write_none: - properties["bending_x"] = ( - self.bending_x - if isinstance(self.bending_x, str) or self.bending_x is None - else self.bending_x.external_id - ) - - if self.bending_y is not None or write_none: - properties["bending_y"] = ( - self.bending_y - if isinstance(self.bending_y, str) or self.bending_y is None - else self.bending_y.external_id - ) - - if self.calculated_tilt_moment is not None or write_none: - properties["calculated_tilt_moment"] = ( - self.calculated_tilt_moment - if isinstance(self.calculated_tilt_moment, str) or self.calculated_tilt_moment is None - else self.calculated_tilt_moment.external_id - ) - - if self.calculated_yaw_moment is not None or write_none: - properties["calculated_yaw_moment"] = ( - self.calculated_yaw_moment - if isinstance(self.calculated_yaw_moment, str) or self.calculated_yaw_moment is None - else self.calculated_yaw_moment.external_id - ) - - if self.torque is not None or write_none: - properties["torque"] = ( - self.torque if isinstance(self.torque, str) or self.torque is None else self.torque.external_id - ) - - if properties: - this_node = dm.NodeApply( - space=self.space, - external_id=self.external_id, - existing_version=None if allow_version_increase else self.data_record.existing_version, - type=as_direct_relation_reference(self.node_type), - sources=[ - dm.NodeOrEdgeData( - source=self._view_id, - properties=properties, - ) - ], - ) - resources.nodes.append(this_node) - cache.add(self.as_tuple_id()) - - if isinstance(self.bending_x, CogniteTimeSeriesWrite): - resources.time_series.append(self.bending_x) - - if isinstance(self.bending_y, CogniteTimeSeriesWrite): - resources.time_series.append(self.bending_y) - - if isinstance(self.calculated_tilt_moment, CogniteTimeSeriesWrite): - resources.time_series.append(self.calculated_tilt_moment) - - if isinstance(self.calculated_yaw_moment, CogniteTimeSeriesWrite): - resources.time_series.append(self.calculated_yaw_moment) - - if isinstance(self.torque, CogniteTimeSeriesWrite): - resources.time_series.append(self.torque) - - return resources - - -class MainShaftApply(MainShaftWrite): - def __new__(cls, *args, **kwargs) -> MainShaftApply: - warnings.warn( - "MainShaftApply is deprecated and will be removed in v1.0. Use MainShaftWrite instead." - "The motivation for this change is that Write is a more descriptive name for the writing version of the" - "MainShaft.", - UserWarning, - stacklevel=2, - ) - return super().__new__(cls) - - -class MainShaftList(DomainModelList[MainShaft]): - """List of main shafts in the read version.""" - - _INSTANCE = MainShaft - - def as_write(self) -> MainShaftWriteList: - """Convert these read versions of main shaft to the writing versions.""" - return MainShaftWriteList([node.as_write() for node in self.data]) - - def as_apply(self) -> MainShaftWriteList: - """Convert these read versions of primitive nullable to the writing versions.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - -class MainShaftWriteList(DomainModelWriteList[MainShaftWrite]): - """List of main shafts in the writing version.""" - - _INSTANCE = MainShaftWrite - - -class MainShaftApplyList(MainShaftWriteList): ... - - -def _create_main_shaft_filter( - view_id: dm.ViewId, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - filter: dm.Filter | None = None, -) -> dm.Filter | None: - filters: list[dm.Filter] = [] - if external_id_prefix is not None: - filters.append(dm.filters.Prefix(["node", "externalId"], value=external_id_prefix)) - if isinstance(space, str): - filters.append(dm.filters.Equals(["node", "space"], value=space)) - if space and isinstance(space, list): - filters.append(dm.filters.In(["node", "space"], values=space)) - if filter: - filters.append(filter) - return dm.filters.And(*filters) if filters else None - - -class _MainShaftQuery(NodeQueryCore[T_DomainModelList, MainShaftList]): - _view_id = MainShaft._view_id - _result_cls = MainShaft - _result_list_cls_end = MainShaftList - - def __init__( - self, - created_types: set[type], - creation_path: list[QueryCore], - client: CogniteClient, - result_list_cls: type[T_DomainModelList], - expression: dm.query.ResultSetExpression | None = None, - connection_name: str | None = None, - connection_type: Literal["reverse-list"] | None = None, - reverse_expression: dm.query.ResultSetExpression | None = None, - ): - - super().__init__( - created_types, - creation_path, - client, - result_list_cls, - expression, - dm.filters.HasData(views=[self._view_id]), - connection_name, - connection_type, - reverse_expression, - ) - - self.space = StringFilter(self, ["node", "space"]) - self.external_id = StringFilter(self, ["node", "externalId"]) - - def list_main_shaft(self, limit: int = DEFAULT_QUERY_LIMIT) -> MainShaftList: - return self._list(limit=limit) - - -class MainShaftQuery(_MainShaftQuery[MainShaftList]): - def __init__(self, client: CogniteClient): - super().__init__(set(), [], client, MainShaftList) diff --git a/examples/windmill/data_classes/_metmast.py b/examples/windmill/data_classes/_metmast.py deleted file mode 100644 index 5d99d972d..000000000 --- a/examples/windmill/data_classes/_metmast.py +++ /dev/null @@ -1,390 +0,0 @@ -from __future__ import annotations - -import warnings -from collections.abc import Sequence -from typing import Any, ClassVar, Literal, no_type_check, Optional, Union - -from cognite.client import data_modeling as dm, CogniteClient -from cognite.client.data_classes import ( - TimeSeries as CogniteTimeSeries, - TimeSeriesWrite as CogniteTimeSeriesWrite, -) -from pydantic import field_validator, model_validator - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - DataRecord, - DataRecordGraphQL, - DataRecordWrite, - DomainModel, - DomainModelWrite, - DomainModelWriteList, - DomainModelList, - DomainRelation, - DomainRelationWrite, - GraphQLCore, - ResourcesWrite, - FileMetadata, - FileMetadataWrite, - FileMetadataGraphQL, - TimeSeries, - TimeSeriesWrite, - TimeSeriesGraphQL, - T_DomainModelList, - as_direct_relation_reference, - as_instance_dict_id, - as_node_id, - as_pygen_node_id, - are_nodes_equal, - is_tuple_id, - select_best_node, - QueryCore, - NodeQueryCore, - StringFilter, - FloatFilter, -) - - -__all__ = [ - "Metmast", - "MetmastWrite", - "MetmastApply", - "MetmastList", - "MetmastWriteList", - "MetmastApplyList", - "MetmastFields", - "MetmastTextFields", - "MetmastGraphQL", -] - - -MetmastTextFields = Literal["external_id", "temperature", "tilt_angle", "wind_speed"] -MetmastFields = Literal["external_id", "position", "temperature", "tilt_angle", "wind_speed"] - -_METMAST_PROPERTIES_BY_FIELD = { - "external_id": "externalId", - "position": "position", - "temperature": "temperature", - "tilt_angle": "tilt_angle", - "wind_speed": "wind_speed", -} - - -class MetmastGraphQL(GraphQLCore): - """This represents the reading version of metmast, used - when data is retrieved from CDF using GraphQL. - - It is used when retrieving data from CDF using GraphQL. - - Args: - space: The space where the node is located. - external_id: The external id of the metmast. - data_record: The data record of the metmast node. - position: The position field. - temperature: The temperature field. - tilt_angle: The tilt angle field. - wind_speed: The wind speed field. - """ - - view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Metmast", "1") - position: Optional[float] = None - temperature: Optional[TimeSeriesGraphQL] = None - tilt_angle: Optional[TimeSeriesGraphQL] = None - wind_speed: Optional[TimeSeriesGraphQL] = None - - @model_validator(mode="before") - def parse_data_record(cls, values: Any) -> Any: - if not isinstance(values, dict): - return values - if "lastUpdatedTime" in values or "createdTime" in values: - values["dataRecord"] = DataRecordGraphQL( - created_time=values.pop("createdTime", None), - last_updated_time=values.pop("lastUpdatedTime", None), - ) - return values - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_read(self) -> Metmast: - """Convert this GraphQL format of metmast to the reading format.""" - if self.data_record is None: - raise ValueError("This object cannot be converted to a read format because it lacks a data record.") - return Metmast( - space=self.space, - external_id=self.external_id, - data_record=DataRecord( - version=0, - last_updated_time=self.data_record.last_updated_time, - created_time=self.data_record.created_time, - ), - position=self.position, - temperature=self.temperature.as_read() if self.temperature else None, - tilt_angle=self.tilt_angle.as_read() if self.tilt_angle else None, - wind_speed=self.wind_speed.as_read() if self.wind_speed else None, - ) - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_write(self) -> MetmastWrite: - """Convert this GraphQL format of metmast to the writing format.""" - return MetmastWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=0), - position=self.position, - temperature=self.temperature.as_write() if self.temperature else None, - tilt_angle=self.tilt_angle.as_write() if self.tilt_angle else None, - wind_speed=self.wind_speed.as_write() if self.wind_speed else None, - ) - - -class Metmast(DomainModel): - """This represents the reading version of metmast. - - It is used to when data is retrieved from CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the metmast. - data_record: The data record of the metmast node. - position: The position field. - temperature: The temperature field. - tilt_angle: The tilt angle field. - wind_speed: The wind speed field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Metmast", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, None] = None - position: Optional[float] = None - temperature: Union[TimeSeries, str, None] = None - tilt_angle: Union[TimeSeries, str, None] = None - wind_speed: Union[TimeSeries, str, None] = None - - def as_write(self) -> MetmastWrite: - """Convert this read version of metmast to the writing version.""" - return MetmastWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=self.data_record.version), - position=self.position, - temperature=( - self.temperature.as_write() if isinstance(self.temperature, CogniteTimeSeries) else self.temperature - ), - tilt_angle=( - self.tilt_angle.as_write() if isinstance(self.tilt_angle, CogniteTimeSeries) else self.tilt_angle - ), - wind_speed=( - self.wind_speed.as_write() if isinstance(self.wind_speed, CogniteTimeSeries) else self.wind_speed - ), - ) - - def as_apply(self) -> MetmastWrite: - """Convert this read version of metmast to the writing version.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - -class MetmastWrite(DomainModelWrite): - """This represents the writing version of metmast. - - It is used to when data is sent to CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the metmast. - data_record: The data record of the metmast node. - position: The position field. - temperature: The temperature field. - tilt_angle: The tilt angle field. - wind_speed: The wind speed field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Metmast", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, dm.NodeId, tuple[str, str], None] = None - position: Optional[float] = None - temperature: Union[TimeSeriesWrite, str, None] = None - tilt_angle: Union[TimeSeriesWrite, str, None] = None - wind_speed: Union[TimeSeriesWrite, str, None] = None - - def _to_instances_write( - self, - cache: set[tuple[str, str]], - write_none: bool = False, - allow_version_increase: bool = False, - ) -> ResourcesWrite: - resources = ResourcesWrite() - if self.as_tuple_id() in cache: - return resources - - properties: dict[str, Any] = {} - - if self.position is not None or write_none: - properties["position"] = self.position - - if self.temperature is not None or write_none: - properties["temperature"] = ( - self.temperature - if isinstance(self.temperature, str) or self.temperature is None - else self.temperature.external_id - ) - - if self.tilt_angle is not None or write_none: - properties["tilt_angle"] = ( - self.tilt_angle - if isinstance(self.tilt_angle, str) or self.tilt_angle is None - else self.tilt_angle.external_id - ) - - if self.wind_speed is not None or write_none: - properties["wind_speed"] = ( - self.wind_speed - if isinstance(self.wind_speed, str) or self.wind_speed is None - else self.wind_speed.external_id - ) - - if properties: - this_node = dm.NodeApply( - space=self.space, - external_id=self.external_id, - existing_version=None if allow_version_increase else self.data_record.existing_version, - type=as_direct_relation_reference(self.node_type), - sources=[ - dm.NodeOrEdgeData( - source=self._view_id, - properties=properties, - ) - ], - ) - resources.nodes.append(this_node) - cache.add(self.as_tuple_id()) - - if isinstance(self.temperature, CogniteTimeSeriesWrite): - resources.time_series.append(self.temperature) - - if isinstance(self.tilt_angle, CogniteTimeSeriesWrite): - resources.time_series.append(self.tilt_angle) - - if isinstance(self.wind_speed, CogniteTimeSeriesWrite): - resources.time_series.append(self.wind_speed) - - return resources - - -class MetmastApply(MetmastWrite): - def __new__(cls, *args, **kwargs) -> MetmastApply: - warnings.warn( - "MetmastApply is deprecated and will be removed in v1.0. Use MetmastWrite instead." - "The motivation for this change is that Write is a more descriptive name for the writing version of the" - "Metmast.", - UserWarning, - stacklevel=2, - ) - return super().__new__(cls) - - -class MetmastList(DomainModelList[Metmast]): - """List of metmasts in the read version.""" - - _INSTANCE = Metmast - - def as_write(self) -> MetmastWriteList: - """Convert these read versions of metmast to the writing versions.""" - return MetmastWriteList([node.as_write() for node in self.data]) - - def as_apply(self) -> MetmastWriteList: - """Convert these read versions of primitive nullable to the writing versions.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - -class MetmastWriteList(DomainModelWriteList[MetmastWrite]): - """List of metmasts in the writing version.""" - - _INSTANCE = MetmastWrite - - -class MetmastApplyList(MetmastWriteList): ... - - -def _create_metmast_filter( - view_id: dm.ViewId, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - filter: dm.Filter | None = None, -) -> dm.Filter | None: - filters: list[dm.Filter] = [] - if min_position is not None or max_position is not None: - filters.append(dm.filters.Range(view_id.as_property_ref("position"), gte=min_position, lte=max_position)) - if external_id_prefix is not None: - filters.append(dm.filters.Prefix(["node", "externalId"], value=external_id_prefix)) - if isinstance(space, str): - filters.append(dm.filters.Equals(["node", "space"], value=space)) - if space and isinstance(space, list): - filters.append(dm.filters.In(["node", "space"], values=space)) - if filter: - filters.append(filter) - return dm.filters.And(*filters) if filters else None - - -class _MetmastQuery(NodeQueryCore[T_DomainModelList, MetmastList]): - _view_id = Metmast._view_id - _result_cls = Metmast - _result_list_cls_end = MetmastList - - def __init__( - self, - created_types: set[type], - creation_path: list[QueryCore], - client: CogniteClient, - result_list_cls: type[T_DomainModelList], - expression: dm.query.ResultSetExpression | None = None, - connection_name: str | None = None, - connection_type: Literal["reverse-list"] | None = None, - reverse_expression: dm.query.ResultSetExpression | None = None, - ): - - super().__init__( - created_types, - creation_path, - client, - result_list_cls, - expression, - dm.filters.HasData(views=[self._view_id]), - connection_name, - connection_type, - reverse_expression, - ) - - self.space = StringFilter(self, ["node", "space"]) - self.external_id = StringFilter(self, ["node", "externalId"]) - self.position = FloatFilter(self, self._view_id.as_property_ref("position")) - self._filter_classes.extend( - [ - self.space, - self.external_id, - self.position, - ] - ) - - def list_metmast(self, limit: int = DEFAULT_QUERY_LIMIT) -> MetmastList: - return self._list(limit=limit) - - -class MetmastQuery(_MetmastQuery[MetmastList]): - def __init__(self, client: CogniteClient): - super().__init__(set(), [], client, MetmastList) diff --git a/examples/windmill/data_classes/_nacelle.py b/examples/windmill/data_classes/_nacelle.py deleted file mode 100644 index 79cbbae44..000000000 --- a/examples/windmill/data_classes/_nacelle.py +++ /dev/null @@ -1,870 +0,0 @@ -from __future__ import annotations - -import warnings -from collections.abc import Sequence -from typing import TYPE_CHECKING, Any, ClassVar, Literal, no_type_check, Optional, Union - -from cognite.client import data_modeling as dm, CogniteClient -from cognite.client.data_classes import ( - TimeSeries as CogniteTimeSeries, - TimeSeriesWrite as CogniteTimeSeriesWrite, -) -from pydantic import Field -from pydantic import field_validator, model_validator - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - DataRecord, - DataRecordGraphQL, - DataRecordWrite, - DomainModel, - DomainModelWrite, - DomainModelWriteList, - DomainModelList, - DomainRelation, - DomainRelationWrite, - GraphQLCore, - ResourcesWrite, - FileMetadata, - FileMetadataWrite, - FileMetadataGraphQL, - TimeSeries, - TimeSeriesWrite, - TimeSeriesGraphQL, - T_DomainModelList, - as_direct_relation_reference, - as_instance_dict_id, - as_node_id, - as_pygen_node_id, - are_nodes_equal, - is_tuple_id, - select_best_node, - QueryCore, - NodeQueryCore, - StringFilter, -) - -if TYPE_CHECKING: - from windmill.data_classes._gearbox import Gearbox, GearboxList, GearboxGraphQL, GearboxWrite, GearboxWriteList - from windmill.data_classes._generator import ( - Generator, - GeneratorList, - GeneratorGraphQL, - GeneratorWrite, - GeneratorWriteList, - ) - from windmill.data_classes._high_speed_shaft import ( - HighSpeedShaft, - HighSpeedShaftList, - HighSpeedShaftGraphQL, - HighSpeedShaftWrite, - HighSpeedShaftWriteList, - ) - from windmill.data_classes._main_shaft import ( - MainShaft, - MainShaftList, - MainShaftGraphQL, - MainShaftWrite, - MainShaftWriteList, - ) - from windmill.data_classes._power_inverter import ( - PowerInverter, - PowerInverterList, - PowerInverterGraphQL, - PowerInverterWrite, - PowerInverterWriteList, - ) - - -__all__ = [ - "Nacelle", - "NacelleWrite", - "NacelleApply", - "NacelleList", - "NacelleWriteList", - "NacelleApplyList", - "NacelleFields", - "NacelleTextFields", - "NacelleGraphQL", -] - - -NacelleTextFields = Literal[ - "external_id", "acc_from_back_side_x", "acc_from_back_side_y", "acc_from_back_side_z", "yaw_direction", "yaw_error" -] -NacelleFields = Literal[ - "external_id", "acc_from_back_side_x", "acc_from_back_side_y", "acc_from_back_side_z", "yaw_direction", "yaw_error" -] - -_NACELLE_PROPERTIES_BY_FIELD = { - "external_id": "externalId", - "acc_from_back_side_x": "acc_from_back_side_x", - "acc_from_back_side_y": "acc_from_back_side_y", - "acc_from_back_side_z": "acc_from_back_side_z", - "yaw_direction": "yaw_direction", - "yaw_error": "yaw_error", -} - - -class NacelleGraphQL(GraphQLCore): - """This represents the reading version of nacelle, used - when data is retrieved from CDF using GraphQL. - - It is used when retrieving data from CDF using GraphQL. - - Args: - space: The space where the node is located. - external_id: The external id of the nacelle. - data_record: The data record of the nacelle node. - acc_from_back_side_x: The acc from back side x field. - acc_from_back_side_y: The acc from back side y field. - acc_from_back_side_z: The acc from back side z field. - gearbox: The gearbox field. - generator: The generator field. - high_speed_shaft: The high speed shaft field. - main_shaft: The main shaft field. - power_inverter: The power inverter field. - yaw_direction: The yaw direction field. - yaw_error: The yaw error field. - """ - - view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Nacelle", "1") - acc_from_back_side_x: Optional[TimeSeriesGraphQL] = None - acc_from_back_side_y: Optional[TimeSeriesGraphQL] = None - acc_from_back_side_z: Optional[TimeSeriesGraphQL] = None - gearbox: Optional[GearboxGraphQL] = Field(default=None, repr=False) - generator: Optional[GeneratorGraphQL] = Field(default=None, repr=False) - high_speed_shaft: Optional[HighSpeedShaftGraphQL] = Field(default=None, repr=False) - main_shaft: Optional[MainShaftGraphQL] = Field(default=None, repr=False) - power_inverter: Optional[PowerInverterGraphQL] = Field(default=None, repr=False) - yaw_direction: Optional[TimeSeriesGraphQL] = None - yaw_error: Optional[TimeSeriesGraphQL] = None - - @model_validator(mode="before") - def parse_data_record(cls, values: Any) -> Any: - if not isinstance(values, dict): - return values - if "lastUpdatedTime" in values or "createdTime" in values: - values["dataRecord"] = DataRecordGraphQL( - created_time=values.pop("createdTime", None), - last_updated_time=values.pop("lastUpdatedTime", None), - ) - return values - - @field_validator("gearbox", "generator", "high_speed_shaft", "main_shaft", "power_inverter", mode="before") - def parse_graphql(cls, value: Any) -> Any: - if not isinstance(value, dict): - return value - if "items" in value: - return value["items"] - return value - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_read(self) -> Nacelle: - """Convert this GraphQL format of nacelle to the reading format.""" - if self.data_record is None: - raise ValueError("This object cannot be converted to a read format because it lacks a data record.") - return Nacelle( - space=self.space, - external_id=self.external_id, - data_record=DataRecord( - version=0, - last_updated_time=self.data_record.last_updated_time, - created_time=self.data_record.created_time, - ), - acc_from_back_side_x=self.acc_from_back_side_x.as_read() if self.acc_from_back_side_x else None, - acc_from_back_side_y=self.acc_from_back_side_y.as_read() if self.acc_from_back_side_y else None, - acc_from_back_side_z=self.acc_from_back_side_z.as_read() if self.acc_from_back_side_z else None, - gearbox=self.gearbox.as_read() if isinstance(self.gearbox, GraphQLCore) else self.gearbox, - generator=self.generator.as_read() if isinstance(self.generator, GraphQLCore) else self.generator, - high_speed_shaft=( - self.high_speed_shaft.as_read() - if isinstance(self.high_speed_shaft, GraphQLCore) - else self.high_speed_shaft - ), - main_shaft=self.main_shaft.as_read() if isinstance(self.main_shaft, GraphQLCore) else self.main_shaft, - power_inverter=( - self.power_inverter.as_read() if isinstance(self.power_inverter, GraphQLCore) else self.power_inverter - ), - yaw_direction=self.yaw_direction.as_read() if self.yaw_direction else None, - yaw_error=self.yaw_error.as_read() if self.yaw_error else None, - ) - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_write(self) -> NacelleWrite: - """Convert this GraphQL format of nacelle to the writing format.""" - return NacelleWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=0), - acc_from_back_side_x=self.acc_from_back_side_x.as_write() if self.acc_from_back_side_x else None, - acc_from_back_side_y=self.acc_from_back_side_y.as_write() if self.acc_from_back_side_y else None, - acc_from_back_side_z=self.acc_from_back_side_z.as_write() if self.acc_from_back_side_z else None, - gearbox=self.gearbox.as_write() if isinstance(self.gearbox, GraphQLCore) else self.gearbox, - generator=self.generator.as_write() if isinstance(self.generator, GraphQLCore) else self.generator, - high_speed_shaft=( - self.high_speed_shaft.as_write() - if isinstance(self.high_speed_shaft, GraphQLCore) - else self.high_speed_shaft - ), - main_shaft=self.main_shaft.as_write() if isinstance(self.main_shaft, GraphQLCore) else self.main_shaft, - power_inverter=( - self.power_inverter.as_write() if isinstance(self.power_inverter, GraphQLCore) else self.power_inverter - ), - yaw_direction=self.yaw_direction.as_write() if self.yaw_direction else None, - yaw_error=self.yaw_error.as_write() if self.yaw_error else None, - ) - - -class Nacelle(DomainModel): - """This represents the reading version of nacelle. - - It is used to when data is retrieved from CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the nacelle. - data_record: The data record of the nacelle node. - acc_from_back_side_x: The acc from back side x field. - acc_from_back_side_y: The acc from back side y field. - acc_from_back_side_z: The acc from back side z field. - gearbox: The gearbox field. - generator: The generator field. - high_speed_shaft: The high speed shaft field. - main_shaft: The main shaft field. - power_inverter: The power inverter field. - yaw_direction: The yaw direction field. - yaw_error: The yaw error field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Nacelle", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, None] = None - acc_from_back_side_x: Union[TimeSeries, str, None] = None - acc_from_back_side_y: Union[TimeSeries, str, None] = None - acc_from_back_side_z: Union[TimeSeries, str, None] = None - gearbox: Union[Gearbox, str, dm.NodeId, None] = Field(default=None, repr=False) - generator: Union[Generator, str, dm.NodeId, None] = Field(default=None, repr=False) - high_speed_shaft: Union[HighSpeedShaft, str, dm.NodeId, None] = Field(default=None, repr=False) - main_shaft: Union[MainShaft, str, dm.NodeId, None] = Field(default=None, repr=False) - power_inverter: Union[PowerInverter, str, dm.NodeId, None] = Field(default=None, repr=False) - yaw_direction: Union[TimeSeries, str, None] = None - yaw_error: Union[TimeSeries, str, None] = None - - def as_write(self) -> NacelleWrite: - """Convert this read version of nacelle to the writing version.""" - return NacelleWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=self.data_record.version), - acc_from_back_side_x=( - self.acc_from_back_side_x.as_write() - if isinstance(self.acc_from_back_side_x, CogniteTimeSeries) - else self.acc_from_back_side_x - ), - acc_from_back_side_y=( - self.acc_from_back_side_y.as_write() - if isinstance(self.acc_from_back_side_y, CogniteTimeSeries) - else self.acc_from_back_side_y - ), - acc_from_back_side_z=( - self.acc_from_back_side_z.as_write() - if isinstance(self.acc_from_back_side_z, CogniteTimeSeries) - else self.acc_from_back_side_z - ), - gearbox=self.gearbox.as_write() if isinstance(self.gearbox, DomainModel) else self.gearbox, - generator=self.generator.as_write() if isinstance(self.generator, DomainModel) else self.generator, - high_speed_shaft=( - self.high_speed_shaft.as_write() - if isinstance(self.high_speed_shaft, DomainModel) - else self.high_speed_shaft - ), - main_shaft=self.main_shaft.as_write() if isinstance(self.main_shaft, DomainModel) else self.main_shaft, - power_inverter=( - self.power_inverter.as_write() if isinstance(self.power_inverter, DomainModel) else self.power_inverter - ), - yaw_direction=( - self.yaw_direction.as_write() - if isinstance(self.yaw_direction, CogniteTimeSeries) - else self.yaw_direction - ), - yaw_error=self.yaw_error.as_write() if isinstance(self.yaw_error, CogniteTimeSeries) else self.yaw_error, - ) - - def as_apply(self) -> NacelleWrite: - """Convert this read version of nacelle to the writing version.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - @classmethod - def _update_connections( - cls, - instances: dict[dm.NodeId | str, Nacelle], # type: ignore[override] - nodes_by_id: dict[dm.NodeId | str, DomainModel], - edges_by_source_node: dict[dm.NodeId, list[dm.Edge | DomainRelation]], - ) -> None: - from ._gearbox import Gearbox - from ._generator import Generator - from ._high_speed_shaft import HighSpeedShaft - from ._main_shaft import MainShaft - from ._power_inverter import PowerInverter - - for instance in instances.values(): - if ( - isinstance(instance.gearbox, (dm.NodeId, str)) - and (gearbox := nodes_by_id.get(instance.gearbox)) - and isinstance(gearbox, Gearbox) - ): - instance.gearbox = gearbox - if ( - isinstance(instance.generator, (dm.NodeId, str)) - and (generator := nodes_by_id.get(instance.generator)) - and isinstance(generator, Generator) - ): - instance.generator = generator - if ( - isinstance(instance.high_speed_shaft, (dm.NodeId, str)) - and (high_speed_shaft := nodes_by_id.get(instance.high_speed_shaft)) - and isinstance(high_speed_shaft, HighSpeedShaft) - ): - instance.high_speed_shaft = high_speed_shaft - if ( - isinstance(instance.main_shaft, (dm.NodeId, str)) - and (main_shaft := nodes_by_id.get(instance.main_shaft)) - and isinstance(main_shaft, MainShaft) - ): - instance.main_shaft = main_shaft - if ( - isinstance(instance.power_inverter, (dm.NodeId, str)) - and (power_inverter := nodes_by_id.get(instance.power_inverter)) - and isinstance(power_inverter, PowerInverter) - ): - instance.power_inverter = power_inverter - - -class NacelleWrite(DomainModelWrite): - """This represents the writing version of nacelle. - - It is used to when data is sent to CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the nacelle. - data_record: The data record of the nacelle node. - acc_from_back_side_x: The acc from back side x field. - acc_from_back_side_y: The acc from back side y field. - acc_from_back_side_z: The acc from back side z field. - gearbox: The gearbox field. - generator: The generator field. - high_speed_shaft: The high speed shaft field. - main_shaft: The main shaft field. - power_inverter: The power inverter field. - yaw_direction: The yaw direction field. - yaw_error: The yaw error field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Nacelle", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, dm.NodeId, tuple[str, str], None] = None - acc_from_back_side_x: Union[TimeSeriesWrite, str, None] = None - acc_from_back_side_y: Union[TimeSeriesWrite, str, None] = None - acc_from_back_side_z: Union[TimeSeriesWrite, str, None] = None - gearbox: Union[GearboxWrite, str, dm.NodeId, None] = Field(default=None, repr=False) - generator: Union[GeneratorWrite, str, dm.NodeId, None] = Field(default=None, repr=False) - high_speed_shaft: Union[HighSpeedShaftWrite, str, dm.NodeId, None] = Field(default=None, repr=False) - main_shaft: Union[MainShaftWrite, str, dm.NodeId, None] = Field(default=None, repr=False) - power_inverter: Union[PowerInverterWrite, str, dm.NodeId, None] = Field(default=None, repr=False) - yaw_direction: Union[TimeSeriesWrite, str, None] = None - yaw_error: Union[TimeSeriesWrite, str, None] = None - - @field_validator("gearbox", "generator", "high_speed_shaft", "main_shaft", "power_inverter", mode="before") - def as_node_id(cls, value: Any) -> Any: - if isinstance(value, dm.DirectRelationReference): - return dm.NodeId(value.space, value.external_id) - elif isinstance(value, tuple) and len(value) == 2 and all(isinstance(item, str) for item in value): - return dm.NodeId(value[0], value[1]) - elif isinstance(value, list): - return [cls.as_node_id(item) for item in value] - return value - - def _to_instances_write( - self, - cache: set[tuple[str, str]], - write_none: bool = False, - allow_version_increase: bool = False, - ) -> ResourcesWrite: - resources = ResourcesWrite() - if self.as_tuple_id() in cache: - return resources - - properties: dict[str, Any] = {} - - if self.acc_from_back_side_x is not None or write_none: - properties["acc_from_back_side_x"] = ( - self.acc_from_back_side_x - if isinstance(self.acc_from_back_side_x, str) or self.acc_from_back_side_x is None - else self.acc_from_back_side_x.external_id - ) - - if self.acc_from_back_side_y is not None or write_none: - properties["acc_from_back_side_y"] = ( - self.acc_from_back_side_y - if isinstance(self.acc_from_back_side_y, str) or self.acc_from_back_side_y is None - else self.acc_from_back_side_y.external_id - ) - - if self.acc_from_back_side_z is not None or write_none: - properties["acc_from_back_side_z"] = ( - self.acc_from_back_side_z - if isinstance(self.acc_from_back_side_z, str) or self.acc_from_back_side_z is None - else self.acc_from_back_side_z.external_id - ) - - if self.gearbox is not None: - properties["gearbox"] = { - "space": self.space if isinstance(self.gearbox, str) else self.gearbox.space, - "externalId": self.gearbox if isinstance(self.gearbox, str) else self.gearbox.external_id, - } - - if self.generator is not None: - properties["generator"] = { - "space": self.space if isinstance(self.generator, str) else self.generator.space, - "externalId": self.generator if isinstance(self.generator, str) else self.generator.external_id, - } - - if self.high_speed_shaft is not None: - properties["high_speed_shaft"] = { - "space": self.space if isinstance(self.high_speed_shaft, str) else self.high_speed_shaft.space, - "externalId": ( - self.high_speed_shaft - if isinstance(self.high_speed_shaft, str) - else self.high_speed_shaft.external_id - ), - } - - if self.main_shaft is not None: - properties["main_shaft"] = { - "space": self.space if isinstance(self.main_shaft, str) else self.main_shaft.space, - "externalId": self.main_shaft if isinstance(self.main_shaft, str) else self.main_shaft.external_id, - } - - if self.power_inverter is not None: - properties["power_inverter"] = { - "space": self.space if isinstance(self.power_inverter, str) else self.power_inverter.space, - "externalId": ( - self.power_inverter if isinstance(self.power_inverter, str) else self.power_inverter.external_id - ), - } - - if self.yaw_direction is not None or write_none: - properties["yaw_direction"] = ( - self.yaw_direction - if isinstance(self.yaw_direction, str) or self.yaw_direction is None - else self.yaw_direction.external_id - ) - - if self.yaw_error is not None or write_none: - properties["yaw_error"] = ( - self.yaw_error - if isinstance(self.yaw_error, str) or self.yaw_error is None - else self.yaw_error.external_id - ) - - if properties: - this_node = dm.NodeApply( - space=self.space, - external_id=self.external_id, - existing_version=None if allow_version_increase else self.data_record.existing_version, - type=as_direct_relation_reference(self.node_type), - sources=[ - dm.NodeOrEdgeData( - source=self._view_id, - properties=properties, - ) - ], - ) - resources.nodes.append(this_node) - cache.add(self.as_tuple_id()) - - if isinstance(self.gearbox, DomainModelWrite): - other_resources = self.gearbox._to_instances_write(cache) - resources.extend(other_resources) - - if isinstance(self.generator, DomainModelWrite): - other_resources = self.generator._to_instances_write(cache) - resources.extend(other_resources) - - if isinstance(self.high_speed_shaft, DomainModelWrite): - other_resources = self.high_speed_shaft._to_instances_write(cache) - resources.extend(other_resources) - - if isinstance(self.main_shaft, DomainModelWrite): - other_resources = self.main_shaft._to_instances_write(cache) - resources.extend(other_resources) - - if isinstance(self.power_inverter, DomainModelWrite): - other_resources = self.power_inverter._to_instances_write(cache) - resources.extend(other_resources) - - if isinstance(self.acc_from_back_side_x, CogniteTimeSeriesWrite): - resources.time_series.append(self.acc_from_back_side_x) - - if isinstance(self.acc_from_back_side_y, CogniteTimeSeriesWrite): - resources.time_series.append(self.acc_from_back_side_y) - - if isinstance(self.acc_from_back_side_z, CogniteTimeSeriesWrite): - resources.time_series.append(self.acc_from_back_side_z) - - if isinstance(self.yaw_direction, CogniteTimeSeriesWrite): - resources.time_series.append(self.yaw_direction) - - if isinstance(self.yaw_error, CogniteTimeSeriesWrite): - resources.time_series.append(self.yaw_error) - - return resources - - -class NacelleApply(NacelleWrite): - def __new__(cls, *args, **kwargs) -> NacelleApply: - warnings.warn( - "NacelleApply is deprecated and will be removed in v1.0. Use NacelleWrite instead." - "The motivation for this change is that Write is a more descriptive name for the writing version of the" - "Nacelle.", - UserWarning, - stacklevel=2, - ) - return super().__new__(cls) - - -class NacelleList(DomainModelList[Nacelle]): - """List of nacelles in the read version.""" - - _INSTANCE = Nacelle - - def as_write(self) -> NacelleWriteList: - """Convert these read versions of nacelle to the writing versions.""" - return NacelleWriteList([node.as_write() for node in self.data]) - - def as_apply(self) -> NacelleWriteList: - """Convert these read versions of primitive nullable to the writing versions.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - @property - def gearbox(self) -> GearboxList: - from ._gearbox import Gearbox, GearboxList - - return GearboxList([item.gearbox for item in self.data if isinstance(item.gearbox, Gearbox)]) - - @property - def generator(self) -> GeneratorList: - from ._generator import Generator, GeneratorList - - return GeneratorList([item.generator for item in self.data if isinstance(item.generator, Generator)]) - - @property - def high_speed_shaft(self) -> HighSpeedShaftList: - from ._high_speed_shaft import HighSpeedShaft, HighSpeedShaftList - - return HighSpeedShaftList( - [item.high_speed_shaft for item in self.data if isinstance(item.high_speed_shaft, HighSpeedShaft)] - ) - - @property - def main_shaft(self) -> MainShaftList: - from ._main_shaft import MainShaft, MainShaftList - - return MainShaftList([item.main_shaft for item in self.data if isinstance(item.main_shaft, MainShaft)]) - - @property - def power_inverter(self) -> PowerInverterList: - from ._power_inverter import PowerInverter, PowerInverterList - - return PowerInverterList( - [item.power_inverter for item in self.data if isinstance(item.power_inverter, PowerInverter)] - ) - - -class NacelleWriteList(DomainModelWriteList[NacelleWrite]): - """List of nacelles in the writing version.""" - - _INSTANCE = NacelleWrite - - @property - def gearbox(self) -> GearboxWriteList: - from ._gearbox import GearboxWrite, GearboxWriteList - - return GearboxWriteList([item.gearbox for item in self.data if isinstance(item.gearbox, GearboxWrite)]) - - @property - def generator(self) -> GeneratorWriteList: - from ._generator import GeneratorWrite, GeneratorWriteList - - return GeneratorWriteList([item.generator for item in self.data if isinstance(item.generator, GeneratorWrite)]) - - @property - def high_speed_shaft(self) -> HighSpeedShaftWriteList: - from ._high_speed_shaft import HighSpeedShaftWrite, HighSpeedShaftWriteList - - return HighSpeedShaftWriteList( - [item.high_speed_shaft for item in self.data if isinstance(item.high_speed_shaft, HighSpeedShaftWrite)] - ) - - @property - def main_shaft(self) -> MainShaftWriteList: - from ._main_shaft import MainShaftWrite, MainShaftWriteList - - return MainShaftWriteList( - [item.main_shaft for item in self.data if isinstance(item.main_shaft, MainShaftWrite)] - ) - - @property - def power_inverter(self) -> PowerInverterWriteList: - from ._power_inverter import PowerInverterWrite, PowerInverterWriteList - - return PowerInverterWriteList( - [item.power_inverter for item in self.data if isinstance(item.power_inverter, PowerInverterWrite)] - ) - - -class NacelleApplyList(NacelleWriteList): ... - - -def _create_nacelle_filter( - view_id: dm.ViewId, - gearbox: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - generator: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - high_speed_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - main_shaft: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - power_inverter: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - filter: dm.Filter | None = None, -) -> dm.Filter | None: - filters: list[dm.Filter] = [] - if isinstance(gearbox, str | dm.NodeId | dm.DirectRelationReference) or is_tuple_id(gearbox): - filters.append(dm.filters.Equals(view_id.as_property_ref("gearbox"), value=as_instance_dict_id(gearbox))) - if gearbox and isinstance(gearbox, Sequence) and not isinstance(gearbox, str) and not is_tuple_id(gearbox): - filters.append( - dm.filters.In(view_id.as_property_ref("gearbox"), values=[as_instance_dict_id(item) for item in gearbox]) - ) - if isinstance(generator, str | dm.NodeId | dm.DirectRelationReference) or is_tuple_id(generator): - filters.append(dm.filters.Equals(view_id.as_property_ref("generator"), value=as_instance_dict_id(generator))) - if generator and isinstance(generator, Sequence) and not isinstance(generator, str) and not is_tuple_id(generator): - filters.append( - dm.filters.In( - view_id.as_property_ref("generator"), values=[as_instance_dict_id(item) for item in generator] - ) - ) - if isinstance(high_speed_shaft, str | dm.NodeId | dm.DirectRelationReference) or is_tuple_id(high_speed_shaft): - filters.append( - dm.filters.Equals(view_id.as_property_ref("high_speed_shaft"), value=as_instance_dict_id(high_speed_shaft)) - ) - if ( - high_speed_shaft - and isinstance(high_speed_shaft, Sequence) - and not isinstance(high_speed_shaft, str) - and not is_tuple_id(high_speed_shaft) - ): - filters.append( - dm.filters.In( - view_id.as_property_ref("high_speed_shaft"), - values=[as_instance_dict_id(item) for item in high_speed_shaft], - ) - ) - if isinstance(main_shaft, str | dm.NodeId | dm.DirectRelationReference) or is_tuple_id(main_shaft): - filters.append(dm.filters.Equals(view_id.as_property_ref("main_shaft"), value=as_instance_dict_id(main_shaft))) - if ( - main_shaft - and isinstance(main_shaft, Sequence) - and not isinstance(main_shaft, str) - and not is_tuple_id(main_shaft) - ): - filters.append( - dm.filters.In( - view_id.as_property_ref("main_shaft"), values=[as_instance_dict_id(item) for item in main_shaft] - ) - ) - if isinstance(power_inverter, str | dm.NodeId | dm.DirectRelationReference) or is_tuple_id(power_inverter): - filters.append( - dm.filters.Equals(view_id.as_property_ref("power_inverter"), value=as_instance_dict_id(power_inverter)) - ) - if ( - power_inverter - and isinstance(power_inverter, Sequence) - and not isinstance(power_inverter, str) - and not is_tuple_id(power_inverter) - ): - filters.append( - dm.filters.In( - view_id.as_property_ref("power_inverter"), values=[as_instance_dict_id(item) for item in power_inverter] - ) - ) - if external_id_prefix is not None: - filters.append(dm.filters.Prefix(["node", "externalId"], value=external_id_prefix)) - if isinstance(space, str): - filters.append(dm.filters.Equals(["node", "space"], value=space)) - if space and isinstance(space, list): - filters.append(dm.filters.In(["node", "space"], values=space)) - if filter: - filters.append(filter) - return dm.filters.And(*filters) if filters else None - - -class _NacelleQuery(NodeQueryCore[T_DomainModelList, NacelleList]): - _view_id = Nacelle._view_id - _result_cls = Nacelle - _result_list_cls_end = NacelleList - - def __init__( - self, - created_types: set[type], - creation_path: list[QueryCore], - client: CogniteClient, - result_list_cls: type[T_DomainModelList], - expression: dm.query.ResultSetExpression | None = None, - connection_name: str | None = None, - connection_type: Literal["reverse-list"] | None = None, - reverse_expression: dm.query.ResultSetExpression | None = None, - ): - from ._gearbox import _GearboxQuery - from ._generator import _GeneratorQuery - from ._high_speed_shaft import _HighSpeedShaftQuery - from ._main_shaft import _MainShaftQuery - from ._power_inverter import _PowerInverterQuery - - super().__init__( - created_types, - creation_path, - client, - result_list_cls, - expression, - dm.filters.HasData(views=[self._view_id]), - connection_name, - connection_type, - reverse_expression, - ) - - if _GearboxQuery not in created_types: - self.gearbox = _GearboxQuery( - created_types.copy(), - self._creation_path, - client, - result_list_cls, - dm.query.NodeResultSetExpression( - through=self._view_id.as_property_ref("gearbox"), - direction="outwards", - ), - connection_name="gearbox", - ) - - if _GeneratorQuery not in created_types: - self.generator = _GeneratorQuery( - created_types.copy(), - self._creation_path, - client, - result_list_cls, - dm.query.NodeResultSetExpression( - through=self._view_id.as_property_ref("generator"), - direction="outwards", - ), - connection_name="generator", - ) - - if _HighSpeedShaftQuery not in created_types: - self.high_speed_shaft = _HighSpeedShaftQuery( - created_types.copy(), - self._creation_path, - client, - result_list_cls, - dm.query.NodeResultSetExpression( - through=self._view_id.as_property_ref("high_speed_shaft"), - direction="outwards", - ), - connection_name="high_speed_shaft", - ) - - if _MainShaftQuery not in created_types: - self.main_shaft = _MainShaftQuery( - created_types.copy(), - self._creation_path, - client, - result_list_cls, - dm.query.NodeResultSetExpression( - through=self._view_id.as_property_ref("main_shaft"), - direction="outwards", - ), - connection_name="main_shaft", - ) - - if _PowerInverterQuery not in created_types: - self.power_inverter = _PowerInverterQuery( - created_types.copy(), - self._creation_path, - client, - result_list_cls, - dm.query.NodeResultSetExpression( - through=self._view_id.as_property_ref("power_inverter"), - direction="outwards", - ), - connection_name="power_inverter", - ) - - self.space = StringFilter(self, ["node", "space"]) - self.external_id = StringFilter(self, ["node", "externalId"]) - - def list_nacelle(self, limit: int = DEFAULT_QUERY_LIMIT) -> NacelleList: - return self._list(limit=limit) - - -class NacelleQuery(_NacelleQuery[NacelleList]): - def __init__(self, client: CogniteClient): - super().__init__(set(), [], client, NacelleList) diff --git a/examples/windmill/data_classes/_power_inverter.py b/examples/windmill/data_classes/_power_inverter.py deleted file mode 100644 index 290c7e181..000000000 --- a/examples/windmill/data_classes/_power_inverter.py +++ /dev/null @@ -1,370 +0,0 @@ -from __future__ import annotations - -import warnings -from collections.abc import Sequence -from typing import Any, ClassVar, Literal, no_type_check, Optional, Union - -from cognite.client import data_modeling as dm, CogniteClient -from cognite.client.data_classes import ( - TimeSeries as CogniteTimeSeries, - TimeSeriesWrite as CogniteTimeSeriesWrite, -) -from pydantic import field_validator, model_validator - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - DataRecord, - DataRecordGraphQL, - DataRecordWrite, - DomainModel, - DomainModelWrite, - DomainModelWriteList, - DomainModelList, - DomainRelation, - DomainRelationWrite, - GraphQLCore, - ResourcesWrite, - FileMetadata, - FileMetadataWrite, - FileMetadataGraphQL, - TimeSeries, - TimeSeriesWrite, - TimeSeriesGraphQL, - T_DomainModelList, - as_direct_relation_reference, - as_instance_dict_id, - as_node_id, - as_pygen_node_id, - are_nodes_equal, - is_tuple_id, - select_best_node, - QueryCore, - NodeQueryCore, - StringFilter, -) - - -__all__ = [ - "PowerInverter", - "PowerInverterWrite", - "PowerInverterApply", - "PowerInverterList", - "PowerInverterWriteList", - "PowerInverterApplyList", - "PowerInverterFields", - "PowerInverterTextFields", - "PowerInverterGraphQL", -] - - -PowerInverterTextFields = Literal["external_id", "active_power_total", "apparent_power_total", "reactive_power_total"] -PowerInverterFields = Literal["external_id", "active_power_total", "apparent_power_total", "reactive_power_total"] - -_POWERINVERTER_PROPERTIES_BY_FIELD = { - "external_id": "externalId", - "active_power_total": "active_power_total", - "apparent_power_total": "apparent_power_total", - "reactive_power_total": "reactive_power_total", -} - - -class PowerInverterGraphQL(GraphQLCore): - """This represents the reading version of power inverter, used - when data is retrieved from CDF using GraphQL. - - It is used when retrieving data from CDF using GraphQL. - - Args: - space: The space where the node is located. - external_id: The external id of the power inverter. - data_record: The data record of the power inverter node. - active_power_total: The active power total field. - apparent_power_total: The apparent power total field. - reactive_power_total: The reactive power total field. - """ - - view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "PowerInverter", "1") - active_power_total: Optional[TimeSeriesGraphQL] = None - apparent_power_total: Optional[TimeSeriesGraphQL] = None - reactive_power_total: Optional[TimeSeriesGraphQL] = None - - @model_validator(mode="before") - def parse_data_record(cls, values: Any) -> Any: - if not isinstance(values, dict): - return values - if "lastUpdatedTime" in values or "createdTime" in values: - values["dataRecord"] = DataRecordGraphQL( - created_time=values.pop("createdTime", None), - last_updated_time=values.pop("lastUpdatedTime", None), - ) - return values - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_read(self) -> PowerInverter: - """Convert this GraphQL format of power inverter to the reading format.""" - if self.data_record is None: - raise ValueError("This object cannot be converted to a read format because it lacks a data record.") - return PowerInverter( - space=self.space, - external_id=self.external_id, - data_record=DataRecord( - version=0, - last_updated_time=self.data_record.last_updated_time, - created_time=self.data_record.created_time, - ), - active_power_total=self.active_power_total.as_read() if self.active_power_total else None, - apparent_power_total=self.apparent_power_total.as_read() if self.apparent_power_total else None, - reactive_power_total=self.reactive_power_total.as_read() if self.reactive_power_total else None, - ) - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_write(self) -> PowerInverterWrite: - """Convert this GraphQL format of power inverter to the writing format.""" - return PowerInverterWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=0), - active_power_total=self.active_power_total.as_write() if self.active_power_total else None, - apparent_power_total=self.apparent_power_total.as_write() if self.apparent_power_total else None, - reactive_power_total=self.reactive_power_total.as_write() if self.reactive_power_total else None, - ) - - -class PowerInverter(DomainModel): - """This represents the reading version of power inverter. - - It is used to when data is retrieved from CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the power inverter. - data_record: The data record of the power inverter node. - active_power_total: The active power total field. - apparent_power_total: The apparent power total field. - reactive_power_total: The reactive power total field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "PowerInverter", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, None] = None - active_power_total: Union[TimeSeries, str, None] = None - apparent_power_total: Union[TimeSeries, str, None] = None - reactive_power_total: Union[TimeSeries, str, None] = None - - def as_write(self) -> PowerInverterWrite: - """Convert this read version of power inverter to the writing version.""" - return PowerInverterWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=self.data_record.version), - active_power_total=( - self.active_power_total.as_write() - if isinstance(self.active_power_total, CogniteTimeSeries) - else self.active_power_total - ), - apparent_power_total=( - self.apparent_power_total.as_write() - if isinstance(self.apparent_power_total, CogniteTimeSeries) - else self.apparent_power_total - ), - reactive_power_total=( - self.reactive_power_total.as_write() - if isinstance(self.reactive_power_total, CogniteTimeSeries) - else self.reactive_power_total - ), - ) - - def as_apply(self) -> PowerInverterWrite: - """Convert this read version of power inverter to the writing version.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - -class PowerInverterWrite(DomainModelWrite): - """This represents the writing version of power inverter. - - It is used to when data is sent to CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the power inverter. - data_record: The data record of the power inverter node. - active_power_total: The active power total field. - apparent_power_total: The apparent power total field. - reactive_power_total: The reactive power total field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "PowerInverter", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, dm.NodeId, tuple[str, str], None] = None - active_power_total: Union[TimeSeriesWrite, str, None] = None - apparent_power_total: Union[TimeSeriesWrite, str, None] = None - reactive_power_total: Union[TimeSeriesWrite, str, None] = None - - def _to_instances_write( - self, - cache: set[tuple[str, str]], - write_none: bool = False, - allow_version_increase: bool = False, - ) -> ResourcesWrite: - resources = ResourcesWrite() - if self.as_tuple_id() in cache: - return resources - - properties: dict[str, Any] = {} - - if self.active_power_total is not None or write_none: - properties["active_power_total"] = ( - self.active_power_total - if isinstance(self.active_power_total, str) or self.active_power_total is None - else self.active_power_total.external_id - ) - - if self.apparent_power_total is not None or write_none: - properties["apparent_power_total"] = ( - self.apparent_power_total - if isinstance(self.apparent_power_total, str) or self.apparent_power_total is None - else self.apparent_power_total.external_id - ) - - if self.reactive_power_total is not None or write_none: - properties["reactive_power_total"] = ( - self.reactive_power_total - if isinstance(self.reactive_power_total, str) or self.reactive_power_total is None - else self.reactive_power_total.external_id - ) - - if properties: - this_node = dm.NodeApply( - space=self.space, - external_id=self.external_id, - existing_version=None if allow_version_increase else self.data_record.existing_version, - type=as_direct_relation_reference(self.node_type), - sources=[ - dm.NodeOrEdgeData( - source=self._view_id, - properties=properties, - ) - ], - ) - resources.nodes.append(this_node) - cache.add(self.as_tuple_id()) - - if isinstance(self.active_power_total, CogniteTimeSeriesWrite): - resources.time_series.append(self.active_power_total) - - if isinstance(self.apparent_power_total, CogniteTimeSeriesWrite): - resources.time_series.append(self.apparent_power_total) - - if isinstance(self.reactive_power_total, CogniteTimeSeriesWrite): - resources.time_series.append(self.reactive_power_total) - - return resources - - -class PowerInverterApply(PowerInverterWrite): - def __new__(cls, *args, **kwargs) -> PowerInverterApply: - warnings.warn( - "PowerInverterApply is deprecated and will be removed in v1.0. Use PowerInverterWrite instead." - "The motivation for this change is that Write is a more descriptive name for the writing version of the" - "PowerInverter.", - UserWarning, - stacklevel=2, - ) - return super().__new__(cls) - - -class PowerInverterList(DomainModelList[PowerInverter]): - """List of power inverters in the read version.""" - - _INSTANCE = PowerInverter - - def as_write(self) -> PowerInverterWriteList: - """Convert these read versions of power inverter to the writing versions.""" - return PowerInverterWriteList([node.as_write() for node in self.data]) - - def as_apply(self) -> PowerInverterWriteList: - """Convert these read versions of primitive nullable to the writing versions.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - -class PowerInverterWriteList(DomainModelWriteList[PowerInverterWrite]): - """List of power inverters in the writing version.""" - - _INSTANCE = PowerInverterWrite - - -class PowerInverterApplyList(PowerInverterWriteList): ... - - -def _create_power_inverter_filter( - view_id: dm.ViewId, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - filter: dm.Filter | None = None, -) -> dm.Filter | None: - filters: list[dm.Filter] = [] - if external_id_prefix is not None: - filters.append(dm.filters.Prefix(["node", "externalId"], value=external_id_prefix)) - if isinstance(space, str): - filters.append(dm.filters.Equals(["node", "space"], value=space)) - if space and isinstance(space, list): - filters.append(dm.filters.In(["node", "space"], values=space)) - if filter: - filters.append(filter) - return dm.filters.And(*filters) if filters else None - - -class _PowerInverterQuery(NodeQueryCore[T_DomainModelList, PowerInverterList]): - _view_id = PowerInverter._view_id - _result_cls = PowerInverter - _result_list_cls_end = PowerInverterList - - def __init__( - self, - created_types: set[type], - creation_path: list[QueryCore], - client: CogniteClient, - result_list_cls: type[T_DomainModelList], - expression: dm.query.ResultSetExpression | None = None, - connection_name: str | None = None, - connection_type: Literal["reverse-list"] | None = None, - reverse_expression: dm.query.ResultSetExpression | None = None, - ): - - super().__init__( - created_types, - creation_path, - client, - result_list_cls, - expression, - dm.filters.HasData(views=[self._view_id]), - connection_name, - connection_type, - reverse_expression, - ) - - self.space = StringFilter(self, ["node", "space"]) - self.external_id = StringFilter(self, ["node", "externalId"]) - - def list_power_inverter(self, limit: int = DEFAULT_QUERY_LIMIT) -> PowerInverterList: - return self._list(limit=limit) - - -class PowerInverterQuery(_PowerInverterQuery[PowerInverterList]): - def __init__(self, client: CogniteClient): - super().__init__(set(), [], client, PowerInverterList) diff --git a/examples/windmill/data_classes/_rotor.py b/examples/windmill/data_classes/_rotor.py deleted file mode 100644 index 34fdcb12a..000000000 --- a/examples/windmill/data_classes/_rotor.py +++ /dev/null @@ -1,346 +0,0 @@ -from __future__ import annotations - -import warnings -from collections.abc import Sequence -from typing import Any, ClassVar, Literal, no_type_check, Optional, Union - -from cognite.client import data_modeling as dm, CogniteClient -from cognite.client.data_classes import ( - TimeSeries as CogniteTimeSeries, - TimeSeriesWrite as CogniteTimeSeriesWrite, -) -from pydantic import field_validator, model_validator - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - DataRecord, - DataRecordGraphQL, - DataRecordWrite, - DomainModel, - DomainModelWrite, - DomainModelWriteList, - DomainModelList, - DomainRelation, - DomainRelationWrite, - GraphQLCore, - ResourcesWrite, - FileMetadata, - FileMetadataWrite, - FileMetadataGraphQL, - TimeSeries, - TimeSeriesWrite, - TimeSeriesGraphQL, - T_DomainModelList, - as_direct_relation_reference, - as_instance_dict_id, - as_node_id, - as_pygen_node_id, - are_nodes_equal, - is_tuple_id, - select_best_node, - QueryCore, - NodeQueryCore, - StringFilter, -) - - -__all__ = [ - "Rotor", - "RotorWrite", - "RotorApply", - "RotorList", - "RotorWriteList", - "RotorApplyList", - "RotorFields", - "RotorTextFields", - "RotorGraphQL", -] - - -RotorTextFields = Literal["external_id", "rotor_speed_controller", "rpm_low_speed_shaft"] -RotorFields = Literal["external_id", "rotor_speed_controller", "rpm_low_speed_shaft"] - -_ROTOR_PROPERTIES_BY_FIELD = { - "external_id": "externalId", - "rotor_speed_controller": "rotor_speed_controller", - "rpm_low_speed_shaft": "rpm_low_speed_shaft", -} - - -class RotorGraphQL(GraphQLCore): - """This represents the reading version of rotor, used - when data is retrieved from CDF using GraphQL. - - It is used when retrieving data from CDF using GraphQL. - - Args: - space: The space where the node is located. - external_id: The external id of the rotor. - data_record: The data record of the rotor node. - rotor_speed_controller: The rotor speed controller field. - rpm_low_speed_shaft: The rpm low speed shaft field. - """ - - view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Rotor", "1") - rotor_speed_controller: Optional[TimeSeriesGraphQL] = None - rpm_low_speed_shaft: Optional[TimeSeriesGraphQL] = None - - @model_validator(mode="before") - def parse_data_record(cls, values: Any) -> Any: - if not isinstance(values, dict): - return values - if "lastUpdatedTime" in values or "createdTime" in values: - values["dataRecord"] = DataRecordGraphQL( - created_time=values.pop("createdTime", None), - last_updated_time=values.pop("lastUpdatedTime", None), - ) - return values - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_read(self) -> Rotor: - """Convert this GraphQL format of rotor to the reading format.""" - if self.data_record is None: - raise ValueError("This object cannot be converted to a read format because it lacks a data record.") - return Rotor( - space=self.space, - external_id=self.external_id, - data_record=DataRecord( - version=0, - last_updated_time=self.data_record.last_updated_time, - created_time=self.data_record.created_time, - ), - rotor_speed_controller=self.rotor_speed_controller.as_read() if self.rotor_speed_controller else None, - rpm_low_speed_shaft=self.rpm_low_speed_shaft.as_read() if self.rpm_low_speed_shaft else None, - ) - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_write(self) -> RotorWrite: - """Convert this GraphQL format of rotor to the writing format.""" - return RotorWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=0), - rotor_speed_controller=self.rotor_speed_controller.as_write() if self.rotor_speed_controller else None, - rpm_low_speed_shaft=self.rpm_low_speed_shaft.as_write() if self.rpm_low_speed_shaft else None, - ) - - -class Rotor(DomainModel): - """This represents the reading version of rotor. - - It is used to when data is retrieved from CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the rotor. - data_record: The data record of the rotor node. - rotor_speed_controller: The rotor speed controller field. - rpm_low_speed_shaft: The rpm low speed shaft field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Rotor", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, None] = None - rotor_speed_controller: Union[TimeSeries, str, None] = None - rpm_low_speed_shaft: Union[TimeSeries, str, None] = None - - def as_write(self) -> RotorWrite: - """Convert this read version of rotor to the writing version.""" - return RotorWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=self.data_record.version), - rotor_speed_controller=( - self.rotor_speed_controller.as_write() - if isinstance(self.rotor_speed_controller, CogniteTimeSeries) - else self.rotor_speed_controller - ), - rpm_low_speed_shaft=( - self.rpm_low_speed_shaft.as_write() - if isinstance(self.rpm_low_speed_shaft, CogniteTimeSeries) - else self.rpm_low_speed_shaft - ), - ) - - def as_apply(self) -> RotorWrite: - """Convert this read version of rotor to the writing version.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - -class RotorWrite(DomainModelWrite): - """This represents the writing version of rotor. - - It is used to when data is sent to CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the rotor. - data_record: The data record of the rotor node. - rotor_speed_controller: The rotor speed controller field. - rpm_low_speed_shaft: The rpm low speed shaft field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Rotor", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, dm.NodeId, tuple[str, str], None] = None - rotor_speed_controller: Union[TimeSeriesWrite, str, None] = None - rpm_low_speed_shaft: Union[TimeSeriesWrite, str, None] = None - - def _to_instances_write( - self, - cache: set[tuple[str, str]], - write_none: bool = False, - allow_version_increase: bool = False, - ) -> ResourcesWrite: - resources = ResourcesWrite() - if self.as_tuple_id() in cache: - return resources - - properties: dict[str, Any] = {} - - if self.rotor_speed_controller is not None or write_none: - properties["rotor_speed_controller"] = ( - self.rotor_speed_controller - if isinstance(self.rotor_speed_controller, str) or self.rotor_speed_controller is None - else self.rotor_speed_controller.external_id - ) - - if self.rpm_low_speed_shaft is not None or write_none: - properties["rpm_low_speed_shaft"] = ( - self.rpm_low_speed_shaft - if isinstance(self.rpm_low_speed_shaft, str) or self.rpm_low_speed_shaft is None - else self.rpm_low_speed_shaft.external_id - ) - - if properties: - this_node = dm.NodeApply( - space=self.space, - external_id=self.external_id, - existing_version=None if allow_version_increase else self.data_record.existing_version, - type=as_direct_relation_reference(self.node_type), - sources=[ - dm.NodeOrEdgeData( - source=self._view_id, - properties=properties, - ) - ], - ) - resources.nodes.append(this_node) - cache.add(self.as_tuple_id()) - - if isinstance(self.rotor_speed_controller, CogniteTimeSeriesWrite): - resources.time_series.append(self.rotor_speed_controller) - - if isinstance(self.rpm_low_speed_shaft, CogniteTimeSeriesWrite): - resources.time_series.append(self.rpm_low_speed_shaft) - - return resources - - -class RotorApply(RotorWrite): - def __new__(cls, *args, **kwargs) -> RotorApply: - warnings.warn( - "RotorApply is deprecated and will be removed in v1.0. Use RotorWrite instead." - "The motivation for this change is that Write is a more descriptive name for the writing version of the" - "Rotor.", - UserWarning, - stacklevel=2, - ) - return super().__new__(cls) - - -class RotorList(DomainModelList[Rotor]): - """List of rotors in the read version.""" - - _INSTANCE = Rotor - - def as_write(self) -> RotorWriteList: - """Convert these read versions of rotor to the writing versions.""" - return RotorWriteList([node.as_write() for node in self.data]) - - def as_apply(self) -> RotorWriteList: - """Convert these read versions of primitive nullable to the writing versions.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - -class RotorWriteList(DomainModelWriteList[RotorWrite]): - """List of rotors in the writing version.""" - - _INSTANCE = RotorWrite - - -class RotorApplyList(RotorWriteList): ... - - -def _create_rotor_filter( - view_id: dm.ViewId, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - filter: dm.Filter | None = None, -) -> dm.Filter | None: - filters: list[dm.Filter] = [] - if external_id_prefix is not None: - filters.append(dm.filters.Prefix(["node", "externalId"], value=external_id_prefix)) - if isinstance(space, str): - filters.append(dm.filters.Equals(["node", "space"], value=space)) - if space and isinstance(space, list): - filters.append(dm.filters.In(["node", "space"], values=space)) - if filter: - filters.append(filter) - return dm.filters.And(*filters) if filters else None - - -class _RotorQuery(NodeQueryCore[T_DomainModelList, RotorList]): - _view_id = Rotor._view_id - _result_cls = Rotor - _result_list_cls_end = RotorList - - def __init__( - self, - created_types: set[type], - creation_path: list[QueryCore], - client: CogniteClient, - result_list_cls: type[T_DomainModelList], - expression: dm.query.ResultSetExpression | None = None, - connection_name: str | None = None, - connection_type: Literal["reverse-list"] | None = None, - reverse_expression: dm.query.ResultSetExpression | None = None, - ): - - super().__init__( - created_types, - creation_path, - client, - result_list_cls, - expression, - dm.filters.HasData(views=[self._view_id]), - connection_name, - connection_type, - reverse_expression, - ) - - self.space = StringFilter(self, ["node", "space"]) - self.external_id = StringFilter(self, ["node", "externalId"]) - - def list_rotor(self, limit: int = DEFAULT_QUERY_LIMIT) -> RotorList: - return self._list(limit=limit) - - -class RotorQuery(_RotorQuery[RotorList]): - def __init__(self, client: CogniteClient): - super().__init__(set(), [], client, RotorList) diff --git a/examples/windmill/data_classes/_sensor_position.py b/examples/windmill/data_classes/_sensor_position.py deleted file mode 100644 index 2e7d7032c..000000000 --- a/examples/windmill/data_classes/_sensor_position.py +++ /dev/null @@ -1,577 +0,0 @@ -from __future__ import annotations - -import warnings -from collections.abc import Sequence -from typing import Any, ClassVar, Literal, no_type_check, Optional, Union - -from cognite.client import data_modeling as dm, CogniteClient -from cognite.client.data_classes import ( - TimeSeries as CogniteTimeSeries, - TimeSeriesWrite as CogniteTimeSeriesWrite, -) -from pydantic import field_validator, model_validator - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - DataRecord, - DataRecordGraphQL, - DataRecordWrite, - DomainModel, - DomainModelWrite, - DomainModelWriteList, - DomainModelList, - DomainRelation, - DomainRelationWrite, - GraphQLCore, - ResourcesWrite, - FileMetadata, - FileMetadataWrite, - FileMetadataGraphQL, - TimeSeries, - TimeSeriesWrite, - TimeSeriesGraphQL, - T_DomainModelList, - as_direct_relation_reference, - as_instance_dict_id, - as_node_id, - as_pygen_node_id, - are_nodes_equal, - is_tuple_id, - select_best_node, - QueryCore, - NodeQueryCore, - StringFilter, - FloatFilter, -) - - -__all__ = [ - "SensorPosition", - "SensorPositionWrite", - "SensorPositionApply", - "SensorPositionList", - "SensorPositionWriteList", - "SensorPositionApplyList", - "SensorPositionFields", - "SensorPositionTextFields", - "SensorPositionGraphQL", -] - - -SensorPositionTextFields = Literal[ - "external_id", - "edgewise_bend_mom_crosstalk_corrected", - "edgewise_bend_mom_offset", - "edgewise_bend_mom_offset_crosstalk_corrected", - "edgewisewise_bend_mom", - "flapwise_bend_mom", - "flapwise_bend_mom_crosstalk_corrected", - "flapwise_bend_mom_offset", - "flapwise_bend_mom_offset_crosstalk_corrected", -] -SensorPositionFields = Literal[ - "external_id", - "edgewise_bend_mom_crosstalk_corrected", - "edgewise_bend_mom_offset", - "edgewise_bend_mom_offset_crosstalk_corrected", - "edgewisewise_bend_mom", - "flapwise_bend_mom", - "flapwise_bend_mom_crosstalk_corrected", - "flapwise_bend_mom_offset", - "flapwise_bend_mom_offset_crosstalk_corrected", - "position", -] - -_SENSORPOSITION_PROPERTIES_BY_FIELD = { - "external_id": "externalId", - "edgewise_bend_mom_crosstalk_corrected": "edgewise_bend_mom_crosstalk_corrected", - "edgewise_bend_mom_offset": "edgewise_bend_mom_offset", - "edgewise_bend_mom_offset_crosstalk_corrected": "edgewise_bend_mom_offset_crosstalk_corrected", - "edgewisewise_bend_mom": "edgewisewise_bend_mom", - "flapwise_bend_mom": "flapwise_bend_mom", - "flapwise_bend_mom_crosstalk_corrected": "flapwise_bend_mom_crosstalk_corrected", - "flapwise_bend_mom_offset": "flapwise_bend_mom_offset", - "flapwise_bend_mom_offset_crosstalk_corrected": "flapwise_bend_mom_offset_crosstalk_corrected", - "position": "position", -} - - -class SensorPositionGraphQL(GraphQLCore): - """This represents the reading version of sensor position, used - when data is retrieved from CDF using GraphQL. - - It is used when retrieving data from CDF using GraphQL. - - Args: - space: The space where the node is located. - external_id: The external id of the sensor position. - data_record: The data record of the sensor position node. - edgewise_bend_mom_crosstalk_corrected: The edgewise bend mom crosstalk corrected field. - edgewise_bend_mom_offset: The edgewise bend mom offset field. - edgewise_bend_mom_offset_crosstalk_corrected: The edgewise bend mom offset crosstalk corrected field. - edgewisewise_bend_mom: The edgewisewise bend mom field. - flapwise_bend_mom: The flapwise bend mom field. - flapwise_bend_mom_crosstalk_corrected: The flapwise bend mom crosstalk corrected field. - flapwise_bend_mom_offset: The flapwise bend mom offset field. - flapwise_bend_mom_offset_crosstalk_corrected: The flapwise bend mom offset crosstalk corrected field. - position: The position field. - """ - - view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "SensorPosition", "1") - edgewise_bend_mom_crosstalk_corrected: Optional[TimeSeriesGraphQL] = None - edgewise_bend_mom_offset: Optional[TimeSeriesGraphQL] = None - edgewise_bend_mom_offset_crosstalk_corrected: Optional[TimeSeriesGraphQL] = None - edgewisewise_bend_mom: Optional[TimeSeriesGraphQL] = None - flapwise_bend_mom: Optional[TimeSeriesGraphQL] = None - flapwise_bend_mom_crosstalk_corrected: Optional[TimeSeriesGraphQL] = None - flapwise_bend_mom_offset: Optional[TimeSeriesGraphQL] = None - flapwise_bend_mom_offset_crosstalk_corrected: Optional[TimeSeriesGraphQL] = None - position: Optional[float] = None - - @model_validator(mode="before") - def parse_data_record(cls, values: Any) -> Any: - if not isinstance(values, dict): - return values - if "lastUpdatedTime" in values or "createdTime" in values: - values["dataRecord"] = DataRecordGraphQL( - created_time=values.pop("createdTime", None), - last_updated_time=values.pop("lastUpdatedTime", None), - ) - return values - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_read(self) -> SensorPosition: - """Convert this GraphQL format of sensor position to the reading format.""" - if self.data_record is None: - raise ValueError("This object cannot be converted to a read format because it lacks a data record.") - return SensorPosition( - space=self.space, - external_id=self.external_id, - data_record=DataRecord( - version=0, - last_updated_time=self.data_record.last_updated_time, - created_time=self.data_record.created_time, - ), - edgewise_bend_mom_crosstalk_corrected=( - self.edgewise_bend_mom_crosstalk_corrected.as_read() - if self.edgewise_bend_mom_crosstalk_corrected - else None - ), - edgewise_bend_mom_offset=self.edgewise_bend_mom_offset.as_read() if self.edgewise_bend_mom_offset else None, - edgewise_bend_mom_offset_crosstalk_corrected=( - self.edgewise_bend_mom_offset_crosstalk_corrected.as_read() - if self.edgewise_bend_mom_offset_crosstalk_corrected - else None - ), - edgewisewise_bend_mom=self.edgewisewise_bend_mom.as_read() if self.edgewisewise_bend_mom else None, - flapwise_bend_mom=self.flapwise_bend_mom.as_read() if self.flapwise_bend_mom else None, - flapwise_bend_mom_crosstalk_corrected=( - self.flapwise_bend_mom_crosstalk_corrected.as_read() - if self.flapwise_bend_mom_crosstalk_corrected - else None - ), - flapwise_bend_mom_offset=self.flapwise_bend_mom_offset.as_read() if self.flapwise_bend_mom_offset else None, - flapwise_bend_mom_offset_crosstalk_corrected=( - self.flapwise_bend_mom_offset_crosstalk_corrected.as_read() - if self.flapwise_bend_mom_offset_crosstalk_corrected - else None - ), - position=self.position, - ) - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_write(self) -> SensorPositionWrite: - """Convert this GraphQL format of sensor position to the writing format.""" - return SensorPositionWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=0), - edgewise_bend_mom_crosstalk_corrected=( - self.edgewise_bend_mom_crosstalk_corrected.as_write() - if self.edgewise_bend_mom_crosstalk_corrected - else None - ), - edgewise_bend_mom_offset=( - self.edgewise_bend_mom_offset.as_write() if self.edgewise_bend_mom_offset else None - ), - edgewise_bend_mom_offset_crosstalk_corrected=( - self.edgewise_bend_mom_offset_crosstalk_corrected.as_write() - if self.edgewise_bend_mom_offset_crosstalk_corrected - else None - ), - edgewisewise_bend_mom=self.edgewisewise_bend_mom.as_write() if self.edgewisewise_bend_mom else None, - flapwise_bend_mom=self.flapwise_bend_mom.as_write() if self.flapwise_bend_mom else None, - flapwise_bend_mom_crosstalk_corrected=( - self.flapwise_bend_mom_crosstalk_corrected.as_write() - if self.flapwise_bend_mom_crosstalk_corrected - else None - ), - flapwise_bend_mom_offset=( - self.flapwise_bend_mom_offset.as_write() if self.flapwise_bend_mom_offset else None - ), - flapwise_bend_mom_offset_crosstalk_corrected=( - self.flapwise_bend_mom_offset_crosstalk_corrected.as_write() - if self.flapwise_bend_mom_offset_crosstalk_corrected - else None - ), - position=self.position, - ) - - -class SensorPosition(DomainModel): - """This represents the reading version of sensor position. - - It is used to when data is retrieved from CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the sensor position. - data_record: The data record of the sensor position node. - edgewise_bend_mom_crosstalk_corrected: The edgewise bend mom crosstalk corrected field. - edgewise_bend_mom_offset: The edgewise bend mom offset field. - edgewise_bend_mom_offset_crosstalk_corrected: The edgewise bend mom offset crosstalk corrected field. - edgewisewise_bend_mom: The edgewisewise bend mom field. - flapwise_bend_mom: The flapwise bend mom field. - flapwise_bend_mom_crosstalk_corrected: The flapwise bend mom crosstalk corrected field. - flapwise_bend_mom_offset: The flapwise bend mom offset field. - flapwise_bend_mom_offset_crosstalk_corrected: The flapwise bend mom offset crosstalk corrected field. - position: The position field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "SensorPosition", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, None] = None - edgewise_bend_mom_crosstalk_corrected: Union[TimeSeries, str, None] = None - edgewise_bend_mom_offset: Union[TimeSeries, str, None] = None - edgewise_bend_mom_offset_crosstalk_corrected: Union[TimeSeries, str, None] = None - edgewisewise_bend_mom: Union[TimeSeries, str, None] = None - flapwise_bend_mom: Union[TimeSeries, str, None] = None - flapwise_bend_mom_crosstalk_corrected: Union[TimeSeries, str, None] = None - flapwise_bend_mom_offset: Union[TimeSeries, str, None] = None - flapwise_bend_mom_offset_crosstalk_corrected: Union[TimeSeries, str, None] = None - position: Optional[float] = None - - def as_write(self) -> SensorPositionWrite: - """Convert this read version of sensor position to the writing version.""" - return SensorPositionWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=self.data_record.version), - edgewise_bend_mom_crosstalk_corrected=( - self.edgewise_bend_mom_crosstalk_corrected.as_write() - if isinstance(self.edgewise_bend_mom_crosstalk_corrected, CogniteTimeSeries) - else self.edgewise_bend_mom_crosstalk_corrected - ), - edgewise_bend_mom_offset=( - self.edgewise_bend_mom_offset.as_write() - if isinstance(self.edgewise_bend_mom_offset, CogniteTimeSeries) - else self.edgewise_bend_mom_offset - ), - edgewise_bend_mom_offset_crosstalk_corrected=( - self.edgewise_bend_mom_offset_crosstalk_corrected.as_write() - if isinstance(self.edgewise_bend_mom_offset_crosstalk_corrected, CogniteTimeSeries) - else self.edgewise_bend_mom_offset_crosstalk_corrected - ), - edgewisewise_bend_mom=( - self.edgewisewise_bend_mom.as_write() - if isinstance(self.edgewisewise_bend_mom, CogniteTimeSeries) - else self.edgewisewise_bend_mom - ), - flapwise_bend_mom=( - self.flapwise_bend_mom.as_write() - if isinstance(self.flapwise_bend_mom, CogniteTimeSeries) - else self.flapwise_bend_mom - ), - flapwise_bend_mom_crosstalk_corrected=( - self.flapwise_bend_mom_crosstalk_corrected.as_write() - if isinstance(self.flapwise_bend_mom_crosstalk_corrected, CogniteTimeSeries) - else self.flapwise_bend_mom_crosstalk_corrected - ), - flapwise_bend_mom_offset=( - self.flapwise_bend_mom_offset.as_write() - if isinstance(self.flapwise_bend_mom_offset, CogniteTimeSeries) - else self.flapwise_bend_mom_offset - ), - flapwise_bend_mom_offset_crosstalk_corrected=( - self.flapwise_bend_mom_offset_crosstalk_corrected.as_write() - if isinstance(self.flapwise_bend_mom_offset_crosstalk_corrected, CogniteTimeSeries) - else self.flapwise_bend_mom_offset_crosstalk_corrected - ), - position=self.position, - ) - - def as_apply(self) -> SensorPositionWrite: - """Convert this read version of sensor position to the writing version.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - -class SensorPositionWrite(DomainModelWrite): - """This represents the writing version of sensor position. - - It is used to when data is sent to CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the sensor position. - data_record: The data record of the sensor position node. - edgewise_bend_mom_crosstalk_corrected: The edgewise bend mom crosstalk corrected field. - edgewise_bend_mom_offset: The edgewise bend mom offset field. - edgewise_bend_mom_offset_crosstalk_corrected: The edgewise bend mom offset crosstalk corrected field. - edgewisewise_bend_mom: The edgewisewise bend mom field. - flapwise_bend_mom: The flapwise bend mom field. - flapwise_bend_mom_crosstalk_corrected: The flapwise bend mom crosstalk corrected field. - flapwise_bend_mom_offset: The flapwise bend mom offset field. - flapwise_bend_mom_offset_crosstalk_corrected: The flapwise bend mom offset crosstalk corrected field. - position: The position field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "SensorPosition", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, dm.NodeId, tuple[str, str], None] = None - edgewise_bend_mom_crosstalk_corrected: Union[TimeSeriesWrite, str, None] = None - edgewise_bend_mom_offset: Union[TimeSeriesWrite, str, None] = None - edgewise_bend_mom_offset_crosstalk_corrected: Union[TimeSeriesWrite, str, None] = None - edgewisewise_bend_mom: Union[TimeSeriesWrite, str, None] = None - flapwise_bend_mom: Union[TimeSeriesWrite, str, None] = None - flapwise_bend_mom_crosstalk_corrected: Union[TimeSeriesWrite, str, None] = None - flapwise_bend_mom_offset: Union[TimeSeriesWrite, str, None] = None - flapwise_bend_mom_offset_crosstalk_corrected: Union[TimeSeriesWrite, str, None] = None - position: Optional[float] = None - - def _to_instances_write( - self, - cache: set[tuple[str, str]], - write_none: bool = False, - allow_version_increase: bool = False, - ) -> ResourcesWrite: - resources = ResourcesWrite() - if self.as_tuple_id() in cache: - return resources - - properties: dict[str, Any] = {} - - if self.edgewise_bend_mom_crosstalk_corrected is not None or write_none: - properties["edgewise_bend_mom_crosstalk_corrected"] = ( - self.edgewise_bend_mom_crosstalk_corrected - if isinstance(self.edgewise_bend_mom_crosstalk_corrected, str) - or self.edgewise_bend_mom_crosstalk_corrected is None - else self.edgewise_bend_mom_crosstalk_corrected.external_id - ) - - if self.edgewise_bend_mom_offset is not None or write_none: - properties["edgewise_bend_mom_offset"] = ( - self.edgewise_bend_mom_offset - if isinstance(self.edgewise_bend_mom_offset, str) or self.edgewise_bend_mom_offset is None - else self.edgewise_bend_mom_offset.external_id - ) - - if self.edgewise_bend_mom_offset_crosstalk_corrected is not None or write_none: - properties["edgewise_bend_mom_offset_crosstalk_corrected"] = ( - self.edgewise_bend_mom_offset_crosstalk_corrected - if isinstance(self.edgewise_bend_mom_offset_crosstalk_corrected, str) - or self.edgewise_bend_mom_offset_crosstalk_corrected is None - else self.edgewise_bend_mom_offset_crosstalk_corrected.external_id - ) - - if self.edgewisewise_bend_mom is not None or write_none: - properties["edgewisewise_bend_mom"] = ( - self.edgewisewise_bend_mom - if isinstance(self.edgewisewise_bend_mom, str) or self.edgewisewise_bend_mom is None - else self.edgewisewise_bend_mom.external_id - ) - - if self.flapwise_bend_mom is not None or write_none: - properties["flapwise_bend_mom"] = ( - self.flapwise_bend_mom - if isinstance(self.flapwise_bend_mom, str) or self.flapwise_bend_mom is None - else self.flapwise_bend_mom.external_id - ) - - if self.flapwise_bend_mom_crosstalk_corrected is not None or write_none: - properties["flapwise_bend_mom_crosstalk_corrected"] = ( - self.flapwise_bend_mom_crosstalk_corrected - if isinstance(self.flapwise_bend_mom_crosstalk_corrected, str) - or self.flapwise_bend_mom_crosstalk_corrected is None - else self.flapwise_bend_mom_crosstalk_corrected.external_id - ) - - if self.flapwise_bend_mom_offset is not None or write_none: - properties["flapwise_bend_mom_offset"] = ( - self.flapwise_bend_mom_offset - if isinstance(self.flapwise_bend_mom_offset, str) or self.flapwise_bend_mom_offset is None - else self.flapwise_bend_mom_offset.external_id - ) - - if self.flapwise_bend_mom_offset_crosstalk_corrected is not None or write_none: - properties["flapwise_bend_mom_offset_crosstalk_corrected"] = ( - self.flapwise_bend_mom_offset_crosstalk_corrected - if isinstance(self.flapwise_bend_mom_offset_crosstalk_corrected, str) - or self.flapwise_bend_mom_offset_crosstalk_corrected is None - else self.flapwise_bend_mom_offset_crosstalk_corrected.external_id - ) - - if self.position is not None or write_none: - properties["position"] = self.position - - if properties: - this_node = dm.NodeApply( - space=self.space, - external_id=self.external_id, - existing_version=None if allow_version_increase else self.data_record.existing_version, - type=as_direct_relation_reference(self.node_type), - sources=[ - dm.NodeOrEdgeData( - source=self._view_id, - properties=properties, - ) - ], - ) - resources.nodes.append(this_node) - cache.add(self.as_tuple_id()) - - if isinstance(self.edgewise_bend_mom_crosstalk_corrected, CogniteTimeSeriesWrite): - resources.time_series.append(self.edgewise_bend_mom_crosstalk_corrected) - - if isinstance(self.edgewise_bend_mom_offset, CogniteTimeSeriesWrite): - resources.time_series.append(self.edgewise_bend_mom_offset) - - if isinstance(self.edgewise_bend_mom_offset_crosstalk_corrected, CogniteTimeSeriesWrite): - resources.time_series.append(self.edgewise_bend_mom_offset_crosstalk_corrected) - - if isinstance(self.edgewisewise_bend_mom, CogniteTimeSeriesWrite): - resources.time_series.append(self.edgewisewise_bend_mom) - - if isinstance(self.flapwise_bend_mom, CogniteTimeSeriesWrite): - resources.time_series.append(self.flapwise_bend_mom) - - if isinstance(self.flapwise_bend_mom_crosstalk_corrected, CogniteTimeSeriesWrite): - resources.time_series.append(self.flapwise_bend_mom_crosstalk_corrected) - - if isinstance(self.flapwise_bend_mom_offset, CogniteTimeSeriesWrite): - resources.time_series.append(self.flapwise_bend_mom_offset) - - if isinstance(self.flapwise_bend_mom_offset_crosstalk_corrected, CogniteTimeSeriesWrite): - resources.time_series.append(self.flapwise_bend_mom_offset_crosstalk_corrected) - - return resources - - -class SensorPositionApply(SensorPositionWrite): - def __new__(cls, *args, **kwargs) -> SensorPositionApply: - warnings.warn( - "SensorPositionApply is deprecated and will be removed in v1.0. Use SensorPositionWrite instead." - "The motivation for this change is that Write is a more descriptive name for the writing version of the" - "SensorPosition.", - UserWarning, - stacklevel=2, - ) - return super().__new__(cls) - - -class SensorPositionList(DomainModelList[SensorPosition]): - """List of sensor positions in the read version.""" - - _INSTANCE = SensorPosition - - def as_write(self) -> SensorPositionWriteList: - """Convert these read versions of sensor position to the writing versions.""" - return SensorPositionWriteList([node.as_write() for node in self.data]) - - def as_apply(self) -> SensorPositionWriteList: - """Convert these read versions of primitive nullable to the writing versions.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - -class SensorPositionWriteList(DomainModelWriteList[SensorPositionWrite]): - """List of sensor positions in the writing version.""" - - _INSTANCE = SensorPositionWrite - - -class SensorPositionApplyList(SensorPositionWriteList): ... - - -def _create_sensor_position_filter( - view_id: dm.ViewId, - min_position: float | None = None, - max_position: float | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - filter: dm.Filter | None = None, -) -> dm.Filter | None: - filters: list[dm.Filter] = [] - if min_position is not None or max_position is not None: - filters.append(dm.filters.Range(view_id.as_property_ref("position"), gte=min_position, lte=max_position)) - if external_id_prefix is not None: - filters.append(dm.filters.Prefix(["node", "externalId"], value=external_id_prefix)) - if isinstance(space, str): - filters.append(dm.filters.Equals(["node", "space"], value=space)) - if space and isinstance(space, list): - filters.append(dm.filters.In(["node", "space"], values=space)) - if filter: - filters.append(filter) - return dm.filters.And(*filters) if filters else None - - -class _SensorPositionQuery(NodeQueryCore[T_DomainModelList, SensorPositionList]): - _view_id = SensorPosition._view_id - _result_cls = SensorPosition - _result_list_cls_end = SensorPositionList - - def __init__( - self, - created_types: set[type], - creation_path: list[QueryCore], - client: CogniteClient, - result_list_cls: type[T_DomainModelList], - expression: dm.query.ResultSetExpression | None = None, - connection_name: str | None = None, - connection_type: Literal["reverse-list"] | None = None, - reverse_expression: dm.query.ResultSetExpression | None = None, - ): - - super().__init__( - created_types, - creation_path, - client, - result_list_cls, - expression, - dm.filters.HasData(views=[self._view_id]), - connection_name, - connection_type, - reverse_expression, - ) - - self.space = StringFilter(self, ["node", "space"]) - self.external_id = StringFilter(self, ["node", "externalId"]) - self.position = FloatFilter(self, self._view_id.as_property_ref("position")) - self._filter_classes.extend( - [ - self.space, - self.external_id, - self.position, - ] - ) - - def list_sensor_position(self, limit: int = DEFAULT_QUERY_LIMIT) -> SensorPositionList: - return self._list(limit=limit) - - -class SensorPositionQuery(_SensorPositionQuery[SensorPositionList]): - def __init__(self, client: CogniteClient): - super().__init__(set(), [], client, SensorPositionList) diff --git a/examples/windmill/data_classes/_windmill.py b/examples/windmill/data_classes/_windmill.py deleted file mode 100644 index c7010e195..000000000 --- a/examples/windmill/data_classes/_windmill.py +++ /dev/null @@ -1,665 +0,0 @@ -from __future__ import annotations - -import warnings -from collections.abc import Sequence -from typing import TYPE_CHECKING, Any, ClassVar, Literal, no_type_check, Optional, Union - -from cognite.client import data_modeling as dm, CogniteClient -from pydantic import Field -from pydantic import field_validator, model_validator - -from windmill.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - DataRecord, - DataRecordGraphQL, - DataRecordWrite, - DomainModel, - DomainModelWrite, - DomainModelWriteList, - DomainModelList, - DomainRelation, - DomainRelationWrite, - GraphQLCore, - ResourcesWrite, - T_DomainModelList, - as_direct_relation_reference, - as_instance_dict_id, - as_node_id, - as_pygen_node_id, - are_nodes_equal, - is_tuple_id, - select_best_node, - QueryCore, - NodeQueryCore, - StringFilter, - FloatFilter, -) - -if TYPE_CHECKING: - from windmill.data_classes._blade import Blade, BladeList, BladeGraphQL, BladeWrite, BladeWriteList - from windmill.data_classes._metmast import Metmast, MetmastList, MetmastGraphQL, MetmastWrite, MetmastWriteList - from windmill.data_classes._nacelle import Nacelle, NacelleList, NacelleGraphQL, NacelleWrite, NacelleWriteList - from windmill.data_classes._rotor import Rotor, RotorList, RotorGraphQL, RotorWrite, RotorWriteList - - -__all__ = [ - "Windmill", - "WindmillWrite", - "WindmillApply", - "WindmillList", - "WindmillWriteList", - "WindmillApplyList", - "WindmillFields", - "WindmillTextFields", - "WindmillGraphQL", -] - - -WindmillTextFields = Literal["external_id", "name", "windfarm"] -WindmillFields = Literal["external_id", "capacity", "name", "windfarm"] - -_WINDMILL_PROPERTIES_BY_FIELD = { - "external_id": "externalId", - "capacity": "capacity", - "name": "name", - "windfarm": "windfarm", -} - - -class WindmillGraphQL(GraphQLCore): - """This represents the reading version of windmill, used - when data is retrieved from CDF using GraphQL. - - It is used when retrieving data from CDF using GraphQL. - - Args: - space: The space where the node is located. - external_id: The external id of the windmill. - data_record: The data record of the windmill node. - blades: The blade field. - capacity: The capacity field. - metmast: The metmast field. - nacelle: The nacelle field. - name: The name field. - rotor: The rotor field. - windfarm: The windfarm field. - """ - - view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Windmill", "1") - blades: Optional[list[BladeGraphQL]] = Field(default=None, repr=False) - capacity: Optional[float] = None - metmast: Optional[list[MetmastGraphQL]] = Field(default=None, repr=False) - nacelle: Optional[NacelleGraphQL] = Field(default=None, repr=False) - name: Optional[str] = None - rotor: Optional[RotorGraphQL] = Field(default=None, repr=False) - windfarm: Optional[str] = None - - @model_validator(mode="before") - def parse_data_record(cls, values: Any) -> Any: - if not isinstance(values, dict): - return values - if "lastUpdatedTime" in values or "createdTime" in values: - values["dataRecord"] = DataRecordGraphQL( - created_time=values.pop("createdTime", None), - last_updated_time=values.pop("lastUpdatedTime", None), - ) - return values - - @field_validator("blades", "metmast", "nacelle", "rotor", mode="before") - def parse_graphql(cls, value: Any) -> Any: - if not isinstance(value, dict): - return value - if "items" in value: - return value["items"] - return value - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_read(self) -> Windmill: - """Convert this GraphQL format of windmill to the reading format.""" - if self.data_record is None: - raise ValueError("This object cannot be converted to a read format because it lacks a data record.") - return Windmill( - space=self.space, - external_id=self.external_id, - data_record=DataRecord( - version=0, - last_updated_time=self.data_record.last_updated_time, - created_time=self.data_record.created_time, - ), - blades=[blade.as_read() for blade in self.blades or []], - capacity=self.capacity, - metmast=[metmast.as_read() for metmast in self.metmast or []], - nacelle=self.nacelle.as_read() if isinstance(self.nacelle, GraphQLCore) else self.nacelle, - name=self.name, - rotor=self.rotor.as_read() if isinstance(self.rotor, GraphQLCore) else self.rotor, - windfarm=self.windfarm, - ) - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_write(self) -> WindmillWrite: - """Convert this GraphQL format of windmill to the writing format.""" - return WindmillWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=0), - blades=[blade.as_write() for blade in self.blades or []], - capacity=self.capacity, - metmast=[metmast.as_write() for metmast in self.metmast or []], - nacelle=self.nacelle.as_write() if isinstance(self.nacelle, GraphQLCore) else self.nacelle, - name=self.name, - rotor=self.rotor.as_write() if isinstance(self.rotor, GraphQLCore) else self.rotor, - windfarm=self.windfarm, - ) - - -class Windmill(DomainModel): - """This represents the reading version of windmill. - - It is used to when data is retrieved from CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the windmill. - data_record: The data record of the windmill node. - blades: The blade field. - capacity: The capacity field. - metmast: The metmast field. - nacelle: The nacelle field. - name: The name field. - rotor: The rotor field. - windfarm: The windfarm field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Windmill", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, None] = None - blades: Optional[list[Union[Blade, str, dm.NodeId]]] = Field(default=None, repr=False) - capacity: Optional[float] = None - metmast: Optional[list[Union[Metmast, str, dm.NodeId]]] = Field(default=None, repr=False) - nacelle: Union[Nacelle, str, dm.NodeId, None] = Field(default=None, repr=False) - name: Optional[str] = None - rotor: Union[Rotor, str, dm.NodeId, None] = Field(default=None, repr=False) - windfarm: Optional[str] = None - - def as_write(self) -> WindmillWrite: - """Convert this read version of windmill to the writing version.""" - return WindmillWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=self.data_record.version), - blades=[blade.as_write() if isinstance(blade, DomainModel) else blade for blade in self.blades or []], - capacity=self.capacity, - metmast=[ - metmast.as_write() if isinstance(metmast, DomainModel) else metmast for metmast in self.metmast or [] - ], - nacelle=self.nacelle.as_write() if isinstance(self.nacelle, DomainModel) else self.nacelle, - name=self.name, - rotor=self.rotor.as_write() if isinstance(self.rotor, DomainModel) else self.rotor, - windfarm=self.windfarm, - ) - - def as_apply(self) -> WindmillWrite: - """Convert this read version of windmill to the writing version.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - @classmethod - def _update_connections( - cls, - instances: dict[dm.NodeId | str, Windmill], # type: ignore[override] - nodes_by_id: dict[dm.NodeId | str, DomainModel], - edges_by_source_node: dict[dm.NodeId, list[dm.Edge | DomainRelation]], - ) -> None: - from ._blade import Blade - from ._metmast import Metmast - from ._nacelle import Nacelle - from ._rotor import Rotor - - for instance in instances.values(): - if ( - isinstance(instance.nacelle, (dm.NodeId, str)) - and (nacelle := nodes_by_id.get(instance.nacelle)) - and isinstance(nacelle, Nacelle) - ): - instance.nacelle = nacelle - if ( - isinstance(instance.rotor, (dm.NodeId, str)) - and (rotor := nodes_by_id.get(instance.rotor)) - and isinstance(rotor, Rotor) - ): - instance.rotor = rotor - if edges := edges_by_source_node.get(instance.as_id()): - blades: list[Blade | str | dm.NodeId] = [] - metmast: list[Metmast | str | dm.NodeId] = [] - for edge in edges: - value: DomainModel | DomainRelation | str | dm.NodeId - if isinstance(edge, DomainRelation): - value = edge - else: - other_end: dm.DirectRelationReference = ( - edge.end_node - if edge.start_node.space == instance.space - and edge.start_node.external_id == instance.external_id - else edge.start_node - ) - destination: dm.NodeId | str = ( - as_node_id(other_end) - if other_end.space != DEFAULT_INSTANCE_SPACE - else other_end.external_id - ) - if destination in nodes_by_id: - value = nodes_by_id[destination] - else: - value = destination - edge_type = edge.edge_type if isinstance(edge, DomainRelation) else edge.type - - if edge_type == dm.DirectRelationReference("power-models", "Windmill.blades") and isinstance( - value, (Blade, str, dm.NodeId) - ): - blades.append(value) - if edge_type == dm.DirectRelationReference("power-models", "Windmill.metmast") and isinstance( - value, (Metmast, str, dm.NodeId) - ): - metmast.append(value) - - instance.blades = blades or None - instance.metmast = metmast or None - - -class WindmillWrite(DomainModelWrite): - """This represents the writing version of windmill. - - It is used to when data is sent to CDF. - - Args: - space: The space where the node is located. - external_id: The external id of the windmill. - data_record: The data record of the windmill node. - blades: The blade field. - capacity: The capacity field. - metmast: The metmast field. - nacelle: The nacelle field. - name: The name field. - rotor: The rotor field. - windfarm: The windfarm field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("power-models", "Windmill", "1") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, dm.NodeId, tuple[str, str], None] = None - blades: Optional[list[Union[BladeWrite, str, dm.NodeId]]] = Field(default=None, repr=False) - capacity: Optional[float] = None - metmast: Optional[list[Union[MetmastWrite, str, dm.NodeId]]] = Field(default=None, repr=False) - nacelle: Union[NacelleWrite, str, dm.NodeId, None] = Field(default=None, repr=False) - name: Optional[str] = None - rotor: Union[RotorWrite, str, dm.NodeId, None] = Field(default=None, repr=False) - windfarm: Optional[str] = None - - @field_validator("blades", "metmast", "nacelle", "rotor", mode="before") - def as_node_id(cls, value: Any) -> Any: - if isinstance(value, dm.DirectRelationReference): - return dm.NodeId(value.space, value.external_id) - elif isinstance(value, tuple) and len(value) == 2 and all(isinstance(item, str) for item in value): - return dm.NodeId(value[0], value[1]) - elif isinstance(value, list): - return [cls.as_node_id(item) for item in value] - return value - - def _to_instances_write( - self, - cache: set[tuple[str, str]], - write_none: bool = False, - allow_version_increase: bool = False, - ) -> ResourcesWrite: - resources = ResourcesWrite() - if self.as_tuple_id() in cache: - return resources - - properties: dict[str, Any] = {} - - if self.capacity is not None or write_none: - properties["capacity"] = self.capacity - - if self.nacelle is not None: - properties["nacelle"] = { - "space": self.space if isinstance(self.nacelle, str) else self.nacelle.space, - "externalId": self.nacelle if isinstance(self.nacelle, str) else self.nacelle.external_id, - } - - if self.name is not None or write_none: - properties["name"] = self.name - - if self.rotor is not None: - properties["rotor"] = { - "space": self.space if isinstance(self.rotor, str) else self.rotor.space, - "externalId": self.rotor if isinstance(self.rotor, str) else self.rotor.external_id, - } - - if self.windfarm is not None or write_none: - properties["windfarm"] = self.windfarm - - if properties: - this_node = dm.NodeApply( - space=self.space, - external_id=self.external_id, - existing_version=None if allow_version_increase else self.data_record.existing_version, - type=as_direct_relation_reference(self.node_type), - sources=[ - dm.NodeOrEdgeData( - source=self._view_id, - properties=properties, - ) - ], - ) - resources.nodes.append(this_node) - cache.add(self.as_tuple_id()) - - edge_type = dm.DirectRelationReference("power-models", "Windmill.blades") - for blade in self.blades or []: - other_resources = DomainRelationWrite.from_edge_to_resources( - cache, - start_node=self, - end_node=blade, - edge_type=edge_type, - write_none=write_none, - allow_version_increase=allow_version_increase, - ) - resources.extend(other_resources) - - edge_type = dm.DirectRelationReference("power-models", "Windmill.metmast") - for metmast in self.metmast or []: - other_resources = DomainRelationWrite.from_edge_to_resources( - cache, - start_node=self, - end_node=metmast, - edge_type=edge_type, - write_none=write_none, - allow_version_increase=allow_version_increase, - ) - resources.extend(other_resources) - - if isinstance(self.nacelle, DomainModelWrite): - other_resources = self.nacelle._to_instances_write(cache) - resources.extend(other_resources) - - if isinstance(self.rotor, DomainModelWrite): - other_resources = self.rotor._to_instances_write(cache) - resources.extend(other_resources) - - return resources - - -class WindmillApply(WindmillWrite): - def __new__(cls, *args, **kwargs) -> WindmillApply: - warnings.warn( - "WindmillApply is deprecated and will be removed in v1.0. Use WindmillWrite instead." - "The motivation for this change is that Write is a more descriptive name for the writing version of the" - "Windmill.", - UserWarning, - stacklevel=2, - ) - return super().__new__(cls) - - -class WindmillList(DomainModelList[Windmill]): - """List of windmills in the read version.""" - - _INSTANCE = Windmill - - def as_write(self) -> WindmillWriteList: - """Convert these read versions of windmill to the writing versions.""" - return WindmillWriteList([node.as_write() for node in self.data]) - - def as_apply(self) -> WindmillWriteList: - """Convert these read versions of primitive nullable to the writing versions.""" - warnings.warn( - "as_apply is deprecated and will be removed in v1.0. Use as_write instead.", - UserWarning, - stacklevel=2, - ) - return self.as_write() - - @property - def blades(self) -> BladeList: - from ._blade import Blade, BladeList - - return BladeList([item for items in self.data for item in items.blades or [] if isinstance(item, Blade)]) - - @property - def metmast(self) -> MetmastList: - from ._metmast import Metmast, MetmastList - - return MetmastList([item for items in self.data for item in items.metmast or [] if isinstance(item, Metmast)]) - - @property - def nacelle(self) -> NacelleList: - from ._nacelle import Nacelle, NacelleList - - return NacelleList([item.nacelle for item in self.data if isinstance(item.nacelle, Nacelle)]) - - @property - def rotor(self) -> RotorList: - from ._rotor import Rotor, RotorList - - return RotorList([item.rotor for item in self.data if isinstance(item.rotor, Rotor)]) - - -class WindmillWriteList(DomainModelWriteList[WindmillWrite]): - """List of windmills in the writing version.""" - - _INSTANCE = WindmillWrite - - @property - def blades(self) -> BladeWriteList: - from ._blade import BladeWrite, BladeWriteList - - return BladeWriteList( - [item for items in self.data for item in items.blades or [] if isinstance(item, BladeWrite)] - ) - - @property - def metmast(self) -> MetmastWriteList: - from ._metmast import MetmastWrite, MetmastWriteList - - return MetmastWriteList( - [item for items in self.data for item in items.metmast or [] if isinstance(item, MetmastWrite)] - ) - - @property - def nacelle(self) -> NacelleWriteList: - from ._nacelle import NacelleWrite, NacelleWriteList - - return NacelleWriteList([item.nacelle for item in self.data if isinstance(item.nacelle, NacelleWrite)]) - - @property - def rotor(self) -> RotorWriteList: - from ._rotor import RotorWrite, RotorWriteList - - return RotorWriteList([item.rotor for item in self.data if isinstance(item.rotor, RotorWrite)]) - - -class WindmillApplyList(WindmillWriteList): ... - - -def _create_windmill_filter( - view_id: dm.ViewId, - min_capacity: float | None = None, - max_capacity: float | None = None, - nacelle: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - rotor: ( - str - | tuple[str, str] - | dm.NodeId - | dm.DirectRelationReference - | Sequence[str | tuple[str, str] | dm.NodeId | dm.DirectRelationReference] - | None - ) = None, - windfarm: str | list[str] | None = None, - windfarm_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - filter: dm.Filter | None = None, -) -> dm.Filter | None: - filters: list[dm.Filter] = [] - if min_capacity is not None or max_capacity is not None: - filters.append(dm.filters.Range(view_id.as_property_ref("capacity"), gte=min_capacity, lte=max_capacity)) - if isinstance(nacelle, str | dm.NodeId | dm.DirectRelationReference) or is_tuple_id(nacelle): - filters.append(dm.filters.Equals(view_id.as_property_ref("nacelle"), value=as_instance_dict_id(nacelle))) - if nacelle and isinstance(nacelle, Sequence) and not isinstance(nacelle, str) and not is_tuple_id(nacelle): - filters.append( - dm.filters.In(view_id.as_property_ref("nacelle"), values=[as_instance_dict_id(item) for item in nacelle]) - ) - if isinstance(name, str): - filters.append(dm.filters.Equals(view_id.as_property_ref("name"), value=name)) - if name and isinstance(name, list): - filters.append(dm.filters.In(view_id.as_property_ref("name"), values=name)) - if name_prefix is not None: - filters.append(dm.filters.Prefix(view_id.as_property_ref("name"), value=name_prefix)) - if isinstance(rotor, str | dm.NodeId | dm.DirectRelationReference) or is_tuple_id(rotor): - filters.append(dm.filters.Equals(view_id.as_property_ref("rotor"), value=as_instance_dict_id(rotor))) - if rotor and isinstance(rotor, Sequence) and not isinstance(rotor, str) and not is_tuple_id(rotor): - filters.append( - dm.filters.In(view_id.as_property_ref("rotor"), values=[as_instance_dict_id(item) for item in rotor]) - ) - if isinstance(windfarm, str): - filters.append(dm.filters.Equals(view_id.as_property_ref("windfarm"), value=windfarm)) - if windfarm and isinstance(windfarm, list): - filters.append(dm.filters.In(view_id.as_property_ref("windfarm"), values=windfarm)) - if windfarm_prefix is not None: - filters.append(dm.filters.Prefix(view_id.as_property_ref("windfarm"), value=windfarm_prefix)) - if external_id_prefix is not None: - filters.append(dm.filters.Prefix(["node", "externalId"], value=external_id_prefix)) - if isinstance(space, str): - filters.append(dm.filters.Equals(["node", "space"], value=space)) - if space and isinstance(space, list): - filters.append(dm.filters.In(["node", "space"], values=space)) - if filter: - filters.append(filter) - return dm.filters.And(*filters) if filters else None - - -class _WindmillQuery(NodeQueryCore[T_DomainModelList, WindmillList]): - _view_id = Windmill._view_id - _result_cls = Windmill - _result_list_cls_end = WindmillList - - def __init__( - self, - created_types: set[type], - creation_path: list[QueryCore], - client: CogniteClient, - result_list_cls: type[T_DomainModelList], - expression: dm.query.ResultSetExpression | None = None, - connection_name: str | None = None, - connection_type: Literal["reverse-list"] | None = None, - reverse_expression: dm.query.ResultSetExpression | None = None, - ): - from ._blade import _BladeQuery - from ._metmast import _MetmastQuery - from ._nacelle import _NacelleQuery - from ._rotor import _RotorQuery - - super().__init__( - created_types, - creation_path, - client, - result_list_cls, - expression, - dm.filters.HasData(views=[self._view_id]), - connection_name, - connection_type, - reverse_expression, - ) - - if _BladeQuery not in created_types: - self.blades = _BladeQuery( - created_types.copy(), - self._creation_path, - client, - result_list_cls, - dm.query.EdgeResultSetExpression( - direction="outwards", - chain_to="destination", - ), - connection_name="blades", - ) - - if _MetmastQuery not in created_types: - self.metmast = _MetmastQuery( - created_types.copy(), - self._creation_path, - client, - result_list_cls, - dm.query.EdgeResultSetExpression( - direction="outwards", - chain_to="destination", - ), - connection_name="metmast", - ) - - if _NacelleQuery not in created_types: - self.nacelle = _NacelleQuery( - created_types.copy(), - self._creation_path, - client, - result_list_cls, - dm.query.NodeResultSetExpression( - through=self._view_id.as_property_ref("nacelle"), - direction="outwards", - ), - connection_name="nacelle", - ) - - if _RotorQuery not in created_types: - self.rotor = _RotorQuery( - created_types.copy(), - self._creation_path, - client, - result_list_cls, - dm.query.NodeResultSetExpression( - through=self._view_id.as_property_ref("rotor"), - direction="outwards", - ), - connection_name="rotor", - ) - - self.space = StringFilter(self, ["node", "space"]) - self.external_id = StringFilter(self, ["node", "externalId"]) - self.capacity = FloatFilter(self, self._view_id.as_property_ref("capacity")) - self.name = StringFilter(self, self._view_id.as_property_ref("name")) - self.windfarm = StringFilter(self, self._view_id.as_property_ref("windfarm")) - self._filter_classes.extend( - [ - self.space, - self.external_id, - self.capacity, - self.name, - self.windfarm, - ] - ) - - def list_windmill(self, limit: int = DEFAULT_QUERY_LIMIT) -> WindmillList: - return self._list(limit=limit) - - -class WindmillQuery(_WindmillQuery[WindmillList]): - def __init__(self, client: CogniteClient): - super().__init__(set(), [], client, WindmillList) diff --git a/tests/data/models/Windmill/model.yaml b/tests/data/models/Windmill/model.yaml deleted file mode 100644 index be819c1a7..000000000 --- a/tests/data/models/Windmill/model.yaml +++ /dev/null @@ -1,916 +0,0 @@ -space: power-models -externalId: Windmill -name: Windmill -description: '' -version: '1' -views: -- space: power-models - externalId: Blade - name: Blade - implements: [] - version: '1' - writable: true - usedFor: node - isGlobal: false - properties: - is_damaged: - container: - space: power-models - externalId: Blade - containerPropertyIdentifier: is_damaged - type: - list: false - type: boolean - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: is_damaged - description: null - name: - container: - space: power-models - externalId: Blade - containerPropertyIdentifier: name - type: - list: false - collation: ucs_basic - type: text - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: name - description: null - sensor_positions: - type: - space: power-models - externalId: Blade.sensor_positions - source: - space: power-models - externalId: SensorPosition - version: '1' - type: view - name: sensor_positions - description: null - edgeSource: null - direction: outwards - connectionType: multi_edge_connection - lastUpdatedTime: 1703508218266 - createdTime: 1703508218266 -- space: power-models - externalId: Gearbox - name: Gearbox - implements: [] - version: '1' - writable: true - usedFor: node - isGlobal: false - properties: - displacement_x: - container: - space: power-models - externalId: Gearbox - containerPropertyIdentifier: displacement_x - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: displacement_x - description: null - displacement_y: - container: - space: power-models - externalId: Gearbox - containerPropertyIdentifier: displacement_y - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: displacement_y - description: null - displacement_z: - container: - space: power-models - externalId: Gearbox - containerPropertyIdentifier: displacement_z - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: displacement_z - description: null - lastUpdatedTime: 1703508218266 - createdTime: 1703508218266 -- space: power-models - externalId: Generator - name: Generator - implements: [] - version: '1' - writable: true - usedFor: node - isGlobal: false - properties: - generator_speed_controller: - container: - space: power-models - externalId: Generator - containerPropertyIdentifier: generator_speed_controller - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: generator_speed_controller - description: null - generator_speed_controller_reference: - container: - space: power-models - externalId: Generator - containerPropertyIdentifier: generator_speed_controller_reference - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: generator_speed_controller_reference - description: null - lastUpdatedTime: 1703508218266 - createdTime: 1703508218266 -- space: power-models - externalId: HighSpeedShaft - name: HighSpeedShaft - implements: [] - version: '1' - writable: true - usedFor: node - isGlobal: false - properties: - bending_moment_y: - container: - space: power-models - externalId: HighSpeedShaft - containerPropertyIdentifier: bending_moment_y - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: bending_moment_y - description: null - bending_monent_x: - container: - space: power-models - externalId: HighSpeedShaft - containerPropertyIdentifier: bending_monent_x - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: bending_monent_x - description: null - torque: - container: - space: power-models - externalId: HighSpeedShaft - containerPropertyIdentifier: torque - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: torque - description: null - lastUpdatedTime: 1703508218266 - createdTime: 1703508218266 -- space: power-models - externalId: MainShaft - name: MainShaft - implements: [] - version: '1' - writable: true - usedFor: node - isGlobal: false - properties: - bending_x: - container: - space: power-models - externalId: MainShaft - containerPropertyIdentifier: bending_x - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: bending_x - description: null - bending_y: - container: - space: power-models - externalId: MainShaft - containerPropertyIdentifier: bending_y - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: bending_y - description: null - calculated_tilt_moment: - container: - space: power-models - externalId: MainShaft - containerPropertyIdentifier: calculated_tilt_moment - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: calculated_tilt_moment - description: null - calculated_yaw_moment: - container: - space: power-models - externalId: MainShaft - containerPropertyIdentifier: calculated_yaw_moment - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: calculated_yaw_moment - description: null - torque: - container: - space: power-models - externalId: MainShaft - containerPropertyIdentifier: torque - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: torque - description: null - lastUpdatedTime: 1703508218266 - createdTime: 1703508218266 -- space: power-models - externalId: Metmast - name: Metmast - implements: [] - version: '1' - writable: true - usedFor: node - isGlobal: false - properties: - position: - container: - space: power-models - externalId: Metmast - containerPropertyIdentifier: position - type: - list: false - type: float64 - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: position - description: null - temperature: - container: - space: power-models - externalId: Metmast - containerPropertyIdentifier: temperature - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: temperature - description: null - tilt_angle: - container: - space: power-models - externalId: Metmast - containerPropertyIdentifier: tilt_angle - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: tilt_angle - description: null - wind_speed: - container: - space: power-models - externalId: Metmast - containerPropertyIdentifier: wind_speed - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: wind_speed - description: null - lastUpdatedTime: 1703508218266 - createdTime: 1703508218266 -- space: power-models - externalId: Nacelle - name: Nacelle - implements: [] - version: '1' - writable: true - usedFor: node - isGlobal: false - properties: - acc_from_back_side_x: - container: - space: power-models - externalId: Nacelle - containerPropertyIdentifier: acc_from_back_side_x - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: acc_from_back_side_x - description: null - acc_from_back_side_y: - container: - space: power-models - externalId: Nacelle - containerPropertyIdentifier: acc_from_back_side_y - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: acc_from_back_side_y - description: null - acc_from_back_side_z: - container: - space: power-models - externalId: Nacelle - containerPropertyIdentifier: acc_from_back_side_z - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: acc_from_back_side_z - description: null - gearbox: - container: - space: power-models - externalId: Nacelle - containerPropertyIdentifier: gearbox - type: - list: false - type: direct - source: - space: power-models - externalId: Gearbox - version: '1' - nullable: true - immutable: false - autoIncrement: false - defaultValue: null - name: gearbox - description: null - generator: - container: - space: power-models - externalId: Nacelle - containerPropertyIdentifier: generator - type: - list: false - type: direct - source: - space: power-models - externalId: Generator - version: '1' - nullable: true - immutable: false - autoIncrement: false - defaultValue: null - name: generator - description: null - high_speed_shaft: - container: - space: power-models - externalId: Nacelle - containerPropertyIdentifier: high_speed_shaft - type: - list: false - type: direct - source: - space: power-models - externalId: HighSpeedShaft - version: '1' - nullable: true - immutable: false - autoIncrement: false - defaultValue: null - name: high_speed_shaft - description: null - main_shaft: - container: - space: power-models - externalId: Nacelle - containerPropertyIdentifier: main_shaft - type: - list: false - type: direct - source: - space: power-models - externalId: MainShaft - version: '1' - nullable: true - immutable: false - autoIncrement: false - defaultValue: null - name: main_shaft - description: null - power_inverter: - container: - space: power-models - externalId: Nacelle - containerPropertyIdentifier: power_inverter - type: - list: false - type: direct - source: - space: power-models - externalId: PowerInverter - version: '1' - nullable: true - immutable: false - autoIncrement: false - defaultValue: null - name: power_inverter - description: null - yaw_direction: - container: - space: power-models - externalId: Nacelle - containerPropertyIdentifier: yaw_direction - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: yaw_direction - description: null - yaw_error: - container: - space: power-models - externalId: Nacelle - containerPropertyIdentifier: yaw_error - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: yaw_error - description: null - lastUpdatedTime: 1703508218266 - createdTime: 1703508218266 -- space: power-models - externalId: PowerInverter - name: PowerInverter - implements: [] - version: '1' - writable: true - usedFor: node - isGlobal: false - properties: - active_power_total: - container: - space: power-models - externalId: PowerInverter - containerPropertyIdentifier: active_power_total - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: active_power_total - description: null - apparent_power_total: - container: - space: power-models - externalId: PowerInverter - containerPropertyIdentifier: apparent_power_total - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: apparent_power_total - description: null - reactive_power_total: - container: - space: power-models - externalId: PowerInverter - containerPropertyIdentifier: reactive_power_total - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: reactive_power_total - description: null - lastUpdatedTime: 1703508218266 - createdTime: 1703508218266 -- space: power-models - externalId: Rotor - name: Rotor - implements: [] - version: '1' - writable: true - usedFor: node - isGlobal: false - properties: - rotor_speed_controller: - container: - space: power-models - externalId: Rotor - containerPropertyIdentifier: rotor_speed_controller - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: rotor_speed_controller - description: null - rpm_low_speed_shaft: - container: - space: power-models - externalId: Rotor - containerPropertyIdentifier: rpm_low_speed_shaft - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: rpm_low_speed_shaft - description: null - lastUpdatedTime: 1703508218266 - createdTime: 1703508218266 -- space: power-models - externalId: SensorPosition - name: SensorPosition - implements: [] - version: '1' - writable: true - usedFor: node - isGlobal: false - properties: - edgewise_bend_mom_crosstalk_corrected: - container: - space: power-models - externalId: SensorPosition - containerPropertyIdentifier: edgewise_bend_mom_crosstalk_corrected - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: edgewise_bend_mom_crosstalk_corrected - description: null - edgewise_bend_mom_offset: - container: - space: power-models - externalId: SensorPosition - containerPropertyIdentifier: edgewise_bend_mom_offset - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: edgewise_bend_mom_offset - description: null - edgewise_bend_mom_offset_crosstalk_corrected: - container: - space: power-models - externalId: SensorPosition - containerPropertyIdentifier: edgewise_bend_mom_offset_crosstalk_corrected - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: edgewise_bend_mom_offset_crosstalk_corrected - description: null - edgewisewise_bend_mom: - container: - space: power-models - externalId: SensorPosition - containerPropertyIdentifier: edgewisewise_bend_mom - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: edgewisewise_bend_mom - description: null - flapwise_bend_mom: - container: - space: power-models - externalId: SensorPosition - containerPropertyIdentifier: flapwise_bend_mom - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: flapwise_bend_mom - description: null - flapwise_bend_mom_crosstalk_corrected: - container: - space: power-models - externalId: SensorPosition - containerPropertyIdentifier: flapwise_bend_mom_crosstalk_corrected - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: flapwise_bend_mom_crosstalk_corrected - description: null - flapwise_bend_mom_offset: - container: - space: power-models - externalId: SensorPosition - containerPropertyIdentifier: flapwise_bend_mom_offset - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: flapwise_bend_mom_offset - description: null - flapwise_bend_mom_offset_crosstalk_corrected: - container: - space: power-models - externalId: SensorPosition - containerPropertyIdentifier: flapwise_bend_mom_offset_crosstalk_corrected - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: flapwise_bend_mom_offset_crosstalk_corrected - description: null - position: - container: - space: power-models - externalId: SensorPosition - containerPropertyIdentifier: position - type: - list: false - type: float64 - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: position - description: null - lastUpdatedTime: 1703508218266 - createdTime: 1703508218266 -- space: power-models - externalId: Windmill - name: Windmill - implements: [] - version: '1' - writable: true - usedFor: node - isGlobal: false - properties: - blades: - type: - space: power-models - externalId: Windmill.blades - source: - space: power-models - externalId: Blade - version: '1' - type: view - name: blades - description: null - edgeSource: null - direction: outwards - connectionType: multi_edge_connection - capacity: - container: - space: power-models - externalId: Windmill - containerPropertyIdentifier: capacity - type: - list: false - type: float64 - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: capacity - description: null - metmast: - type: - space: power-models - externalId: Windmill.metmast - source: - space: power-models - externalId: Metmast - version: '1' - type: view - name: metmast - description: null - edgeSource: null - direction: outwards - connectionType: multi_edge_connection - nacelle: - container: - space: power-models - externalId: Windmill - containerPropertyIdentifier: nacelle - type: - list: false - type: direct - source: - space: power-models - externalId: Nacelle - version: '1' - nullable: true - immutable: false - autoIncrement: false - defaultValue: null - name: nacelle - description: null - name: - container: - space: power-models - externalId: Windmill - containerPropertyIdentifier: name - type: - list: false - collation: ucs_basic - type: text - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: name - description: null - rotor: - container: - space: power-models - externalId: Windmill - containerPropertyIdentifier: rotor - type: - list: false - type: direct - source: - space: power-models - externalId: Rotor - version: '1' - nullable: true - immutable: false - autoIncrement: false - defaultValue: null - name: rotor - description: null - windfarm: - container: - space: power-models - externalId: Windmill - containerPropertyIdentifier: windfarm - type: - list: false - collation: ucs_basic - type: text - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: windfarm - description: null - lastUpdatedTime: 1703508218266 - createdTime: 1703508218266 -isGlobal: false -lastUpdatedTime: 1703508242349 -createdTime: 1703508219198