industrial-model 0.1.6__py3-none-any.whl → 0.1.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,13 @@
1
1
  from typing import Any
2
2
 
3
3
  from cognite.client import CogniteClient
4
+ from cognite.client.data_classes.data_modeling import Edge, Node
5
+ from cognite.client.data_classes.data_modeling.query import (
6
+ Query as CogniteQuery,
7
+ )
8
+ from cognite.client.data_classes.data_modeling.query import (
9
+ QueryResult as CogniteQueryResult,
10
+ )
4
11
 
5
12
  from industrial_model.cognite_adapters.optimizer import QueryOptimizer
6
13
  from industrial_model.config import DataModelId
@@ -11,6 +18,11 @@ from .query_mapper import QueryMapper
11
18
  from .query_result_mapper import (
12
19
  QueryResultMapper,
13
20
  )
21
+ from .utils import (
22
+ append_nodes_and_edges,
23
+ get_query_for_dependencies_pagination,
24
+ map_nodes_and_edges,
25
+ )
14
26
  from .view_mapper import ViewMapper
15
27
 
16
28
 
@@ -42,10 +54,20 @@ class CogniteAdapter:
42
54
  cognite_query
43
55
  )
44
56
 
45
- page_result, next_cursor = self._result_mapper.map_nodes(
46
- view_external_id, query_result
57
+ dependencies_data = self._query_dependencies_pages(
58
+ cognite_query, query_result, view_external_id
59
+ )
60
+
61
+ query_result_data = append_nodes_and_edges(
62
+ map_nodes_and_edges(query_result, cognite_query),
63
+ dependencies_data,
47
64
  )
48
65
 
66
+ page_result = self._result_mapper.map_nodes(
67
+ view_external_id,
68
+ query_result_data,
69
+ )
70
+ next_cursor = query_result.cursors.get(view_external_id)
49
71
  data.extend(page_result)
50
72
 
51
73
  last_page = len(page_result) < statement.limit_ or not next_cursor
@@ -53,4 +75,27 @@ class CogniteAdapter:
53
75
  cognite_query.cursors = {view_external_id: next_cursor_}
54
76
 
55
77
  if not all_pages or last_page:
56
- return data, next_cursor
78
+ return data, next_cursor_
79
+
80
+ def _query_dependencies_pages(
81
+ self,
82
+ cognite_query: CogniteQuery,
83
+ query_result: CogniteQueryResult,
84
+ view_external_id: str,
85
+ ) -> dict[str, list[Node | Edge]] | None:
86
+ new_query = get_query_for_dependencies_pagination(
87
+ cognite_query, query_result, view_external_id
88
+ )
89
+ if not new_query:
90
+ return None
91
+
92
+ new_query_result = self._cognite_client.data_modeling.instances.query(
93
+ new_query
94
+ )
95
+
96
+ result = map_nodes_and_edges(new_query_result, new_query)
97
+
98
+ nested_results = self._query_dependencies_pages(
99
+ new_query, new_query_result, view_external_id
100
+ )
101
+ return append_nodes_and_edges(result, nested_results)
@@ -8,9 +8,6 @@ from cognite.client.data_classes.data_modeling import (
8
8
  Node,
9
9
  View,
10
10
  )
11
- from cognite.client.data_classes.data_modeling.query import (
12
- QueryResult,
13
- )
14
11
  from cognite.client.data_classes.data_modeling.views import (
15
12
  MultiReverseDirectRelation,
16
13
  SingleReverseDirectRelation,
@@ -26,8 +23,8 @@ class QueryResultMapper:
26
23
  self._view_mapper = view_mapper
27
24
 
28
25
  def map_nodes(
29
- self, root_node: str, query_result: QueryResult
30
- ) -> tuple[list[dict[str, Any]], str | None]:
26
+ self, root_node: str, query_result: dict[str, list[Node | Edge]]
27
+ ) -> list[dict[str, Any]]:
31
28
  if root_node not in query_result:
32
29
  raise ValueError(
33
30
  f"{root_node} is not available in the query result"
@@ -37,7 +34,7 @@ class QueryResultMapper:
37
34
 
38
35
  values = self._map_node_property(root_node, root_view, query_result)
39
36
  if not values:
40
- return [], None
37
+ return []
41
38
 
42
39
  data = [
43
40
  node
@@ -45,13 +42,13 @@ class QueryResultMapper:
45
42
  for node in self._nodes_to_dict(nodes)
46
43
  ]
47
44
 
48
- return data, query_result.cursors.get(root_node)
45
+ return data
49
46
 
50
47
  def _map_node_property(
51
48
  self,
52
49
  key: str,
53
50
  view: View,
54
- query_result: QueryResult,
51
+ query_result: dict[str, list[Node | Edge]],
55
52
  result_property_key: str | None = None,
56
53
  ) -> dict[tuple[str, str], list[Node]] | None:
57
54
  if key not in query_result:
@@ -61,12 +58,6 @@ class QueryResultMapper:
61
58
 
62
59
  view_id = view.as_id()
63
60
 
64
- nodes = (
65
- node
66
- for node in query_result.get_nodes(key)
67
- if isinstance(node, Node)
68
- )
69
-
70
61
  def get_node_id(node: Node) -> tuple[str, str]:
71
62
  if not result_property_key:
72
63
  return (node.space, node.external_id)
@@ -79,8 +70,13 @@ class QueryResultMapper:
79
70
 
80
71
  return entry.get("space", ""), entry.get("externalId", "")
81
72
 
73
+ nodes = {
74
+ (node.space, node.external_id): node
75
+ for node in query_result[key]
76
+ if isinstance(node, Node)
77
+ }
82
78
  result: defaultdict[tuple[str, str], list[Node]] = defaultdict(list)
83
- for node in nodes:
79
+ for node in nodes.values():
84
80
  properties = node.properties.get(view_id, {})
85
81
  node_id = get_node_id(node)
86
82
  for mapping_key, (values, is_list) in mappings.items():
@@ -108,7 +104,10 @@ class QueryResultMapper:
108
104
  return dict(result)
109
105
 
110
106
  def _get_property_mappings(
111
- self, key: str, view: View, query_result: QueryResult
107
+ self,
108
+ key: str,
109
+ view: View,
110
+ query_result: dict[str, list[Node | Edge]],
112
111
  ) -> dict[str, tuple[dict[tuple[str, str], list[Node]], bool]]:
113
112
  mappings: dict[
114
113
  str, tuple[dict[tuple[str, str], list[Node]], bool]
@@ -168,7 +167,7 @@ class QueryResultMapper:
168
167
  self,
169
168
  key: str,
170
169
  view: View,
171
- query_result: QueryResult,
170
+ query_result: dict[str, list[Node | Edge]],
172
171
  edge_direction: EDGE_DIRECTION,
173
172
  ) -> dict[tuple[str, str], list[Node]] | None:
174
173
  edge_key = f"{key}{NESTED_SEP}{EDGE_MARKER}"
@@ -181,12 +180,12 @@ class QueryResultMapper:
181
180
 
182
181
  result: defaultdict[tuple[str, str], list[Node]] = defaultdict(list)
183
182
 
184
- edges = (
185
- edge
186
- for edge in query_result.get_edges(edge_key)
183
+ edges = {
184
+ (edge.space, edge.external_id): edge
185
+ for edge in query_result[edge_key]
187
186
  if isinstance(edge, Edge)
188
- )
189
- for edge in edges:
187
+ }
188
+ for edge in edges.values():
190
189
  entry_key, node_key = (
191
190
  (
192
191
  edge.end_node.as_tuple(),
@@ -1,11 +1,30 @@
1
1
  from typing import Literal
2
2
 
3
3
  from cognite.client.data_classes.data_modeling import (
4
+ Edge,
5
+ Node,
6
+ NodeListWithCursor,
4
7
  View,
5
8
  ViewId,
6
9
  )
10
+ from cognite.client.data_classes.data_modeling.query import (
11
+ Query as CogniteQuery,
12
+ )
13
+ from cognite.client.data_classes.data_modeling.query import (
14
+ QueryResult as CogniteQueryResult,
15
+ )
16
+ from cognite.client.data_classes.data_modeling.query import (
17
+ ResultSetExpression,
18
+ )
19
+ from cognite.client.data_classes.data_modeling.query import (
20
+ Select as CogniteSelect,
21
+ )
7
22
 
8
- from industrial_model.models import TViewInstance
23
+ from industrial_model.constants import MAX_LIMIT
24
+ from industrial_model.models import (
25
+ TViewInstance,
26
+ get_parent_and_children_nodes,
27
+ )
9
28
 
10
29
  NODE_PROPERTIES = {"externalId", "space", "createdTime", "deletedTime"}
11
30
  INTANCE_TYPE = Literal["node", "edge"]
@@ -31,3 +50,111 @@ def get_cognite_instance_ids(
31
50
 
32
51
  def get_cognite_instance_id(instance_id: TViewInstance) -> dict[str, str]:
33
52
  return {"space": instance_id.space, "externalId": instance_id.external_id}
53
+
54
+
55
+ def get_query_for_dependencies_pagination(
56
+ query: CogniteQuery,
57
+ query_result: CogniteQueryResult,
58
+ view_external_id: str,
59
+ ) -> CogniteQuery | None:
60
+ nodes_parent, nodes_children = get_parent_and_children_nodes(
61
+ set(query_result.cursors.keys())
62
+ )
63
+
64
+ leaf_cursors = _get_leaf_cursors(
65
+ query_result, view_external_id, nodes_parent, nodes_children
66
+ )
67
+
68
+ if not leaf_cursors:
69
+ return None
70
+
71
+ return _create_query(query, nodes_parent, nodes_children, leaf_cursors)
72
+
73
+
74
+ def map_nodes_and_edges(
75
+ query_result: CogniteQueryResult, query: CogniteQuery
76
+ ) -> dict[str, list[Node | Edge]]:
77
+ result_schema = query.instance_type_by_result_expression()
78
+
79
+ return {
80
+ key: query_result.get_nodes(key).data
81
+ if entity_type is NodeListWithCursor
82
+ else query_result.get_edges(key).data
83
+ for key, entity_type in result_schema.items()
84
+ if entity_type is not None
85
+ }
86
+
87
+
88
+ def append_nodes_and_edges(
89
+ initial_dataset: dict[str, list[Node | Edge]],
90
+ additional_dataset: dict[str, list[Node | Edge]] | None,
91
+ ) -> dict[str, list[Node | Edge]]:
92
+ if not additional_dataset:
93
+ return initial_dataset
94
+ for key, additional_data in additional_dataset.items():
95
+ if key not in initial_dataset:
96
+ initial_dataset[key] = []
97
+ initial_dataset[key].extend(additional_data)
98
+ return initial_dataset
99
+
100
+
101
+ def _create_query(
102
+ previous_query: CogniteQuery,
103
+ nodes_parent: dict[str, set[str]],
104
+ nodes_children: dict[str, set[str]],
105
+ leaf_cursors: dict[str, str],
106
+ ) -> CogniteQuery:
107
+ with_: dict[str, ResultSetExpression] = {}
108
+ select_: dict[str, CogniteSelect] = {}
109
+ final_cursors: dict[str, str | None] = {}
110
+
111
+ for cursor_key, cursor_value in leaf_cursors.items():
112
+ children = nodes_children[cursor_key]
113
+ parent = nodes_parent[cursor_key]
114
+
115
+ valid_keys = parent.union(children)
116
+ valid_keys.add(cursor_key)
117
+
118
+ with_.update(
119
+ {k: v for k, v in previous_query.with_.items() if k in valid_keys}
120
+ )
121
+ select_.update(
122
+ {k: v for k, v in previous_query.select.items() if k in valid_keys}
123
+ )
124
+
125
+ final_cursors.update(
126
+ {k: v for k, v in previous_query.cursors.items() if k in parent}
127
+ )
128
+ final_cursors[cursor_key] = cursor_value
129
+
130
+ return CogniteQuery(with_=with_, select=select_, cursors=final_cursors)
131
+
132
+
133
+ def _get_leaf_cursors(
134
+ query_result: CogniteQueryResult,
135
+ view_external_id: str,
136
+ nodes_parent: dict[str, set[str]],
137
+ nodes_children: dict[str, set[str]],
138
+ ) -> dict[str, str]:
139
+ target_cursors: dict[str, str] = {}
140
+ for cursor_key, cursor_value in query_result.cursors.items():
141
+ if (
142
+ cursor_key == view_external_id
143
+ or not cursor_value
144
+ or len(query_result[cursor_key]) != MAX_LIMIT
145
+ ):
146
+ continue
147
+
148
+ children = nodes_children[cursor_key]
149
+
150
+ target_cursors_keys = target_cursors.keys()
151
+ if len(children.intersection(target_cursors_keys)) > 0:
152
+ continue
153
+
154
+ parent = nodes_parent[cursor_key]
155
+ cursor_to_remove = parent.intersection(target_cursors_keys)
156
+ for cursor_key_to_remove in cursor_to_remove:
157
+ target_cursors.pop(cursor_key_to_remove)
158
+
159
+ target_cursors[cursor_key] = cursor_value
160
+ return target_cursors
@@ -7,7 +7,7 @@ from .entities import (
7
7
  ViewInstance,
8
8
  ViewInstanceConfig,
9
9
  )
10
- from .schemas import get_schema_properties
10
+ from .schemas import get_parent_and_children_nodes, get_schema_properties
11
11
 
12
12
  __all__ = [
13
13
  "RootModel",
@@ -18,4 +18,5 @@ __all__ = [
18
18
  "PaginatedResult",
19
19
  "ViewInstanceConfig",
20
20
  "get_schema_properties",
21
+ "get_parent_and_children_nodes",
21
22
  ]
@@ -12,6 +12,8 @@ from typing import (
12
12
 
13
13
  from pydantic import BaseModel
14
14
 
15
+ from industrial_model.constants import EDGE_MARKER, NESTED_SEP
16
+
15
17
  TBaseModel = TypeVar("TBaseModel", bound=BaseModel)
16
18
 
17
19
 
@@ -28,6 +30,32 @@ def get_schema_properties(
28
30
  return [f"{prefix}{nested_separator}{key}" for key in keys]
29
31
 
30
32
 
33
+ def get_parent_and_children_nodes(
34
+ keys: set[str],
35
+ ) -> tuple[dict[str, set[str]], dict[str, set[str]]]:
36
+ nodes_parent: dict[str, set[str]] = {}
37
+ nodes_children: defaultdict[str, set[str]] = defaultdict(set)
38
+ for key in keys:
39
+ key_parts = key.split(NESTED_SEP)
40
+ parent_paths = {
41
+ NESTED_SEP.join(key_parts[:i])
42
+ for i in range(len(key_parts) - 1, 0, -1)
43
+ }
44
+
45
+ valid_paths: set[str] = set()
46
+ for parent_path in parent_paths:
47
+ parent_path_edge_marker = parent_path + NESTED_SEP + EDGE_MARKER
48
+ if parent_path_edge_marker in keys:
49
+ valid_paths.add(parent_path_edge_marker)
50
+ nodes_children[parent_path_edge_marker].add(key)
51
+ if parent_path in keys:
52
+ valid_paths.add(parent_path)
53
+ nodes_children[parent_path].add(key)
54
+
55
+ nodes_parent[key] = valid_paths
56
+ return nodes_parent, dict(nodes_children)
57
+
58
+
31
59
  def _get_type_properties(
32
60
  cls: type[BaseModel], visited_count: defaultdict[type, int] | None = None
33
61
  ) -> dict[str, Any] | None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: industrial-model
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Industrial Model ORM
5
5
  Author-email: Lucas Alves <lucasrosaalves@gmail.com>
6
6
  Classifier: Programming Language :: Python
@@ -3,26 +3,26 @@ industrial_model/config.py,sha256=wzSKVKHIdGlxCRtu0PIeL3SLYvnQBR4Py46CcETxa8U,23
3
3
  industrial_model/constants.py,sha256=wtFdxkR9gojqekwXeyVCof6VUkJYuqjjgAgVVKrcxaw,450
4
4
  industrial_model/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  industrial_model/utils.py,sha256=OuuAkIqu-551axsZJPuqHu5d9iReGcfY9ROBFtHIwkY,933
6
- industrial_model/cognite_adapters/__init__.py,sha256=9N4oT7tWZx5wjVr7q64Ef5FLT75Omep48xGYv1PhxV8,1917
6
+ industrial_model/cognite_adapters/__init__.py,sha256=SjI-cO8tsZpd1uBSyP6nt3WvWbMHnWzf3v1X5oDTNvw,3436
7
7
  industrial_model/cognite_adapters/filter_mapper.py,sha256=NqH-OW7_iKFY9POCG8W3KjkwXUgrZP1d_yxDx1J0fXM,3859
8
8
  industrial_model/cognite_adapters/optimizer.py,sha256=G8I07jJ9tarE5GZXXUSpTMKUei6ptV-cudJSLsoykX4,2223
9
9
  industrial_model/cognite_adapters/query_mapper.py,sha256=3fEcaLsGjLKIh-g1BbMcffQ6rp99JeCW555iJo8JW44,6260
10
- industrial_model/cognite_adapters/query_result_mapper.py,sha256=jUreXqsaLnq7hp6T_JZQNRtL8TL8hoLSFvD4SkJ4TEE,7002
10
+ industrial_model/cognite_adapters/query_result_mapper.py,sha256=NowXR501CywuuX5X_UdVVq7cus9JcmsnOBuTsJ774qU,7013
11
11
  industrial_model/cognite_adapters/sort_mapper.py,sha256=RJUAYlZGXoYzK0PwX63cibRF_L-MUq9g2ZsC2EeNIF4,696
12
- industrial_model/cognite_adapters/utils.py,sha256=nBYHK8697L2yGEAcyKjQV9NEZQmWy_aSGq_iMl7gofM,907
12
+ industrial_model/cognite_adapters/utils.py,sha256=rztCtS10ZTQmXfBv0nLgDiQIMWemhdSFK-SwrbRjVxM,4792
13
13
  industrial_model/cognite_adapters/view_mapper.py,sha256=lnv64KezSQTAr6XdcExa8d92GU5Ll9K9HfMcQGzhX6k,488
14
14
  industrial_model/engines/__init__.py,sha256=7aGHrUm2MxIq39vR8h0xu3i1zNOuT9H9U-q4lV3nErQ,102
15
15
  industrial_model/engines/async_engine.py,sha256=3Y4Ao14CDJJAZFbgTX9I6LOJUxfe8nwOOukGVeiw914,1070
16
16
  industrial_model/engines/engine.py,sha256=8-wfktRLZpad_V7_F1Vz7vtjE_bQhga_jXXjuwSgfrE,1897
17
- industrial_model/models/__init__.py,sha256=YcNLjpYnxdU_wTpn1n0AhwlZ-XHWYqX6FzfbuevG3jo,406
17
+ industrial_model/models/__init__.py,sha256=q2cI-WutiT-gmRlwC5CnacWqtQo0oSIr7Ohn9hK9jes,474
18
18
  industrial_model/models/base.py,sha256=jbiaICJ0R1mmxXtDqxxlVdq-tTX4RLdqnLTgs9HLm_4,1279
19
19
  industrial_model/models/entities.py,sha256=NrFV_a3ZP6Y4b1M2PFl_746qcCsexsyO2cuB0snZhkw,1319
20
- industrial_model/models/schemas.py,sha256=FzreMCR2TGX89-tMRpCNdcYqHj1u9QlocRLNwE_J2rA,3366
20
+ industrial_model/models/schemas.py,sha256=LKNPDnUy1jtMyOHDf28En9vThhdzOSswewIcjC_y-6U,4393
21
21
  industrial_model/queries/__init__.py,sha256=7aheTE5qs03rxWm9fmGWptbz_p9OIXXYD8if56cqs18,227
22
22
  industrial_model/queries/models.py,sha256=iiHQ7-cfg0nukEv5PoCx9QPF-w1gVSnoNbXBOK9Mzeo,1185
23
23
  industrial_model/queries/params.py,sha256=ehgCoR5n6E-tkEuoymZ2lkLcSzMaBAx_HnyJ7sWpqz0,964
24
24
  industrial_model/statements/__init__.py,sha256=9fD-qpNXIkrjoahxC_R6hS4DKSVelehimvRPKbpYfA0,1775
25
25
  industrial_model/statements/expressions.py,sha256=Sar1cIvy3sYi7tkWJN3ylHlZ252oN2mZJpZ1TX9jN3s,4940
26
- industrial_model-0.1.6.dist-info/METADATA,sha256=M-BS6_DyB4pIwwqPlvBp4_7XCqFDBbdver_EXwZ8WMo,4806
27
- industrial_model-0.1.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
28
- industrial_model-0.1.6.dist-info/RECORD,,
26
+ industrial_model-0.1.7.dist-info/METADATA,sha256=Fk1SRRo3Yg3kqBJN2-H1ZICh1Cz00E03ZEVlAksGQFA,4806
27
+ industrial_model-0.1.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
28
+ industrial_model-0.1.7.dist-info/RECORD,,