diff --git a/cognite/pygen/_core/templates/api_class_node.py.jinja b/cognite/pygen/_core/templates/api_class_node.py.jinja index d35508775..1efbbb60e 100644 --- a/cognite/pygen/_core/templates/api_class_node.py.jinja +++ b/cognite/pygen/_core/templates/api_class_node.py.jinja @@ -81,6 +81,12 @@ class {{ api_class.name }}({% if data_class.is_writable %}NodeAPI{% else %}NodeR A query API for {{ data_class.doc_list_name}}. """ + warnings.warn( + "This method is deprecated and will soon be removed. " + "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = {{ data_class.filter_name }}( self._view_id,{% for parm in list_method.parameters %} diff --git a/docs/usage/edge_properties.ipynb b/docs/usage/edge_properties.ipynb index e928f4262..3b58b789b 100644 --- a/docs/usage/edge_properties.ipynb +++ b/docs/usage/edge_properties.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 5, + "execution_count": 1, "id": "e32acfc3-632c-479e-95be-331461c4d5f9", "metadata": { "editable": true, @@ -17,43 +17,26 @@ "source": [ "# This is just to enable improting the generated SDK from the examples folder in the pygen repository\n", "import sys\n", + "import warnings\n", "\n", "from tests.constants import REPO_ROOT\n", "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", "sys.path.append(str(REPO_ROOT / \"examples\"))" ] }, { - "attachments": { - "1b7b0795-705e-4cee-88da-f0f1e7d82be6.png": { - "image/png": "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" - } - }, + "attachments": {}, "cell_type": "markdown", "id": "34ed5247-d76c-4b22-a5d9-6ead02628433", "metadata": {}, "source": [ "# Working with Edges\n", "\n", - "Ofte edges in our data model is simply connecting two nodes. However, it is possible to have properties on edges as we have on nodes. This can be useful for filtering on these properties to select which nodes and edges we want.\n", - "\n", - "In this example, we will use the model shown below in the Cognite Data Fusion interface:\n", - "\n", - "![image.png](attachment:1b7b0795-705e-4cee-88da-f0f1e7d82be6.png)\n", - "\n", - "\n", - "We note that we have one two `views`/`types` used for `nodes`, `UnitProcedure`and `EquipmentModule`, and one for edges `StartEndTime`. A `UnitProcedure` is connected through multiple `EquipmentModules` thorugh the `StartEndTime` edge.\n", + "In this tutorial, we use edges with properties. We notice that the connection between `WindTurbine` and `Metmast`goes through an edge with the property `distance`.\n", "\n", - "\n", - "First, we have to generate an SDK for the `EquipmentUnit` model. The call below shows a truncated version of the call used to generate the SDK.\n", - "\n", - "```bash\n", - "pygen generate --client-name EquipmentUnitClient --to-level-package equipment_unit\n", - "```\n", - "\n", - "Note that you can use any of the methods for generating the SDK as described in see the [QuickStart guides](../quickstart/cdf_notebook.html).\n", - "\n", - "Second, we have setup a `config.toml`in the current working directory with credentials to connect to Cognite Data Fusion\n" + "\n" ] }, { @@ -63,7 +46,7 @@ "metadata": {}, "outputs": [], "source": [ - "from equipment_unit import EquipmentUnitClient" + "from wind_turbine import WindTurbineClient" ] }, { @@ -73,7 +56,7 @@ "metadata": {}, "outputs": [], "source": [ - "pygen = EquipmentUnitClient.from_toml(\"config.toml\")" + "pygen = WindTurbineClient.from_toml(\"config.toml\")" ] }, { @@ -89,12 +72,12 @@ "id": "a095350c-834d-4606-8582-b536ca8a9282", "metadata": {}, "source": [ - "The `StartEndTime` edges are available on the `unit_procedure` class, and have a `list` method we can use to list and filter all edges of this type." + "The `Distance` edges are available on the `wind_turbine` class, and have a `list` method we can use to list and filter all edges of this type." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "id": "c5f449e7-7daf-42fb-8417-73f70f212094", "metadata": {}, "outputs": [ @@ -124,102 +107,94 @@ " edge_type\n", " start_node\n", " end_node\n", - " end_time\n", - " start_time\n", + " distance\n", " data_record\n", " \n", " \n", " \n", " \n", " 0\n", - " IntegrationTestsImmutable\n", - " unit_procedure:Kelly Evans:equipment_module:Ja...\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", - " 2023-11-09 11:25:22+00:00\n", - " 2023-11-01 12:19:16+00:00\n", - " {'version': 1, 'last_updated_time': 2023-11-13...\n", + " sp_wind\n", + " turbine1_to_utsira\n", + " {'space': 'sp_pygen_power_enterprise', 'extern...\n", + " {'space': 'sp_wind', 'external_id': 'hornsea_1...\n", + " {'space': 'sp_wind', 'external_id': 'utsira_st...\n", + " 1000.0\n", + " {'version': 1, 'last_updated_time': 2024-11-16...\n", " \n", " \n", " 1\n", - " IntegrationTestsImmutable\n", - " unit_procedure:Kelly Evans:equipment_module:Le...\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", - " 2023-10-28 19:31:25+00:00\n", - " 2023-10-23 10:12:59+00:00\n", - " {'version': 1, 'last_updated_time': 2023-11-13...\n", + " sp_wind\n", + " turbine2_to_utsira\n", + " {'space': 'sp_pygen_power_enterprise', 'extern...\n", + " {'space': 'sp_wind', 'external_id': 'hornsea_1...\n", + " {'space': 'sp_wind', 'external_id': 'utsira_st...\n", + " 1200.0\n", + " {'version': 1, 'last_updated_time': 2024-11-16...\n", " \n", " \n", " 2\n", - " IntegrationTestsImmutable\n", - " unit_procedure:Kelly Evans:equipment_module:Gr...\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", - " 2023-11-13 18:01:26+00:00\n", - " 2023-11-12 13:56:38+00:00\n", - " {'version': 1, 'last_updated_time': 2023-11-13...\n", + " sp_wind\n", + " turbine3_to_utsira\n", + " {'space': 'sp_pygen_power_enterprise', 'extern...\n", + " {'space': 'sp_wind', 'external_id': 'hornsea_1...\n", + " {'space': 'sp_wind', 'external_id': 'utsira_st...\n", + " 700.0\n", + " {'version': 1, 'last_updated_time': 2024-11-16...\n", " \n", " \n", " 3\n", - " IntegrationTestsImmutable\n", - " unit_procedure:Kelly Evans:equipment_module:Ro...\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", - " 2023-11-11 23:23:43+00:00\n", - " 2023-11-08 21:17:07+00:00\n", - " {'version': 1, 'last_updated_time': 2023-11-13...\n", + " sp_wind\n", + " turbine4_to_utsira\n", + " {'space': 'sp_pygen_power_enterprise', 'extern...\n", + " {'space': 'sp_wind', 'external_id': 'hornsea_1...\n", + " {'space': 'sp_wind', 'external_id': 'utsira_st...\n", + " 1500.0\n", + " {'version': 1, 'last_updated_time': 2024-11-16...\n", " \n", " \n", " 4\n", - " IntegrationTestsImmutable\n", - " unit_procedure:Kelly Evans:equipment_module:Aa...\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", - " 2023-11-09 01:52:04+00:00\n", - " 2023-11-01 23:01:38+00:00\n", - " {'version': 1, 'last_updated_time': 2023-11-13...\n", + " sp_wind\n", + " turbine5_to_utsira\n", + " {'space': 'sp_pygen_power_enterprise', 'extern...\n", + " {'space': 'sp_wind', 'external_id': 'hornsea_1...\n", + " {'space': 'sp_wind', 'external_id': 'utsira_st...\n", + " 2100.0\n", + " {'version': 1, 'last_updated_time': 2024-11-16...\n", " \n", " \n", "\n", "" ], "text/plain": [ - "StartEndTimeList([StartEndTime(space='IntegrationTestsImmutable', external_id='unit_procedure:Kelly Evans:equipment_module:Jamie Campbell', edge_type=DirectRelationReference(space='IntegrationTestsImmutable', external_id='UnitProcedure.equipment_module'), start_node=DirectRelationReference(space='IntegrationTestsImmutable', external_id='unit_procedure:Kelly Evans'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), deleted_time=None), end_node=NodeId(space='IntegrationTestsImmutable', external_id='equipment_module:Jamie Campbell'), end_time=datetime.datetime(2023, 11, 9, 11, 25, 22, tzinfo=TzInfo(UTC)), start_time=datetime.datetime(2023, 11, 1, 12, 19, 16, tzinfo=TzInfo(UTC))),\n", - " StartEndTime(space='IntegrationTestsImmutable', external_id='unit_procedure:Kelly Evans:equipment_module:Lee Coleman', edge_type=DirectRelationReference(space='IntegrationTestsImmutable', external_id='UnitProcedure.equipment_module'), start_node=DirectRelationReference(space='IntegrationTestsImmutable', external_id='unit_procedure:Kelly Evans'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), deleted_time=None), end_node=NodeId(space='IntegrationTestsImmutable', external_id='equipment_module:Lee Coleman'), end_time=datetime.datetime(2023, 10, 28, 19, 31, 25, tzinfo=TzInfo(UTC)), start_time=datetime.datetime(2023, 10, 23, 10, 12, 59, tzinfo=TzInfo(UTC))),\n", - " StartEndTime(space='IntegrationTestsImmutable', external_id='unit_procedure:Kelly Evans:equipment_module:Gregory Campbell', edge_type=DirectRelationReference(space='IntegrationTestsImmutable', external_id='UnitProcedure.equipment_module'), start_node=DirectRelationReference(space='IntegrationTestsImmutable', external_id='unit_procedure:Kelly Evans'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), deleted_time=None), end_node=NodeId(space='IntegrationTestsImmutable', external_id='equipment_module:Gregory Campbell'), end_time=datetime.datetime(2023, 11, 13, 18, 1, 26, tzinfo=TzInfo(UTC)), start_time=datetime.datetime(2023, 11, 12, 13, 56, 38, tzinfo=TzInfo(UTC))),\n", - " StartEndTime(space='IntegrationTestsImmutable', external_id='unit_procedure:Kelly Evans:equipment_module:Robert West', edge_type=DirectRelationReference(space='IntegrationTestsImmutable', external_id='UnitProcedure.equipment_module'), start_node=DirectRelationReference(space='IntegrationTestsImmutable', external_id='unit_procedure:Kelly Evans'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), deleted_time=None), end_node=NodeId(space='IntegrationTestsImmutable', external_id='equipment_module:Robert West'), end_time=datetime.datetime(2023, 11, 11, 23, 23, 43, tzinfo=TzInfo(UTC)), start_time=datetime.datetime(2023, 11, 8, 21, 17, 7, tzinfo=TzInfo(UTC))),\n", - " StartEndTime(space='IntegrationTestsImmutable', external_id='unit_procedure:Kelly Evans:equipment_module:Aaron Vega', edge_type=DirectRelationReference(space='IntegrationTestsImmutable', external_id='UnitProcedure.equipment_module'), start_node=DirectRelationReference(space='IntegrationTestsImmutable', external_id='unit_procedure:Kelly Evans'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), deleted_time=None), end_node=NodeId(space='IntegrationTestsImmutable', external_id='equipment_module:Aaron Vega'), end_time=datetime.datetime(2023, 11, 9, 1, 52, 4, tzinfo=TzInfo(UTC)), start_time=datetime.datetime(2023, 11, 1, 23, 1, 38, tzinfo=TzInfo(UTC)))])" + "DistanceList([Distance(space='sp_wind', external_id='turbine1_to_utsira', edge_type=DirectRelationReference(space='sp_pygen_power_enterprise', external_id='Distance'), start_node=DirectRelationReference(space='sp_wind', external_id='hornsea_1_mill_1'), end_node=NodeId(space='sp_wind', external_id='utsira_station'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), deleted_time=None), distance=1000.0),\n", + " Distance(space='sp_wind', external_id='turbine2_to_utsira', edge_type=DirectRelationReference(space='sp_pygen_power_enterprise', external_id='Distance'), start_node=DirectRelationReference(space='sp_wind', external_id='hornsea_1_mill_2'), end_node=NodeId(space='sp_wind', external_id='utsira_station'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), deleted_time=None), distance=1200.0),\n", + " Distance(space='sp_wind', external_id='turbine3_to_utsira', edge_type=DirectRelationReference(space='sp_pygen_power_enterprise', external_id='Distance'), start_node=DirectRelationReference(space='sp_wind', external_id='hornsea_1_mill_3'), end_node=NodeId(space='sp_wind', external_id='utsira_station'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), deleted_time=None), distance=700.0),\n", + " Distance(space='sp_wind', external_id='turbine4_to_utsira', edge_type=DirectRelationReference(space='sp_pygen_power_enterprise', external_id='Distance'), start_node=DirectRelationReference(space='sp_wind', external_id='hornsea_1_mill_4'), end_node=NodeId(space='sp_wind', external_id='utsira_station'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), deleted_time=None), distance=1500.0),\n", + " Distance(space='sp_wind', external_id='turbine5_to_utsira', edge_type=DirectRelationReference(space='sp_pygen_power_enterprise', external_id='Distance'), start_node=DirectRelationReference(space='sp_wind', external_id='hornsea_1_mill_5'), end_node=NodeId(space='sp_wind', external_id='utsira_station'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), deleted_time=None), distance=2100.0)])" ] }, - "execution_count": 4, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "pygen.unit_procedure.work_units_edge.list(limit=5)" + "pygen.wind_turbine.metmast_edge.list(limit=5)" ] }, { "cell_type": "markdown", - "id": "3b0a4961-c58b-49f6-b1d0-34510279f096", + "id": "688a4dc5-f57d-4547-b1a1-1bfd5195d42f", "metadata": {}, "source": [ - "We can, for example, filter on the start of the edge, which are `UnitProcedures`.\n", - "\n", - "First, we list a few unit procedures and selecet the `external_id` of one of them to filter on" + "The `Distance` edge is also used in the data model from `MetMast` to `WindTurbine` so we can list it from the metmast as well." ] }, { "cell_type": "code", - "execution_count": 6, - "id": "812f5a20-3ee5-4e17-8782-7f7f357c60f7", + "execution_count": 8, + "id": "5f9b4896-02a2-48cc-96aa-dd3d592c17cc", "metadata": {}, "outputs": [ { @@ -245,189 +220,109 @@ " \n", " space\n", " external_id\n", - " name\n", - " type_\n", - " work_orders\n", - " work_units\n", - " node_type\n", + " edge_type\n", + " start_node\n", + " end_node\n", + " distance\n", " data_record\n", " \n", " \n", " \n", " \n", " 0\n", - " IntegrationTestsImmutable\n", - " unit_procedure:Kelly Evans\n", - " Kelly Evans\n", - " green\n", - " None\n", - " None\n", - " None\n", - " {'version': 1, 'last_updated_time': 2023-11-13...\n", + " sp_wind\n", + " turbine1_to_utsira\n", + " {'space': 'sp_pygen_power_enterprise', 'extern...\n", + " {'space': 'sp_wind', 'external_id': 'hornsea_1...\n", + " {'space': 'sp_wind', 'external_id': 'utsira_st...\n", + " 1000.0\n", + " {'version': 1, 'last_updated_time': 2024-11-16...\n", " \n", " \n", " 1\n", - " IntegrationTestsImmutable\n", - " unit_procedure:Craig Carson\n", - " Craig Carson\n", - " yellow\n", - " None\n", - " None\n", - " None\n", - " {'version': 1, 'last_updated_time': 2023-11-13...\n", + " sp_wind\n", + " turbine2_to_utsira\n", + " {'space': 'sp_pygen_power_enterprise', 'extern...\n", + " {'space': 'sp_wind', 'external_id': 'hornsea_1...\n", + " {'space': 'sp_wind', 'external_id': 'utsira_st...\n", + " 1200.0\n", + " {'version': 1, 'last_updated_time': 2024-11-16...\n", " \n", " \n", " 2\n", - " IntegrationTestsImmutable\n", - " unit_procedure:Felicia Smith\n", - " Felicia Smith\n", - " blue\n", - " None\n", - " None\n", - " None\n", - " {'version': 1, 'last_updated_time': 2023-11-13...\n", + " sp_wind\n", + " turbine4_to_utsira\n", + " {'space': 'sp_pygen_power_enterprise', 'extern...\n", + " {'space': 'sp_wind', 'external_id': 'hornsea_1...\n", + " {'space': 'sp_wind', 'external_id': 'utsira_st...\n", + " 1500.0\n", + " {'version': 1, 'last_updated_time': 2024-11-16...\n", " \n", " \n", " 3\n", - " IntegrationTestsImmutable\n", - " unit_procedure:Scott Patel\n", - " Scott Patel\n", - " yellow\n", - " None\n", - " None\n", - " None\n", - " {'version': 1, 'last_updated_time': 2023-11-13...\n", + " sp_wind\n", + " turbine5_to_utsira\n", + " {'space': 'sp_pygen_power_enterprise', 'extern...\n", + " {'space': 'sp_wind', 'external_id': 'hornsea_1...\n", + " {'space': 'sp_wind', 'external_id': 'utsira_st...\n", + " 2100.0\n", + " {'version': 1, 'last_updated_time': 2024-11-16...\n", " \n", " \n", " 4\n", - " IntegrationTestsImmutable\n", - " unit_procedure:Matthew Gonzalez\n", - " Matthew Gonzalez\n", - " red\n", - " None\n", - " None\n", - " None\n", - " {'version': 1, 'last_updated_time': 2023-11-13...\n", + " sp_wind\n", + " turbine3_to_hitra\n", + " {'space': 'sp_pygen_power_enterprise', 'extern...\n", + " {'space': 'sp_wind', 'external_id': 'hornsea_1...\n", + " {'space': 'sp_wind', 'external_id': 'hitra_sta...\n", + " 1100.0\n", + " {'version': 1, 'last_updated_time': 2024-11-16...\n", " \n", " \n", "\n", "" ], "text/plain": [ - "UnitProcedureList([UnitProcedure(space='IntegrationTestsImmutable', external_id='unit_procedure:Kelly Evans', data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, name='Kelly Evans', type_='green'),\n", - " UnitProcedure(space='IntegrationTestsImmutable', external_id='unit_procedure:Craig Carson', data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 43, 2, 9000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 43, 2, 9000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, name='Craig Carson', type_='yellow'),\n", - " UnitProcedure(space='IntegrationTestsImmutable', external_id='unit_procedure:Felicia Smith', data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 43, 2, 386000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 43, 2, 386000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, name='Felicia Smith', type_='blue'),\n", - " UnitProcedure(space='IntegrationTestsImmutable', external_id='unit_procedure:Scott Patel', data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 43, 2, 795000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 43, 2, 795000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, name='Scott Patel', type_='yellow'),\n", - " UnitProcedure(space='IntegrationTestsImmutable', external_id='unit_procedure:Matthew Gonzalez', data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, name='Matthew Gonzalez', type_='red')])" + "DistanceList([Distance(space='sp_wind', external_id='turbine1_to_utsira', edge_type=DirectRelationReference(space='sp_pygen_power_enterprise', external_id='Distance'), start_node=DirectRelationReference(space='sp_wind', external_id='hornsea_1_mill_1'), end_node=NodeId(space='sp_wind', external_id='utsira_station'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), deleted_time=None), distance=1000.0),\n", + " Distance(space='sp_wind', external_id='turbine2_to_utsira', edge_type=DirectRelationReference(space='sp_pygen_power_enterprise', external_id='Distance'), start_node=DirectRelationReference(space='sp_wind', external_id='hornsea_1_mill_2'), end_node=NodeId(space='sp_wind', external_id='utsira_station'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), deleted_time=None), distance=1200.0),\n", + " Distance(space='sp_wind', external_id='turbine4_to_utsira', edge_type=DirectRelationReference(space='sp_pygen_power_enterprise', external_id='Distance'), start_node=DirectRelationReference(space='sp_wind', external_id='hornsea_1_mill_4'), end_node=NodeId(space='sp_wind', external_id='utsira_station'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), deleted_time=None), distance=1500.0),\n", + " Distance(space='sp_wind', external_id='turbine5_to_utsira', edge_type=DirectRelationReference(space='sp_pygen_power_enterprise', external_id='Distance'), start_node=DirectRelationReference(space='sp_wind', external_id='hornsea_1_mill_5'), end_node=NodeId(space='sp_wind', external_id='utsira_station'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), deleted_time=None), distance=2100.0),\n", + " Distance(space='sp_wind', external_id='turbine3_to_hitra', edge_type=DirectRelationReference(space='sp_pygen_power_enterprise', external_id='Distance'), start_node=DirectRelationReference(space='sp_wind', external_id='hornsea_1_mill_3'), end_node=NodeId(space='sp_wind', external_id='hitra_station'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), deleted_time=None), distance=1100.0)])" ] }, - "execution_count": 6, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "pygen.unit_procedure.list(limit=5, retrieve_edges=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "f69f4dea-dcf9-4fda-a699-f019978c438a", - "metadata": {}, - "outputs": [], - "source": [ - "from datetime import datetime, timezone" + "pygen.metmast.wind_turbines_edge.list(min_distance=1000, limit=5)" ] }, { "cell_type": "markdown", - "id": "18acaffe-d189-43ce-bc04-e54c5734a2a1", - "metadata": {}, - "source": [ - "In addition, lets filter on the minimum start time" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "94082f87-b506-4dc8-9a22-83a95b961663", + "id": "3b0a4961-c58b-49f6-b1d0-34510279f096", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
spaceexternal_idedge_typestart_nodeend_nodeend_timestart_timedata_record
0IntegrationTestsImmutableunit_procedure:Scott Patel:equipment_module:Mi...{'space': 'IntegrationTestsImmutable', 'extern...{'space': 'IntegrationTestsImmutable', 'extern...{'space': 'IntegrationTestsImmutable', 'extern...2023-11-07 18:58:39+00:002023-11-04 23:22:42+00:00{'version': 1, 'last_updated_time': 2023-11-13...
\n", - "
" - ], - "text/plain": [ - "StartEndTimeList([StartEndTime(space='IntegrationTestsImmutable', external_id='unit_procedure:Scott Patel:equipment_module:Michael Bolton', edge_type=DirectRelationReference(space='IntegrationTestsImmutable', external_id='UnitProcedure.equipment_module'), start_node=DirectRelationReference(space='IntegrationTestsImmutable', external_id='unit_procedure:Scott Patel'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 43, 2, 795000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 43, 2, 795000, tzinfo=TzInfo(UTC)), deleted_time=None), end_node=NodeId(space='IntegrationTestsImmutable', external_id='equipment_module:Michael Bolton'), end_time=datetime.datetime(2023, 11, 7, 18, 58, 39, tzinfo=TzInfo(UTC)), start_time=datetime.datetime(2023, 11, 4, 23, 22, 42, tzinfo=TzInfo(UTC)))])" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ - "pygen.unit_procedure.work_units_edge.list(\n", - " from_unit_procedure=\"unit_procedure:Scott Patel\", min_start_time=datetime(2023, 11, 1, tzinfo=timezone.utc)\n", - ")" + "Note, that we now also are filtering on the edge." ] }, { "cell_type": "markdown", - "id": "ae8c5d01-3e75-49a2-b621-e320d82d0a4f", + "id": "3955c1a0-7b1e-4426-a86b-55576ee36d02", "metadata": {}, "source": [ - "Or we can find all the edges that ends in a specific `EquipmentModule`. " + "## Query/Filterin/Selecting with Edges\n", + "\n", + "Edges are seldom used in isolation. However, they can be very powerful when we want to fetch data based on the properties in the edge.\n", + "\n", + "For example, if we want to find all wheather stations within 1000 meter of a given WindTurbine we can write" ] }, { "cell_type": "code", - "execution_count": 9, - "id": "63442c56-92a0-46a3-83f6-2296d0713235", + "execution_count": 14, + "id": "649aab44-643a-4dce-bec9-6e45969fd8fe", "metadata": {}, "outputs": [ { @@ -453,184 +348,62 @@ " \n", " space\n", " external_id\n", - " description\n", - " name\n", - " sensor_value\n", - " type_\n", - " node_type\n", + " position\n", + " temperature\n", + " tilt_angle\n", + " wind_speed\n", " data_record\n", " \n", " \n", " \n", " \n", " 0\n", - " IntegrationTestsImmutable\n", - " equipment_module:Jamie Campbell\n", - " Study cover mouth party. Art get scene travel....\n", - " Jamie Campbell\n", - " Common music like ahead meeting animal. Value ...\n", - " red\n", - " None\n", - " {'version': 1, 'last_updated_time': 2023-11-13...\n", + " sp_wind\n", + " utsira_station\n", + " 62.0\n", + " utsira_station_temperature\n", + " utsira_station_tilt_angle\n", + " utsira_station_wind_speed\n", + " {'version': 1, 'last_updated_time': 2024-11-16...\n", " \n", " \n", " 1\n", - " IntegrationTestsImmutable\n", - " equipment_module:Lee Coleman\n", - " Item account east him heavy politics if. Six n...\n", - " Lee Coleman\n", - " Fill may perhaps. Leg law beautiful.\\nRaise co...\n", - " red\n", - " None\n", - " {'version': 1, 'last_updated_time': 2023-11-13...\n", - " \n", - " \n", - " 2\n", - " IntegrationTestsImmutable\n", - " equipment_module:Gregory Campbell\n", - " Suggest practice imagine amount occur wind lat...\n", - " Gregory Campbell\n", - " Beat turn put Mrs Mrs by PM individual.\\nStudy...\n", - " red\n", - " None\n", - " {'version': 1, 'last_updated_time': 2023-11-13...\n", - " \n", - " \n", - " 3\n", - " IntegrationTestsImmutable\n", - " equipment_module:Robert West\n", - " Listen director card nothing together red. Nat...\n", - " Robert West\n", - " Drive compare career American happy yet produc...\n", - " green\n", - " None\n", - " {'version': 1, 'last_updated_time': 2023-11-13...\n", - " \n", - " \n", - " 4\n", - " IntegrationTestsImmutable\n", - " equipment_module:Aaron Vega\n", - " Computer leave again soldier source. Level com...\n", - " Aaron Vega\n", - " It painting the vote factor rate major. Struct...\n", - " yellow\n", - " None\n", - " {'version': 1, 'last_updated_time': 2023-11-13...\n", + " sp_wind\n", + " hitra_station\n", + " 63.0\n", + " hitra_station_temperature\n", + " hitra_station_tilt_angle\n", + " hitra_station_wind_speed\n", + " {'version': 1, 'last_updated_time': 2024-11-16...\n", " \n", " \n", "\n", "" ], "text/plain": [ - "EquipmentModuleList([EquipmentModule(space='IntegrationTestsImmutable', external_id='equipment_module:Jamie Campbell', data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, description='Study cover mouth party. Art get scene travel. Culture field baby federal option.\\nStory growth message yard. Against keep material.', name='Jamie Campbell', sensor_value='Common music like ahead meeting animal. Value thank food play.', type_='red'),\n", - " EquipmentModule(space='IntegrationTestsImmutable', external_id='equipment_module:Lee Coleman', data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, description='Item account east him heavy politics if. Six nor wind clearly notice.\\nAnother tell natural their partner. Recent media thank lot.', name='Lee Coleman', sensor_value='Fill may perhaps. Leg law beautiful.\\nRaise condition theory career writer.', type_='red'),\n", - " EquipmentModule(space='IntegrationTestsImmutable', external_id='equipment_module:Gregory Campbell', data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, description='Suggest practice imagine amount occur wind late. Put herself simply accept picture peace.\\nWestern way realize treatment each. Small thus key party analysis would responsibility.', name='Gregory Campbell', sensor_value='Beat turn put Mrs Mrs by PM individual.\\nStudy about impact poor sense environment. Military art month help suffer soon develop.\\nBack in exactly recognize bank. Heavy that speak.', type_='red'),\n", - " EquipmentModule(space='IntegrationTestsImmutable', external_id='equipment_module:Robert West', data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, description='Listen director card nothing together red. Nation worker old own point.\\nCold go give on people have. Law newspaper interesting bit people tree air. Today total majority film quickly fall.', name='Robert West', sensor_value='Drive compare career American happy yet produce.\\nPull voice wrong. Couple natural it father. Good less can piece.\\nCan issue through each. Member parent black friend certainly answer.', type_='green'),\n", - " EquipmentModule(space='IntegrationTestsImmutable', external_id='equipment_module:Aaron Vega', data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, description='Computer leave again soldier source. Level computer own then.\\nTruth month the government third. Central what together dinner. Soldier cell rock support.', name='Aaron Vega', sensor_value='It painting the vote factor rate major. Structure activity relationship.\\nProvide together sure way manager animal. Sign politics opportunity.', type_='yellow')])" + "MetmastList([Metmast(space='sp_wind', external_id='utsira_station', data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2024, 11, 16, 14, 8, 1, 544000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2024, 11, 16, 14, 8, 1, 544000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, position=62.0, temperature='utsira_station_temperature', tilt_angle='utsira_station_tilt_angle', wind_speed='utsira_station_wind_speed'),\n", + " Metmast(space='sp_wind', external_id='hitra_station', data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2024, 11, 16, 14, 8, 1, 544000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2024, 11, 16, 14, 8, 1, 544000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, position=63.0, temperature='hitra_station_temperature', tilt_angle='hitra_station_tilt_angle', wind_speed='hitra_station_wind_speed')])" ] }, - "execution_count": 9, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "equipment = pygen.equipment_module.list(limit=5)\n", - "equipment" + "metmast = (\n", + " pygen.wind_turbine.select()\n", + " .name.equals(\"hornsea_1_mill_1\")\n", + " .metmast.distance.range(0, 1000)\n", + " .end_node.list_metmast(limit=-1)\n", + ")\n", + "metmast" ] }, { "cell_type": "code", - "execution_count": 10, - "id": "a026d5b8-7f18-4bbc-b929-4e78361850d3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
spaceexternal_idedge_typestart_nodeend_nodeend_timestart_timedata_record
0IntegrationTestsImmutableunit_procedure:Kelly Evans:equipment_module:Gr...{'space': 'IntegrationTestsImmutable', 'extern...{'space': 'IntegrationTestsImmutable', 'extern...{'space': 'IntegrationTestsImmutable', 'extern...2023-11-13 18:01:26+00:002023-11-12 13:56:38+00:00{'version': 1, 'last_updated_time': 2023-11-13...
\n", - "
" - ], - "text/plain": [ - "StartEndTimeList([StartEndTime(space='IntegrationTestsImmutable', external_id='unit_procedure:Kelly Evans:equipment_module:Gregory Campbell', edge_type=DirectRelationReference(space='IntegrationTestsImmutable', external_id='UnitProcedure.equipment_module'), start_node=DirectRelationReference(space='IntegrationTestsImmutable', external_id='unit_procedure:Kelly Evans'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 42, 55, 179000, tzinfo=TzInfo(UTC)), deleted_time=None), end_node=NodeId(space='IntegrationTestsImmutable', external_id='equipment_module:Gregory Campbell'), end_time=datetime.datetime(2023, 11, 13, 18, 1, 26, tzinfo=TzInfo(UTC)), start_time=datetime.datetime(2023, 11, 12, 13, 56, 38, tzinfo=TzInfo(UTC)))])" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pygen.unit_procedure.work_units_edge.list(to_equipment_module=\"equipment_module:Gregory Campbell\")" - ] - }, - { - "cell_type": "markdown", - "id": "9261b178-fee2-48af-bd71-2bb00811be07", - "metadata": {}, - "source": [ - "## Query on Nodes through Edges\n", - "\n", - "It can be cumbersome to first lookup the unit procedures and the equipment modules, and then, do another call to find the edges you want. In addition, you may not only want the `StartEndTime` edge but also the node, `EquipmentModule` at the end of the edges." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "0f67af92-3c59-4b49-a5c5-75201e8c3436", - "metadata": {}, - "outputs": [], - "source": [ - "unit_procedures = (\n", - " pygen.unit_procedure(type_=\"red\").work_units(min_start_time_edge=datetime(2023, 11, 1, tzinfo=timezone.utc)).query()\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "c742133b-367c-4dff-814a-08c1139c1fae", + "execution_count": 15, + "id": "a88b2480-06c6-4e51-8877-62ece1a95d4c", "metadata": {}, "outputs": [ { @@ -656,172 +429,61 @@ " \n", " space\n", " external_id\n", + " capacity\n", " name\n", - " type_\n", - " work_orders\n", - " work_units\n", - " node_type\n", + " blades\n", + " datasheets\n", + " metmast\n", + " nacelle\n", + " rotor\n", + " windfarm\n", " data_record\n", " \n", " \n", " \n", " \n", " 0\n", - " IntegrationTestsImmutable\n", - " unit_procedure:Matthew Gonzalez\n", - " Matthew Gonzalez\n", - " red\n", - " None\n", - " [{'space': 'IntegrationTestsImmutable', 'exter...\n", - " None\n", - " {'version': 1, 'last_updated_time': 2023-11-13...\n", + " sp_wind\n", + " hornsea_1_mill_1\n", + " 7.0\n", + " hornsea_1_mill_1\n", + " [hornsea_1_mill_1_blade_A, hornsea_1_mill_1_bl...\n", + " [windmill_schematics]\n", + " [{'space': 'sp_wind', 'external_id': 'turbine1...\n", + " hornsea_1_mill_1_nacelle\n", + " hornsea_1_mill_1_rotor\n", + " Hornsea 1\n", + " {'version': 4, 'last_updated_time': 2024-11-16...\n", " \n", " \n", "\n", "" ], "text/plain": [ - "UnitProcedureList([UnitProcedure(space='IntegrationTestsImmutable', external_id='unit_procedure:Matthew Gonzalez', data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, name='Matthew Gonzalez', type_='red')])" + "WindTurbineList([WindTurbine(space='sp_wind', external_id='hornsea_1_mill_1', data_record=DataRecord(version=4, last_updated_time=datetime.datetime(2024, 11, 16, 14, 59, 42, 454000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2024, 11, 16, 14, 8, 1, 544000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, capacity=7.0, description=None, name='hornsea_1_mill_1', power_curve=None, windfarm='Hornsea 1')])" ] }, - "execution_count": 12, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "unit_procedures" - ] - }, - { - "cell_type": "markdown", - "id": "a5ec2430-56a1-4d47-9725-56baa74844a5", - "metadata": {}, - "source": [ - "We note that this time we are getting a nested object back, meaning each of the unit procedures have edges to the equipment module with properties on them. \n", - "We can illustrate this by looping through:" + "turbine = (\n", + " pygen.wind_turbine.select()\n", + " .name.equals(\"hornsea_1_mill_1\")\n", + " .metmast.distance.range(0, 1000)\n", + " .end_node.list_full(limit=-1)\n", + ")\n", + "turbine" ] }, { "cell_type": "code", - "execution_count": 13, - "id": "01c7022d-7e5a-4693-b79a-ed6f1dff77de", - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.display import Markdown, display" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "3ef8480c-aaff-4ac0-b272-b07d45e0c0a3", + "execution_count": 19, + "id": "f464477f-cc58-44d5-aede-646dad9150e2", "metadata": {}, "outputs": [ - { - "data": { - "text/markdown": [ - "### Unit Procedure Matthew Gonzalez" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
value
spaceIntegrationTestsImmutable
external_idunit_procedure:Matthew Gonzalez
data_record{'version': 1, 'last_updated_time': 2023-11-13...
node_typeNone
nameMatthew Gonzalez
type_red
work_ordersNone
work_units[{'space': 'IntegrationTestsImmutable', 'exter...
\n", - "
" - ], - "text/plain": [ - "UnitProcedure(space='IntegrationTestsImmutable', external_id='unit_procedure:Matthew Gonzalez', data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, name='Matthew Gonzalez', type_='red')" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/markdown": [ - "#### Work Units" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/markdown": [ - "##### StartEndTime Edge" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, { "data": { "text/html": [ @@ -849,231 +511,47 @@ " \n", " \n", " space\n", - " IntegrationTestsImmutable\n", + " sp_wind\n", " \n", " \n", " external_id\n", - " unit_procedure:Matthew Gonzalez:equipment_modu...\n", + " turbine1_to_utsira\n", " \n", " \n", " edge_type\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", + " {'space': 'sp_pygen_power_enterprise', 'extern...\n", " \n", " \n", " start_node\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", - " \n", - " \n", - " data_record\n", - " {'version': 1, 'last_updated_time': 2023-11-13...\n", + " {'space': 'sp_wind', 'external_id': 'hornsea_1...\n", " \n", " \n", " end_node\n", - " {'space': 'IntegrationTestsImmutable', 'extern...\n", - " \n", - " \n", - " end_time\n", - " 2023-11-11 14:33:45+00:00\n", - " \n", - " \n", - " start_time\n", - " 2023-11-11 06:48:59+00:00\n", - " \n", - " \n", - "\n", - "" - ], - "text/plain": [ - "StartEndTime(space='IntegrationTestsImmutable', external_id='unit_procedure:Matthew Gonzalez:equipment_module:Hannah Mcgee', edge_type=DirectRelationReference(space='IntegrationTestsImmutable', external_id='UnitProcedure.equipment_module'), start_node=DirectRelationReference(space='IntegrationTestsImmutable', external_id='unit_procedure:Matthew Gonzalez'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)), deleted_time=None), end_node=EquipmentModule(space='IntegrationTestsImmutable', external_id='equipment_module:Hannah Mcgee', data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, description='Box likely camera short part different half. List case professional conference. Conference without up particular mouth security both.', name='Hannah Mcgee', sensor_value='Hospital wear trouble part about question. Defense expect show similar avoid. Recent level drug law control war.', type_='orange'), end_time=datetime.datetime(2023, 11, 11, 14, 33, 45, tzinfo=TzInfo(UTC)), start_time=datetime.datetime(2023, 11, 11, 6, 48, 59, tzinfo=TzInfo(UTC)))" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/markdown": [ - "##### Equipment Module" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", "
value
spaceIntegrationTestsImmutable
external_idequipment_module:Hannah Mcgee{'space': 'sp_wind', 'external_id': 'utsira_st...
data_record{'version': 1, 'last_updated_time': 2023-11-13...
node_typeNone
descriptionBox likely camera short part different half. L...
nameHannah Mcgee
sensor_valueHospital wear trouble part about question. Def...{'version': 1, 'last_updated_time': 2024-11-16...
type_orangedistance1000.0
\n", "
" ], "text/plain": [ - "EquipmentModule(space='IntegrationTestsImmutable', external_id='equipment_module:Hannah Mcgee', data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, description='Box likely camera short part different half. List case professional conference. Conference without up particular mouth security both.', name='Hannah Mcgee', sensor_value='Hospital wear trouble part about question. Defense expect show similar avoid. Recent level drug law control war.', type_='orange')" + "Distance(space='sp_wind', external_id='turbine1_to_utsira', edge_type=DirectRelationReference(space='sp_pygen_power_enterprise', external_id='Distance'), start_node=DirectRelationReference(space='sp_wind', external_id='hornsea_1_mill_1'), end_node=Metmast(space='sp_wind', external_id='utsira_station', data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2024, 11, 16, 14, 8, 1, 544000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2024, 11, 16, 14, 8, 1, 544000, tzinfo=TzInfo(UTC)), deleted_time=None), node_type=None, position=62.0, temperature='utsira_station_temperature', tilt_angle='utsira_station_tilt_angle', wind_speed='utsira_station_wind_speed'), data_record=DataRecord(version=1, last_updated_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), created_time=datetime.datetime(2024, 11, 16, 14, 15, 45, 543000, tzinfo=TzInfo(UTC)), deleted_time=None), distance=1000.0)" ] }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "for unit_procedure in unit_procedures:\n", - " display(Markdown(f\"### Unit Procedure {unit_procedure.name}\"))\n", - " display(unit_procedure)\n", - " display(Markdown(\"#### Work Units\"))\n", - " for work_unit in unit_procedure.work_units:\n", - " display(Markdown(\"##### StartEndTime Edge\"))\n", - " display(work_unit)\n", - " display(Markdown(\"##### Equipment Module\"))\n", - " display(work_unit.end_node)\n", - " break" - ] - }, - { - "cell_type": "markdown", - "id": "9606786f-9355-4f6f-aa47-154aebc4bf01", - "metadata": {}, - "source": [ - "This type of queries that get nodes + edges back, which in our example, means the `UnitProcedure` with the `StartEndTime` edge and the `EquipmentModule` node, are more expensive than simply fetching the edges alone. Thus, it is recommended that you always filter to the few unit procedures you are interessted in, instead listing everything." - ] - }, - { - "cell_type": "markdown", - "id": "33c302c9-77e2-436a-95fb-f667dcf533de", - "metadata": {}, - "source": [ - "To see the entire nested object, we can use method `dump`" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "5e032f81-b983-49c3-9640-49535ff1c59c", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'space': 'IntegrationTestsImmutable',\n", - " 'externalId': 'unit_procedure:Matthew Gonzalez',\n", - " 'data_record': {'version': 1,\n", - " 'last_updated_time': datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)),\n", - " 'created_time': datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)),\n", - " 'deleted_time': None},\n", - " 'node_type': None,\n", - " 'name': 'Matthew Gonzalez',\n", - " 'type': 'red',\n", - " 'work_orders': None,\n", - " 'work_units': [{'space': 'IntegrationTestsImmutable',\n", - " 'externalId': 'unit_procedure:Matthew Gonzalez:equipment_module:Hannah Mcgee',\n", - " 'edge_type': {'space': 'IntegrationTestsImmutable',\n", - " 'external_id': 'UnitProcedure.equipment_module'},\n", - " 'start_node': {'space': 'IntegrationTestsImmutable',\n", - " 'external_id': 'unit_procedure:Matthew Gonzalez'},\n", - " 'data_record': {'version': 1,\n", - " 'last_updated_time': datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)),\n", - " 'created_time': datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)),\n", - " 'deleted_time': None},\n", - " 'end_node': {'space': 'IntegrationTestsImmutable',\n", - " 'externalId': 'equipment_module:Hannah Mcgee',\n", - " 'data_record': {'version': 1,\n", - " 'last_updated_time': datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)),\n", - " 'created_time': datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)),\n", - " 'deleted_time': None},\n", - " 'node_type': None,\n", - " 'description': 'Box likely camera short part different half. List case professional conference. Conference without up particular mouth security both.',\n", - " 'name': 'Hannah Mcgee',\n", - " 'sensor_value': 'Hospital wear trouble part about question. Defense expect show similar avoid. Recent level drug law control war.',\n", - " 'type': 'orange'},\n", - " 'end_time': datetime.datetime(2023, 11, 11, 14, 33, 45, tzinfo=TzInfo(UTC)),\n", - " 'start_time': datetime.datetime(2023, 11, 11, 6, 48, 59, tzinfo=TzInfo(UTC))},\n", - " {'space': 'IntegrationTestsImmutable',\n", - " 'externalId': 'unit_procedure:Matthew Gonzalez:equipment_module:Arthur Simon',\n", - " 'edge_type': {'space': 'IntegrationTestsImmutable',\n", - " 'external_id': 'UnitProcedure.equipment_module'},\n", - " 'start_node': {'space': 'IntegrationTestsImmutable',\n", - " 'external_id': 'unit_procedure:Matthew Gonzalez'},\n", - " 'data_record': {'version': 1,\n", - " 'last_updated_time': datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)),\n", - " 'created_time': datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)),\n", - " 'deleted_time': None},\n", - " 'end_node': {'space': 'IntegrationTestsImmutable',\n", - " 'externalId': 'equipment_module:Arthur Simon',\n", - " 'data_record': {'version': 1,\n", - " 'last_updated_time': datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)),\n", - " 'created_time': datetime.datetime(2023, 11, 13, 19, 43, 3, 123000, tzinfo=TzInfo(UTC)),\n", - " 'deleted_time': None},\n", - " 'node_type': None,\n", - " 'description': 'Go the mind color condition. Try memory each next single simple condition live.\\nToday threat when system. Catch official method. Box east price page.',\n", - " 'name': 'Arthur Simon',\n", - " 'sensor_value': 'See then every player doctor artist table. Since do by method get.\\nLittle toward control everything professional. Conference tonight imagine. Baby although office until idea score.',\n", - " 'type': 'orange'},\n", - " 'end_time': datetime.datetime(2023, 11, 9, 21, 28, 35, tzinfo=TzInfo(UTC)),\n", - " 'start_time': datetime.datetime(2023, 11, 7, 19, 10, 55, tzinfo=TzInfo(UTC))}]}" - ] - }, - "execution_count": 15, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "unit_procedures[0].dump()" + "turbine[0].metmast[0]" ] }, { @@ -1096,38 +574,33 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 20, "id": "2eedcac2-d566-4e4d-aab7-0f6a55caf437", "metadata": {}, "outputs": [], "source": [ - "from cognite.client.data_classes import TimeSeries\n", - "from equipment_unit.data_classes import EquipmentModuleWrite, StartEndTimeWrite, UnitProcedureWrite" + "from wind_turbine import data_classes as data_cls" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 26, "id": "8755569b-dc09-45e2-86ca-dfc9f0777638", "metadata": {}, "outputs": [], "source": [ - "new_procedure = UnitProcedureWrite(\n", - " external_id=\"procedure:new_procedure\",\n", - " name=\"New procedure\",\n", - " type_=\"New type\",\n", - " work_units=[\n", - " StartEndTimeWrite(\n", - " start_time=\"2021-01-01T00:00:00Z\",\n", - " end_time=\"2021-01-02T00:00:00Z\",\n", - " end_node=EquipmentModuleWrite(\n", - " external_id=\"module:new_module\",\n", - " name=\"New module\",\n", - " type_=\"New type\",\n", - " sensor_value=TimeSeries(external_id=\"timeseries:123\"),\n", - " description=\"New description\",\n", + "new_turbine = data_cls.WindTurbineWrite(\n", + " external_id=\"doctriono_b\",\n", + " name=\"A new Wind Turbine\",\n", + " capacity=8.0,\n", + " metmast=[\n", + " data_cls.DistanceWrite(\n", + " distance=500.0,\n", + " end_node=data_cls.MetmastWrite(\n", + " external_id=\"doctrino_weather\",\n", + " position=42.0,\n", " ),\n", - " ),\n", + " )\n", " ],\n", ")" ] @@ -1137,22 +610,22 @@ "id": "d01197a5-5a99-4623-8ef4-bff1e97f4c97", "metadata": {}, "source": [ - "We can now create the new procedure, `procedure:new_procedure` with one work_unit which is connected to the equipment module, `module:new_module`." + "We can now create the new turbine with a memast startion" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 27, "id": "ee02ed18-3fd7-485a-b57c-35ed0da96840", "metadata": {}, "outputs": [], "source": [ - "created = pygen.upsert(new_procedure)" + "created = pygen.upsert(new_turbine)" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 28, "id": "f89f701e-2095-4aa9-a7ee-a4b1db3b3f25", "metadata": {}, "outputs": [ @@ -1189,34 +662,34 @@ " \n", " \n", " 0\n", - " IntegrationTestsImmutable\n", + " sp_wind\n", " node\n", - " procedure:new_procedure\n", + " doctriono_b\n", " 1\n", - " False\n", - " 2024-03-24 16:01:50.855\n", - " 2024-03-24 16:01:50.855\n", + " True\n", + " 2024-11-16 21:19:33.212\n", + " 2024-11-16 21:19:33.212\n", " \n", " \n", " 1\n", - " IntegrationTestsImmutable\n", + " sp_wind\n", " node\n", - " module:new_module\n", + " doctrino_weather\n", " 1\n", - " False\n", - " 2024-03-24 16:01:50.855\n", - " 2024-03-24 16:01:50.855\n", + " True\n", + " 2024-11-16 21:19:33.212\n", + " 2024-11-16 21:19:33.212\n", " \n", " \n", "\n", "" ], "text/plain": [ - "NodeApplyResultList([,\n", - " ])" + "NodeApplyResultList([,\n", + " ])" ] }, - "execution_count": 18, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -1227,7 +700,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 29, "id": "dbccf29d-d70c-4241-89e6-684d4abeea9f", "metadata": {}, "outputs": [ @@ -1264,23 +737,23 @@ " \n", " \n", " 0\n", - " IntegrationTestsImmutable\n", + " sp_wind\n", " edge\n", - " procedure:new_procedure:module:new_module\n", + " doctriono_b:doctrino_weather\n", " 1\n", - " False\n", - " 2024-03-24 16:01:50.855\n", - " 2024-03-24 16:01:50.855\n", + " True\n", + " 2024-11-16 21:19:33.212\n", + " 2024-11-16 21:19:33.212\n", " \n", " \n", "\n", "" ], "text/plain": [ - "EdgeApplyResultList([])" + "EdgeApplyResultList([])" ] }, - "execution_count": 19, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -1291,7 +764,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 30, "id": "1bb77238-8bc6-4311-851e-946da33b5881", "metadata": {}, "outputs": [ @@ -1316,37 +789,18 @@ " \n", " \n", " \n", - " external_id\n", - " is_string\n", - " metadata\n", - " is_step\n", - " security_categories\n", - " id\n", - " created_time\n", - " last_updated_time\n", " \n", " \n", " \n", - " \n", - " 0\n", - " timeseries:123\n", - " False\n", - " {}\n", - " False\n", - " []\n", - " 4887056089316248\n", - " 2024-03-24 16:01:51.161\n", - " 2024-03-24 16:01:51.161\n", - " \n", " \n", "\n", "" ], "text/plain": [ - "TimeSeriesList([])" + "TimeSeriesList([])" ] }, - "execution_count": 20, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -1355,211 +809,161 @@ "created.time_series" ] }, - { - "cell_type": "markdown", - "id": "6410f1f7-d1bc-41bc-a848-60497c7f8e2f", - "metadata": {}, - "source": [ - "## (Advanced) External ID Hook\n", - "`pygen` sets the `external_id` for edges automatically for you. This is done with an `external_id` hook, and if you want to control this behavior you can overwrite this hook to control how external ids are set.\n" - ] - }, { "cell_type": "code", - "execution_count": 21, - "id": "d0e4ffc8-2e35-48e2-9da3-b34afa1583f7", - "metadata": {}, - "outputs": [], - "source": [ - "from cognite.client.data_classes import TimeSeries\n", - "from equipment_unit.data_classes import DomainRelationWrite, EquipmentModuleWrite, StartEndTimeWrite, UnitProcedureWrite" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "3c600abf-f445-4637-984a-c1e3022076b7", + "execution_count": 32, + "id": "73a7a944-abc1-45ad-9f66-0530aee46592", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - " 'str'>" + "InstancesDeleteResult(nodes=[NodeId(space='sp_wind', external_id='doctriono_b'), NodeId(space='sp_wind', external_id='doctrino_weather')], edges=[EdgeId(space='sp_wind', external_id='doctriono_b:doctrino_weather')])" ] }, - "execution_count": 22, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "DomainRelationWrite.external_id_factory" + "# Clean\n", + "pygen.delete(new_turbine)" ] }, { "cell_type": "markdown", - "id": "3daec491-f0d6-4cec-ae53-b224e2f60949", - "metadata": {}, - "source": [ - "If we repeat the example above, we can see what the external ID of the `StartEndTime` edge is set to" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "b546e7b9-9b17-4799-ad35-686b4dc7de35", + "id": "6410f1f7-d1bc-41bc-a848-60497c7f8e2f", "metadata": {}, - "outputs": [], "source": [ - "new_procedure = UnitProcedureWrite(\n", - " external_id=\"procedure:new_procedure\",\n", - " name=\"New procedure\",\n", - " type_=\"New type\",\n", - " work_units=[\n", - " StartEndTimeWrite(\n", - " start_time=\"2021-01-01T00:00:00Z\",\n", - " end_time=\"2021-01-02T00:00:00Z\",\n", - " end_node=EquipmentModuleWrite(\n", - " external_id=\"module:new_module\",\n", - " name=\"New module\",\n", - " type_=\"New type\",\n", - " sensor_value=TimeSeries(external_id=\"timeseries:123\"),\n", - " description=\"New description\",\n", - " ),\n", - " ),\n", - " ],\n", - ")" + "## (Advanced) External ID Hook\n", + "`pygen` sets the `external_id` for edges automatically for you. This is done with an `external_id` hook, and if you want to control this behavior you can overwrite this hook to control how external ids are set.\n" ] }, { "cell_type": "code", - "execution_count": 24, - "id": "60abda43-6047-4fad-882d-ae1d718d1925", + "execution_count": 35, + "id": "d0e4ffc8-2e35-48e2-9da3-b34afa1583f7", "metadata": {}, "outputs": [], "source": [ - "resources = new_procedure.to_instances_write()" + "from wind_turbine import data_classes as data_cls" ] }, { "cell_type": "code", - "execution_count": 25, - "id": "25c8d2b4-9990-49aa-8e18-970855d5fb29", + "execution_count": 36, + "id": "3c600abf-f445-4637-984a-c1e3022076b7", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'procedure:new_procedure:module:new_module'" + " 'str'>" ] }, - "execution_count": 25, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "resources.edges[0].external_id" + "data_cls.DomainRelationWrite.external_id_factory" ] }, { "cell_type": "markdown", - "id": "22b93de7-2141-4551-9d81-60bb95bbbd4b", + "id": "28f420ea-0ff0-488c-b112-b10a11406cc0", "metadata": {}, "source": [ - "As we see from the example above, the default behavior is to concatenate the `start_node`, `UnitProcedure`, and `end_node`, `EquipmentModule`, `external_id`. \n", - "\n", - "In the example below, we replace the default `external_id_factory` for edges with our own that prefixes edge ids with the name of the start node class and\n", - "the field name of the edge type." + "We can for example set this to a uuid" ] }, { "cell_type": "code", - "execution_count": 26, - "id": "7b87d757-2352-4033-958b-327de73b91c7", + "execution_count": 37, + "id": "94bb003a-5a61-4c45-9a4a-857320a7debf", "metadata": {}, "outputs": [], "source": [ - "from cognite.client import data_modeling as dm\n", - "from equipment_unit.data_classes import DomainModelWrite" + "from uuid import uuid4" ] }, { "cell_type": "code", - "execution_count": 27, - "id": "99affcdb-b6c4-4e06-aaf8-dd24400dab27", + "execution_count": 44, + "id": "94db9b2f-20e1-4673-9917-dcf497f83ea3", "metadata": {}, "outputs": [], "source": [ - "def my_edge_external_id_factory(\n", - " start_node: DomainModelWrite, end_node: dm.DirectRelationReference, edge_type: dm.DirectRelationReference\n", - ") -> str:\n", - " return (\n", - " f\"{type(start_node).__name__}_{edge_type.external_id.split('.')[1]}:\"\n", - " f\"{start_node.external_id}:{end_node.external_id}\"\n", - " )" + "def my_id_creator(*_, **__) -> str:\n", + " return str(uuid4())" ] }, { "cell_type": "code", - "execution_count": 28, - "id": "19353d69-6e6e-4619-ba13-462fdbc8176a", + "execution_count": 45, + "id": "72876b3e-6893-43d6-96e2-0aff410d4da7", "metadata": {}, "outputs": [], "source": [ - "DomainRelationWrite.external_id_factory = my_edge_external_id_factory" + "data_cls.DomainRelationWrite.external_id_factory = my_id_creator" + ] + }, + { + "cell_type": "markdown", + "id": "3daec491-f0d6-4cec-ae53-b224e2f60949", + "metadata": {}, + "source": [ + "We now repeat the example above" ] }, { "cell_type": "code", - "execution_count": 29, - "id": "4a65eb3e-ab73-4f46-9188-e16ce903cd89", + "execution_count": 46, + "id": "b546e7b9-9b17-4799-ad35-686b4dc7de35", "metadata": {}, "outputs": [], "source": [ - "new_procedure = UnitProcedureWrite(\n", - " external_id=\"procedure:new_procedure\",\n", - " name=\"New procedure\",\n", - " type_=\"New type\",\n", - " work_units=[\n", - " StartEndTimeWrite(\n", - " start_time=\"2021-01-01T00:00:00Z\",\n", - " end_time=\"2021-01-02T00:00:00Z\",\n", - " end_node=EquipmentModuleWrite(\n", - " external_id=\"module:new_module\",\n", - " name=\"New module\",\n", - " type_=\"New type\",\n", - " sensor_value=TimeSeries(external_id=\"timeseries:123\"),\n", - " description=\"New description\",\n", + "new_turbine = data_cls.WindTurbineWrite(\n", + " external_id=\"doctriono_b\",\n", + " name=\"A new Wind Turbine\",\n", + " capacity=8.0,\n", + " metmast=[\n", + " data_cls.DistanceWrite(\n", + " distance=500.0,\n", + " end_node=data_cls.MetmastWrite(\n", + " external_id=\"doctrino_weather\",\n", + " position=42.0,\n", " ),\n", - " ),\n", + " )\n", " ],\n", ")" ] }, { "cell_type": "code", - "execution_count": 30, - "id": "6929572f-03ed-4d28-b186-39a8d854e3f8", + "execution_count": 47, + "id": "60abda43-6047-4fad-882d-ae1d718d1925", "metadata": {}, "outputs": [], "source": [ - "resources = new_procedure.to_instances_write()" + "resources = new_turbine.to_instances_write()" ] }, { "cell_type": "code", - "execution_count": 31, - "id": "1301d314-9fed-4d7f-872e-8c2d77314356", + "execution_count": 48, + "id": "25c8d2b4-9990-49aa-8e18-970855d5fb29", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'UnitProcedureWrite_equipment_module:procedure:new_procedure:module:new_module'" + "'4a1c28c7-3296-4742-9d9e-25aa6cf826ea'" ] }, - "execution_count": 31, + "execution_count": 48, "metadata": {}, "output_type": "execute_result" } @@ -1568,6 +972,14 @@ "resources.edges[0].external_id" ] }, + { + "cell_type": "markdown", + "id": "e431fbc8-3c82-4c90-966d-e1498b571cf3", + "metadata": {}, + "source": [ + "We notice that this time the external ID of the edge is set to an UUID" + ] + }, { "cell_type": "markdown", "id": "b2376680-6ce2-4031-9cfe-4036d0288233", @@ -1578,12 +990,12 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 61, "id": "18ebcf45-0fe3-4a53-9045-f92e298b8f61", "metadata": {}, "outputs": [], "source": [ - "DomainRelationWrite.reset_external_id_factory()" + "data_cls.DomainRelationWrite.reset_external_id_factory()" ] }, { @@ -1596,51 +1008,47 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 63, "id": "777c8476-f2bc-4073-a292-daf71318bf94", "metadata": {}, "outputs": [], "source": [ - "new_procedure = UnitProcedureWrite(\n", - " external_id=\"procedure:new_procedure\",\n", - " name=\"New procedure\",\n", - " type_=\"New type\",\n", - " work_units=[\n", - " StartEndTimeWrite(\n", - " external_id=\"work_unit:2021-01-01T00:00:00Z\",\n", - " start_time=\"2021-01-01T00:00:00Z\",\n", - " end_time=\"2021-01-02T00:00:00Z\",\n", - " end_node=EquipmentModuleWrite(\n", - " external_id=\"module:new_module\",\n", - " name=\"New module\",\n", - " type_=\"New type\",\n", - " sensor_value=TimeSeries(external_id=\"timeseries:123\"),\n", - " description=\"New description\",\n", + "new_turbine = data_cls.WindTurbineWrite(\n", + " external_id=\"doctriono_b\",\n", + " name=\"A new Wind Turbine\",\n", + " capacity=8.0,\n", + " metmast=[\n", + " data_cls.DistanceWrite(\n", + " external_id=\"distance_from_doctrino_b_to_doctrino_weather\",\n", + " distance=500.0,\n", + " end_node=data_cls.MetmastWrite(\n", + " external_id=\"doctrino_weather\",\n", + " position=42.0,\n", " ),\n", - " ),\n", + " )\n", " ],\n", ")" ] }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 64, "id": "a14f6fb8-f9c5-4111-9fe5-16c6eb0ccd5a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'work_unit:2021-01-01T00:00:00Z'" + "'distance_from_doctrino_b_to_doctrino_weather'" ] }, - "execution_count": 34, + "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "new_procedure.to_instances_write().edges[0].external_id" + "new_turbine.to_instances_write().edges[0].external_id" ] }, { diff --git a/examples/cognite_core/_api/cognite_360_image.py b/examples/cognite_core/_api/cognite_360_image.py index b1c3ee307..65c3c0db2 100644 --- a/examples/cognite_core/_api/cognite_360_image.py +++ b/examples/cognite_core/_api/cognite_360_image.py @@ -185,6 +185,11 @@ def __call__( A query API for Cognite 360 images. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_360_image_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_360_image_collection.py b/examples/cognite_core/_api/cognite_360_image_collection.py index 444df9f95..f08fabf78 100644 --- a/examples/cognite_core/_api/cognite_360_image_collection.py +++ b/examples/cognite_core/_api/cognite_360_image_collection.py @@ -96,6 +96,11 @@ def __call__( A query API for Cognite 360 image collections. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_360_image_collection_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_360_image_model.py b/examples/cognite_core/_api/cognite_360_image_model.py index 08cb3f321..20c78abc3 100644 --- a/examples/cognite_core/_api/cognite_360_image_model.py +++ b/examples/cognite_core/_api/cognite_360_image_model.py @@ -90,6 +90,11 @@ def __call__( A query API for Cognite 360 image models. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_360_image_model_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_360_image_station.py b/examples/cognite_core/_api/cognite_360_image_station.py index 4b56c000e..483ad6b30 100644 --- a/examples/cognite_core/_api/cognite_360_image_station.py +++ b/examples/cognite_core/_api/cognite_360_image_station.py @@ -81,6 +81,11 @@ def __call__( A query API for Cognite 360 image stations. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_360_image_station_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_3_d_model.py b/examples/cognite_core/_api/cognite_3_d_model.py index c3ed433b2..3d6c6831d 100644 --- a/examples/cognite_core/_api/cognite_3_d_model.py +++ b/examples/cognite_core/_api/cognite_3_d_model.py @@ -95,6 +95,11 @@ def __call__( A query API for Cognite 3D models. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_3_d_model_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_3_d_object.py b/examples/cognite_core/_api/cognite_3_d_object.py index debee25e5..1c1507e37 100644 --- a/examples/cognite_core/_api/cognite_3_d_object.py +++ b/examples/cognite_core/_api/cognite_3_d_object.py @@ -112,6 +112,11 @@ def __call__( A query API for Cognite 3D objects. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_3_d_object_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_3_d_revision.py b/examples/cognite_core/_api/cognite_3_d_revision.py index 5f1623755..46b2a1d37 100644 --- a/examples/cognite_core/_api/cognite_3_d_revision.py +++ b/examples/cognite_core/_api/cognite_3_d_revision.py @@ -91,6 +91,11 @@ def __call__( A query API for Cognite 3D revisions. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_3_d_revision_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_3_d_transformation_node.py b/examples/cognite_core/_api/cognite_3_d_transformation_node.py index 6f08e1906..e31f29b85 100644 --- a/examples/cognite_core/_api/cognite_3_d_transformation_node.py +++ b/examples/cognite_core/_api/cognite_3_d_transformation_node.py @@ -116,6 +116,11 @@ def __call__( A query API for Cognite 3D transformation nodes. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_3_d_transformation_node_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_activity.py b/examples/cognite_core/_api/cognite_activity.py index 11cf480a7..e6d4b3a2d 100644 --- a/examples/cognite_core/_api/cognite_activity.py +++ b/examples/cognite_core/_api/cognite_activity.py @@ -158,6 +158,11 @@ def __call__( A query API for Cognite activities. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_activity_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_asset.py b/examples/cognite_core/_api/cognite_asset.py index a3a855ef7..2a1a5c80c 100644 --- a/examples/cognite_core/_api/cognite_asset.py +++ b/examples/cognite_core/_api/cognite_asset.py @@ -177,6 +177,11 @@ def __call__( A query API for Cognite assets. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_asset_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_asset_class.py b/examples/cognite_core/_api/cognite_asset_class.py index 965b1f8cf..69888aa34 100644 --- a/examples/cognite_core/_api/cognite_asset_class.py +++ b/examples/cognite_core/_api/cognite_asset_class.py @@ -87,6 +87,11 @@ def __call__( A query API for Cognite asset class. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_asset_clas_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_asset_type.py b/examples/cognite_core/_api/cognite_asset_type.py index 11a4683d4..4e6acbd20 100644 --- a/examples/cognite_core/_api/cognite_asset_type.py +++ b/examples/cognite_core/_api/cognite_asset_type.py @@ -97,6 +97,11 @@ def __call__( A query API for Cognite asset types. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_asset_type_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_cad_model.py b/examples/cognite_core/_api/cognite_cad_model.py index fc9f2ade0..d4e3cab02 100644 --- a/examples/cognite_core/_api/cognite_cad_model.py +++ b/examples/cognite_core/_api/cognite_cad_model.py @@ -88,6 +88,11 @@ def __call__( A query API for Cognite cad models. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_cad_model_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_cad_node.py b/examples/cognite_core/_api/cognite_cad_node.py index 001771d57..17ea99c9b 100644 --- a/examples/cognite_core/_api/cognite_cad_node.py +++ b/examples/cognite_core/_api/cognite_cad_node.py @@ -111,6 +111,11 @@ def __call__( A query API for Cognite cad nodes. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_cad_node_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_cad_revision.py b/examples/cognite_core/_api/cognite_cad_revision.py index 5447a2e1e..8c8992b9b 100644 --- a/examples/cognite_core/_api/cognite_cad_revision.py +++ b/examples/cognite_core/_api/cognite_cad_revision.py @@ -87,6 +87,11 @@ def __call__( A query API for Cognite cad revisions. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_cad_revision_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_cube_map.py b/examples/cognite_core/_api/cognite_cube_map.py index 39421db3c..9c55eec02 100644 --- a/examples/cognite_core/_api/cognite_cube_map.py +++ b/examples/cognite_core/_api/cognite_cube_map.py @@ -127,6 +127,11 @@ def __call__( A query API for Cognite cube maps. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_cube_map_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_describable_node.py b/examples/cognite_core/_api/cognite_describable_node.py index 354038a53..15de6e400 100644 --- a/examples/cognite_core/_api/cognite_describable_node.py +++ b/examples/cognite_core/_api/cognite_describable_node.py @@ -123,6 +123,11 @@ def __call__( A query API for Cognite describable nodes. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_describable_node_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_equipment.py b/examples/cognite_core/_api/cognite_equipment.py index 34f4dacd9..43987ca57 100644 --- a/examples/cognite_core/_api/cognite_equipment.py +++ b/examples/cognite_core/_api/cognite_equipment.py @@ -154,6 +154,11 @@ def __call__( A query API for Cognite equipments. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_equipment_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_equipment_type.py b/examples/cognite_core/_api/cognite_equipment_type.py index 60f02b40a..deeb38661 100644 --- a/examples/cognite_core/_api/cognite_equipment_type.py +++ b/examples/cognite_core/_api/cognite_equipment_type.py @@ -95,6 +95,11 @@ def __call__( A query API for Cognite equipment types. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_equipment_type_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_file.py b/examples/cognite_core/_api/cognite_file.py index b77f8dd1d..4b8888e45 100644 --- a/examples/cognite_core/_api/cognite_file.py +++ b/examples/cognite_core/_api/cognite_file.py @@ -147,6 +147,11 @@ def __call__( A query API for Cognite files. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_file_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_file_category.py b/examples/cognite_core/_api/cognite_file_category.py index c1d4d6431..e9db393de 100644 --- a/examples/cognite_core/_api/cognite_file_category.py +++ b/examples/cognite_core/_api/cognite_file_category.py @@ -91,6 +91,11 @@ def __call__( A query API for Cognite file categories. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_file_category_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_point_cloud_model.py b/examples/cognite_core/_api/cognite_point_cloud_model.py index 6a3dcb905..5938c671a 100644 --- a/examples/cognite_core/_api/cognite_point_cloud_model.py +++ b/examples/cognite_core/_api/cognite_point_cloud_model.py @@ -92,6 +92,11 @@ def __call__( A query API for Cognite point cloud models. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_point_cloud_model_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_point_cloud_revision.py b/examples/cognite_core/_api/cognite_point_cloud_revision.py index d3b163538..5a11b0c1c 100644 --- a/examples/cognite_core/_api/cognite_point_cloud_revision.py +++ b/examples/cognite_core/_api/cognite_point_cloud_revision.py @@ -92,6 +92,11 @@ def __call__( A query API for Cognite point cloud revisions. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_point_cloud_revision_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_point_cloud_volume.py b/examples/cognite_core/_api/cognite_point_cloud_volume.py index ff38d4531..01b978e32 100644 --- a/examples/cognite_core/_api/cognite_point_cloud_volume.py +++ b/examples/cognite_core/_api/cognite_point_cloud_volume.py @@ -118,6 +118,11 @@ def __call__( A query API for Cognite point cloud volumes. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_point_cloud_volume_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_schedulable.py b/examples/cognite_core/_api/cognite_schedulable.py index 27225ad75..3d508d17e 100644 --- a/examples/cognite_core/_api/cognite_schedulable.py +++ b/examples/cognite_core/_api/cognite_schedulable.py @@ -92,6 +92,11 @@ def __call__( A query API for Cognite schedulables. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_schedulable_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_source_system.py b/examples/cognite_core/_api/cognite_source_system.py index 983de8572..dae6b036a 100644 --- a/examples/cognite_core/_api/cognite_source_system.py +++ b/examples/cognite_core/_api/cognite_source_system.py @@ -87,6 +87,11 @@ def __call__( A query API for Cognite source systems. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_source_system_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_sourceable_node.py b/examples/cognite_core/_api/cognite_sourceable_node.py index 2e499f6ce..56a37a2d4 100644 --- a/examples/cognite_core/_api/cognite_sourceable_node.py +++ b/examples/cognite_core/_api/cognite_sourceable_node.py @@ -120,6 +120,11 @@ def __call__( A query API for Cognite sourceable nodes. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_sourceable_node_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_time_series.py b/examples/cognite_core/_api/cognite_time_series.py index 349d5f9f3..d97106e09 100644 --- a/examples/cognite_core/_api/cognite_time_series.py +++ b/examples/cognite_core/_api/cognite_time_series.py @@ -151,6 +151,11 @@ def __call__( A query API for Cognite time series. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_time_series_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_unit.py b/examples/cognite_core/_api/cognite_unit.py index f2b7fd4b9..152c6996f 100644 --- a/examples/cognite_core/_api/cognite_unit.py +++ b/examples/cognite_core/_api/cognite_unit.py @@ -93,6 +93,11 @@ def __call__( A query API for Cognite units. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_unit_filter( self._view_id, diff --git a/examples/cognite_core/_api/cognite_visualizable.py b/examples/cognite_core/_api/cognite_visualizable.py index 8f6c46bb7..330112f15 100644 --- a/examples/cognite_core/_api/cognite_visualizable.py +++ b/examples/cognite_core/_api/cognite_visualizable.py @@ -84,6 +84,11 @@ def __call__( A query API for Cognite visualizables. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cognite_visualizable_filter( self._view_id, diff --git a/examples/equipment_unit/__init__.py b/examples/equipment_unit/__init__.py deleted file mode 100644 index 95f2b78da..000000000 --- a/examples/equipment_unit/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from equipment_unit._api_client import EquipmentUnitClient - -__all__ = ["EquipmentUnitClient"] diff --git a/examples/equipment_unit/_api/__init__.py b/examples/equipment_unit/_api/__init__.py deleted file mode 100644 index 510bf5b29..000000000 --- a/examples/equipment_unit/_api/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -from equipment_unit._api.equipment_module import EquipmentModuleAPI -from equipment_unit._api.equipment_module_query import EquipmentModuleQueryAPI -from equipment_unit._api.equipment_module_sensor_value import EquipmentModuleSensorValueAPI -from equipment_unit._api.unit_procedure import UnitProcedureAPI -from equipment_unit._api.unit_procedure_query import UnitProcedureQueryAPI -from equipment_unit._api.unit_procedure_work_orders import UnitProcedureWorkOrdersAPI -from equipment_unit._api.unit_procedure_work_units import UnitProcedureWorkUnitsAPI -from equipment_unit._api.work_order import WorkOrderAPI -from equipment_unit._api.work_order_query import WorkOrderQueryAPI - -__all__ = [ - "EquipmentModuleAPI", - "EquipmentModuleQueryAPI", - "EquipmentModuleSensorValueAPI", - "UnitProcedureAPI", - "UnitProcedureQueryAPI", - "UnitProcedureWorkOrdersAPI", - "UnitProcedureWorkUnitsAPI", - "WorkOrderAPI", - "WorkOrderQueryAPI", -] diff --git a/examples/equipment_unit/_api/_core.py b/examples/equipment_unit/_api/_core.py deleted file mode 100644 index 14f05feba..000000000 --- a/examples/equipment_unit/_api/_core.py +++ /dev/null @@ -1,594 +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 equipment_unit import data_classes -from equipment_unit.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("IntegrationTestsImmutable", "EquipmentUnit", "2"): { - "EquipmentModule": data_classes.EquipmentModuleGraphQL, - "StartEndTime": data_classes.StartEndTimeGraphQL, - "UnitProcedure": data_classes.UnitProcedureGraphQL, - "WorkOrder": data_classes.WorkOrderGraphQL, - }, -} diff --git a/examples/equipment_unit/_api/equipment_module.py b/examples/equipment_unit/_api/equipment_module.py deleted file mode 100644 index 24c06b2a6..000000000 --- a/examples/equipment_unit/_api/equipment_module.py +++ /dev/null @@ -1,564 +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 equipment_unit.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - NodeQueryStep, - EdgeQueryStep, - DataClassQueryBuilder, -) -from equipment_unit.data_classes import ( - DomainModelCore, - DomainModelWrite, - ResourcesWriteResult, - EquipmentModule, - EquipmentModuleWrite, - EquipmentModuleFields, - EquipmentModuleList, - EquipmentModuleWriteList, - EquipmentModuleTextFields, -) -from equipment_unit.data_classes._equipment_module import ( - EquipmentModuleQuery, - _EQUIPMENTMODULE_PROPERTIES_BY_FIELD, - _create_equipment_module_filter, -) -from equipment_unit._api._core import ( - DEFAULT_LIMIT_READ, - Aggregations, - NodeAPI, - SequenceNotStr, -) -from equipment_unit._api.equipment_module_sensor_value import EquipmentModuleSensorValueAPI -from equipment_unit._api.equipment_module_query import EquipmentModuleQueryAPI - - -class EquipmentModuleAPI(NodeAPI[EquipmentModule, EquipmentModuleWrite, EquipmentModuleList, EquipmentModuleWriteList]): - _view_id = dm.ViewId("IntegrationTestsImmutable", "EquipmentModule", "b1cd4bf14a7a33") - _properties_by_field = _EQUIPMENTMODULE_PROPERTIES_BY_FIELD - _class_type = EquipmentModule - _class_list = EquipmentModuleList - _class_write_list = EquipmentModuleWriteList - - def __init__(self, client: CogniteClient): - super().__init__(client=client) - - self.sensor_value = EquipmentModuleSensorValueAPI(client, self._view_id) - - def __call__( - self, - description: str | list[str] | None = None, - description_prefix: str | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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, - ) -> EquipmentModuleQueryAPI[EquipmentModuleList]: - """Query starting at equipment modules. - - Args: - description: The description to filter on. - description_prefix: The prefix of the description to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type 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 equipment modules 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 equipment modules. - - """ - has_data = dm.filters.HasData(views=[self._view_id]) - filter_ = _create_equipment_module_filter( - self._view_id, - description, - description_prefix, - name, - name_prefix, - type_, - type_prefix, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - builder = DataClassQueryBuilder(EquipmentModuleList) - return EquipmentModuleQueryAPI(self._client, builder, filter_, limit) - - def apply( - self, - equipment_module: EquipmentModuleWrite | Sequence[EquipmentModuleWrite], - replace: bool = False, - write_none: bool = False, - ) -> ResourcesWriteResult: - """Add or update (upsert) equipment modules. - - Args: - equipment_module: Equipment module or sequence of equipment modules 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 equipment_module: - - >>> from equipment_unit import EquipmentUnitClient - >>> from equipment_unit.data_classes import EquipmentModuleWrite - >>> client = EquipmentUnitClient() - >>> equipment_module = EquipmentModuleWrite(external_id="my_equipment_module", ...) - >>> result = client.equipment_module.apply(equipment_module) - - """ - 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.equipment_module.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(equipment_module, replace, write_none) - - def delete( - self, external_id: str | SequenceNotStr[str], space: str = DEFAULT_INSTANCE_SPACE - ) -> dm.InstancesDeleteResult: - """Delete one or more equipment module. - - Args: - external_id: External id of the equipment module to delete. - space: The space where all the equipment module are located. - - Returns: - The instance(s), i.e., nodes and edges which has been deleted. Empty list if nothing was deleted. - - Examples: - - Delete equipment_module by id: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> client.equipment_module.delete("my_equipment_module") - """ - 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.equipment_module.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 - ) -> EquipmentModule | None: ... - - @overload - def retrieve( - self, external_id: SequenceNotStr[str | dm.NodeId | tuple[str, str]], space: str = DEFAULT_INSTANCE_SPACE - ) -> EquipmentModuleList: ... - - def retrieve( - self, - external_id: str | dm.NodeId | tuple[str, str] | SequenceNotStr[str | dm.NodeId | tuple[str, str]], - space: str = DEFAULT_INSTANCE_SPACE, - ) -> EquipmentModule | EquipmentModuleList | None: - """Retrieve one or more equipment modules by id(s). - - Args: - external_id: External id or list of external ids of the equipment modules. - space: The space where all the equipment modules are located. - - Returns: - The requested equipment modules. - - Examples: - - Retrieve equipment_module by id: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> equipment_module = client.equipment_module.retrieve("my_equipment_module") - - """ - return self._retrieve(external_id, space) - - def search( - self, - query: str, - properties: EquipmentModuleTextFields | SequenceNotStr[EquipmentModuleTextFields] | None = None, - description: str | list[str] | None = None, - description_prefix: str | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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: EquipmentModuleFields | SequenceNotStr[EquipmentModuleFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> EquipmentModuleList: - """Search equipment modules - - Args: - query: The search query, - properties: The property to search, if nothing is passed all text fields will be searched. - description: The description to filter on. - description_prefix: The prefix of the description to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type 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 equipment modules 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 equipment modules matching the query. - - Examples: - - Search for 'my_equipment_module' in all text properties: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> equipment_modules = client.equipment_module.search('my_equipment_module') - - """ - filter_ = _create_equipment_module_filter( - self._view_id, - description, - description_prefix, - name, - name_prefix, - type_, - type_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: EquipmentModuleFields | SequenceNotStr[EquipmentModuleFields] | None = None, - query: str | None = None, - search_property: EquipmentModuleTextFields | SequenceNotStr[EquipmentModuleTextFields] | None = None, - description: str | list[str] | None = None, - description_prefix: str | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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: EquipmentModuleFields | SequenceNotStr[EquipmentModuleFields] | None = None, - query: str | None = None, - search_property: EquipmentModuleTextFields | SequenceNotStr[EquipmentModuleTextFields] | None = None, - description: str | list[str] | None = None, - description_prefix: str | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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: EquipmentModuleFields | SequenceNotStr[EquipmentModuleFields], - property: EquipmentModuleFields | SequenceNotStr[EquipmentModuleFields] | None = None, - query: str | None = None, - search_property: EquipmentModuleTextFields | SequenceNotStr[EquipmentModuleTextFields] | None = None, - description: str | list[str] | None = None, - description_prefix: str | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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: EquipmentModuleFields | SequenceNotStr[EquipmentModuleFields] | None = None, - property: EquipmentModuleFields | SequenceNotStr[EquipmentModuleFields] | None = None, - query: str | None = None, - search_property: EquipmentModuleTextFields | SequenceNotStr[EquipmentModuleTextFields] | None = None, - description: str | list[str] | None = None, - description_prefix: str | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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 equipment modules - - 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. - description: The description to filter on. - description_prefix: The prefix of the description to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type 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 equipment modules 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 equipment modules in space `my_space`: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> result = client.equipment_module.aggregate("count", space="my_space") - - """ - - filter_ = _create_equipment_module_filter( - self._view_id, - description, - description_prefix, - name, - name_prefix, - type_, - type_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: EquipmentModuleFields, - interval: float, - query: str | None = None, - search_property: EquipmentModuleTextFields | SequenceNotStr[EquipmentModuleTextFields] | None = None, - description: str | list[str] | None = None, - description_prefix: str | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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 equipment modules - - 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. - description: The description to filter on. - description_prefix: The prefix of the description to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type 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 equipment modules 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_equipment_module_filter( - self._view_id, - description, - description_prefix, - name, - name_prefix, - type_, - type_prefix, - external_id_prefix, - space, - filter, - ) - return self._histogram( - property, - interval, - query, - search_property, # type: ignore[arg-type] - limit, - filter_, - ) - - def query(self) -> EquipmentModuleQuery: - """Start a query for equipment modules.""" - warnings.warn("This method is renamed to .select", UserWarning, stacklevel=2) - return EquipmentModuleQuery(self._client) - - def select(self) -> EquipmentModuleQuery: - """Start selecting from equipment modules.""" - warnings.warn( - "The .select is in alpha and is subject to breaking changes without notice.", UserWarning, stacklevel=2 - ) - return EquipmentModuleQuery(self._client) - - def list( - self, - description: str | list[str] | None = None, - description_prefix: str | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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: EquipmentModuleFields | Sequence[EquipmentModuleFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> EquipmentModuleList: - """List/filter equipment modules - - Args: - description: The description to filter on. - description_prefix: The prefix of the description to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type 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 equipment modules 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 equipment modules - - Examples: - - List equipment modules and limit to 5: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> equipment_modules = client.equipment_module.list(limit=5) - - """ - filter_ = _create_equipment_module_filter( - self._view_id, - description, - description_prefix, - name, - name_prefix, - type_, - type_prefix, - 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/equipment_unit/_api/equipment_module_query.py b/examples/equipment_unit/_api/equipment_module_query.py deleted file mode 100644 index 0a8647080..000000000 --- a/examples/equipment_unit/_api/equipment_module_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 equipment_unit.data_classes import ( - DomainModelCore, - EquipmentModule, -) -from equipment_unit._api._core import ( - DEFAULT_QUERY_LIMIT, - EdgeQueryStep, - NodeQueryStep, - DataClassQueryBuilder, - QueryAPI, - T_DomainModelList, - _create_edge_filter, -) - - -class EquipmentModuleQueryAPI(QueryAPI[T_DomainModelList]): - _view_id = dm.ViewId("IntegrationTestsImmutable", "EquipmentModule", "b1cd4bf14a7a33") - - 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=EquipmentModule, - 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/equipment_unit/_api/equipment_module_sensor_value.py b/examples/equipment_unit/_api/equipment_module_sensor_value.py deleted file mode 100644 index d74ab1ef2..000000000 --- a/examples/equipment_unit/_api/equipment_module_sensor_value.py +++ /dev/null @@ -1,536 +0,0 @@ -from __future__ import annotations - -import datetime -import warnings -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 equipment_unit.data_classes._equipment_module import _create_equipment_module_filter -from equipment_unit.data_classes._core import QueryStep, DataClassQueryBuilder, DomainModelList -from equipment_unit._api._core import DEFAULT_LIMIT_READ - - -ColumnNames = Literal["description", "name", "sensor_value", "type"] - - -class EquipmentModuleSensorValueQuery: - 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 `equipment_module.sensor_value` 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_sensor_value' from 2 weeks ago up until now:: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> equipment_module_datapoints = client.equipment_module.sensor_value(external_id="my_sensor_value").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 `equipment_module.sensor_value` 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_sensor_value' from 2 weeks ago up until now:: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> equipment_module_datapoints = client.equipment_module.sensor_value(external_id="my_sensor_value").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] = "sensor_value", - ) -> pd.DataFrame: - """`Retrieve DataFrames for the `equipment_module.sensor_value` 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 sensor_value - - - 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_sensor_value' from 2 weeks ago up until now:: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> equipment_module_datapoints = client.equipment_module.sensor_value(external_id="my_sensor_value").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] = "sensor_value", - ) -> pd.DataFrame: - """Retrieve DataFrames for the `equipment_module.sensor_value` 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 sensor_value - - - Returns: - A ``DataFrame`` with the requested datapoints. - - Examples: - - In this example, - get weekly aggregates for the 'my_sensor_value' for the first month of 2023 in Oslo time: - - >>> from equipment_unit import EquipmentUnitClient - >>> from datetime import datetime, timezone - >>> client = EquipmentUnitClient() - >>> equipment_module_datapoints = client.equipment_module.sensor_value( - ... external_id="my_sensor_value").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] = "sensor_value" - ) -> dict[str, list[str]]: - return _retrieve_timeseries_external_ids_with_extra_sensor_value( - 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 == "sensor_value": - 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 EquipmentModuleSensorValueAPI: - def __init__(self, client: CogniteClient, view_id: dm.ViewId): - self._client = client - self._view_id = view_id - - def __call__( - self, - description: str | list[str] | None = None, - description_prefix: str | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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, - ) -> EquipmentModuleSensorValueQuery: - """Query timeseries `equipment_module.sensor_value` - - Args: - description: The description to filter on. - description_prefix: The prefix of the description to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type 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 equipment modules 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 equipment_module.sensor_value timeseries - selected in this method. - - Examples: - - Retrieve all data for 5 equipment_module.sensor_value timeseries: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> equipment_modules = client.equipment_module.sensor_value(limit=5).retrieve() - - """ - warnings.warn( - "This method is deprecated and will soon be removed. " - "Use the .select()...data.retrieve_dataframe() method instead.", - UserWarning, - stacklevel=2, - ) - filter_ = _create_equipment_module_filter( - self._view_id, - description, - description_prefix, - name, - name_prefix, - type_, - type_prefix, - external_id_prefix, - space, - filter, - ) - - return EquipmentModuleSensorValueQuery( - client=self._client, - view_id=self._view_id, - timeseries_limit=limit, - filter=filter_, - ) - - def list( - self, - description: str | list[str] | None = None, - description_prefix: str | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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, - ) -> TimeSeriesList: - """List timeseries `equipment_module.sensor_value` - - Args: - description: The description to filter on. - description_prefix: The prefix of the description to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type 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 equipment modules 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 equipment_module.sensor_value. - - Examples: - - List equipment_module.sensor_value and limit to 5: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> equipment_modules = client.equipment_module.sensor_value.list(limit=5) - - """ - filter_ = _create_equipment_module_filter( - self._view_id, - description, - description_prefix, - name, - name_prefix, - type_, - type_prefix, - external_id_prefix, - space, - filter, - ) - external_ids = _retrieve_timeseries_external_ids_with_extra_sensor_value( - 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_sensor_value( - client: CogniteClient, - view_id: dm.ViewId, - filter_: dm.Filter | None, - limit: int, - extra_properties: ColumnNames | list[ColumnNames] = "sensor_value", -) -> dict[str, list[str]]: - properties = {"sensor_value"} - 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("sensor_value")) - 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["sensor_value"] - 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/equipment_unit/_api/unit_procedure.py b/examples/equipment_unit/_api/unit_procedure.py deleted file mode 100644 index aa0937150..000000000 --- a/examples/equipment_unit/_api/unit_procedure.py +++ /dev/null @@ -1,628 +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 equipment_unit.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - NodeQueryStep, - EdgeQueryStep, - DataClassQueryBuilder, -) -from equipment_unit.data_classes import ( - DomainModelCore, - DomainModelWrite, - ResourcesWriteResult, - UnitProcedure, - UnitProcedureWrite, - UnitProcedureFields, - UnitProcedureList, - UnitProcedureWriteList, - UnitProcedureTextFields, - StartEndTime, - StartEndTimeWrite, - StartEndTimeList, - EquipmentModule, - StartEndTime, - WorkOrder, -) -from equipment_unit.data_classes._unit_procedure import ( - UnitProcedureQuery, - _UNITPROCEDURE_PROPERTIES_BY_FIELD, - _create_unit_procedure_filter, -) -from equipment_unit._api._core import ( - DEFAULT_LIMIT_READ, - Aggregations, - NodeAPI, - SequenceNotStr, -) -from equipment_unit._api.unit_procedure_work_orders import UnitProcedureWorkOrdersAPI -from equipment_unit._api.unit_procedure_work_units import UnitProcedureWorkUnitsAPI -from equipment_unit._api.unit_procedure_query import UnitProcedureQueryAPI - - -class UnitProcedureAPI(NodeAPI[UnitProcedure, UnitProcedureWrite, UnitProcedureList, UnitProcedureWriteList]): - _view_id = dm.ViewId("IntegrationTestsImmutable", "UnitProcedure", "a6e2fea1e1c664") - _properties_by_field = _UNITPROCEDURE_PROPERTIES_BY_FIELD - _class_type = UnitProcedure - _class_list = UnitProcedureList - _class_write_list = UnitProcedureWriteList - - def __init__(self, client: CogniteClient): - super().__init__(client=client) - - self.work_orders_edge = UnitProcedureWorkOrdersAPI(client) - self.work_units_edge = UnitProcedureWorkUnitsAPI(client) - - def __call__( - self, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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, - ) -> UnitProcedureQueryAPI[UnitProcedureList]: - """Query starting at unit procedures. - - Args: - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type 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 unit procedures 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 unit procedures. - - """ - has_data = dm.filters.HasData(views=[self._view_id]) - filter_ = _create_unit_procedure_filter( - self._view_id, - name, - name_prefix, - type_, - type_prefix, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - builder = DataClassQueryBuilder(UnitProcedureList) - return UnitProcedureQueryAPI(self._client, builder, filter_, limit) - - def apply( - self, - unit_procedure: UnitProcedureWrite | Sequence[UnitProcedureWrite], - replace: bool = False, - write_none: bool = False, - ) -> ResourcesWriteResult: - """Add or update (upsert) unit procedures. - - Note: This method iterates through all nodes and timeseries linked to unit_procedure and creates them including the edges - between the nodes. For example, if any of `work_orders` or `work_units` are set, then these - nodes as well as any nodes linked to them, and all the edges linking these nodes will be created. - - Args: - unit_procedure: Unit procedure or sequence of unit procedures 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 unit_procedure: - - >>> from equipment_unit import EquipmentUnitClient - >>> from equipment_unit.data_classes import UnitProcedureWrite - >>> client = EquipmentUnitClient() - >>> unit_procedure = UnitProcedureWrite(external_id="my_unit_procedure", ...) - >>> result = client.unit_procedure.apply(unit_procedure) - - """ - 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.unit_procedure.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(unit_procedure, replace, write_none) - - def delete( - self, external_id: str | SequenceNotStr[str], space: str = DEFAULT_INSTANCE_SPACE - ) -> dm.InstancesDeleteResult: - """Delete one or more unit procedure. - - Args: - external_id: External id of the unit procedure to delete. - space: The space where all the unit procedure are located. - - Returns: - The instance(s), i.e., nodes and edges which has been deleted. Empty list if nothing was deleted. - - Examples: - - Delete unit_procedure by id: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> client.unit_procedure.delete("my_unit_procedure") - """ - 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.unit_procedure.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 - ) -> UnitProcedure | None: ... - - @overload - def retrieve( - self, external_id: SequenceNotStr[str | dm.NodeId | tuple[str, str]], space: str = DEFAULT_INSTANCE_SPACE - ) -> UnitProcedureList: ... - - def retrieve( - self, - external_id: str | dm.NodeId | tuple[str, str] | SequenceNotStr[str | dm.NodeId | tuple[str, str]], - space: str = DEFAULT_INSTANCE_SPACE, - ) -> UnitProcedure | UnitProcedureList | None: - """Retrieve one or more unit procedures by id(s). - - Args: - external_id: External id or list of external ids of the unit procedures. - space: The space where all the unit procedures are located. - - Returns: - The requested unit procedures. - - Examples: - - Retrieve unit_procedure by id: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> unit_procedure = client.unit_procedure.retrieve("my_unit_procedure") - - """ - return self._retrieve( - external_id, - space, - retrieve_edges=True, - edge_api_name_type_direction_view_id_penta=[ - ( - self.work_orders_edge, - "work_orders", - dm.DirectRelationReference("IntegrationTestsImmutable", "UnitProcedure.work_order"), - "outwards", - dm.ViewId("IntegrationTestsImmutable", "WorkOrder", "c5543fb2b1bc81"), - ), - ( - self.work_units_edge, - "work_units", - dm.DirectRelationReference("IntegrationTestsImmutable", "UnitProcedure.equipment_module"), - "outwards", - dm.ViewId("IntegrationTestsImmutable", "EquipmentModule", "b1cd4bf14a7a33"), - ), - ], - ) - - def search( - self, - query: str, - properties: UnitProcedureTextFields | SequenceNotStr[UnitProcedureTextFields] | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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: UnitProcedureFields | SequenceNotStr[UnitProcedureFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> UnitProcedureList: - """Search unit procedures - - Args: - query: The search query, - properties: The property to search, if nothing is passed all text fields will be searched. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type 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 unit procedures 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 unit procedures matching the query. - - Examples: - - Search for 'my_unit_procedure' in all text properties: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> unit_procedures = client.unit_procedure.search('my_unit_procedure') - - """ - filter_ = _create_unit_procedure_filter( - self._view_id, - name, - name_prefix, - type_, - type_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: UnitProcedureFields | SequenceNotStr[UnitProcedureFields] | None = None, - query: str | None = None, - search_property: UnitProcedureTextFields | SequenceNotStr[UnitProcedureTextFields] | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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: UnitProcedureFields | SequenceNotStr[UnitProcedureFields] | None = None, - query: str | None = None, - search_property: UnitProcedureTextFields | SequenceNotStr[UnitProcedureTextFields] | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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: UnitProcedureFields | SequenceNotStr[UnitProcedureFields], - property: UnitProcedureFields | SequenceNotStr[UnitProcedureFields] | None = None, - query: str | None = None, - search_property: UnitProcedureTextFields | SequenceNotStr[UnitProcedureTextFields] | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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: UnitProcedureFields | SequenceNotStr[UnitProcedureFields] | None = None, - property: UnitProcedureFields | SequenceNotStr[UnitProcedureFields] | None = None, - query: str | None = None, - search_property: UnitProcedureTextFields | SequenceNotStr[UnitProcedureTextFields] | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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 unit procedures - - 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. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type 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 unit procedures 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 unit procedures in space `my_space`: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> result = client.unit_procedure.aggregate("count", space="my_space") - - """ - - filter_ = _create_unit_procedure_filter( - self._view_id, - name, - name_prefix, - type_, - type_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: UnitProcedureFields, - interval: float, - query: str | None = None, - search_property: UnitProcedureTextFields | SequenceNotStr[UnitProcedureTextFields] | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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 unit procedures - - 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. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type 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 unit procedures 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_unit_procedure_filter( - self._view_id, - name, - name_prefix, - type_, - type_prefix, - external_id_prefix, - space, - filter, - ) - return self._histogram( - property, - interval, - query, - search_property, # type: ignore[arg-type] - limit, - filter_, - ) - - def query(self) -> UnitProcedureQuery: - """Start a query for unit procedures.""" - warnings.warn("This method is renamed to .select", UserWarning, stacklevel=2) - return UnitProcedureQuery(self._client) - - def select(self) -> UnitProcedureQuery: - """Start selecting from unit procedures.""" - warnings.warn( - "The .select is in alpha and is subject to breaking changes without notice.", UserWarning, stacklevel=2 - ) - return UnitProcedureQuery(self._client) - - def list( - self, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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: UnitProcedureFields | Sequence[UnitProcedureFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - retrieve_connections: Literal["skip", "identifier", "full"] = "skip", - ) -> UnitProcedureList: - """List/filter unit procedures - - Args: - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type 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 unit procedures 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 `work_orders` and `work_units` for the unit procedures. 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 unit procedures - - Examples: - - List unit procedures and limit to 5: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> unit_procedures = client.unit_procedure.list(limit=5) - - """ - filter_ = _create_unit_procedure_filter( - self._view_id, - name, - name_prefix, - type_, - type_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(UnitProcedureList) - 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] - ), - UnitProcedure, - max_retrieve_limit=limit, - raw_filter=filter_, - ) - ) - from_root = builder.get_from() - edge_work_orders = builder.create_name(from_root) - builder.append( - EdgeQueryStep( - edge_work_orders, - dm.query.EdgeResultSetExpression( - from_=from_root, - direction="outwards", - chain_to="destination", - ), - StartEndTime, - ) - ) - edge_work_units = builder.create_name(from_root) - builder.append( - EdgeQueryStep( - edge_work_units, - dm.query.EdgeResultSetExpression( - from_=from_root, - direction="outwards", - chain_to="destination", - ), - StartEndTime, - ) - ) - if retrieve_connections == "full": - builder.append( - NodeQueryStep( - builder.create_name(edge_work_orders), - dm.query.NodeResultSetExpression( - from_=edge_work_orders, - filter=dm.filters.HasData(views=[WorkOrder._view_id]), - ), - WorkOrder, - ) - ) - builder.append( - NodeQueryStep( - builder.create_name(edge_work_units), - dm.query.NodeResultSetExpression( - from_=edge_work_units, - filter=dm.filters.HasData(views=[EquipmentModule._view_id]), - ), - EquipmentModule, - ) - ) - # 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/equipment_unit/_api/unit_procedure_query.py b/examples/equipment_unit/_api/unit_procedure_query.py deleted file mode 100644 index 35ed70a98..000000000 --- a/examples/equipment_unit/_api/unit_procedure_query.py +++ /dev/null @@ -1,248 +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 equipment_unit.data_classes import ( - DomainModelCore, - UnitProcedure, - StartEndTime, -) -from equipment_unit.data_classes._work_order import ( - WorkOrder, - _create_work_order_filter, -) -from equipment_unit.data_classes._equipment_module import ( - EquipmentModule, - _create_equipment_module_filter, -) -from equipment_unit._api._core import ( - DEFAULT_QUERY_LIMIT, - EdgeQueryStep, - NodeQueryStep, - DataClassQueryBuilder, - QueryAPI, - T_DomainModelList, - _create_edge_filter, -) - -from equipment_unit.data_classes._start_end_time import ( - _create_start_end_time_filter, -) - -if TYPE_CHECKING: - from equipment_unit._api.work_order_query import WorkOrderQueryAPI - from equipment_unit._api.equipment_module_query import EquipmentModuleQueryAPI - - -class UnitProcedureQueryAPI(QueryAPI[T_DomainModelList]): - _view_id = dm.ViewId("IntegrationTestsImmutable", "UnitProcedure", "a6e2fea1e1c664") - - 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=UnitProcedure, - max_retrieve_limit=limit, - ) - ) - - def work_orders( - self, - description: str | list[str] | None = None, - description_prefix: str | None = None, - performed_by: str | list[str] | None = None, - performed_by_prefix: str | None = None, - type_: str | list[str] | None = None, - type_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - min_end_time_edge: datetime.datetime | None = None, - max_end_time_edge: datetime.datetime | None = None, - min_start_time_edge: datetime.datetime | None = None, - max_start_time_edge: datetime.datetime | 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, - ) -> WorkOrderQueryAPI[T_DomainModelList]: - """Query along the work order edges of the unit procedure. - - Args: - description: The description to filter on. - description_prefix: The prefix of the description to filter on. - performed_by: The performed by to filter on. - performed_by_prefix: The prefix of the performed by to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - min_end_time_edge: The minimum value of the end time to filter on. - max_end_time_edge: The maximum value of the end time to filter on. - min_start_time_edge: The minimum value of the start time to filter on. - max_start_time_edge: The maximum value of the start time 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 work order edges to return. Defaults to 3. Set to -1, float("inf") or None - to return all items. - - Returns: - WorkOrderQueryAPI: The query API for the work order. - """ - from .work_order_query import WorkOrderQueryAPI - - # from is a string as we added a node query step in the __init__ method - from_ = cast(str, self._builder.get_from()) - edge_view = StartEndTime._view_id - edge_filter = _create_start_end_time_filter( - dm.DirectRelationReference("IntegrationTestsImmutable", "UnitProcedure.work_order"), - edge_view, - min_end_time=min_end_time_edge, - max_end_time=max_end_time_edge, - min_start_time=min_start_time_edge, - max_start_time=max_start_time_edge, - 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", - ), - result_cls=StartEndTime, - max_retrieve_limit=limit, - ) - ) - - view_id = WorkOrderQueryAPI._view_id - has_data = dm.filters.HasData(views=[view_id]) - node_filer = _create_work_order_filter( - view_id, - description, - description_prefix, - performed_by, - performed_by_prefix, - type_, - type_prefix, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - return WorkOrderQueryAPI(self._client, self._builder, node_filer, limit) - - def work_units( - self, - description: str | list[str] | None = None, - description_prefix: str | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_prefix: str | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - min_end_time_edge: datetime.datetime | None = None, - max_end_time_edge: datetime.datetime | None = None, - min_start_time_edge: datetime.datetime | None = None, - max_start_time_edge: datetime.datetime | 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, - ) -> EquipmentModuleQueryAPI[T_DomainModelList]: - """Query along the work unit edges of the unit procedure. - - Args: - description: The description to filter on. - description_prefix: The prefix of the description to filter on. - name: The name to filter on. - name_prefix: The prefix of the name to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type to filter on. - external_id_prefix: The prefix of the external ID to filter on. - space: The space to filter on. - min_end_time_edge: The minimum value of the end time to filter on. - max_end_time_edge: The maximum value of the end time to filter on. - min_start_time_edge: The minimum value of the start time to filter on. - max_start_time_edge: The maximum value of the start time 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 work unit edges to return. Defaults to 3. Set to -1, float("inf") or None - to return all items. - - Returns: - EquipmentModuleQueryAPI: The query API for the equipment module. - """ - from .equipment_module_query import EquipmentModuleQueryAPI - - # from is a string as we added a node query step in the __init__ method - from_ = cast(str, self._builder.get_from()) - edge_view = StartEndTime._view_id - edge_filter = _create_start_end_time_filter( - dm.DirectRelationReference("IntegrationTestsImmutable", "UnitProcedure.equipment_module"), - edge_view, - min_end_time=min_end_time_edge, - max_end_time=max_end_time_edge, - min_start_time=min_start_time_edge, - max_start_time=max_start_time_edge, - 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", - ), - result_cls=StartEndTime, - max_retrieve_limit=limit, - ) - ) - - view_id = EquipmentModuleQueryAPI._view_id - has_data = dm.filters.HasData(views=[view_id]) - node_filer = _create_equipment_module_filter( - view_id, - description, - description_prefix, - name, - name_prefix, - type_, - type_prefix, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - return EquipmentModuleQueryAPI(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/equipment_unit/_api/unit_procedure_work_orders.py b/examples/equipment_unit/_api/unit_procedure_work_orders.py deleted file mode 100644 index 0b33ac4b1..000000000 --- a/examples/equipment_unit/_api/unit_procedure_work_orders.py +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import annotations - -import datetime - -from cognite.client import data_modeling as dm - -from equipment_unit.data_classes import ( - StartEndTime, - StartEndTimeList, - StartEndTimeWrite, -) -from equipment_unit.data_classes._start_end_time import _create_start_end_time_filter - -from equipment_unit._api._core import DEFAULT_LIMIT_READ, EdgePropertyAPI -from equipment_unit.data_classes._core import DEFAULT_INSTANCE_SPACE - - -class UnitProcedureWorkOrdersAPI(EdgePropertyAPI): - _view_id = dm.ViewId("IntegrationTestsImmutable", "StartEndTime", "d416e0ed98186b") - _class_type = StartEndTime - _class_write_type = StartEndTimeWrite - _class_list = StartEndTimeList - - def list( - self, - from_unit_procedure: str | list[str] | dm.NodeId | list[dm.NodeId] | None = None, - from_unit_procedure_space: str = DEFAULT_INSTANCE_SPACE, - to_work_order: str | list[str] | dm.NodeId | list[dm.NodeId] | None = None, - to_work_order_space: str = DEFAULT_INSTANCE_SPACE, - min_end_time: datetime.datetime | None = None, - max_end_time: datetime.datetime | None = None, - min_start_time: datetime.datetime | None = None, - max_start_time: datetime.datetime | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit=DEFAULT_LIMIT_READ, - ) -> StartEndTimeList: - """List work order edges of a unit procedure. - - Args: - from_unit_procedure: ID of the source unit procedure. - from_unit_procedure_space: Location of the unit procedures. - to_work_order: ID of the target work order. - to_work_order_space: Location of the work orders. - min_end_time: The minimum value of the end time to filter on. - max_end_time: The maximum value of the end time to filter on. - min_start_time: The minimum value of the start time to filter on. - max_start_time: The maximum value of the start time 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 work order edges to return. Defaults to 25. Set to -1, float("inf") or None - to return all items. - - Returns: - The requested work order edges. - - Examples: - - List 5 work order edges connected to "my_unit_procedure": - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> unit_procedure = client.unit_procedure.work_orders_edge.list("my_unit_procedure", limit=5) - - """ - filter_ = _create_start_end_time_filter( - dm.DirectRelationReference("IntegrationTestsImmutable", "UnitProcedure.work_order"), - self._view_id, - from_unit_procedure, - from_unit_procedure_space, - to_work_order, - to_work_order_space, - min_end_time, - max_end_time, - min_start_time, - max_start_time, - external_id_prefix, - space, - ) - return self._list(filter_=filter_, limit=limit) diff --git a/examples/equipment_unit/_api/unit_procedure_work_units.py b/examples/equipment_unit/_api/unit_procedure_work_units.py deleted file mode 100644 index 69433fdc1..000000000 --- a/examples/equipment_unit/_api/unit_procedure_work_units.py +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import annotations - -import datetime - -from cognite.client import data_modeling as dm - -from equipment_unit.data_classes import ( - StartEndTime, - StartEndTimeList, - StartEndTimeWrite, -) -from equipment_unit.data_classes._start_end_time import _create_start_end_time_filter - -from equipment_unit._api._core import DEFAULT_LIMIT_READ, EdgePropertyAPI -from equipment_unit.data_classes._core import DEFAULT_INSTANCE_SPACE - - -class UnitProcedureWorkUnitsAPI(EdgePropertyAPI): - _view_id = dm.ViewId("IntegrationTestsImmutable", "StartEndTime", "d416e0ed98186b") - _class_type = StartEndTime - _class_write_type = StartEndTimeWrite - _class_list = StartEndTimeList - - def list( - self, - from_unit_procedure: str | list[str] | dm.NodeId | list[dm.NodeId] | None = None, - from_unit_procedure_space: str = DEFAULT_INSTANCE_SPACE, - to_equipment_module: str | list[str] | dm.NodeId | list[dm.NodeId] | None = None, - to_equipment_module_space: str = DEFAULT_INSTANCE_SPACE, - min_end_time: datetime.datetime | None = None, - max_end_time: datetime.datetime | None = None, - min_start_time: datetime.datetime | None = None, - max_start_time: datetime.datetime | None = None, - external_id_prefix: str | None = None, - space: str | list[str] | None = None, - limit=DEFAULT_LIMIT_READ, - ) -> StartEndTimeList: - """List work unit edges of a unit procedure. - - Args: - from_unit_procedure: ID of the source unit procedure. - from_unit_procedure_space: Location of the unit procedures. - to_equipment_module: ID of the target equipment module. - to_equipment_module_space: Location of the equipment modules. - min_end_time: The minimum value of the end time to filter on. - max_end_time: The maximum value of the end time to filter on. - min_start_time: The minimum value of the start time to filter on. - max_start_time: The maximum value of the start time 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 work unit edges to return. Defaults to 25. Set to -1, float("inf") or None - to return all items. - - Returns: - The requested work unit edges. - - Examples: - - List 5 work unit edges connected to "my_unit_procedure": - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> unit_procedure = client.unit_procedure.work_units_edge.list("my_unit_procedure", limit=5) - - """ - filter_ = _create_start_end_time_filter( - dm.DirectRelationReference("IntegrationTestsImmutable", "UnitProcedure.equipment_module"), - self._view_id, - from_unit_procedure, - from_unit_procedure_space, - to_equipment_module, - to_equipment_module_space, - min_end_time, - max_end_time, - min_start_time, - max_start_time, - external_id_prefix, - space, - ) - return self._list(filter_=filter_, limit=limit) diff --git a/examples/equipment_unit/_api/work_order.py b/examples/equipment_unit/_api/work_order.py deleted file mode 100644 index d64041dfc..000000000 --- a/examples/equipment_unit/_api/work_order.py +++ /dev/null @@ -1,561 +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 equipment_unit.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DEFAULT_QUERY_LIMIT, - NodeQueryStep, - EdgeQueryStep, - DataClassQueryBuilder, -) -from equipment_unit.data_classes import ( - DomainModelCore, - DomainModelWrite, - ResourcesWriteResult, - WorkOrder, - WorkOrderWrite, - WorkOrderFields, - WorkOrderList, - WorkOrderWriteList, - WorkOrderTextFields, -) -from equipment_unit.data_classes._work_order import ( - WorkOrderQuery, - _WORKORDER_PROPERTIES_BY_FIELD, - _create_work_order_filter, -) -from equipment_unit._api._core import ( - DEFAULT_LIMIT_READ, - Aggregations, - NodeAPI, - SequenceNotStr, -) -from equipment_unit._api.work_order_query import WorkOrderQueryAPI - - -class WorkOrderAPI(NodeAPI[WorkOrder, WorkOrderWrite, WorkOrderList, WorkOrderWriteList]): - _view_id = dm.ViewId("IntegrationTestsImmutable", "WorkOrder", "c5543fb2b1bc81") - _properties_by_field = _WORKORDER_PROPERTIES_BY_FIELD - _class_type = WorkOrder - _class_list = WorkOrderList - _class_write_list = WorkOrderWriteList - - def __init__(self, client: CogniteClient): - super().__init__(client=client) - - def __call__( - self, - description: str | list[str] | None = None, - description_prefix: str | None = None, - performed_by: str | list[str] | None = None, - performed_by_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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, - ) -> WorkOrderQueryAPI[WorkOrderList]: - """Query starting at work orders. - - Args: - description: The description to filter on. - description_prefix: The prefix of the description to filter on. - performed_by: The performed by to filter on. - performed_by_prefix: The prefix of the performed by to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type 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 work orders 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 work orders. - - """ - has_data = dm.filters.HasData(views=[self._view_id]) - filter_ = _create_work_order_filter( - self._view_id, - description, - description_prefix, - performed_by, - performed_by_prefix, - type_, - type_prefix, - external_id_prefix, - space, - (filter and dm.filters.And(filter, has_data)) or has_data, - ) - builder = DataClassQueryBuilder(WorkOrderList) - return WorkOrderQueryAPI(self._client, builder, filter_, limit) - - def apply( - self, - work_order: WorkOrderWrite | Sequence[WorkOrderWrite], - replace: bool = False, - write_none: bool = False, - ) -> ResourcesWriteResult: - """Add or update (upsert) work orders. - - Args: - work_order: Work order or sequence of work orders 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 work_order: - - >>> from equipment_unit import EquipmentUnitClient - >>> from equipment_unit.data_classes import WorkOrderWrite - >>> client = EquipmentUnitClient() - >>> work_order = WorkOrderWrite(external_id="my_work_order", ...) - >>> result = client.work_order.apply(work_order) - - """ - 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.work_order.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(work_order, replace, write_none) - - def delete( - self, external_id: str | SequenceNotStr[str], space: str = DEFAULT_INSTANCE_SPACE - ) -> dm.InstancesDeleteResult: - """Delete one or more work order. - - Args: - external_id: External id of the work order to delete. - space: The space where all the work order are located. - - Returns: - The instance(s), i.e., nodes and edges which has been deleted. Empty list if nothing was deleted. - - Examples: - - Delete work_order by id: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> client.work_order.delete("my_work_order") - """ - 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.work_order.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 - ) -> WorkOrder | None: ... - - @overload - def retrieve( - self, external_id: SequenceNotStr[str | dm.NodeId | tuple[str, str]], space: str = DEFAULT_INSTANCE_SPACE - ) -> WorkOrderList: ... - - def retrieve( - self, - external_id: str | dm.NodeId | tuple[str, str] | SequenceNotStr[str | dm.NodeId | tuple[str, str]], - space: str = DEFAULT_INSTANCE_SPACE, - ) -> WorkOrder | WorkOrderList | None: - """Retrieve one or more work orders by id(s). - - Args: - external_id: External id or list of external ids of the work orders. - space: The space where all the work orders are located. - - Returns: - The requested work orders. - - Examples: - - Retrieve work_order by id: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> work_order = client.work_order.retrieve("my_work_order") - - """ - return self._retrieve(external_id, space) - - def search( - self, - query: str, - properties: WorkOrderTextFields | SequenceNotStr[WorkOrderTextFields] | None = None, - description: str | list[str] | None = None, - description_prefix: str | None = None, - performed_by: str | list[str] | None = None, - performed_by_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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: WorkOrderFields | SequenceNotStr[WorkOrderFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> WorkOrderList: - """Search work orders - - Args: - query: The search query, - properties: The property to search, if nothing is passed all text fields will be searched. - description: The description to filter on. - description_prefix: The prefix of the description to filter on. - performed_by: The performed by to filter on. - performed_by_prefix: The prefix of the performed by to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type 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 work orders 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 work orders matching the query. - - Examples: - - Search for 'my_work_order' in all text properties: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> work_orders = client.work_order.search('my_work_order') - - """ - filter_ = _create_work_order_filter( - self._view_id, - description, - description_prefix, - performed_by, - performed_by_prefix, - type_, - type_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: WorkOrderFields | SequenceNotStr[WorkOrderFields] | None = None, - query: str | None = None, - search_property: WorkOrderTextFields | SequenceNotStr[WorkOrderTextFields] | None = None, - description: str | list[str] | None = None, - description_prefix: str | None = None, - performed_by: str | list[str] | None = None, - performed_by_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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: WorkOrderFields | SequenceNotStr[WorkOrderFields] | None = None, - query: str | None = None, - search_property: WorkOrderTextFields | SequenceNotStr[WorkOrderTextFields] | None = None, - description: str | list[str] | None = None, - description_prefix: str | None = None, - performed_by: str | list[str] | None = None, - performed_by_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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: WorkOrderFields | SequenceNotStr[WorkOrderFields], - property: WorkOrderFields | SequenceNotStr[WorkOrderFields] | None = None, - query: str | None = None, - search_property: WorkOrderTextFields | SequenceNotStr[WorkOrderTextFields] | None = None, - description: str | list[str] | None = None, - description_prefix: str | None = None, - performed_by: str | list[str] | None = None, - performed_by_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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: WorkOrderFields | SequenceNotStr[WorkOrderFields] | None = None, - property: WorkOrderFields | SequenceNotStr[WorkOrderFields] | None = None, - query: str | None = None, - search_property: WorkOrderTextFields | SequenceNotStr[WorkOrderTextFields] | None = None, - description: str | list[str] | None = None, - description_prefix: str | None = None, - performed_by: str | list[str] | None = None, - performed_by_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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 work orders - - 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. - description: The description to filter on. - description_prefix: The prefix of the description to filter on. - performed_by: The performed by to filter on. - performed_by_prefix: The prefix of the performed by to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type 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 work orders 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 work orders in space `my_space`: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> result = client.work_order.aggregate("count", space="my_space") - - """ - - filter_ = _create_work_order_filter( - self._view_id, - description, - description_prefix, - performed_by, - performed_by_prefix, - type_, - type_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: WorkOrderFields, - interval: float, - query: str | None = None, - search_property: WorkOrderTextFields | SequenceNotStr[WorkOrderTextFields] | None = None, - description: str | list[str] | None = None, - description_prefix: str | None = None, - performed_by: str | list[str] | None = None, - performed_by_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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 work orders - - 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. - description: The description to filter on. - description_prefix: The prefix of the description to filter on. - performed_by: The performed by to filter on. - performed_by_prefix: The prefix of the performed by to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type 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 work orders 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_work_order_filter( - self._view_id, - description, - description_prefix, - performed_by, - performed_by_prefix, - type_, - type_prefix, - external_id_prefix, - space, - filter, - ) - return self._histogram( - property, - interval, - query, - search_property, # type: ignore[arg-type] - limit, - filter_, - ) - - def query(self) -> WorkOrderQuery: - """Start a query for work orders.""" - warnings.warn("This method is renamed to .select", UserWarning, stacklevel=2) - return WorkOrderQuery(self._client) - - def select(self) -> WorkOrderQuery: - """Start selecting from work orders.""" - warnings.warn( - "The .select is in alpha and is subject to breaking changes without notice.", UserWarning, stacklevel=2 - ) - return WorkOrderQuery(self._client) - - def list( - self, - description: str | list[str] | None = None, - description_prefix: str | None = None, - performed_by: str | list[str] | None = None, - performed_by_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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: WorkOrderFields | Sequence[WorkOrderFields] | None = None, - direction: Literal["ascending", "descending"] = "ascending", - sort: InstanceSort | list[InstanceSort] | None = None, - ) -> WorkOrderList: - """List/filter work orders - - Args: - description: The description to filter on. - description_prefix: The prefix of the description to filter on. - performed_by: The performed by to filter on. - performed_by_prefix: The prefix of the performed by to filter on. - type_: The type to filter on. - type_prefix: The prefix of the type 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 work orders 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 work orders - - Examples: - - List work orders and limit to 5: - - >>> from equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> work_orders = client.work_order.list(limit=5) - - """ - filter_ = _create_work_order_filter( - self._view_id, - description, - description_prefix, - performed_by, - performed_by_prefix, - type_, - type_prefix, - 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/equipment_unit/_api/work_order_query.py b/examples/equipment_unit/_api/work_order_query.py deleted file mode 100644 index 8e122c962..000000000 --- a/examples/equipment_unit/_api/work_order_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 equipment_unit.data_classes import ( - DomainModelCore, - WorkOrder, -) -from equipment_unit._api._core import ( - DEFAULT_QUERY_LIMIT, - EdgeQueryStep, - NodeQueryStep, - DataClassQueryBuilder, - QueryAPI, - T_DomainModelList, - _create_edge_filter, -) - - -class WorkOrderQueryAPI(QueryAPI[T_DomainModelList]): - _view_id = dm.ViewId("IntegrationTestsImmutable", "WorkOrder", "c5543fb2b1bc81") - - 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=WorkOrder, - 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/equipment_unit/_api_client.py b/examples/equipment_unit/_api_client.py deleted file mode 100644 index bbbea914b..000000000 --- a/examples/equipment_unit/_api_client.py +++ /dev/null @@ -1,252 +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 equipment_unit._api import ( - EquipmentModuleAPI, - UnitProcedureAPI, - WorkOrderAPI, -) -from equipment_unit._api._core import SequenceNotStr, GraphQLQueryResponse -from equipment_unit.data_classes._core import DEFAULT_INSTANCE_SPACE, GraphQLList -from equipment_unit import data_classes - - -class EquipmentUnitClient: - """ - EquipmentUnitClient - - Generated with: - pygen = 0.99.49 - cognite-sdk = 7.66.0 - pydantic = 2.9.2 - - Data Model: - space: IntegrationTestsImmutable - externalId: EquipmentUnit - version: 2 - """ - - 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.equipment_module = EquipmentModuleAPI(client) - self.unit_procedure = UnitProcedureAPI(client) - self.work_order = WorkOrderAPI(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 equipment_unit import EquipmentUnitClient - >>> client = EquipmentUnitClient() - >>> 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 EquipmentUnit 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("IntegrationTestsImmutable", "EquipmentUnit", "2") - 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 - ) -> EquipmentUnitClient: - 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") -> EquipmentUnitClient: - 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 """EquipmentUnitClient generated from data model ("IntegrationTestsImmutable", "EquipmentUnit", "2")
-with the following APIs available
-    .equipment_module
-    .unit_procedure
-    .work_order
-
-and with the methods:
-    .upsert - Create or update any instance.
-    .delete - Delete instances.
-""" diff --git a/examples/equipment_unit/data_classes/__init__.py b/examples/equipment_unit/data_classes/__init__.py deleted file mode 100644 index 158c2906f..000000000 --- a/examples/equipment_unit/data_classes/__init__.py +++ /dev/null @@ -1,133 +0,0 @@ -from equipment_unit.data_classes._core import ( - DataRecord, - DataRecordGraphQL, - DataRecordWrite, - DomainModel, - DomainModelCore, - DomainModelWrite, - DomainModelList, - DomainRelationWrite, - GraphQLCore, - GraphQLList, - ResourcesWrite, - ResourcesWriteResult, - PageInfo, - TimeSeriesGraphQL, - FileMetadataGraphQL, - SequenceColumnGraphQL, - SequenceGraphQL, -) -from ._equipment_module import ( - EquipmentModule, - EquipmentModuleApply, - EquipmentModuleApplyList, - EquipmentModuleFields, - EquipmentModuleGraphQL, - EquipmentModuleList, - EquipmentModuleTextFields, - EquipmentModuleWrite, - EquipmentModuleWriteList, -) -from ._start_end_time import ( - StartEndTime, - StartEndTimeApply, - StartEndTimeApplyList, - StartEndTimeFields, - StartEndTimeGraphQL, - StartEndTimeList, - StartEndTimeTextFields, - StartEndTimeWrite, - StartEndTimeWriteList, -) -from ._unit_procedure import ( - UnitProcedure, - UnitProcedureApply, - UnitProcedureApplyList, - UnitProcedureFields, - UnitProcedureGraphQL, - UnitProcedureList, - UnitProcedureTextFields, - UnitProcedureWrite, - UnitProcedureWriteList, -) -from ._work_order import ( - WorkOrder, - WorkOrderApply, - WorkOrderApplyList, - WorkOrderFields, - WorkOrderGraphQL, - WorkOrderList, - WorkOrderTextFields, - WorkOrderWrite, - WorkOrderWriteList, -) - -EquipmentModule.model_rebuild() -EquipmentModuleGraphQL.model_rebuild() -EquipmentModuleWrite.model_rebuild() -EquipmentModuleApply.model_rebuild() -UnitProcedure.model_rebuild() -UnitProcedureGraphQL.model_rebuild() -UnitProcedureWrite.model_rebuild() -UnitProcedureApply.model_rebuild() -StartEndTime.model_rebuild() -StartEndTimeGraphQL.model_rebuild() -StartEndTimeWrite.model_rebuild() -StartEndTimeApply.model_rebuild() - - -__all__ = [ - "DataRecord", - "DataRecordGraphQL", - "DataRecordWrite", - "ResourcesWrite", - "DomainModel", - "DomainModelCore", - "DomainModelWrite", - "DomainModelList", - "DomainRelationWrite", - "GraphQLCore", - "GraphQLList", - "ResourcesWriteResult", - "PageInfo", - "TimeSeriesGraphQL", - "FileMetadataGraphQL", - "SequenceColumnGraphQL", - "SequenceGraphQL", - "EquipmentModule", - "EquipmentModuleGraphQL", - "EquipmentModuleWrite", - "EquipmentModuleApply", - "EquipmentModuleList", - "EquipmentModuleWriteList", - "EquipmentModuleApplyList", - "EquipmentModuleFields", - "EquipmentModuleTextFields", - "StartEndTime", - "StartEndTimeGraphQL", - "StartEndTimeWrite", - "StartEndTimeApply", - "StartEndTimeList", - "StartEndTimeWriteList", - "StartEndTimeApplyList", - "StartEndTimeFields", - "StartEndTimeTextFields", - "UnitProcedure", - "UnitProcedureGraphQL", - "UnitProcedureWrite", - "UnitProcedureApply", - "UnitProcedureList", - "UnitProcedureWriteList", - "UnitProcedureApplyList", - "UnitProcedureFields", - "UnitProcedureTextFields", - "WorkOrder", - "WorkOrderGraphQL", - "WorkOrderWrite", - "WorkOrderApply", - "WorkOrderList", - "WorkOrderWriteList", - "WorkOrderApplyList", - "WorkOrderFields", - "WorkOrderTextFields", -] diff --git a/examples/equipment_unit/data_classes/_core/__init__.py b/examples/equipment_unit/data_classes/_core/__init__.py deleted file mode 100644 index a88f01b30..000000000 --- a/examples/equipment_unit/data_classes/_core/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from equipment_unit.data_classes._core.constants import * # noqa -from equipment_unit.data_classes._core.base import * # noqa -from equipment_unit.data_classes._core.cdf_external import * # noqa -from equipment_unit.data_classes._core.datapoints_api import * # noqa -from equipment_unit.data_classes._core.helpers import * # noqa -from equipment_unit.data_classes._core.query import * # noqa diff --git a/examples/equipment_unit/data_classes/_core/base.py b/examples/equipment_unit/data_classes/_core/base.py deleted file mode 100644 index 7c13c7f3c..000000000 --- a/examples/equipment_unit/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 equipment_unit.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/equipment_unit/data_classes/_core/cdf_external.py b/examples/equipment_unit/data_classes/_core/cdf_external.py deleted file mode 100644 index 6712f57eb..000000000 --- a/examples/equipment_unit/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/equipment_unit/data_classes/_core/constants.py b/examples/equipment_unit/data_classes/_core/constants.py deleted file mode 100644 index 012b4f81b..000000000 --- a/examples/equipment_unit/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 = "IntegrationTestsImmutable" -# 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/equipment_unit/data_classes/_core/datapoints_api.py b/examples/equipment_unit/data_classes/_core/datapoints_api.py deleted file mode 100644 index eb21d0de7..000000000 --- a/examples/equipment_unit/data_classes/_core/datapoints_api.py +++ /dev/null @@ -1,96 +0,0 @@ -from collections.abc import Callable - -import pandas as pd -import datetime -from cognite.client import CogniteClient -from cognite.client.data_classes.data_modeling.ids import NodeId -from cognite.client.data_classes.datapoints import Aggregate -from cognite.client.utils._time import ZoneInfo - -from equipment_unit.data_classes._core.constants import DEFAULT_QUERY_LIMIT - - -class DataPointsAPI: - def __init__(self, client: CogniteClient, get_node_ids: Callable[[int], list[NodeId]]) -> None: - self._client = client - self._get_node_ids = get_node_ids - - def retrieve_dataframe( - self, - start: int | str | datetime.datetime | None = None, - end: int | str | datetime.datetime | None = None, - aggregates: Aggregate | str | list[Aggregate | str] | None = None, - granularity: str | None = None, - timezone: str | datetime.timezone | ZoneInfo | None = None, - target_unit: str | None = None, - target_unit_system: str | None = None, - limit: int | None = None, - timeseries_limit: int = DEFAULT_QUERY_LIMIT, - include_outside_points: bool = False, - ignore_unknown_ids: bool = False, - include_status: bool = False, - ignore_bad_datapoints: bool = True, - treat_uncertain_as_bad: bool = True, - uniform_index: bool = False, - include_aggregate_name: bool = True, - include_granularity_name: bool = False, - ) -> pd.DataFrame: - """Get datapoints directly in a pandas dataframe. - - Time series support status codes like Good, Uncertain and Bad. You can read more in the Cognite Data Fusion developer documentation on - `status codes. `_ - - Note: - For many more usage examples, check out the :py:meth:`~DatapointsAPI.retrieve` method which accepts exactly the same arguments. - - Args: - start (int | str | datetime.datetime | None): Inclusive start. Default: 1970-01-01 UTC. - end (int | str | datetime.datetime | None): Exclusive end. Default: "now" - aggregates (Aggregate | str | list[Aggregate | str] | None): Single aggregate or list of aggregates to retrieve. Available options: ``average``, ``continuous_variance``, ``count``, ``count_bad``, ``count_good``, ``count_uncertain``, ``discrete_variance``, ``duration_bad``, ``duration_good``, ``duration_uncertain``, ``interpolation``, ``max``, ``min``, ``step_interpolation``, ``sum`` and ``total_variation``. Default: None (raw datapoints returned) - granularity (str | None): The granularity to fetch aggregates at. Can be given as an abbreviation or spelled out for clarity: ``s/second(s)``, ``m/minute(s)``, ``h/hour(s)``, ``d/day(s)``, ``w/week(s)``, ``mo/month(s)``, ``q/quarter(s)``, or ``y/year(s)``. Examples: ``30s``, ``5m``, ``1day``, ``2weeks``. Default: None. - timezone (str | datetime.timezone | ZoneInfo | None): For raw datapoints, which timezone to use when displaying (will not affect what is retrieved). For aggregates, which timezone to align to for granularity 'hour' and longer. Align to the start of the hour, -day or -month. For timezones of type Region/Location, like 'Europe/Oslo', pass a string or ``ZoneInfo`` instance. The aggregate duration will then vary, typically due to daylight saving time. You can also use a fixed offset from UTC by passing a string like '+04:00', 'UTC-7' or 'UTC-02:30' or an instance of ``datetime.timezone``. Note: Historical timezones with second offset are not supported, and timezones with minute offsets (e.g. UTC+05:30 or Asia/Kolkata) may take longer to execute. - target_unit (str | None): The unit_external_id of the datapoints returned. If the time series does not have a 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 (str | None): The unit system of the datapoints returned. Cannot be used with target_unit. - limit (int | None): Maximum number of datapoints to return for each time series. Default: None (no limit) - timeseries_limit (int): Maximum number of timeseries to fetch (columns in the dataframe). Default: 5 - include_outside_points (bool): Whether to include outside points. Not allowed when fetching aggregates. Default: False - ignore_unknown_ids (bool): Whether to ignore missing time series rather than raising an exception. Default: False - include_status (bool): Also return the status code, an integer, for each datapoint in the response. Only relevant for raw datapoint queries, not aggregates. - ignore_bad_datapoints (bool): Treat datapoints with a bad status code as if they do not exist. If set to false, raw queries will include bad datapoints in the response, and aggregates will in general omit the time period between a bad datapoint and the next good datapoint. Also, the period between a bad datapoint and the previous good datapoint will be considered constant. Default: True. - treat_uncertain_as_bad (bool): Treat datapoints with uncertain status codes as bad. If false, treat datapoints with uncertain status codes as good. Used for both raw queries and aggregates. Default: True. - uniform_index (bool): 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 (bool): Include 'aggregate' in the column name, e.g. `my-ts|average`. Ignored for raw time series. Default: True - include_granularity_name (bool): Include 'granularity' in the column name, e.g. `my-ts|12h`. Added after 'aggregate' when present. Ignored for raw time series. Default: False - - Returns: - pd.DataFrame: A pandas DataFrame containing the requested time series. The ordering of columns is ids first, then external_ids. For time series with multiple aggregates, they will be sorted in alphabetical order ("average" before "max"). - - Warning: - If you have duplicated time series in your query, the dataframe columns will also contain duplicates. - - When retrieving raw datapoints with ``ignore_bad_datapoints=False``, bad datapoints with the value NaN can not be distinguished from those - missing a value (due to being stored in a numpy array); all will become NaNs in the dataframe. - """ - node_ids = self._get_node_ids(timeseries_limit) - if not node_ids: - return pd.DataFrame() - return self._client.time_series.data.retrieve_dataframe( - instance_id=node_ids, - start=start, - end=end, - aggregates=aggregates, - granularity=granularity, - timezone=timezone, - target_unit=target_unit, - target_unit_system=target_unit_system, - limit=limit, - include_outside_points=include_outside_points, - ignore_unknown_ids=ignore_unknown_ids, - include_status=include_status, - ignore_bad_datapoints=ignore_bad_datapoints, - treat_uncertain_as_bad=treat_uncertain_as_bad, - uniform_index=uniform_index, - include_aggregate_name=include_aggregate_name, - include_granularity_name=include_granularity_name, - column_names="instance_id", - ) diff --git a/examples/equipment_unit/data_classes/_core/helpers.py b/examples/equipment_unit/data_classes/_core/helpers.py deleted file mode 100644 index 16d2b1323..000000000 --- a/examples/equipment_unit/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 equipment_unit.data_classes._core.base import DomainModel, T_DomainModel -from equipment_unit.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/equipment_unit/data_classes/_core/query.py b/examples/equipment_unit/data_classes/_core/query.py deleted file mode 100644 index 2ccfcbfc4..000000000 --- a/examples/equipment_unit/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 equipment_unit.data_classes._core.base import ( - DomainModelList, - T_DomainList, - DomainRelationList, - DomainModelCore, - T_DomainModelList, - DomainRelation, - DomainModel, -) -from equipment_unit.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 equipment_unit.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/equipment_unit/data_classes/_equipment_module.py b/examples/equipment_unit/data_classes/_equipment_module.py deleted file mode 100644 index 30b9a04bc..000000000 --- a/examples/equipment_unit/data_classes/_equipment_module.py +++ /dev/null @@ -1,396 +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 -from pydantic import field_validator, model_validator - -from equipment_unit.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__ = [ - "EquipmentModule", - "EquipmentModuleWrite", - "EquipmentModuleApply", - "EquipmentModuleList", - "EquipmentModuleWriteList", - "EquipmentModuleApplyList", - "EquipmentModuleFields", - "EquipmentModuleTextFields", - "EquipmentModuleGraphQL", -] - - -EquipmentModuleTextFields = Literal["external_id", "description", "name", "sensor_value", "type_"] -EquipmentModuleFields = Literal["external_id", "description", "name", "sensor_value", "type_"] - -_EQUIPMENTMODULE_PROPERTIES_BY_FIELD = { - "external_id": "externalId", - "description": "description", - "name": "name", - "sensor_value": "sensor_value", - "type_": "type", -} - - -class EquipmentModuleGraphQL(GraphQLCore): - """This represents the reading version of equipment module, 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 equipment module. - data_record: The data record of the equipment module node. - description: The description field. - name: The name field. - sensor_value: The sensor value field. - type_: The type field. - """ - - view_id: ClassVar[dm.ViewId] = dm.ViewId("IntegrationTestsImmutable", "EquipmentModule", "b1cd4bf14a7a33") - description: Optional[str] = None - name: Optional[str] = None - sensor_value: Optional[TimeSeriesGraphQL] = None - type_: Optional[str] = Field(None, alias="type") - - @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) -> EquipmentModule: - """Convert this GraphQL format of equipment module 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 EquipmentModule( - 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, - ), - description=self.description, - name=self.name, - sensor_value=self.sensor_value.as_read() if self.sensor_value else None, - type_=self.type_, - ) - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_write(self) -> EquipmentModuleWrite: - """Convert this GraphQL format of equipment module to the writing format.""" - return EquipmentModuleWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=0), - description=self.description, - name=self.name, - sensor_value=self.sensor_value.as_write() if self.sensor_value else None, - type_=self.type_, - ) - - -class EquipmentModule(DomainModel): - """This represents the reading version of equipment module. - - 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 equipment module. - data_record: The data record of the equipment module node. - description: The description field. - name: The name field. - sensor_value: The sensor value field. - type_: The type field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("IntegrationTestsImmutable", "EquipmentModule", "b1cd4bf14a7a33") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, None] = None - description: Optional[str] = None - name: Optional[str] = None - sensor_value: Union[TimeSeries, str, None] = None - type_: Optional[str] = Field(None, alias="type") - - def as_write(self) -> EquipmentModuleWrite: - """Convert this read version of equipment module to the writing version.""" - return EquipmentModuleWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=self.data_record.version), - description=self.description, - name=self.name, - sensor_value=( - self.sensor_value.as_write() if isinstance(self.sensor_value, CogniteTimeSeries) else self.sensor_value - ), - type_=self.type_, - ) - - def as_apply(self) -> EquipmentModuleWrite: - """Convert this read version of equipment module 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 EquipmentModuleWrite(DomainModelWrite): - """This represents the writing version of equipment module. - - 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 equipment module. - data_record: The data record of the equipment module node. - description: The description field. - name: The name field. - sensor_value: The sensor value field. - type_: The type field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("IntegrationTestsImmutable", "EquipmentModule", "b1cd4bf14a7a33") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, dm.NodeId, tuple[str, str], None] = None - description: Optional[str] = None - name: Optional[str] = None - sensor_value: Union[TimeSeriesWrite, str, None] = None - type_: Optional[str] = Field(None, alias="type") - - 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.description is not None or write_none: - properties["description"] = self.description - - if self.name is not None or write_none: - properties["name"] = self.name - - if self.sensor_value is not None or write_none: - properties["sensor_value"] = ( - self.sensor_value - if isinstance(self.sensor_value, str) or self.sensor_value is None - else self.sensor_value.external_id - ) - - if self.type_ is not None or write_none: - properties["type"] = self.type_ - - 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.sensor_value, CogniteTimeSeriesWrite): - resources.time_series.append(self.sensor_value) - - return resources - - -class EquipmentModuleApply(EquipmentModuleWrite): - def __new__(cls, *args, **kwargs) -> EquipmentModuleApply: - warnings.warn( - "EquipmentModuleApply is deprecated and will be removed in v1.0. Use EquipmentModuleWrite instead." - "The motivation for this change is that Write is a more descriptive name for the writing version of the" - "EquipmentModule.", - UserWarning, - stacklevel=2, - ) - return super().__new__(cls) - - -class EquipmentModuleList(DomainModelList[EquipmentModule]): - """List of equipment modules in the read version.""" - - _INSTANCE = EquipmentModule - - def as_write(self) -> EquipmentModuleWriteList: - """Convert these read versions of equipment module to the writing versions.""" - return EquipmentModuleWriteList([node.as_write() for node in self.data]) - - def as_apply(self) -> EquipmentModuleWriteList: - """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 EquipmentModuleWriteList(DomainModelWriteList[EquipmentModuleWrite]): - """List of equipment modules in the writing version.""" - - _INSTANCE = EquipmentModuleWrite - - -class EquipmentModuleApplyList(EquipmentModuleWriteList): ... - - -def _create_equipment_module_filter( - view_id: dm.ViewId, - description: str | list[str] | None = None, - description_prefix: str | None = None, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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(description, str): - filters.append(dm.filters.Equals(view_id.as_property_ref("description"), value=description)) - if description and isinstance(description, list): - filters.append(dm.filters.In(view_id.as_property_ref("description"), values=description)) - if description_prefix is not None: - filters.append(dm.filters.Prefix(view_id.as_property_ref("description"), value=description_prefix)) - 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(type_, str): - filters.append(dm.filters.Equals(view_id.as_property_ref("type"), value=type_)) - if type_ and isinstance(type_, list): - filters.append(dm.filters.In(view_id.as_property_ref("type"), values=type_)) - if type_prefix is not None: - filters.append(dm.filters.Prefix(view_id.as_property_ref("type"), value=type_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 _EquipmentModuleQuery(NodeQueryCore[T_DomainModelList, EquipmentModuleList]): - _view_id = EquipmentModule._view_id - _result_cls = EquipmentModule - _result_list_cls_end = EquipmentModuleList - - 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.description = StringFilter(self, self._view_id.as_property_ref("description")) - self.name = StringFilter(self, self._view_id.as_property_ref("name")) - self.type_ = StringFilter(self, self._view_id.as_property_ref("type")) - self._filter_classes.extend( - [ - self.space, - self.external_id, - self.description, - self.name, - self.type_, - ] - ) - - def list_equipment_module(self, limit: int = DEFAULT_QUERY_LIMIT) -> EquipmentModuleList: - return self._list(limit=limit) - - -class EquipmentModuleQuery(_EquipmentModuleQuery[EquipmentModuleList]): - def __init__(self, client: CogniteClient): - super().__init__(set(), [], client, EquipmentModuleList) diff --git a/examples/equipment_unit/data_classes/_start_end_time.py b/examples/equipment_unit/data_classes/_start_end_time.py deleted file mode 100644 index 80e9df41e..000000000 --- a/examples/equipment_unit/data_classes/_start_end_time.py +++ /dev/null @@ -1,428 +0,0 @@ -from __future__ import annotations - -import datetime -import warnings -from typing import Any, ClassVar, Literal, no_type_check, Optional, TYPE_CHECKING, Union - -from cognite.client import data_modeling as dm, CogniteClient - -from equipment_unit.data_classes._core import ( - DEFAULT_INSTANCE_SPACE, - DataRecord, - DataRecordWrite, - DomainModel, - DomainModelCore, - DomainModelWrite, - DomainRelation, - DomainRelationWrite, - DomainRelationList, - DomainRelationWriteList, - GraphQLCore, - ResourcesWrite, - DomainModelList, - T_DomainList, - EdgeQueryCore, - NodeQueryCore, - QueryCore, - StringFilter, - TimestampFilter, -) -from equipment_unit.data_classes._unit_procedure import UnitProcedureWrite -from equipment_unit.data_classes._equipment_module import EquipmentModule, EquipmentModuleGraphQL, EquipmentModuleWrite -from equipment_unit.data_classes._work_order import WorkOrder, WorkOrderGraphQL, WorkOrderWrite - -if TYPE_CHECKING: - from equipment_unit.data_classes._equipment_module import ( - EquipmentModule, - EquipmentModuleGraphQL, - EquipmentModuleWrite, - ) - from equipment_unit.data_classes._work_order import WorkOrder, WorkOrderGraphQL, WorkOrderWrite - - -__all__ = [ - "StartEndTime", - "StartEndTimeWrite", - "StartEndTimeApply", - "StartEndTimeList", - "StartEndTimeWriteList", - "StartEndTimeApplyList", - "StartEndTimeFields", -] - - -StartEndTimeTextFields = Literal["external_id",] -StartEndTimeFields = Literal["external_id", "end_time", "start_time"] -_STARTENDTIME_PROPERTIES_BY_FIELD = { - "external_id": "externalId", - "end_time": "end_time", - "start_time": "start_time", -} - - -class StartEndTimeGraphQL(GraphQLCore): - """This represents the reading version of start end time, 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 start end time. - data_record: The data record of the start end time node. - end_node: The end node of this edge. - end_time: The end time field. - start_time: The start time field. - """ - - view_id: ClassVar[dm.ViewId] = dm.ViewId("IntegrationTestsImmutable", "StartEndTime", "d416e0ed98186b") - end_node: Union[EquipmentModuleGraphQL, WorkOrderGraphQL, None] = None - end_time: Optional[datetime.datetime] = None - start_time: Optional[datetime.datetime] = None - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_read(self) -> StartEndTime: - """Convert this GraphQL format of start end time 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 StartEndTime( - 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, - ), - end_node=self.end_node.as_read() if isinstance(self.end_node, GraphQLCore) else self.end_node, - end_time=self.end_time, - start_time=self.start_time, - ) - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_write(self) -> StartEndTimeWrite: - """Convert this GraphQL format of start end time to the writing format.""" - return StartEndTimeWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=0), - end_node=self.end_node.as_write() if isinstance(self.end_node, DomainModel) else self.end_node, - end_time=self.end_time, - start_time=self.start_time, - ) - - -class StartEndTime(DomainRelation): - """This represents the reading version of start end time. - - 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 start end time. - data_record: The data record of the start end time edge. - end_node: The end node of this edge. - end_time: The end time field. - start_time: The start time field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("IntegrationTestsImmutable", "StartEndTime", "d416e0ed98186b") - space: str = DEFAULT_INSTANCE_SPACE - end_node: Union[EquipmentModule, WorkOrder, str, dm.NodeId] - end_time: Optional[datetime.datetime] = None - start_time: Optional[datetime.datetime] = None - - def as_write(self) -> StartEndTimeWrite: - """Convert this read version of start end time to the writing version.""" - return StartEndTimeWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=self.data_record.version), - end_node=self.end_node.as_write() if isinstance(self.end_node, DomainModel) else self.end_node, - end_time=self.end_time, - start_time=self.start_time, - ) - - def as_apply(self) -> StartEndTimeWrite: - """Convert this read version of start end time 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 StartEndTimeWrite(DomainRelationWrite): - """This represents the writing version of start end time. - - 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 start end time. - data_record: The data record of the start end time edge. - end_node: The end node of this edge. - end_time: The end time field. - start_time: The start time field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("IntegrationTestsImmutable", "StartEndTime", "d416e0ed98186b") - space: str = DEFAULT_INSTANCE_SPACE - end_node: Union[EquipmentModuleWrite, WorkOrderWrite, str, dm.NodeId] - end_time: Optional[datetime.datetime] = None - start_time: Optional[datetime.datetime] = None - - 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: - resources = ResourcesWrite() - if self.external_id and (self.space, self.external_id) in cache: - return resources - - _validate_end_node(start_node, self.end_node) - - if isinstance(self.end_node, DomainModelWrite): - end_node = self.end_node.as_direct_reference() - elif isinstance(self.end_node, str): - end_node = dm.DirectRelationReference(self.space, self.end_node) - elif isinstance(self.end_node, dm.NodeId): - end_node = dm.DirectRelationReference(self.end_node.space, self.end_node.external_id) - else: - raise ValueError(f"Invalid type for equipment_module: {type(self.end_node)}") - - external_id = self.external_id or DomainRelationWrite.external_id_factory(start_node, self.end_node, edge_type) - - properties: dict[str, Any] = {} - - if self.end_time is not None or write_none: - properties["end_time"] = self.end_time.isoformat(timespec="milliseconds") if self.end_time else None - - if self.start_time is not None or write_none: - properties["start_time"] = self.start_time.isoformat(timespec="milliseconds") if self.start_time else None - - if properties: - this_edge = dm.EdgeApply( - space=self.space, - external_id=external_id, - type=edge_type, - start_node=start_node.as_direct_reference(), - end_node=end_node, - existing_version=None if allow_version_increase else self.data_record.existing_version, - sources=[ - dm.NodeOrEdgeData( - source=self._view_id, - properties=properties, - ) - ], - ) - resources.edges.append(this_edge) - cache.add((self.space, external_id)) - - if isinstance(self.end_node, DomainModelWrite): - other_resources = self.end_node._to_instances_write(cache) - resources.extend(other_resources) - - return resources - - -class StartEndTimeApply(StartEndTimeWrite): - def __new__(cls, *args, **kwargs) -> StartEndTimeApply: - warnings.warn( - "StartEndTimeApply is deprecated and will be removed in v1.0. Use StartEndTimeWrite instead." - "The motivation for this change is that Write is a more descriptive name for the writing version of the" - "StartEndTime.", - UserWarning, - stacklevel=2, - ) - return super().__new__(cls) - - -class StartEndTimeList(DomainRelationList[StartEndTime]): - """List of start end times in the reading version.""" - - _INSTANCE = StartEndTime - - def as_write(self) -> StartEndTimeWriteList: - """Convert this read version of start end time list to the writing version.""" - return StartEndTimeWriteList([edge.as_write() for edge in self]) - - def as_apply(self) -> StartEndTimeWriteList: - """Convert these read versions of start end time list 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 StartEndTimeWriteList(DomainRelationWriteList[StartEndTimeWrite]): - """List of start end times in the writing version.""" - - _INSTANCE = StartEndTimeWrite - - -class StartEndTimeApplyList(StartEndTimeWriteList): ... - - -def _create_start_end_time_filter( - edge_type: dm.DirectRelationReference, - view_id: dm.ViewId, - 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, - min_end_time: datetime.datetime | None = None, - max_end_time: datetime.datetime | None = None, - min_start_time: datetime.datetime | None = None, - max_start_time: datetime.datetime | None = None, - 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 min_end_time is not None or max_end_time is not None: - filters.append( - dm.filters.Range( - view_id.as_property_ref("end_time"), - gte=min_end_time.isoformat(timespec="milliseconds") if min_end_time else None, - lte=max_end_time.isoformat(timespec="milliseconds") if max_end_time else None, - ) - ) - if min_start_time is not None or max_start_time is not None: - filters.append( - dm.filters.Range( - view_id.as_property_ref("start_time"), - gte=min_start_time.isoformat(timespec="milliseconds") if min_start_time else None, - lte=max_start_time.isoformat(timespec="milliseconds") if max_start_time else None, - ) - ) - if external_id_prefix is not None: - filters.append(dm.filters.Prefix(["edge", "externalId"], value=external_id_prefix)) - if 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) - - -_EXPECTED_START_NODES_BY_END_NODE: dict[type[DomainModelWrite], set[type[DomainModelWrite]]] = { - EquipmentModuleWrite: {UnitProcedureWrite}, - WorkOrderWrite: {UnitProcedureWrite}, -} - - -def _validate_end_node( - start_node: DomainModelWrite, end_node: Union[EquipmentModuleWrite, WorkOrderWrite, str, dm.NodeId] -) -> None: - if isinstance(end_node, (str, dm.NodeId)): - # Nothing to validate - return - if type(end_node) not in _EXPECTED_START_NODES_BY_END_NODE: - raise ValueError( - f"Invalid end node type: {type(end_node)}. Should be one of {[t.__name__ for t in _EXPECTED_START_NODES_BY_END_NODE.keys()]}" - ) - if type(start_node) not in _EXPECTED_START_NODES_BY_END_NODE[type(end_node)]: - raise ValueError( - f"Invalid end node type: {type(end_node)}. Expected one of: {_EXPECTED_START_NODES_BY_END_NODE[type(end_node)]}" - ) - - -class _StartEndTimeQuery(EdgeQueryCore[T_DomainList, StartEndTimeList]): - _view_id = StartEndTime._view_id - _result_cls = StartEndTime - _result_list_cls_end = StartEndTimeList - - def __init__( - self, - created_types: set[type], - creation_path: list[QueryCore], - client: CogniteClient, - result_list_cls: type[T_DomainList], - end_node_cls: type[NodeQueryCore], - expression: dm.query.ResultSetExpression | None = None, - connection_name: str | None = None, - ): - from ._equipment_module import _EquipmentModuleQuery - from ._work_order import _WorkOrderQuery - - super().__init__(created_types, creation_path, client, result_list_cls, expression, None, connection_name) - if end_node_cls not in created_types: - self.end_node = end_node_cls( - created_types=created_types.copy(), - creation_path=self._creation_path, - client=client, - result_list_cls=result_list_cls, # type: ignore[type-var] - expression=dm.query.NodeResultSetExpression(), - ) - - self.space = StringFilter(self, ["node", "space"]) - self.external_id = StringFilter(self, ["node", "externalId"]) - self.end_time = TimestampFilter(self, self._view_id.as_property_ref("end_time")) - self.start_time = TimestampFilter(self, self._view_id.as_property_ref("start_time")) - self._filter_classes.extend( - [ - self.space, - self.external_id, - self.end_time, - self.start_time, - ] - ) diff --git a/examples/equipment_unit/data_classes/_unit_procedure.py b/examples/equipment_unit/data_classes/_unit_procedure.py deleted file mode 100644 index 975a40de0..000000000 --- a/examples/equipment_unit/data_classes/_unit_procedure.py +++ /dev/null @@ -1,518 +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 equipment_unit.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, -) - -if TYPE_CHECKING: - from equipment_unit.data_classes._start_end_time import ( - StartEndTime, - StartEndTimeList, - StartEndTimeGraphQL, - StartEndTimeWrite, - StartEndTimeWriteList, - ) - - -__all__ = [ - "UnitProcedure", - "UnitProcedureWrite", - "UnitProcedureApply", - "UnitProcedureList", - "UnitProcedureWriteList", - "UnitProcedureApplyList", - "UnitProcedureFields", - "UnitProcedureTextFields", - "UnitProcedureGraphQL", -] - - -UnitProcedureTextFields = Literal["external_id", "name", "type_"] -UnitProcedureFields = Literal["external_id", "name", "type_"] - -_UNITPROCEDURE_PROPERTIES_BY_FIELD = { - "external_id": "externalId", - "name": "name", - "type_": "type", -} - - -class UnitProcedureGraphQL(GraphQLCore): - """This represents the reading version of unit procedure, 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 unit procedure. - data_record: The data record of the unit procedure node. - name: The name field. - type_: The type field. - work_orders: The work order field. - work_units: The work unit field. - """ - - view_id: ClassVar[dm.ViewId] = dm.ViewId("IntegrationTestsImmutable", "UnitProcedure", "a6e2fea1e1c664") - name: Optional[str] = None - type_: Optional[str] = Field(None, alias="type") - work_orders: Optional[list[StartEndTimeGraphQL]] = Field(default=None, repr=False) - work_units: Optional[list[StartEndTimeGraphQL]] = 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("work_orders", "work_units", 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) -> UnitProcedure: - """Convert this GraphQL format of unit procedure 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 UnitProcedure( - 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, - ), - name=self.name, - type_=self.type_, - work_orders=[work_order.as_read() for work_order in self.work_orders or []], - work_units=[work_unit.as_read() for work_unit in self.work_units or []], - ) - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_write(self) -> UnitProcedureWrite: - """Convert this GraphQL format of unit procedure to the writing format.""" - return UnitProcedureWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=0), - name=self.name, - type_=self.type_, - work_orders=[work_order.as_write() for work_order in self.work_orders or []], - work_units=[work_unit.as_write() for work_unit in self.work_units or []], - ) - - -class UnitProcedure(DomainModel): - """This represents the reading version of unit procedure. - - 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 unit procedure. - data_record: The data record of the unit procedure node. - name: The name field. - type_: The type field. - work_orders: The work order field. - work_units: The work unit field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("IntegrationTestsImmutable", "UnitProcedure", "a6e2fea1e1c664") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, None] = None - name: Optional[str] = None - type_: Optional[str] = Field(None, alias="type") - work_orders: Optional[list[StartEndTime]] = Field(default=None, repr=False) - work_units: Optional[list[StartEndTime]] = Field(default=None, repr=False) - - def as_write(self) -> UnitProcedureWrite: - """Convert this read version of unit procedure to the writing version.""" - return UnitProcedureWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=self.data_record.version), - name=self.name, - type_=self.type_, - work_orders=[work_order.as_write() for work_order in self.work_orders or []], - work_units=[work_unit.as_write() for work_unit in self.work_units or []], - ) - - def as_apply(self) -> UnitProcedureWrite: - """Convert this read version of unit procedure 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, UnitProcedure], # type: ignore[override] - nodes_by_id: dict[dm.NodeId | str, DomainModel], - edges_by_source_node: dict[dm.NodeId, list[dm.Edge | DomainRelation]], - ) -> None: - from ._start_end_time import StartEndTime - - for instance in instances.values(): - if edges := edges_by_source_node.get(instance.as_id()): - work_orders: list[StartEndTime] = [] - work_units: list[StartEndTime] = [] - 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( - "IntegrationTestsImmutable", "UnitProcedure.work_order" - ) and isinstance(value, StartEndTime): - work_orders.append(value) - if end_node := nodes_by_id.get(as_pygen_node_id(value.end_node)): - value.end_node = end_node # type: ignore[assignment] - if edge_type == dm.DirectRelationReference( - "IntegrationTestsImmutable", "UnitProcedure.equipment_module" - ) and isinstance(value, StartEndTime): - work_units.append(value) - if end_node := nodes_by_id.get(as_pygen_node_id(value.end_node)): - value.end_node = end_node # type: ignore[assignment] - - instance.work_orders = work_orders - instance.work_units = work_units - - -class UnitProcedureWrite(DomainModelWrite): - """This represents the writing version of unit procedure. - - 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 unit procedure. - data_record: The data record of the unit procedure node. - name: The name field. - type_: The type field. - work_orders: The work order field. - work_units: The work unit field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("IntegrationTestsImmutable", "UnitProcedure", "a6e2fea1e1c664") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, dm.NodeId, tuple[str, str], None] = None - name: Optional[str] = None - type_: Optional[str] = Field(None, alias="type") - work_orders: Optional[list[StartEndTimeWrite]] = Field(default=None, repr=False) - work_units: Optional[list[StartEndTimeWrite]] = Field(default=None, repr=False) - - @field_validator("work_orders", "work_units", 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.name is not None or write_none: - properties["name"] = self.name - - if self.type_ is not None or write_none: - properties["type"] = self.type_ - - 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()) - - for work_order in self.work_orders or []: - if isinstance(work_order, DomainRelationWrite): - other_resources = work_order._to_instances_write( - cache, - self, - dm.DirectRelationReference("IntegrationTestsImmutable", "UnitProcedure.work_order"), - ) - resources.extend(other_resources) - - for work_unit in self.work_units or []: - if isinstance(work_unit, DomainRelationWrite): - other_resources = work_unit._to_instances_write( - cache, - self, - dm.DirectRelationReference("IntegrationTestsImmutable", "UnitProcedure.equipment_module"), - ) - resources.extend(other_resources) - - return resources - - -class UnitProcedureApply(UnitProcedureWrite): - def __new__(cls, *args, **kwargs) -> UnitProcedureApply: - warnings.warn( - "UnitProcedureApply is deprecated and will be removed in v1.0. Use UnitProcedureWrite instead." - "The motivation for this change is that Write is a more descriptive name for the writing version of the" - "UnitProcedure.", - UserWarning, - stacklevel=2, - ) - return super().__new__(cls) - - -class UnitProcedureList(DomainModelList[UnitProcedure]): - """List of unit procedures in the read version.""" - - _INSTANCE = UnitProcedure - - def as_write(self) -> UnitProcedureWriteList: - """Convert these read versions of unit procedure to the writing versions.""" - return UnitProcedureWriteList([node.as_write() for node in self.data]) - - def as_apply(self) -> UnitProcedureWriteList: - """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 work_orders(self) -> StartEndTimeList: - from ._start_end_time import StartEndTime, StartEndTimeList - - return StartEndTimeList( - [item for items in self.data for item in items.work_orders or [] if isinstance(item, StartEndTime)] - ) - - @property - def work_units(self) -> StartEndTimeList: - from ._start_end_time import StartEndTime, StartEndTimeList - - return StartEndTimeList( - [item for items in self.data for item in items.work_units or [] if isinstance(item, StartEndTime)] - ) - - -class UnitProcedureWriteList(DomainModelWriteList[UnitProcedureWrite]): - """List of unit procedures in the writing version.""" - - _INSTANCE = UnitProcedureWrite - - @property - def work_orders(self) -> StartEndTimeWriteList: - from ._start_end_time import StartEndTimeWrite, StartEndTimeWriteList - - return StartEndTimeWriteList( - [item for items in self.data for item in items.work_orders or [] if isinstance(item, StartEndTimeWrite)] - ) - - @property - def work_units(self) -> StartEndTimeWriteList: - from ._start_end_time import StartEndTimeWrite, StartEndTimeWriteList - - return StartEndTimeWriteList( - [item for items in self.data for item in items.work_units or [] if isinstance(item, StartEndTimeWrite)] - ) - - -class UnitProcedureApplyList(UnitProcedureWriteList): ... - - -def _create_unit_procedure_filter( - view_id: dm.ViewId, - name: str | list[str] | None = None, - name_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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(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(type_, str): - filters.append(dm.filters.Equals(view_id.as_property_ref("type"), value=type_)) - if type_ and isinstance(type_, list): - filters.append(dm.filters.In(view_id.as_property_ref("type"), values=type_)) - if type_prefix is not None: - filters.append(dm.filters.Prefix(view_id.as_property_ref("type"), value=type_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 _UnitProcedureQuery(NodeQueryCore[T_DomainModelList, UnitProcedureList]): - _view_id = UnitProcedure._view_id - _result_cls = UnitProcedure - _result_list_cls_end = UnitProcedureList - - 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 ._equipment_module import _EquipmentModuleQuery - from ._start_end_time import _StartEndTimeQuery - from ._work_order import _WorkOrderQuery - - 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 _StartEndTimeQuery not in created_types: - self.work_orders = _StartEndTimeQuery( - created_types.copy(), - self._creation_path, - client, - result_list_cls, - _WorkOrderQuery, - dm.query.EdgeResultSetExpression( - direction="outwards", - chain_to="destination", - ), - connection_name="work_orders", - ) - - if _StartEndTimeQuery not in created_types: - self.work_units = _StartEndTimeQuery( - created_types.copy(), - self._creation_path, - client, - result_list_cls, - _EquipmentModuleQuery, - dm.query.EdgeResultSetExpression( - direction="outwards", - chain_to="destination", - ), - connection_name="work_units", - ) - - self.space = StringFilter(self, ["node", "space"]) - self.external_id = StringFilter(self, ["node", "externalId"]) - self.name = StringFilter(self, self._view_id.as_property_ref("name")) - self.type_ = StringFilter(self, self._view_id.as_property_ref("type")) - self._filter_classes.extend( - [ - self.space, - self.external_id, - self.name, - self.type_, - ] - ) - - def list_unit_procedure(self, limit: int = DEFAULT_QUERY_LIMIT) -> UnitProcedureList: - return self._list(limit=limit) - - -class UnitProcedureQuery(_UnitProcedureQuery[UnitProcedureList]): - def __init__(self, client: CogniteClient): - super().__init__(set(), [], client, UnitProcedureList) diff --git a/examples/equipment_unit/data_classes/_work_order.py b/examples/equipment_unit/data_classes/_work_order.py deleted file mode 100644 index 81b49d59c..000000000 --- a/examples/equipment_unit/data_classes/_work_order.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 pydantic import Field -from pydantic import field_validator, model_validator - -from equipment_unit.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, -) - - -__all__ = [ - "WorkOrder", - "WorkOrderWrite", - "WorkOrderApply", - "WorkOrderList", - "WorkOrderWriteList", - "WorkOrderApplyList", - "WorkOrderFields", - "WorkOrderTextFields", - "WorkOrderGraphQL", -] - - -WorkOrderTextFields = Literal["external_id", "description", "performed_by", "type_"] -WorkOrderFields = Literal["external_id", "description", "performed_by", "type_"] - -_WORKORDER_PROPERTIES_BY_FIELD = { - "external_id": "externalId", - "description": "description", - "performed_by": "performedBy", - "type_": "type", -} - - -class WorkOrderGraphQL(GraphQLCore): - """This represents the reading version of work order, 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 work order. - data_record: The data record of the work order node. - description: The description field. - performed_by: The performed by field. - type_: The type field. - """ - - view_id: ClassVar[dm.ViewId] = dm.ViewId("IntegrationTestsImmutable", "WorkOrder", "c5543fb2b1bc81") - description: Optional[str] = None - performed_by: Optional[str] = Field(None, alias="performedBy") - type_: Optional[str] = Field(None, alias="type") - - @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) -> WorkOrder: - """Convert this GraphQL format of work order 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 WorkOrder( - 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, - ), - description=self.description, - performed_by=self.performed_by, - type_=self.type_, - ) - - # We do the ignore argument type as we let pydantic handle the type checking - @no_type_check - def as_write(self) -> WorkOrderWrite: - """Convert this GraphQL format of work order to the writing format.""" - return WorkOrderWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=0), - description=self.description, - performed_by=self.performed_by, - type_=self.type_, - ) - - -class WorkOrder(DomainModel): - """This represents the reading version of work order. - - 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 work order. - data_record: The data record of the work order node. - description: The description field. - performed_by: The performed by field. - type_: The type field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("IntegrationTestsImmutable", "WorkOrder", "c5543fb2b1bc81") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, None] = None - description: Optional[str] = None - performed_by: Optional[str] = Field(None, alias="performedBy") - type_: Optional[str] = Field(None, alias="type") - - def as_write(self) -> WorkOrderWrite: - """Convert this read version of work order to the writing version.""" - return WorkOrderWrite( - space=self.space, - external_id=self.external_id, - data_record=DataRecordWrite(existing_version=self.data_record.version), - description=self.description, - performed_by=self.performed_by, - type_=self.type_, - ) - - def as_apply(self) -> WorkOrderWrite: - """Convert this read version of work order 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 WorkOrderWrite(DomainModelWrite): - """This represents the writing version of work order. - - 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 work order. - data_record: The data record of the work order node. - description: The description field. - performed_by: The performed by field. - type_: The type field. - """ - - _view_id: ClassVar[dm.ViewId] = dm.ViewId("IntegrationTestsImmutable", "WorkOrder", "c5543fb2b1bc81") - - space: str = DEFAULT_INSTANCE_SPACE - node_type: Union[dm.DirectRelationReference, dm.NodeId, tuple[str, str], None] = None - description: Optional[str] = None - performed_by: Optional[str] = Field(None, alias="performedBy") - type_: Optional[str] = Field(None, alias="type") - - 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.description is not None or write_none: - properties["description"] = self.description - - if self.performed_by is not None or write_none: - properties["performedBy"] = self.performed_by - - if self.type_ is not None or write_none: - properties["type"] = self.type_ - - 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()) - - return resources - - -class WorkOrderApply(WorkOrderWrite): - def __new__(cls, *args, **kwargs) -> WorkOrderApply: - warnings.warn( - "WorkOrderApply is deprecated and will be removed in v1.0. Use WorkOrderWrite instead." - "The motivation for this change is that Write is a more descriptive name for the writing version of the" - "WorkOrder.", - UserWarning, - stacklevel=2, - ) - return super().__new__(cls) - - -class WorkOrderList(DomainModelList[WorkOrder]): - """List of work orders in the read version.""" - - _INSTANCE = WorkOrder - - def as_write(self) -> WorkOrderWriteList: - """Convert these read versions of work order to the writing versions.""" - return WorkOrderWriteList([node.as_write() for node in self.data]) - - def as_apply(self) -> WorkOrderWriteList: - """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 WorkOrderWriteList(DomainModelWriteList[WorkOrderWrite]): - """List of work orders in the writing version.""" - - _INSTANCE = WorkOrderWrite - - -class WorkOrderApplyList(WorkOrderWriteList): ... - - -def _create_work_order_filter( - view_id: dm.ViewId, - description: str | list[str] | None = None, - description_prefix: str | None = None, - performed_by: str | list[str] | None = None, - performed_by_prefix: str | None = None, - type_: str | list[str] | None = None, - type_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(description, str): - filters.append(dm.filters.Equals(view_id.as_property_ref("description"), value=description)) - if description and isinstance(description, list): - filters.append(dm.filters.In(view_id.as_property_ref("description"), values=description)) - if description_prefix is not None: - filters.append(dm.filters.Prefix(view_id.as_property_ref("description"), value=description_prefix)) - if isinstance(performed_by, str): - filters.append(dm.filters.Equals(view_id.as_property_ref("performedBy"), value=performed_by)) - if performed_by and isinstance(performed_by, list): - filters.append(dm.filters.In(view_id.as_property_ref("performedBy"), values=performed_by)) - if performed_by_prefix is not None: - filters.append(dm.filters.Prefix(view_id.as_property_ref("performedBy"), value=performed_by_prefix)) - if isinstance(type_, str): - filters.append(dm.filters.Equals(view_id.as_property_ref("type"), value=type_)) - if type_ and isinstance(type_, list): - filters.append(dm.filters.In(view_id.as_property_ref("type"), values=type_)) - if type_prefix is not None: - filters.append(dm.filters.Prefix(view_id.as_property_ref("type"), value=type_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 _WorkOrderQuery(NodeQueryCore[T_DomainModelList, WorkOrderList]): - _view_id = WorkOrder._view_id - _result_cls = WorkOrder - _result_list_cls_end = WorkOrderList - - 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.description = StringFilter(self, self._view_id.as_property_ref("description")) - self.performed_by = StringFilter(self, self._view_id.as_property_ref("performedBy")) - self.type_ = StringFilter(self, self._view_id.as_property_ref("type")) - self._filter_classes.extend( - [ - self.space, - self.external_id, - self.description, - self.performed_by, - self.type_, - ] - ) - - def list_work_order(self, limit: int = DEFAULT_QUERY_LIMIT) -> WorkOrderList: - return self._list(limit=limit) - - -class WorkOrderQuery(_WorkOrderQuery[WorkOrderList]): - def __init__(self, client: CogniteClient): - super().__init__(set(), [], client, WorkOrderList) diff --git a/examples/omni/_api/cdf_external_references.py b/examples/omni/_api/cdf_external_references.py index 74f1cd784..96815b950 100644 --- a/examples/omni/_api/cdf_external_references.py +++ b/examples/omni/_api/cdf_external_references.py @@ -76,6 +76,11 @@ def __call__( A query API for cdf external references. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cdf_external_reference_filter( self._view_id, diff --git a/examples/omni/_api/cdf_external_references_listed.py b/examples/omni/_api/cdf_external_references_listed.py index 66a6b919a..4409305bc 100644 --- a/examples/omni/_api/cdf_external_references_listed.py +++ b/examples/omni/_api/cdf_external_references_listed.py @@ -79,6 +79,11 @@ def __call__( A query API for cdf external references listeds. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_cdf_external_references_listed_filter( self._view_id, diff --git a/examples/omni/_api/connection_item_a.py b/examples/omni/_api/connection_item_a.py index cd6ebe035..06413eba8 100644 --- a/examples/omni/_api/connection_item_a.py +++ b/examples/omni/_api/connection_item_a.py @@ -96,6 +96,11 @@ def __call__( A query API for connection item as. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_connection_item_a_filter( self._view_id, diff --git a/examples/omni/_api/connection_item_b.py b/examples/omni/_api/connection_item_b.py index 6d4f71bf1..c927d6ebd 100644 --- a/examples/omni/_api/connection_item_b.py +++ b/examples/omni/_api/connection_item_b.py @@ -79,6 +79,11 @@ def __call__( A query API for connection item bs. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_connection_item_b_filter( self._view_id, diff --git a/examples/omni/_api/connection_item_c_node.py b/examples/omni/_api/connection_item_c_node.py index 2be295fd0..d3728a57e 100644 --- a/examples/omni/_api/connection_item_c_node.py +++ b/examples/omni/_api/connection_item_c_node.py @@ -77,6 +77,11 @@ def __call__( A query API for connection item c nodes. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_connection_item_c_node_filter( self._view_id, diff --git a/examples/omni/_api/connection_item_d.py b/examples/omni/_api/connection_item_d.py index cdaa0fe93..b7ccc87d0 100644 --- a/examples/omni/_api/connection_item_d.py +++ b/examples/omni/_api/connection_item_d.py @@ -95,6 +95,11 @@ def __call__( A query API for connection item ds. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_connection_item_d_filter( self._view_id, diff --git a/examples/omni/_api/connection_item_e.py b/examples/omni/_api/connection_item_e.py index 7ddd30d27..eb34626da 100644 --- a/examples/omni/_api/connection_item_e.py +++ b/examples/omni/_api/connection_item_e.py @@ -93,6 +93,11 @@ def __call__( A query API for connection item es. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_connection_item_e_filter( self._view_id, diff --git a/examples/omni/_api/connection_item_f.py b/examples/omni/_api/connection_item_f.py index af98622d5..190e563f3 100644 --- a/examples/omni/_api/connection_item_f.py +++ b/examples/omni/_api/connection_item_f.py @@ -94,6 +94,11 @@ def __call__( A query API for connection item fs. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_connection_item_f_filter( self._view_id, diff --git a/examples/omni/_api/connection_item_g.py b/examples/omni/_api/connection_item_g.py index 11fd8f519..d453e6ac5 100644 --- a/examples/omni/_api/connection_item_g.py +++ b/examples/omni/_api/connection_item_g.py @@ -81,6 +81,11 @@ def __call__( A query API for connection item gs. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_connection_item_g_filter( self._view_id, diff --git a/examples/omni/_api/dependent_on_non_writable.py b/examples/omni/_api/dependent_on_non_writable.py index 2780d3a23..eb04fb667 100644 --- a/examples/omni/_api/dependent_on_non_writable.py +++ b/examples/omni/_api/dependent_on_non_writable.py @@ -81,6 +81,11 @@ def __call__( A query API for dependent on non writables. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_dependent_on_non_writable_filter( self._view_id, diff --git a/examples/omni/_api/empty.py b/examples/omni/_api/empty.py index 40a20f568..74a91cb06 100644 --- a/examples/omni/_api/empty.py +++ b/examples/omni/_api/empty.py @@ -100,6 +100,11 @@ def __call__( A query API for empties. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_empty_filter( self._view_id, diff --git a/examples/omni/_api/implementation_1.py b/examples/omni/_api/implementation_1.py index a0bf0989b..fd26d000a 100644 --- a/examples/omni/_api/implementation_1.py +++ b/examples/omni/_api/implementation_1.py @@ -85,6 +85,11 @@ def __call__( A query API for implementation 1. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_implementation_1_filter( self._view_id, diff --git a/examples/omni/_api/implementation_1_non_writeable.py b/examples/omni/_api/implementation_1_non_writeable.py index 32e575f68..be8917df4 100644 --- a/examples/omni/_api/implementation_1_non_writeable.py +++ b/examples/omni/_api/implementation_1_non_writeable.py @@ -78,6 +78,11 @@ def __call__( A query API for implementation 1 non writeables. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_implementation_1_non_writeable_filter( self._view_id, diff --git a/examples/omni/_api/implementation_2.py b/examples/omni/_api/implementation_2.py index 73916b376..1e0d56905 100644 --- a/examples/omni/_api/implementation_2.py +++ b/examples/omni/_api/implementation_2.py @@ -77,6 +77,11 @@ def __call__( A query API for implementation 2. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_implementation_2_filter( self._view_id, diff --git a/examples/omni/_api/main_interface.py b/examples/omni/_api/main_interface.py index d4499ea6c..9126218dd 100644 --- a/examples/omni/_api/main_interface.py +++ b/examples/omni/_api/main_interface.py @@ -79,6 +79,11 @@ def __call__( A query API for main interfaces. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_main_interface_filter( self._view_id, diff --git a/examples/omni/_api/primitive_nullable.py b/examples/omni/_api/primitive_nullable.py index 1dd98cc39..3672a0341 100644 --- a/examples/omni/_api/primitive_nullable.py +++ b/examples/omni/_api/primitive_nullable.py @@ -102,6 +102,11 @@ def __call__( A query API for primitive nullables. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_primitive_nullable_filter( self._view_id, diff --git a/examples/omni/_api/primitive_nullable_listed.py b/examples/omni/_api/primitive_nullable_listed.py index 9c90ef8cd..090084a36 100644 --- a/examples/omni/_api/primitive_nullable_listed.py +++ b/examples/omni/_api/primitive_nullable_listed.py @@ -77,6 +77,11 @@ def __call__( A query API for primitive nullable listeds. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_primitive_nullable_listed_filter( self._view_id, diff --git a/examples/omni/_api/primitive_required.py b/examples/omni/_api/primitive_required.py index d531b4e0d..f8e6f7ef6 100644 --- a/examples/omni/_api/primitive_required.py +++ b/examples/omni/_api/primitive_required.py @@ -102,6 +102,11 @@ def __call__( A query API for primitive requireds. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_primitive_required_filter( self._view_id, diff --git a/examples/omni/_api/primitive_required_listed.py b/examples/omni/_api/primitive_required_listed.py index 9943bac0c..e6791773f 100644 --- a/examples/omni/_api/primitive_required_listed.py +++ b/examples/omni/_api/primitive_required_listed.py @@ -77,6 +77,11 @@ def __call__( A query API for primitive required listeds. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_primitive_required_listed_filter( self._view_id, diff --git a/examples/omni/_api/primitive_with_defaults.py b/examples/omni/_api/primitive_with_defaults.py index 18d8c6144..dfc82409c 100644 --- a/examples/omni/_api/primitive_with_defaults.py +++ b/examples/omni/_api/primitive_with_defaults.py @@ -87,6 +87,11 @@ def __call__( A query API for primitive with defaults. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_primitive_with_default_filter( self._view_id, diff --git a/examples/omni/_api/sub_interface.py b/examples/omni/_api/sub_interface.py index abe498e7e..bbae7417c 100644 --- a/examples/omni/_api/sub_interface.py +++ b/examples/omni/_api/sub_interface.py @@ -85,6 +85,11 @@ def __call__( A query API for sub interfaces. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_sub_interface_filter( self._view_id, diff --git a/examples/omni_multi/_api/implementation_1_v_1.py b/examples/omni_multi/_api/implementation_1_v_1.py index b4b3e0a23..3d955f55c 100644 --- a/examples/omni_multi/_api/implementation_1_v_1.py +++ b/examples/omni_multi/_api/implementation_1_v_1.py @@ -82,6 +82,11 @@ def __call__( A query API for implementation 1 v 1. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_implementation_1_v_1_filter( self._view_id, diff --git a/examples/omni_multi/_api/implementation_1_v_2.py b/examples/omni_multi/_api/implementation_1_v_2.py index 3305bb132..f17e61c33 100644 --- a/examples/omni_multi/_api/implementation_1_v_2.py +++ b/examples/omni_multi/_api/implementation_1_v_2.py @@ -82,6 +82,11 @@ def __call__( A query API for implementation 1 v 2. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_implementation_1_v_2_filter( self._view_id, diff --git a/examples/omni_multi/_api/main_interface.py b/examples/omni_multi/_api/main_interface.py index 8fff3de7a..efe16645c 100644 --- a/examples/omni_multi/_api/main_interface.py +++ b/examples/omni_multi/_api/main_interface.py @@ -76,6 +76,11 @@ def __call__( A query API for main interfaces. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_main_interface_filter( self._view_id, diff --git a/examples/omni_multi/_api/sub_interface.py b/examples/omni_multi/_api/sub_interface.py index 4a4c74cb9..0a553277d 100644 --- a/examples/omni_multi/_api/sub_interface.py +++ b/examples/omni_multi/_api/sub_interface.py @@ -80,6 +80,11 @@ def __call__( A query API for sub interfaces. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_sub_interface_filter( self._view_id, diff --git a/examples/omni_sub/_api/connection_item_a.py b/examples/omni_sub/_api/connection_item_a.py index 053bc7f36..b3b8a8e2d 100644 --- a/examples/omni_sub/_api/connection_item_a.py +++ b/examples/omni_sub/_api/connection_item_a.py @@ -93,6 +93,11 @@ def __call__( A query API for connection item as. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_connection_item_a_filter( self._view_id, diff --git a/examples/omni_sub/_api/connection_item_b.py b/examples/omni_sub/_api/connection_item_b.py index 9610f3dce..fe513cc0a 100644 --- a/examples/omni_sub/_api/connection_item_b.py +++ b/examples/omni_sub/_api/connection_item_b.py @@ -78,6 +78,11 @@ def __call__( A query API for connection item bs. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_connection_item_b_filter( self._view_id, diff --git a/examples/omni_sub/_api/connection_item_c_node.py b/examples/omni_sub/_api/connection_item_c_node.py index bc76f857b..851c29e12 100644 --- a/examples/omni_sub/_api/connection_item_c_node.py +++ b/examples/omni_sub/_api/connection_item_c_node.py @@ -76,6 +76,11 @@ def __call__( A query API for connection item c nodes. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_connection_item_c_node_filter( self._view_id, diff --git a/examples/wind_turbine/_api/blade.py b/examples/wind_turbine/_api/blade.py index d8da3457b..2749a6cff 100644 --- a/examples/wind_turbine/_api/blade.py +++ b/examples/wind_turbine/_api/blade.py @@ -76,6 +76,11 @@ def __call__( A query API for blades. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_blade_filter( self._view_id, diff --git a/examples/wind_turbine/_api/data_sheet.py b/examples/wind_turbine/_api/data_sheet.py index 312c163f0..313e759ac 100644 --- a/examples/wind_turbine/_api/data_sheet.py +++ b/examples/wind_turbine/_api/data_sheet.py @@ -92,6 +92,11 @@ def __call__( A query API for data sheets. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_data_sheet_filter( self._view_id, diff --git a/examples/wind_turbine/_api/gearbox.py b/examples/wind_turbine/_api/gearbox.py index 5c4bd386e..33e24a3ce 100644 --- a/examples/wind_turbine/_api/gearbox.py +++ b/examples/wind_turbine/_api/gearbox.py @@ -97,6 +97,11 @@ def __call__( A query API for gearboxes. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_gearbox_filter( self._view_id, diff --git a/examples/wind_turbine/_api/generating_unit.py b/examples/wind_turbine/_api/generating_unit.py index 63cb4bd80..30de97aed 100644 --- a/examples/wind_turbine/_api/generating_unit.py +++ b/examples/wind_turbine/_api/generating_unit.py @@ -87,6 +87,11 @@ def __call__( A query API for generating units. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_generating_unit_filter( self._view_id, diff --git a/examples/wind_turbine/_api/generator.py b/examples/wind_turbine/_api/generator.py index a9af83a42..933b96cce 100644 --- a/examples/wind_turbine/_api/generator.py +++ b/examples/wind_turbine/_api/generator.py @@ -88,6 +88,11 @@ def __call__( A query API for generators. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_generator_filter( self._view_id, diff --git a/examples/wind_turbine/_api/high_speed_shaft.py b/examples/wind_turbine/_api/high_speed_shaft.py index 0514da60e..0203f8860 100644 --- a/examples/wind_turbine/_api/high_speed_shaft.py +++ b/examples/wind_turbine/_api/high_speed_shaft.py @@ -97,6 +97,11 @@ def __call__( A query API for high speed shafts. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_high_speed_shaft_filter( self._view_id, diff --git a/examples/wind_turbine/_api/main_shaft.py b/examples/wind_turbine/_api/main_shaft.py index 28e7400f5..a80b52a79 100644 --- a/examples/wind_turbine/_api/main_shaft.py +++ b/examples/wind_turbine/_api/main_shaft.py @@ -115,6 +115,11 @@ def __call__( A query API for main shafts. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_main_shaft_filter( self._view_id, diff --git a/examples/wind_turbine/_api/metmast.py b/examples/wind_turbine/_api/metmast.py index 6399683d7..5d8a4f276 100644 --- a/examples/wind_turbine/_api/metmast.py +++ b/examples/wind_turbine/_api/metmast.py @@ -87,6 +87,11 @@ def __call__( A query API for metmasts. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_metmast_filter( self._view_id, diff --git a/examples/wind_turbine/_api/nacelle.py b/examples/wind_turbine/_api/nacelle.py index 98847c22a..9e612817b 100644 --- a/examples/wind_turbine/_api/nacelle.py +++ b/examples/wind_turbine/_api/nacelle.py @@ -165,6 +165,11 @@ def __call__( A query API for nacelles. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_nacelle_filter( self._view_id, diff --git a/examples/wind_turbine/_api/power_inverter.py b/examples/wind_turbine/_api/power_inverter.py index 304f0d164..519660ada 100644 --- a/examples/wind_turbine/_api/power_inverter.py +++ b/examples/wind_turbine/_api/power_inverter.py @@ -97,6 +97,11 @@ def __call__( A query API for power inverters. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_power_inverter_filter( self._view_id, diff --git a/examples/wind_turbine/_api/rotor.py b/examples/wind_turbine/_api/rotor.py index f022f3ba5..2750adf74 100644 --- a/examples/wind_turbine/_api/rotor.py +++ b/examples/wind_turbine/_api/rotor.py @@ -88,6 +88,11 @@ def __call__( A query API for rotors. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_rotor_filter( self._view_id, diff --git a/examples/wind_turbine/_api/sensor_position.py b/examples/wind_turbine/_api/sensor_position.py index deed0d5a4..553455d08 100644 --- a/examples/wind_turbine/_api/sensor_position.py +++ b/examples/wind_turbine/_api/sensor_position.py @@ -156,6 +156,11 @@ def __call__( A query API for sensor positions. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_sensor_position_filter( self._view_id, diff --git a/examples/wind_turbine/_api/sensor_time_series.py b/examples/wind_turbine/_api/sensor_time_series.py index 94dd5f381..686a2554d 100644 --- a/examples/wind_turbine/_api/sensor_time_series.py +++ b/examples/wind_turbine/_api/sensor_time_series.py @@ -93,6 +93,11 @@ def __call__( A query API for sensor time series. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_sensor_time_series_filter( self._view_id, diff --git a/examples/wind_turbine/_api/solar_panel.py b/examples/wind_turbine/_api/solar_panel.py index feccc928b..e4d042934 100644 --- a/examples/wind_turbine/_api/solar_panel.py +++ b/examples/wind_turbine/_api/solar_panel.py @@ -100,6 +100,11 @@ def __call__( A query API for solar panels. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_solar_panel_filter( self._view_id, diff --git a/examples/wind_turbine/_api/wind_turbine.py b/examples/wind_turbine/_api/wind_turbine.py index f97b17364..5860be3b8 100644 --- a/examples/wind_turbine/_api/wind_turbine.py +++ b/examples/wind_turbine/_api/wind_turbine.py @@ -133,6 +133,11 @@ def __call__( A query API for wind turbines. """ + warnings.warn( + "This method is deprecated and will soon be removed. " "Use the .select() method instead.", + UserWarning, + stacklevel=2, + ) has_data = dm.filters.HasData(views=[self._view_id]) filter_ = _create_wind_turbine_filter( self._view_id, diff --git a/tests/constants.py b/tests/constants.py index 03a020fe5..9ee80c804 100644 --- a/tests/constants.py +++ b/tests/constants.py @@ -130,14 +130,6 @@ def load_read_nodes(self, data_model_id: dm.DataModelId) -> dm.NodeList: instance_space=None, ) -EQUIPMENT_UNIT_SDK = ExampleSDK( - data_model_ids=[DataModelId("IntegrationTestsImmutable", "EquipmentUnit", "2")], - _top_level_package="equipment_unit", - client_name="EquipmentUnitClient", - generate_sdk=True, - instance_space="IntegrationTestsImmutable", -) - CORE_SDK = ExampleSDK( data_model_ids=[DataModelId("cdf_cdm", "CogniteCore", "v1")], _top_level_package="cognite_core", @@ -163,27 +155,6 @@ def load_read_nodes(self, data_model_id: dm.DataModelId) -> dm.NodeList: ) -class EquipmentSDKFiles: - client_dir = EQUIPMENT_UNIT_SDK.client_dir - client = client_dir / "_api_client.py" - data_classes = client_dir / "data_classes" - core_data = data_classes / "_core.py" - start_end_time_data = data_classes / "_start_end_time.py" - unit_procedure_data = data_classes / "_unit_procedure.py" - equipment_module_data = data_classes / "_equipment_module.py" - - api = client_dir / "_api" - equipment_api = api / "equipment_module.py" - equipment_module_sensor_value_api = api / "equipment_module_sensor_value.py" - - unit_procedure_api = api / "unit_procedure.py" - unit_procedure_query = api / "unit_procedure_query.py" - unit_procedure_work_units = api / "unit_procedure_work_units.py" - core_api = api / "_core.py" - - data_init = data_classes / "__init__.py" - - class OmniFiles: client_dir = OMNI_SDK.client_dir client = client_dir / "_api_client.py" diff --git a/tests/data/models/EquipmentUnit/model.yaml b/tests/data/models/EquipmentUnit/model.yaml deleted file mode 100644 index 20345003d..000000000 --- a/tests/data/models/EquipmentUnit/model.yaml +++ /dev/null @@ -1,262 +0,0 @@ -space: IntegrationTestsImmutable -externalId: EquipmentUnit -name: EquipmentUnit -description: '' -version: '2' -views: -- space: IntegrationTestsImmutable - externalId: EquipmentModule - name: EquipmentModule - implements: [] - version: b1cd4bf14a7a33 - writable: true - usedFor: node - isGlobal: false - properties: - description: - container: - space: IntegrationTestsImmutable - externalId: EquipmentModule - containerPropertyIdentifier: description - type: - list: false - collation: ucs_basic - type: text - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: description - description: null - name: - container: - space: IntegrationTestsImmutable - externalId: EquipmentModule - 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_value: - container: - space: IntegrationTestsImmutable - externalId: EquipmentModule - containerPropertyIdentifier: sensor_value - type: - list: false - type: timeseries - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: sensor_value - description: null - type: - container: - space: IntegrationTestsImmutable - externalId: EquipmentModule - containerPropertyIdentifier: type - type: - list: false - collation: ucs_basic - type: text - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: type - description: null - lastUpdatedTime: 1703424931271 - createdTime: 1703424931271 -- space: IntegrationTestsImmutable - externalId: StartEndTime - name: StartEndTime - implements: [] - version: d416e0ed98186b - writable: true - usedFor: edge - isGlobal: false - properties: - end_time: - container: - space: IntegrationTestsImmutable - externalId: StartEndTime - containerPropertyIdentifier: end_time - type: - list: false - type: timestamp - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: end_time - description: null - start_time: - container: - space: IntegrationTestsImmutable - externalId: StartEndTime - containerPropertyIdentifier: start_time - type: - list: false - type: timestamp - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: start_time - description: null - lastUpdatedTime: 1703424931271 - createdTime: 1703424931271 -- space: IntegrationTestsImmutable - externalId: UnitProcedure - name: UnitProcedure - implements: [] - version: a6e2fea1e1c664 - writable: true - usedFor: node - isGlobal: false - properties: - name: - container: - space: IntegrationTestsImmutable - externalId: UnitProcedure - containerPropertyIdentifier: name - type: - list: false - collation: ucs_basic - type: text - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: name - description: null - type: - container: - space: IntegrationTestsImmutable - externalId: UnitProcedure - containerPropertyIdentifier: type - type: - list: false - collation: ucs_basic - type: text - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: type - description: null - work_orders: - type: - space: IntegrationTestsImmutable - externalId: UnitProcedure.work_order - source: - space: IntegrationTestsImmutable - externalId: WorkOrder - version: c5543fb2b1bc81 - type: view - name: work_orders - description: null - edgeSource: - space: IntegrationTestsImmutable - externalId: StartEndTime - version: d416e0ed98186b - type: view - direction: outwards - connectionType: multi_edge_connection - work_units: - type: - space: IntegrationTestsImmutable - externalId: UnitProcedure.equipment_module - source: - space: IntegrationTestsImmutable - externalId: EquipmentModule - version: b1cd4bf14a7a33 - type: view - name: work_units - description: null - edgeSource: - space: IntegrationTestsImmutable - externalId: StartEndTime - version: d416e0ed98186b - type: view - direction: outwards - connectionType: multi_edge_connection - lastUpdatedTime: 1703424931271 - createdTime: 1703424931271 -- space: IntegrationTestsImmutable - externalId: WorkOrder - name: WorkOrder - implements: [] - version: c5543fb2b1bc81 - writable: true - usedFor: node - isGlobal: false - properties: - description: - container: - space: IntegrationTestsImmutable - externalId: WorkOrder - containerPropertyIdentifier: description - type: - list: false - collation: ucs_basic - type: text - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: description - description: null - performedBy: - container: - space: IntegrationTestsImmutable - externalId: WorkOrder - containerPropertyIdentifier: performedBy - type: - list: false - collation: ucs_basic - type: text - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: performedBy - description: null - type: - container: - space: IntegrationTestsImmutable - externalId: WorkOrder - containerPropertyIdentifier: type - type: - list: false - collation: ucs_basic - type: text - nullable: true - immutable: false - autoIncrement: false - source: null - defaultValue: null - name: type - description: null - lastUpdatedTime: 1703424931271 - createdTime: 1703424931271 -isGlobal: false -lastUpdatedTime: 1703485511412 -createdTime: 1703424932659 diff --git a/tests/test_integration/test_list_edges.py b/tests/test_integration/test_list_edges.py new file mode 100644 index 000000000..553f32eb6 --- /dev/null +++ b/tests/test_integration/test_list_edges.py @@ -0,0 +1,35 @@ +from wind_turbine import WindTurbineClient +from wind_turbine import data_classes as data_cls + + +def test_edges_to_pandas(turbine_client: WindTurbineClient) -> None: + df = turbine_client.wind_turbine.metmast_edge.list(max_distance=1000, limit=3).to_pandas() + assert len(df) <= 3 + assert not df.empty + assert "distance" in df.columns + assert sum(df["distance"] <= 1000) == len(df) + + +def test_upsert_with_edge(turbine_client: WindTurbineClient) -> None: + new_turbine = data_cls.WindTurbineWrite( + external_id="doctriono_b", + name="A new Wind Turbine", + capacity=8.0, + metmast=[ + data_cls.DistanceWrite( + distance=500.0, + end_node=data_cls.MetmastWrite( + external_id="doctrino_weather", + position=42.0, + ), + ) + ], + ) + + try: + created = turbine_client.upsert(new_turbine) + + assert len(created.nodes) == 2 + assert len(created.edges) == 1 + finally: + turbine_client.delete(new_turbine) diff --git a/tests/test_integration/test_sdks/__init__.py b/tests/test_integration/test_sdks/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/test_integration/test_sdks/test_equipment_sdk/__init__.py b/tests/test_integration/test_sdks/test_equipment_sdk/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/test_integration/test_sdks/test_equipment_sdk/conftest.py b/tests/test_integration/test_sdks/test_equipment_sdk/conftest.py deleted file mode 100644 index f4c097741..000000000 --- a/tests/test_integration/test_sdks/test_equipment_sdk/conftest.py +++ /dev/null @@ -1,7 +0,0 @@ -import pytest -from equipment_unit import EquipmentUnitClient - - -@pytest.fixture() -def workorder(client_config) -> EquipmentUnitClient: - return EquipmentUnitClient.azure_project(**client_config) diff --git a/tests/test_integration/test_sdks/test_equipment_sdk/test_unit_procedure.py b/tests/test_integration/test_sdks/test_equipment_sdk/test_unit_procedure.py deleted file mode 100644 index cafbeb676..000000000 --- a/tests/test_integration/test_sdks/test_equipment_sdk/test_unit_procedure.py +++ /dev/null @@ -1,104 +0,0 @@ -import pytest -from cognite.client import CogniteClient -from cognite.client.data_classes import TimeSeriesWrite -from equipment_unit.data_classes import ( - EquipmentModule, - EquipmentModuleWrite, - StartEndTime, - StartEndTimeList, - StartEndTimeWrite, - UnitProcedureList, - UnitProcedureWrite, -) - -from examples.equipment_unit import EquipmentUnitClient - - -@pytest.fixture -def start_end_time_edges(workorder: EquipmentUnitClient) -> StartEndTimeList: - edges = workorder.unit_procedure.work_units_edge.list(limit=-1) - assert len(edges) > 2, "There should be at least three edge in the list" - assert isinstance(edges, StartEndTimeList) - return edges - - -@pytest.fixture -def unit_procedure_list(workorder: EquipmentUnitClient) -> UnitProcedureList: - nodes = workorder.unit_procedure.list(limit=5) - assert len(nodes) > 2, "There should be at least three node in the list" - assert isinstance(nodes, UnitProcedureList) - return nodes - - -def test_edges_to_pandas(start_end_time_edges: StartEndTimeList) -> None: - df = start_end_time_edges.to_pandas() - assert len(df) == len(start_end_time_edges) - - -def test_unit_procedure_list_to_pandas(unit_procedure_list: UnitProcedureList) -> None: - df = unit_procedure_list.to_pandas() - assert len(df) == len(unit_procedure_list) - - -def test_single_unit_procedure_to_pandas(unit_procedure_list: UnitProcedureList) -> None: - procedure = unit_procedure_list[0] - - series = procedure.to_pandas() - assert len(series) == 8, "We only have the four properties + the external id, space, node type, and data_record" - - -def test_filter_start_end_time_edges(start_end_time_edges: StartEndTimeList, workorder: EquipmentUnitClient) -> None: - sorted_by_start_time = sorted(start_end_time_edges, key=lambda x: x.start_time) - - filtered = workorder.unit_procedure.work_units_edge.list( - min_start_time=sorted_by_start_time[1].start_time, limit=-1 - ) - - assert len(filtered) == len(sorted_by_start_time) - 1 - - -def test_filter_unit_procedure_through_edge(workorder: EquipmentUnitClient) -> None: - unit_procedures = workorder.unit_procedure(type_="red", limit=3).work_units(limit=5).query() - - assert 1 <= len(unit_procedures) <= 3 - assert all(procedure.type_ == "red" for procedure in unit_procedures) - for unit_procedure in unit_procedures: - for work_unit in unit_procedure.work_units: - assert isinstance(work_unit, StartEndTime) - assert isinstance(work_unit.end_node, EquipmentModule) - - -def test_apply_unit_procedure_with_edge(workorder: EquipmentUnitClient, cognite_client: CogniteClient) -> None: - new_procedure = UnitProcedureWrite( - external_id="procedure:new_procedure", - name="New procedure", - type_="New type", - work_units=[ - StartEndTimeWrite( - start_time="2021-01-01T00:00:00Z", - end_time="2021-01-01T00:00:00Z", - end_node=EquipmentModuleWrite( - external_id="module:new_module", - name="New module", - equipment_module_type="New type", - sensor_value=TimeSeriesWrite( - external_id="timeseries:123", - is_step=True, - description="This is a test timeseries, it should not persist", - ), - description="New description", - ), - ), - ], - ) - - instances = new_procedure.to_instances_write() - try: - created = workorder.unit_procedure.apply(new_procedure) - - assert len(created.nodes) == 2 - assert len(created.edges) == 1 - assert len(created.time_series) == 1 - finally: - cognite_client.data_modeling.instances.delete(instances.nodes.as_ids(), instances.edges.as_ids()) - cognite_client.time_series.delete(external_id=instances.time_series.as_external_ids(), ignore_unknown_ids=True) diff --git a/tests/test_unit/test_generator/conftest.py b/tests/test_unit/test_generator/conftest.py index 37ca88e7f..e567a84ed 100644 --- a/tests/test_unit/test_generator/conftest.py +++ b/tests/test_unit/test_generator/conftest.py @@ -1,32 +1,9 @@ import pytest from cognite.client import data_modeling as dm -from tests.constants import ( - EQUIPMENT_UNIT_SDK, - PUMP_SDK, -) +from tests.constants import PUMP_SDK @pytest.fixture(scope="session") def pump_model() -> dm.DataModel[dm.View]: return PUMP_SDK.load_data_model() - - -@pytest.fixture(scope="session") -def equipment_unit_model() -> dm.DataModel[dm.View]: - return EQUIPMENT_UNIT_SDK.load_data_model() - - -@pytest.fixture(scope="session") -def unit_procedure_view(equipment_unit_model: dm.DataModel) -> dm.View: - return next(v for v in equipment_unit_model.views if v.name == "UnitProcedure") - - -@pytest.fixture(scope="session") -def equipment_module_view(equipment_unit_model: dm.DataModel) -> dm.View: - return next(v for v in equipment_unit_model.views if v.name == "EquipmentModule") - - -@pytest.fixture(scope="session") -def start_end_time_view(equipment_unit_model: dm.DataModel) -> dm.View: - return next(v for v in equipment_unit_model.views if v.name == "StartEndTime") diff --git a/tests/test_unit/test_generator/test_sdk_generator_equipment_unit_sdk.py b/tests/test_unit/test_generator/test_sdk_generator_equipment_unit_sdk.py deleted file mode 100644 index a01df7015..000000000 --- a/tests/test_unit/test_generator/test_sdk_generator_equipment_unit_sdk.py +++ /dev/null @@ -1,191 +0,0 @@ -from __future__ import annotations - -import difflib - -import pytest -from cognite.client import data_modeling as dm - -from cognite.pygen._core.generators import APIGenerator, MultiAPIGenerator, SDKGenerator -from cognite.pygen._generator import CodeFormatter -from cognite.pygen.config import PygenConfig -from tests.constants import EQUIPMENT_UNIT_SDK, EquipmentSDKFiles - - -@pytest.fixture -def sdk_generator(equipment_unit_model: dm.DataModel[dm.View], pygen_config: PygenConfig) -> SDKGenerator: - return SDKGenerator( - EQUIPMENT_UNIT_SDK.top_level_package, - EQUIPMENT_UNIT_SDK.client_name, - equipment_unit_model, - config=pygen_config, - default_instance_space=EQUIPMENT_UNIT_SDK.instance_space, - ) - - -@pytest.fixture -def multi_api_generator(sdk_generator: SDKGenerator) -> MultiAPIGenerator: - return sdk_generator._multi_api_generator - - -@pytest.fixture -def unit_procedure_api_generator(multi_api_generator: MultiAPIGenerator, unit_procedure_view: dm.View) -> APIGenerator: - api_generator = multi_api_generator[unit_procedure_view.as_id()] - assert api_generator is not None, "Could not find API generator for unit procedure view" - return api_generator - - -@pytest.fixture -def equipment_module_api_generator( - multi_api_generator: MultiAPIGenerator, equipment_module_view: dm.View -) -> APIGenerator: - api_generator = multi_api_generator[equipment_module_view.as_id()] - assert api_generator is not None, "Could not find API generator for equipment module view" - return api_generator - - -@pytest.fixture -def start_end_time_api_generator(multi_api_generator: MultiAPIGenerator, start_end_time_view: dm.View) -> APIGenerator: - api_generator = multi_api_generator.api_by_type_by_view_id["edge"][start_end_time_view.as_id()] - assert api_generator is not None, "Could not find API generator for start end time view" - return api_generator - - -def test_generate_data_class_file_unit_procedure( - unit_procedure_api_generator: APIGenerator, pygen_config: PygenConfig, code_formatter: CodeFormatter -): - # Arrange - expected = EquipmentSDKFiles.unit_procedure_data.read_text() - - # Act - actual = unit_procedure_api_generator.generate_data_class_file() - actual = code_formatter.format_code(actual) - - # Assert - assert actual == expected - - -def test_generate_data_class_start_end_time( - start_end_time_api_generator: APIGenerator, pygen_config: PygenConfig, code_formatter: CodeFormatter -): - # Arrange - expected = EquipmentSDKFiles.start_end_time_data.read_text() - - # Act - actual = start_end_time_api_generator.generate_data_class_file() - actual = code_formatter.format_code(actual) - - # Assert - assert actual == expected - - -def test_generate_data_class_equipment_module( - equipment_module_api_generator: APIGenerator, pygen_config: PygenConfig, code_formatter: CodeFormatter -): - # Arrange - expected = EquipmentSDKFiles.equipment_module_data.read_text() - - # Act - actual = equipment_module_api_generator.generate_data_class_file() - actual = code_formatter.format_code(actual) - - # Assert - assert actual == expected - - -def test_create_view_api_classes_equipment_module( - equipment_module_api_generator: APIGenerator, code_formatter: CodeFormatter -): - # Arrange - expected = EquipmentSDKFiles.equipment_api.read_text() - - # Act - actual = equipment_module_api_generator.generate_api_file(EQUIPMENT_UNIT_SDK.client_name) - actual = code_formatter.format_code(actual) - - # Assert - assert actual == expected - - -def test_generate_equipment_module_sensor_value_api( - equipment_module_api_generator: APIGenerator, code_formatter: CodeFormatter -): - # Arrange - expected = EquipmentSDKFiles.equipment_module_sensor_value_api.read_text() - - # Act - _, actual = next(equipment_module_api_generator.generate_timeseries_api_files(EQUIPMENT_UNIT_SDK.client_name)) - actual = code_formatter.format_code(actual) - - # Assert - assert actual == expected - - -def test_create_view_api_classes_unit_procedure( - unit_procedure_api_generator: APIGenerator, code_formatter: CodeFormatter -): - # Arrange - expected = EquipmentSDKFiles.unit_procedure_api.read_text() - - # Act - actual = unit_procedure_api_generator.generate_api_file(EQUIPMENT_UNIT_SDK.client_name) - actual = code_formatter.format_code(actual) - - # Assert - assert actual == expected - - -def test_create_view_api_classes_unit_procedure_query( - unit_procedure_api_generator: APIGenerator, code_formatter: CodeFormatter -): - # Arrange - expected = EquipmentSDKFiles.unit_procedure_query.read_text() - - # Act - actual = unit_procedure_api_generator.generate_api_query_file(EQUIPMENT_UNIT_SDK.client_name) - actual = code_formatter.format_code(actual) - - # Assert - assert actual == expected - - -def test_create_view_api_classes_unit_procedure_work_units( - unit_procedure_api_generator: APIGenerator, code_formatter: CodeFormatter -): - # Arrange - expected = EquipmentSDKFiles.unit_procedure_work_units.read_text() - - # Act - - actual_by_file_name = { - filename: content - for filename, content in unit_procedure_api_generator.generate_edge_api_files(EQUIPMENT_UNIT_SDK.client_name) - } - actual = actual_by_file_name[EquipmentSDKFiles.unit_procedure_work_units.stem] - actual = code_formatter.format_code(actual) - - # Assert - assert actual == expected - - -def test_generate_data_class_init_file(multi_api_generator: MultiAPIGenerator, code_formatter: CodeFormatter): - # Arrange - expected = EquipmentSDKFiles.data_init.read_text() - - # Act - actual = multi_api_generator.generate_data_classes_init_file() - actual = code_formatter.format_code(actual) - - # Assert - assert actual == expected - - -def test_create_api_client(sdk_generator: SDKGenerator, code_formatter: CodeFormatter): - # Arrange - expected = EquipmentSDKFiles.client.read_text() - - # Act - actual = sdk_generator._generate_api_client_file() - actual = code_formatter.format_code(actual) - - # Assert - assert actual == expected, "\n".join(difflib.unified_diff(expected.splitlines(), actual.splitlines()))