Writing Instances: Upsert¶
We assume that you have generated a SDK for the Windmill
model and have a client ready to go.
The SDK generated by SDK
supports nested upsert
from windmill import WindmillClient
client = WindmillClient.from_toml("config.toml")
Constructing new Windmill¶
When constructing a new Windmill we need to use the generated data classes from pygen
.
We can import this as follows:
from cognite.client.data_classes import TimeSeries
from windmill.data_classes import WindmillWrite, BladeWrite, NacelleWrite, RotorWrite
The location of the data classes is determined by the parameter top_level_package
which is set when you generate the SDK,
either using the generate_sdk_notebook
(simplified wrapper around) or generate_sdk
. If you don't set it, it will be
default set to the external_id of the data model converted to snake_case. For this example, the external_id=Windmill
thus the top_level_package = windmill
.
Lets construct a new windmill with TimeSeries. Note the example below is not complete (some TimeSeries and components are missing), but is kept short to make it easier to grasp
new_windmill = WindmillWrite(
external_id="windmill:demo",
capacity=10.0,
windfarm="Fornebu",
name="Windmill ATH",
rotor=RotorWrite(
external_id="windmill:demo:rotor",
rotor_speed_controller=TimeSeries(external_id="windmill:demo:rotor:speed_controller"),
rpm_low_speed_shaft=TimeSeries(external_id="windmill:demo:rotor:rpm_low_speed_shaft"),
),
nacelle=NacelleWrite(
external_id="windmill:demo:nacelle",
acc_from_back_side_x=TimeSeries(external_id="windmill:demo:acc_from_back_side_x"),
acc_from_back_side_y=TimeSeries(external_id="windmill:demo:acc_from_back_side_y"),
acc_from_back_side_z=TimeSeries(external_id="windmill:demo:acc_from_back_side_z"),
),
blades=[
BladeWrite(
external_id="windmill:demo:blade1",
is_damaged=False,
name="Blade 1",
),
BladeWrite(
external_id="windmill:demo:blade2",
is_damaged=False,
name="Blade 2",
),
BladeWrite(
external_id="windmill:demo:blade3",
is_damaged=True,
name="Blade 3",
),
]
)
When writing nested data we can specify edges either with an external id for the end node, or another data class.
The advangage of using a nested data class is that we can express edges without being explicit. In the example above, we are able to express that the blades Blade 1-3
are connected to the windmill windmill ATH
and that the windmill ATH
is also linked to a nacelle and rotor.
Inspecting Resources to create¶
We can inspect the nodes, edges and other resources that will be created by using the .to_instances_write
on the new windmill object.
resources = new_windmill.to_instances_write()
len(resources.nodes), len(resources.edges), len(resources.time_series)
(6, 3, 5)
resources.nodes
space | instance_type | external_id | sources | |
---|---|---|---|---|
0 | windmill-instances | node | windmill:demo | [{'properties': {'capacity': 10.0, 'nacelle': ... |
1 | windmill-instances | node | windmill:demo:blade1 | [{'properties': {'is_damaged': False, 'name': ... |
2 | windmill-instances | node | windmill:demo:blade2 | [{'properties': {'is_damaged': False, 'name': ... |
3 | windmill-instances | node | windmill:demo:blade3 | [{'properties': {'is_damaged': True, 'name': '... |
4 | windmill-instances | node | windmill:demo:nacelle | [{'properties': {'acc_from_back_side_x': 'wind... |
5 | windmill-instances | node | windmill:demo:rotor | [{'properties': {'rotor_speed_controller': 'wi... |
resources.edges
space | instance_type | external_id | type | start_node | end_node | |
---|---|---|---|---|---|---|
0 | windmill-instances | edge | windmill:demo:windmill:demo:blade1 | {'space': 'power-models', 'external_id': 'Wind... | {'space': 'windmill-instances', 'external_id':... | {'space': 'windmill-instances', 'external_id':... |
1 | windmill-instances | edge | windmill:demo:windmill:demo:blade2 | {'space': 'power-models', 'external_id': 'Wind... | {'space': 'windmill-instances', 'external_id':... | {'space': 'windmill-instances', 'external_id':... |
2 | windmill-instances | edge | windmill:demo:windmill:demo:blade3 | {'space': 'power-models', 'external_id': 'Wind... | {'space': 'windmill-instances', 'external_id':... | {'space': 'windmill-instances', 'external_id':... |
resources.time_series
external_id | |
---|---|
0 | windmill:demo:acc_from_back_side_x |
1 | windmill:demo:acc_from_back_side_y |
2 | windmill:demo:acc_from_back_side_z |
3 | windmill:demo:rotor:speed_controller |
4 | windmill:demo:rotor:rpm_low_speed_shaft |
Creating new Windmill¶
Optinal Reading: Why client.upsert
and not client.windmill.upsert
?
In contrast from other methods, the .upsert
method is on the client
instead of the individual API class. So instead of client.windmill.upsert
, we use client.upsert
.
The reason for this is that the new_windmill
we created above is enhanced by pygen
with all the information needed to write it correctly to our data model. This means that all .upsert
methods are the same, this is in contrast to methods such as .list
and .retrieve
which are specialized for each data type.
Furthermore, the reason for not duplicating the .upsert
methods on each API class (client.windmill.upsert
, client.blade.upsert
, and so on) is that encourages an anti-pattern (bad practice), in which nodes and edges are created in small batches. It is much more efficient to create all nodes and edges in as few batches as possible.
created = client.upsert(new_windmill)
Note that the call above created 6 nodes, 3 edges and 5 time series:
created.nodes
space | instance_type | external_id | version | was_modified | last_updated_time | created_time | |
---|---|---|---|---|---|---|---|
0 | windmill-instances | node | windmill:demo | 1 | False | 2024-03-09 07:46:38.476 | 2024-03-09 07:46:38.476 |
1 | windmill-instances | node | windmill:demo:blade1 | 1 | False | 2024-03-09 07:46:38.476 | 2024-03-09 07:46:38.476 |
2 | windmill-instances | node | windmill:demo:blade2 | 1 | False | 2024-03-09 07:46:38.476 | 2024-03-09 07:46:38.476 |
3 | windmill-instances | node | windmill:demo:blade3 | 1 | False | 2024-03-09 07:46:38.476 | 2024-03-09 07:46:38.476 |
4 | windmill-instances | node | windmill:demo:nacelle | 2 | False | 2024-03-09 07:46:38.476 | 2024-03-09 07:46:38.476 |
5 | windmill-instances | node | windmill:demo:rotor | 2 | False | 2024-03-09 07:46:38.476 | 2024-03-09 07:46:38.476 |
created.edges
space | instance_type | external_id | version | was_modified | last_updated_time | created_time | |
---|---|---|---|---|---|---|---|
0 | windmill-instances | edge | windmill:demo:windmill:demo:blade1 | 1 | False | 2024-03-09 07:46:38.476 | 2024-03-09 07:46:38.476 |
1 | windmill-instances | edge | windmill:demo:windmill:demo:blade2 | 1 | False | 2024-03-09 07:46:38.476 | 2024-03-09 07:46:38.476 |
2 | windmill-instances | edge | windmill:demo:windmill:demo:blade3 | 1 | False | 2024-03-09 07:46:38.476 | 2024-03-09 07:46:38.476 |
created.time_series
external_id | is_string | metadata | is_step | security_categories | id | created_time | last_updated_time | |
---|---|---|---|---|---|---|---|---|
0 | windmill:demo:acc_from_back_side_x | False | {} | False | [] | 603352752860776 | 2024-03-09 07:46:38.795 | 2024-03-09 07:46:38.795 |
1 | windmill:demo:acc_from_back_side_y | False | {} | False | [] | 8897475002466037 | 2024-03-09 07:46:38.795 | 2024-03-09 07:46:38.795 |
2 | windmill:demo:acc_from_back_side_z | False | {} | False | [] | 6612912285568599 | 2024-03-09 07:46:38.795 | 2024-03-09 07:46:38.795 |
3 | windmill:demo:rotor:speed_controller | False | {} | False | [] | 2092859219187419 | 2024-03-09 07:46:38.795 | 2024-03-09 07:46:38.795 |
4 | windmill:demo:rotor:rpm_low_speed_shaft | False | {} | False | [] | 6022217811754461 | 2024-03-09 07:46:38.795 | 2024-03-09 07:46:38.795 |
We can inspect the newly created windmill by calling retrieve with the external id
client.windmill.retrieve(new_windmill.external_id)
value | |
---|---|
space | windmill-instances |
external_id | windmill:demo |
data_record | {'version': 1, 'last_updated_time': 2024-03-09... |
node_type | None |
blades | [windmill:demo:blade1, windmill:demo:blade2, w... |
capacity | 10.0 |
metmast | None |
nacelle | windmill:demo:nacelle |
name | Windmill ATH |
rotor | windmill:demo:rotor |
windfarm | Fornebu |
Upsert Parameters replace
, write_none
, and allow_version_increase
¶
The upsert method have several parameters that control how the upsert call should be done. In this section, we will go through each of these flags.
Parameter: replace
¶
The replace
flag decide what to do if the item we are upserting already exists. If replace
is set to True
all properties of the existing item will be replaced by the properties set in the upsert call,
and the properties not included will be to null. If replace
is set to False
, then only the properties included in the upsert call will be updated.
Let's demonstrate this by creating a new blade and update it. We start by creating a new blade and call upsert on it.
A blade has two properties: name
and is_damaged
. In addition, it has edges to the sensor_positions
connected to the blade. In this example, we will focus on the two properties, which are both nullable, meaning that they are optional.
from windmill.data_classes import BladeWrite
new_blade = BladeWrite(
external_id="windmill:demo:blade4",
name="Demo Blade",
)
created_blade = client.upsert(new_blade)
created_blade.nodes
space | instance_type | external_id | version | was_modified | last_updated_time | created_time | |
---|---|---|---|---|---|---|---|
0 | windmill-instances | node | windmill:demo:blade4 | 1 | True | 2024-03-09 08:05:30.667 | 2024-03-09 08:05:30.667 |
retrieved_blade = client.blade.retrieve(new_blade.external_id)
retrieved_blade
value | |
---|---|
space | windmill-instances |
external_id | windmill:demo:blade4 |
data_record | {'version': 1, 'last_updated_time': 2024-03-09... |
node_type | None |
is_damaged | None |
name | Demo Blade |
sensor_positions | None |
We can see that the is_damaged
property is not set, while we have the name
property set. We will now update the blade with the is_damaged
property set to True
.
updated_blade = BladeWrite(
external_id="windmill:demo:blade4",
is_damaged=True,
)
update = client.upsert(updated_blade, replace=False)
update.nodes
space | instance_type | external_id | version | was_modified | last_updated_time | created_time | |
---|---|---|---|---|---|---|---|
0 | windmill-instances | node | windmill:demo:blade4 | 2 | True | 2024-03-09 08:06:02.273 | 2024-03-09 08:05:30.667 |
retrieved_blade = client.blade.retrieve(new_blade.external_id)
retrieved_blade
value | |
---|---|
space | windmill-instances |
external_id | windmill:demo:blade4 |
data_record | {'version': 2, 'last_updated_time': 2024-03-09... |
node_type | None |
is_damaged | True |
name | Demo Blade |
sensor_positions | None |
We see that the blade property is_damaged
is now set to True
, while the name
property is unchanged. This is because we set replace
to False
. If we set replace
to True
, then the name
property would be set to null
. Let's create a new update were we set the is_damaged
property to False
and use replace
set to True
.
blade_update2 = BladeWrite(
external_id="windmill:demo:blade4",
is_damaged=False,
)
update2 = client.upsert(blade_update2, replace=True)
update2.nodes
space | instance_type | external_id | version | was_modified | last_updated_time | created_time | |
---|---|---|---|---|---|---|---|
0 | windmill-instances | node | windmill:demo:blade4 | 3 | True | 2024-03-09 08:08:21.675 | 2024-03-09 08:05:30.667 |
retrieved_blade = client.blade.retrieve(new_blade.external_id)
retrieved_blade
value | |
---|---|
space | windmill-instances |
external_id | windmill:demo:blade4 |
data_record | {'version': 3, 'last_updated_time': 2024-03-09... |
node_type | None |
is_damaged | False |
name | None |
sensor_positions | None |
Notice that the name
property is now set to null
as we set replace
to True
.
# Cleanup
client.delete("windmill:demo:blade4")
InstancesDeleteResult(nodes=[NodeId(space='windmill-instances', external_id='windmill:demo:blade4')], edges=[])
Parameter: write_none
¶
By default, when calling .upsert
properties which are set to None
are not sent to the server. This is because we interpret None
as "skip this property". If you want to set a property to null
you can use the write_none
flag.
Let's demonstrate this by creating a new blade with the is_damaged
property set to True
, and then update it to be not set.
from windmill.data_classes import BladeWrite
new_blade = BladeWrite(
external_id="windmill:demo:write_none",
name="Demo Blade",
is_damaged=True
)
client.upsert(new_blade).nodes
space | instance_type | external_id | version | was_modified | last_updated_time | created_time | |
---|---|---|---|---|---|---|---|
0 | windmill-instances | node | windmill:demo:write_none | 1 | True | 2024-03-09 08:14:36.354 | 2024-03-09 08:14:36.354 |
retrieved_blade = client.blade.retrieve(new_blade.external_id)
retrieved_blade
value | |
---|---|
space | windmill-instances |
external_id | windmill:demo:write_none |
data_record | {'version': 1, 'last_updated_time': 2024-03-09... |
node_type | None |
is_damaged | True |
name | Demo Blade |
sensor_positions | None |
updated_blade = BladeWrite(
external_id="windmill:demo:write_none",
is_damaged=None,
)
client.upsert(updated_blade, write_none=False).nodes
retrieved_blade = client.blade.retrieve(new_blade.external_id)
retrieved_blade
value | |
---|---|
space | windmill-instances |
external_id | windmill:demo:write_none |
data_record | {'version': 1, 'last_updated_time': 2024-03-09... |
node_type | None |
is_damaged | True |
name | Demo Blade |
sensor_positions | None |
We see that when we set the is_damaged
property to None
it is not sent to the server. If we set write_none
to True
then the is_damaged
property would be set to null
.
client.upsert(updated_blade, write_none=True).nodes
space | instance_type | external_id | version | was_modified | last_updated_time | created_time | |
---|---|---|---|---|---|---|---|
0 | windmill-instances | node | windmill:demo:write_none | 2 | True | 2024-03-09 08:16:21.394 | 2024-03-09 08:14:36.354 |
retrieved_blade = client.blade.retrieve(new_blade.external_id)
retrieved_blade
value | |
---|---|
space | windmill-instances |
external_id | windmill:demo:write_none |
data_record | {'version': 2, 'last_updated_time': 2024-03-09... |
node_type | None |
is_damaged | None |
name | None |
sensor_positions | None |
Notice that the is_damaged
property is now set to null
as well as the name
property as now all properties are explicitly set to null
in the upsert call.
# Cleanup
client.delete("windmill:demo:write_none")
InstancesDeleteResult(nodes=[NodeId(space='windmill-instances', external_id='windmill:demo:write_none')], edges=[])
Parameter: allow_version_increase
¶
If you notice in the last examples, that when updating the blade, the version of the returning node is increasing. This is because each time we do a change to the blade node it is registered and the version is increased. You can control this behavior by setting the existing_version
property in data_record
of the blade node. Let's demonstrate with an example
from windmill.data_classes import BladeWrite, DataRecordWrite
new_blade = BladeWrite(
external_id="windmill:demo:allow_version_increase",
name="Demo Blade",
is_damaged=True
)
client.upsert(new_blade).nodes
space | instance_type | external_id | version | was_modified | last_updated_time | created_time | |
---|---|---|---|---|---|---|---|
0 | windmill-instances | node | windmill:demo:allow_version_increase | 1 | True | 2024-03-09 08:43:35.316 | 2024-03-09 08:43:35.316 |
We see that we have version 1 of the blade. If we want to avoid overwriting this blade by accident, we can set the existing_version
property to lower than the version we want to avoid overwriting in the data_record
of the blade node. This will be 0
in this case.
new_blade2 = BladeWrite(
external_id="windmill:demo:allow_version_increase",
name="Demo Blade",
is_damaged=False,
data_record=DataRecordWrite(existing_version=0)
)
client.upsert(new_blade2).nodes
--------------------------------------------------------------------------- CogniteAPIError Traceback (most recent call last) Cell In[122], line 1 ----> 1 client.upsert(new_blade2).nodes File ~\Projects\internal\pygen\examples\windmill\_api_client.py:116, in WindmillClient.upsert(self, items, replace, write_none, allow_version_increase) 107 for item in items: 108 instances.extend( 109 item._to_instances_write( 110 cache, (...) 114 ) 115 ) --> 116 result = self._client.data_modeling.instances.apply( 117 nodes=instances.nodes, 118 edges=instances.edges, 119 auto_create_start_nodes=True, 120 auto_create_end_nodes=True, 121 replace=replace, 122 ) 123 time_series = [] 124 if instances.time_series: File ~\AppData\Local\pypoetry\Cache\virtualenvs\cognite-pygen-9ATJrLbx-py3.9\lib\site-packages\cognite\client\_api\data_modeling\instances.py:654, in InstancesAPI.apply(self, nodes, edges, auto_create_start_nodes, auto_create_end_nodes, auto_create_direct_relations, skip_on_version_conflict, replace) 651 edges = edges or [] 652 edges = edges if isinstance(edges, Sequence) else [edges] --> 654 res = self._create_multiple( 655 items=cast(Sequence[WriteableCogniteResource], (*nodes, *edges)), 656 list_cls=_NodeOrEdgeApplyResultList, 657 resource_cls=_NodeOrEdgeApplyResultAdapter, # type: ignore[type-var] 658 extra_body_fields=other_parameters, 659 input_resource_cls=_NodeOrEdgeApplyAdapter, # type: ignore[arg-type] 660 executor=ConcurrencySettings.get_data_modeling_executor(), 661 ) 662 return InstancesApplyResult( 663 nodes=NodeApplyResultList([item for item in res if isinstance(item, NodeApplyResult)]), 664 edges=EdgeApplyResultList([item for item in res if isinstance(item, EdgeApplyResult)]), 665 ) File ~\AppData\Local\pypoetry\Cache\virtualenvs\cognite-pygen-9ATJrLbx-py3.9\lib\site-packages\cognite\client\_api_client.py:837, in APIClient._create_multiple(self, items, list_cls, resource_cls, resource_path, params, headers, extra_body_fields, limit, input_resource_cls, executor, api_subversion) 834 return dumped 835 return el --> 837 summary.raise_compound_exception_if_failed_tasks( 838 task_unwrap_fn=lambda task: task[1]["items"], 839 task_list_element_unwrap_fn=unwrap_element, 840 str_format_element_fn=str_format_element, 841 ) 842 created_resources = summary.joined_results(lambda res: res.json()["items"]) 844 if single_item: File ~\AppData\Local\pypoetry\Cache\virtualenvs\cognite-pygen-9ATJrLbx-py3.9\lib\site-packages\cognite\client\utils\_concurrency.py:73, in TasksSummary.raise_compound_exception_if_failed_tasks(self, task_unwrap_fn, task_list_element_unwrap_fn, str_format_element_fn) 71 task_lists = dict(successful=successful, failed=failed, unknown=unknown, skipped=skipped) 72 if self.unknown_error: ---> 73 self._raise_basic_api_error(str_format_element_fn, **task_lists) 74 if self.not_found_error: 75 self._raise_not_found_error(str_format_element_fn, **task_lists) File ~\AppData\Local\pypoetry\Cache\virtualenvs\cognite-pygen-9ATJrLbx-py3.9\lib\site-packages\cognite\client\utils\_concurrency.py:99, in TasksSummary._raise_basic_api_error(self, unwrap_fn, **task_lists) 97 def _raise_basic_api_error(self, unwrap_fn: Callable, **task_lists: list) -> NoReturn: 98 if isinstance(self.unknown_error, CogniteAPIError) and (task_lists["failed"] or task_lists["unknown"]): ---> 99 raise CogniteAPIError( 100 message=self.unknown_error.message, 101 code=self.unknown_error.code, 102 x_request_id=self.unknown_error.x_request_id, 103 missing=self.missing, 104 duplicated=self.duplicated, 105 extra=self.unknown_error.extra, 106 unwrap_fn=unwrap_fn, 107 **task_lists, 108 ) 109 raise self.unknown_error CogniteAPIError: A version conflict caused the ingest to fail. | code: 409 | X-Request-ID: b32dc882-b07e-9e64-b2cd-b1125526b19a The API Failed to process some items. Successful (2xx): [] Unknown (5xx): [] Failed (4xx): [windmill-instances:windmill:demo:allow_version_increase, ...] Additional error info: { "isAutoRetryable": false }
This can cause problems when we want to migrate data from one project to another, or from one data model to another. It is a common pattern that we use the pygen
generated SDK to retrieve from one project and then use the .as_write
method to turn the retrieved read format of a node into the write format. We want to ensure we always will overwrite the nodes in the new project. Then, we can use the allow_version_increase
flag to ensure that we always overwrite the all the nodes and edges we are writing will have set existing_version
to None
which will ensure that we always overwrite the nodes and edges.
retrieved_blade = client.blade.retrieve(new_blade.external_id)
retrieved_blade
value | |
---|---|
space | windmill-instances |
external_id | windmill:demo:allow_version_increase |
data_record | {'version': 1, 'last_updated_time': 2024-03-09... |
node_type | None |
is_damaged | True |
name | Demo Blade |
sensor_positions | None |
client.upsert(BladeWrite(external_id="windmill:demo:allow_version_increase", name="Updated", is_damaged=True)).nodes
space | instance_type | external_id | version | was_modified | last_updated_time | created_time | |
---|---|---|---|---|---|---|---|
0 | windmill-instances | node | windmill:demo:allow_version_increase | 2 | True | 2024-03-09 08:44:50.490 | 2024-03-09 08:43:35.316 |
writeable_blade = retrieved_blade.as_write()
writeable_blade
value | |
---|---|
space | windmill-instances |
external_id | windmill:demo:allow_version_increase |
data_record | {'existing_version': 1} |
node_type | None |
is_damaged | True |
name | Demo Blade |
sensor_positions | [] |
client.upsert(writeable_blade).nodes
--------------------------------------------------------------------------- CogniteAPIError Traceback (most recent call last) Cell In[130], line 1 ----> 1 client.upsert(writeable_blade).nodes File ~\Projects\internal\pygen\examples\windmill\_api_client.py:116, in WindmillClient.upsert(self, items, replace, write_none, allow_version_increase) 107 for item in items: 108 instances.extend( 109 item._to_instances_write( 110 cache, (...) 114 ) 115 ) --> 116 result = self._client.data_modeling.instances.apply( 117 nodes=instances.nodes, 118 edges=instances.edges, 119 auto_create_start_nodes=True, 120 auto_create_end_nodes=True, 121 replace=replace, 122 ) 123 time_series = [] 124 if instances.time_series: File ~\AppData\Local\pypoetry\Cache\virtualenvs\cognite-pygen-9ATJrLbx-py3.9\lib\site-packages\cognite\client\_api\data_modeling\instances.py:654, in InstancesAPI.apply(self, nodes, edges, auto_create_start_nodes, auto_create_end_nodes, auto_create_direct_relations, skip_on_version_conflict, replace) 651 edges = edges or [] 652 edges = edges if isinstance(edges, Sequence) else [edges] --> 654 res = self._create_multiple( 655 items=cast(Sequence[WriteableCogniteResource], (*nodes, *edges)), 656 list_cls=_NodeOrEdgeApplyResultList, 657 resource_cls=_NodeOrEdgeApplyResultAdapter, # type: ignore[type-var] 658 extra_body_fields=other_parameters, 659 input_resource_cls=_NodeOrEdgeApplyAdapter, # type: ignore[arg-type] 660 executor=ConcurrencySettings.get_data_modeling_executor(), 661 ) 662 return InstancesApplyResult( 663 nodes=NodeApplyResultList([item for item in res if isinstance(item, NodeApplyResult)]), 664 edges=EdgeApplyResultList([item for item in res if isinstance(item, EdgeApplyResult)]), 665 ) File ~\AppData\Local\pypoetry\Cache\virtualenvs\cognite-pygen-9ATJrLbx-py3.9\lib\site-packages\cognite\client\_api_client.py:837, in APIClient._create_multiple(self, items, list_cls, resource_cls, resource_path, params, headers, extra_body_fields, limit, input_resource_cls, executor, api_subversion) 834 return dumped 835 return el --> 837 summary.raise_compound_exception_if_failed_tasks( 838 task_unwrap_fn=lambda task: task[1]["items"], 839 task_list_element_unwrap_fn=unwrap_element, 840 str_format_element_fn=str_format_element, 841 ) 842 created_resources = summary.joined_results(lambda res: res.json()["items"]) 844 if single_item: File ~\AppData\Local\pypoetry\Cache\virtualenvs\cognite-pygen-9ATJrLbx-py3.9\lib\site-packages\cognite\client\utils\_concurrency.py:73, in TasksSummary.raise_compound_exception_if_failed_tasks(self, task_unwrap_fn, task_list_element_unwrap_fn, str_format_element_fn) 71 task_lists = dict(successful=successful, failed=failed, unknown=unknown, skipped=skipped) 72 if self.unknown_error: ---> 73 self._raise_basic_api_error(str_format_element_fn, **task_lists) 74 if self.not_found_error: 75 self._raise_not_found_error(str_format_element_fn, **task_lists) File ~\AppData\Local\pypoetry\Cache\virtualenvs\cognite-pygen-9ATJrLbx-py3.9\lib\site-packages\cognite\client\utils\_concurrency.py:99, in TasksSummary._raise_basic_api_error(self, unwrap_fn, **task_lists) 97 def _raise_basic_api_error(self, unwrap_fn: Callable, **task_lists: list) -> NoReturn: 98 if isinstance(self.unknown_error, CogniteAPIError) and (task_lists["failed"] or task_lists["unknown"]): ---> 99 raise CogniteAPIError( 100 message=self.unknown_error.message, 101 code=self.unknown_error.code, 102 x_request_id=self.unknown_error.x_request_id, 103 missing=self.missing, 104 duplicated=self.duplicated, 105 extra=self.unknown_error.extra, 106 unwrap_fn=unwrap_fn, 107 **task_lists, 108 ) 109 raise self.unknown_error CogniteAPIError: A version conflict caused the ingest to fail. | code: 409 | X-Request-ID: 21a8a106-1469-9551-af54-3ad177aaf677 The API Failed to process some items. Successful (2xx): [] Unknown (5xx): [] Failed (4xx): [windmill-instances:windmill:demo:allow_version_increase, ...] Additional error info: { "isAutoRetryable": false }
client.upsert(writeable_blade, allow_version_increase=True).nodes
space | instance_type | external_id | version | was_modified | last_updated_time | created_time | |
---|---|---|---|---|---|---|---|
0 | windmill-instances | node | windmill:demo:allow_version_increase | 3 | True | 2024-03-09 08:45:12.212 | 2024-03-09 08:43:35.316 |
We see that the allow_version_increase
flag ensures that we always overwrite the nodes and edges.
# Cleanup
client.delete("windmill:demo:allow_version_increase")
InstancesDeleteResult(nodes=[NodeId(space='windmill-instances', external_id='windmill:demo:allow_version_increase')], edges=[])
Creating from JSON
Format¶
See the quick start guide data population for an example of creating instances from JSON
.
Deleting Instances¶
You can delete by passing and external ID or a sequence of external id to the delete method.
We can delete the newly created windmill
client.windmill.list()
external_id | node_type | blades | capacity | metmast | nacelle | name | rotor | windfarm | |
---|---|---|---|---|---|---|---|---|---|
0 | hornsea_1_mill_3 | None | [blade:1, blade:2, blade:3] | 7.0 | None | nacelle:1 | hornsea_1_mill_3 | rotor:1 | Hornsea 1 |
1 | hornsea_1_mill_2 | None | [blade:4, blade:5, blade:6] | 7.0 | None | nacelle:2 | hornsea_1_mill_2 | rotor:2 | Hornsea 1 |
2 | hornsea_1_mill_1 | None | [blade:7, blade:8, blade:9] | 7.0 | None | nacelle:3 | hornsea_1_mill_1 | rotor:3 | Hornsea 1 |
3 | hornsea_1_mill_4 | None | [blade:10, blade:11, blade:12] | 7.0 | None | nacelle:4 | hornsea_1_mill_4 | rotor:4 | Hornsea 1 |
4 | hornsea_1_mill_5 | None | [blade:13, blade:14, blade:15] | 7.0 | None | nacelle:5 | hornsea_1_mill_5 | rotor:5 | Hornsea 1 |
5 | windmill:demo | None | [windmill:demo:blade1, windmill:demo:blade2, w... | 10.0 | None | windmill:demo:nacelle | Windmill ATH | windmill:demo:rotor | Fornebu |
Same as .upsert
, the delete method is located on the client and not the API class.
client.delete("windmill:demo")
InstancesDeleteResult(nodes=[NodeId(space='windmill-instances', external_id='windmill:demo')], edges=[])
After the delete call the new windmill is gone
client.windmill.list()
external_id | node_type | blades | capacity | metmast | nacelle | name | rotor | windfarm | |
---|---|---|---|---|---|---|---|---|---|
0 | hornsea_1_mill_3 | None | [blade:1, blade:2, blade:3] | 7.0 | None | nacelle:1 | hornsea_1_mill_3 | rotor:1 | Hornsea 1 |
1 | hornsea_1_mill_2 | None | [blade:4, blade:5, blade:6] | 7.0 | None | nacelle:2 | hornsea_1_mill_2 | rotor:2 | Hornsea 1 |
2 | hornsea_1_mill_1 | None | [blade:7, blade:8, blade:9] | 7.0 | None | nacelle:3 | hornsea_1_mill_1 | rotor:3 | Hornsea 1 |
3 | hornsea_1_mill_4 | None | [blade:10, blade:11, blade:12] | 7.0 | None | nacelle:4 | hornsea_1_mill_4 | rotor:4 | Hornsea 1 |
4 | hornsea_1_mill_5 | None | [blade:13, blade:14, blade:15] | 7.0 | None | nacelle:5 | hornsea_1_mill_5 | rotor:5 | Hornsea 1 |