industrial-model 0.1.6__tar.gz → 0.1.8__tar.gz

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.
Files changed (40) hide show
  1. {industrial_model-0.1.6 → industrial_model-0.1.8}/PKG-INFO +1 -1
  2. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/cognite_adapters/__init__.py +48 -3
  3. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/cognite_adapters/query_result_mapper.py +23 -23
  4. industrial_model-0.1.8/industrial_model/cognite_adapters/utils.py +160 -0
  5. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/models/__init__.py +2 -1
  6. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/models/schemas.py +28 -0
  7. {industrial_model-0.1.6 → industrial_model-0.1.8}/pyproject.toml +1 -1
  8. {industrial_model-0.1.6 → industrial_model-0.1.8}/uv.lock +1 -1
  9. industrial_model-0.1.6/industrial_model/cognite_adapters/utils.py +0 -33
  10. {industrial_model-0.1.6 → industrial_model-0.1.8}/.gitignore +0 -0
  11. {industrial_model-0.1.6 → industrial_model-0.1.8}/.python-version +0 -0
  12. {industrial_model-0.1.6 → industrial_model-0.1.8}/.vscode/settings.json +0 -0
  13. {industrial_model-0.1.6 → industrial_model-0.1.8}/README.md +0 -0
  14. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/__init__.py +0 -0
  15. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/cognite_adapters/filter_mapper.py +0 -0
  16. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/cognite_adapters/optimizer.py +0 -0
  17. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/cognite_adapters/query_mapper.py +0 -0
  18. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/cognite_adapters/sort_mapper.py +0 -0
  19. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/cognite_adapters/view_mapper.py +0 -0
  20. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/config.py +0 -0
  21. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/constants.py +0 -0
  22. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/engines/__init__.py +0 -0
  23. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/engines/async_engine.py +0 -0
  24. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/engines/engine.py +0 -0
  25. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/models/base.py +0 -0
  26. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/models/entities.py +0 -0
  27. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/py.typed +0 -0
  28. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/queries/__init__.py +0 -0
  29. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/queries/models.py +0 -0
  30. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/queries/params.py +0 -0
  31. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/statements/__init__.py +0 -0
  32. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/statements/expressions.py +0 -0
  33. {industrial_model-0.1.6 → industrial_model-0.1.8}/industrial_model/utils.py +0 -0
  34. {industrial_model-0.1.6 → industrial_model-0.1.8}/scripts/build.sh +0 -0
  35. {industrial_model-0.1.6 → industrial_model-0.1.8}/scripts/format.sh +0 -0
  36. {industrial_model-0.1.6 → industrial_model-0.1.8}/scripts/lint.sh +0 -0
  37. {industrial_model-0.1.6 → industrial_model-0.1.8}/tests/__init__.py +0 -0
  38. {industrial_model-0.1.6 → industrial_model-0.1.8}/tests/cognite-sdk-config.yaml +0 -0
  39. {industrial_model-0.1.6 → industrial_model-0.1.8}/tests/hubs.py +0 -0
  40. {industrial_model-0.1.6 → industrial_model-0.1.8}/tests/tests_adapter.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: industrial-model
3
- Version: 0.1.6
3
+ Version: 0.1.8
4
4
  Summary: Industrial Model ORM
5
5
  Author-email: Lucas Alves <lucasrosaalves@gmail.com>
6
6
  Classifier: Programming Language :: Python
@@ -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,14 @@ class QueryResultMapper:
79
70
 
80
71
  return entry.get("space", ""), entry.get("externalId", "")
81
72
 
73
+ visited: set[tuple[str, str]] = set()
82
74
  result: defaultdict[tuple[str, str], list[Node]] = defaultdict(list)
83
- for node in nodes:
75
+ for node in query_result[key]:
76
+ identify = (node.space, node.external_id)
77
+ if not isinstance(node, Node) or identify in visited:
78
+ continue
79
+
80
+ visited.add(identify)
84
81
  properties = node.properties.get(view_id, {})
85
82
  node_id = get_node_id(node)
86
83
  for mapping_key, (values, is_list) in mappings.items():
@@ -108,7 +105,10 @@ class QueryResultMapper:
108
105
  return dict(result)
109
106
 
110
107
  def _get_property_mappings(
111
- self, key: str, view: View, query_result: QueryResult
108
+ self,
109
+ key: str,
110
+ view: View,
111
+ query_result: dict[str, list[Node | Edge]],
112
112
  ) -> dict[str, tuple[dict[tuple[str, str], list[Node]], bool]]:
113
113
  mappings: dict[
114
114
  str, tuple[dict[tuple[str, str], list[Node]], bool]
@@ -168,7 +168,7 @@ class QueryResultMapper:
168
168
  self,
169
169
  key: str,
170
170
  view: View,
171
- query_result: QueryResult,
171
+ query_result: dict[str, list[Node | Edge]],
172
172
  edge_direction: EDGE_DIRECTION,
173
173
  ) -> dict[tuple[str, str], list[Node]] | None:
174
174
  edge_key = f"{key}{NESTED_SEP}{EDGE_MARKER}"
@@ -179,14 +179,14 @@ class QueryResultMapper:
179
179
  if not nodes:
180
180
  return None
181
181
 
182
+ visited: set[tuple[str, str]] = set()
182
183
  result: defaultdict[tuple[str, str], list[Node]] = defaultdict(list)
184
+ for edge in query_result[edge_key]:
185
+ identify = (edge.space, edge.external_id)
186
+ if not isinstance(edge, Edge) or identify in visited:
187
+ continue
183
188
 
184
- edges = (
185
- edge
186
- for edge in query_result.get_edges(edge_key)
187
- if isinstance(edge, Edge)
188
- )
189
- for edge in edges:
189
+ visited.add(identify)
190
190
  entry_key, node_key = (
191
191
  (
192
192
  edge.end_node.as_tuple(),
@@ -0,0 +1,160 @@
1
+ from typing import Literal
2
+
3
+ from cognite.client.data_classes.data_modeling import (
4
+ Edge,
5
+ Node,
6
+ NodeListWithCursor,
7
+ View,
8
+ ViewId,
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
+ )
22
+
23
+ from industrial_model.constants import MAX_LIMIT
24
+ from industrial_model.models import (
25
+ TViewInstance,
26
+ get_parent_and_children_nodes,
27
+ )
28
+
29
+ NODE_PROPERTIES = {"externalId", "space", "createdTime", "deletedTime"}
30
+ INTANCE_TYPE = Literal["node", "edge"]
31
+
32
+
33
+ def get_property_ref(
34
+ property: str, view: View | ViewId, instance_type: INTANCE_TYPE = "node"
35
+ ) -> tuple[str, str, str] | tuple[str, str]:
36
+ return (
37
+ view.as_property_ref(property)
38
+ if property not in NODE_PROPERTIES
39
+ else (instance_type, property)
40
+ )
41
+
42
+
43
+ def get_cognite_instance_ids(
44
+ instance_ids: list[TViewInstance],
45
+ ) -> list[dict[str, str]]:
46
+ return [
47
+ get_cognite_instance_id(instance_id) for instance_id in instance_ids
48
+ ]
49
+
50
+
51
+ def get_cognite_instance_id(instance_id: TViewInstance) -> dict[str, str]:
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
  [project]
2
2
  name = "industrial-model"
3
- version = "0.1.6"
3
+ version = "0.1.8"
4
4
  description = "Industrial Model ORM"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -215,7 +215,7 @@ wheels = [
215
215
 
216
216
  [[package]]
217
217
  name = "industrial-model"
218
- version = "0.1.6"
218
+ version = "0.1.8"
219
219
  source = { editable = "." }
220
220
  dependencies = [
221
221
  { name = "anyio" },
@@ -1,33 +0,0 @@
1
- from typing import Literal
2
-
3
- from cognite.client.data_classes.data_modeling import (
4
- View,
5
- ViewId,
6
- )
7
-
8
- from industrial_model.models import TViewInstance
9
-
10
- NODE_PROPERTIES = {"externalId", "space", "createdTime", "deletedTime"}
11
- INTANCE_TYPE = Literal["node", "edge"]
12
-
13
-
14
- def get_property_ref(
15
- property: str, view: View | ViewId, instance_type: INTANCE_TYPE = "node"
16
- ) -> tuple[str, str, str] | tuple[str, str]:
17
- return (
18
- view.as_property_ref(property)
19
- if property not in NODE_PROPERTIES
20
- else (instance_type, property)
21
- )
22
-
23
-
24
- def get_cognite_instance_ids(
25
- instance_ids: list[TViewInstance],
26
- ) -> list[dict[str, str]]:
27
- return [
28
- get_cognite_instance_id(instance_id) for instance_id in instance_ids
29
- ]
30
-
31
-
32
- def get_cognite_instance_id(instance_id: TViewInstance) -> dict[str, str]:
33
- return {"space": instance_id.space, "externalId": instance_id.external_id}