industrial-model 0.1.30__tar.gz → 0.1.32__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 (34) hide show
  1. {industrial_model-0.1.30 → industrial_model-0.1.32}/PKG-INFO +1 -1
  2. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/cognite_adapters/filter_mapper.py +32 -5
  3. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/cognite_adapters/query_mapper.py +16 -2
  4. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/cognite_adapters/query_result_mapper.py +34 -8
  5. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/models/entities.py +2 -2
  6. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/statements/__init__.py +22 -0
  7. {industrial_model-0.1.30 → industrial_model-0.1.32}/pyproject.toml +1 -1
  8. {industrial_model-0.1.30 → industrial_model-0.1.32}/.gitignore +0 -0
  9. {industrial_model-0.1.30 → industrial_model-0.1.32}/README.md +0 -0
  10. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/__init__.py +0 -0
  11. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/cognite_adapters/__init__.py +0 -0
  12. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/cognite_adapters/aggregation_mapper.py +0 -0
  13. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/cognite_adapters/models.py +0 -0
  14. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/cognite_adapters/optimizer.py +0 -0
  15. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/cognite_adapters/search_mapper.py +0 -0
  16. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/cognite_adapters/sort_mapper.py +0 -0
  17. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/cognite_adapters/upsert_mapper.py +0 -0
  18. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/cognite_adapters/utils.py +0 -0
  19. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/cognite_adapters/view_mapper.py +0 -0
  20. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/config.py +0 -0
  21. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/constants.py +0 -0
  22. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/engines/__init__.py +0 -0
  23. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/engines/async_engine.py +0 -0
  24. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/engines/engine.py +0 -0
  25. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/models/__init__.py +0 -0
  26. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/models/base.py +0 -0
  27. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/models/schemas.py +0 -0
  28. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/py.typed +0 -0
  29. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/queries/__init__.py +0 -0
  30. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/queries/models.py +0 -0
  31. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/queries/params.py +0 -0
  32. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/queries/utils.py +0 -0
  33. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/statements/expressions.py +0 -0
  34. {industrial_model-0.1.30 → industrial_model-0.1.32}/industrial_model/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: industrial-model
3
- Version: 0.1.30
3
+ Version: 0.1.32
4
4
  Summary: Industrial Model ORM
5
5
  Author-email: Lucas Alves <lucasrosaalves@gmail.com>
6
6
  Classifier: Programming Language :: Python
@@ -2,7 +2,11 @@ from datetime import date, datetime
2
2
  from typing import Any
3
3
 
4
4
  import cognite.client.data_classes.filters as cdf_filters
5
- from cognite.client.data_classes.data_modeling import MappedProperty, View
5
+ from cognite.client.data_classes.data_modeling import (
6
+ EdgeConnection,
7
+ MappedProperty,
8
+ View,
9
+ )
6
10
 
7
11
  from industrial_model.cognite_adapters.utils import get_property_ref
8
12
  from industrial_model.models.entities import InstanceId
@@ -11,6 +15,7 @@ from industrial_model.statements import (
11
15
  Expression,
12
16
  LeafExpression,
13
17
  )
18
+ from industrial_model.statements.expressions import Column
14
19
  from industrial_model.utils import datetime_to_ms_iso_timestamp
15
20
 
16
21
  from .view_mapper import ViewMapper
@@ -26,15 +31,37 @@ class FilterMapper:
26
31
  result: list[cdf_filters.Filter] = []
27
32
  for expression in expressions:
28
33
  if isinstance(expression, BoolExpression):
29
- result.append(self.to_cdf_filter_bool(expression, root_view))
34
+ result.append(self._to_cdf_filter_bool(expression, root_view))
30
35
  elif isinstance(expression, LeafExpression):
31
- result.append(self.to_cdf_filter_leaf(expression, root_view))
36
+ result.append(self._to_cdf_filter_leaf(expression, root_view))
32
37
  else:
33
38
  cls_name = expression.__class__.__name__
34
39
  raise ValueError(f"Expression not implemented {cls_name}")
35
40
  return result
36
41
 
37
- def to_cdf_filter_bool(
42
+ def map_edges(
43
+ self,
44
+ edges_expressions: list[tuple[Column, list[Expression]]],
45
+ root_view: View,
46
+ nested_separator: str,
47
+ ) -> dict[str, list[cdf_filters.Filter]]:
48
+ result_dict: dict[str, list[cdf_filters.Filter]] = {}
49
+
50
+ for column, expressions in edges_expressions:
51
+ view_property = root_view.properties.get(column.property)
52
+ if not isinstance(view_property, EdgeConnection):
53
+ raise ValueError(f"Property {column.property} is not an edge")
54
+
55
+ filters = self.map(
56
+ expressions,
57
+ self._view_mapper.get_view(view_property.source.external_id),
58
+ )
59
+ result_key = root_view.external_id + nested_separator + column.property
60
+ result_dict.setdefault(result_key, []).extend(filters)
61
+
62
+ return result_dict
63
+
64
+ def _to_cdf_filter_bool(
38
65
  self, expression: BoolExpression, root_view: View
39
66
  ) -> cdf_filters.Filter:
40
67
  arguments = self.map(expression.filters, root_view)
@@ -48,7 +75,7 @@ class FilterMapper:
48
75
 
49
76
  raise NotImplementedError(f"Operator {self.operator} not implemented")
50
77
 
51
- def to_cdf_filter_leaf(
78
+ def _to_cdf_filter_leaf(
52
79
  self,
53
80
  expression: LeafExpression,
54
81
  root_view: View,
@@ -58,8 +58,12 @@ class QueryMapper:
58
58
 
59
59
  relations = get_schema_properties(statement.entity, NESTED_SEP, root_node)
60
60
 
61
+ edge_filters = self._filter_mapper.map_edges(
62
+ statement.where_edge_clauses, root_view, NESTED_SEP
63
+ )
64
+
61
65
  properties = self._include_statements(
62
- root_node, root_view, relations, with_, select_
66
+ root_node, root_view, relations, edge_filters, with_, select_
63
67
  )
64
68
 
65
69
  select_[root_node] = self._get_select(root_view_id, properties)
@@ -80,6 +84,7 @@ class QueryMapper:
80
84
  key: str,
81
85
  view: View,
82
86
  relations_to_include: list[str] | None,
87
+ edge_filters: dict[str, list[filters.Filter]],
83
88
  with_: dict[str, ResultSetExpression],
84
89
  select_: dict[str, Select],
85
90
  ) -> list[str]:
@@ -101,6 +106,7 @@ class QueryMapper:
101
106
  property_key,
102
107
  self._view_mapper.get_view(property.source.external_id),
103
108
  relations_to_include,
109
+ edge_filters,
104
110
  with_,
105
111
  select_,
106
112
  )
@@ -121,6 +127,7 @@ class QueryMapper:
121
127
  property_key,
122
128
  self._view_mapper.get_view(property.source.external_id),
123
129
  relations_to_include,
130
+ edge_filters,
124
131
  with_,
125
132
  select_,
126
133
  )
@@ -139,10 +146,16 @@ class QueryMapper:
139
146
  elif isinstance(property, EdgeConnection) and property.source:
140
147
  edge_property_key = f"{property_key}{NESTED_SEP}{EDGE_MARKER}"
141
148
 
149
+ edge_filter = edge_filters.get(property_key)
150
+
142
151
  with_[edge_property_key] = EdgeResultSetExpression(
143
152
  from_=key,
144
153
  max_distance=1,
145
- filter=filters.Equals(["edge", "type"], property.type.dump()),
154
+ filter=filters.Equals(
155
+ ["edge", "type"],
156
+ property.type.dump(),
157
+ ),
158
+ node_filter=filters.And(*edge_filter) if edge_filter else None,
146
159
  direction=property.direction,
147
160
  limit=MAX_LIMIT,
148
161
  )
@@ -157,6 +170,7 @@ class QueryMapper:
157
170
  property_key,
158
171
  self._view_mapper.get_view(property.source.external_id),
159
172
  relations_to_include,
173
+ edge_filters,
160
174
  with_,
161
175
  select_,
162
176
  )
@@ -10,6 +10,10 @@ from cognite.client.data_classes.data_modeling import (
10
10
  NodeList,
11
11
  View,
12
12
  )
13
+ from cognite.client.data_classes.data_modeling.data_types import (
14
+ ListablePropertyType,
15
+ )
16
+ from cognite.client.data_classes.data_modeling.instances import PropertyValue
13
17
  from cognite.client.data_classes.data_modeling.views import (
14
18
  MultiReverseDirectRelation,
15
19
  SingleReverseDirectRelation,
@@ -112,13 +116,13 @@ class QueryResultMapper:
112
116
  ):
113
117
  continue
114
118
 
115
- element_key: tuple[str, str] = (
116
- (element.get("space", ""), element.get("externalId", ""))
117
- if isinstance(element, dict)
118
- else (node.space, node.external_id)
119
- )
119
+ element_keys = self._get_element_keys(node, element)
120
120
 
121
- node_entries = mapping_nodes.get(element_key)
121
+ node_entries = [
122
+ item
123
+ for element_key in element_keys
124
+ for item in mapping_nodes.get(element_key, [])
125
+ ]
122
126
  if not node_entries:
123
127
  if mapping_key in properties:
124
128
  properties.pop(mapping_key)
@@ -126,7 +130,11 @@ class QueryResultMapper:
126
130
 
127
131
  entry_data = self.nodes_to_dict(node_entries)
128
132
  properties[mapping_key] = entry_data if is_list else entry_data[0]
129
- edge_entries = mapping_edges.get(element_key)
133
+ edge_entries = [
134
+ item
135
+ for element_key in element_keys
136
+ for item in mapping_edges.get(element_key, [])
137
+ ]
130
138
  if edge_entries:
131
139
  edges_mapping[mapping_key] = self._edges_to_model(edge_entries)
132
140
  properties["_edges"] = edges_mapping
@@ -137,6 +145,21 @@ class QueryResultMapper:
137
145
 
138
146
  return dict(result)
139
147
 
148
+ def _get_element_keys(
149
+ self, node: Node, element: PropertyValue | None
150
+ ) -> list[tuple[str, str]]:
151
+ if isinstance(element, dict):
152
+ return [(element.get("space", ""), element.get("externalId", ""))]
153
+
154
+ if isinstance(element, list):
155
+ return [
156
+ (item.get("space", ""), item.get("externalId", ""))
157
+ for item in element
158
+ if isinstance(item, dict)
159
+ ]
160
+
161
+ return [(node.space, node.external_id)]
162
+
140
163
  def _get_property_mappings(
141
164
  self,
142
165
  key: str,
@@ -159,7 +182,10 @@ class QueryResultMapper:
159
182
  self._view_mapper.get_view(property.source.external_id),
160
183
  query_result,
161
184
  )
162
- is_list = False
185
+ is_list = (
186
+ isinstance(property.type, ListablePropertyType)
187
+ and property.type.is_list
188
+ )
163
189
  connection_type = ConnectionTypeEnum.DIRECT_RELATION
164
190
  elif isinstance(property, SingleReverseDirectRelation) and property.source:
165
191
  nodes = self._map_node_property(
@@ -16,7 +16,7 @@ from industrial_model.statements import Column
16
16
  from .base import DBModelMetaclass, RootModel
17
17
 
18
18
 
19
- class InstanceId(RootModel):
19
+ class InstanceId(RootModel, metaclass=DBModelMetaclass):
20
20
  external_id: str
21
21
  space: str
22
22
 
@@ -51,7 +51,7 @@ class ViewInstanceConfig(TypedDict, total=False):
51
51
  view_code: str | None
52
52
 
53
53
 
54
- class ViewInstance(InstanceId, metaclass=DBModelMetaclass):
54
+ class ViewInstance(InstanceId):
55
55
  view_config: ClassVar[ViewInstanceConfig] = ViewInstanceConfig()
56
56
 
57
57
  _edges: dict[str, list[EdgeContainer]] = PrivateAttr(default_factory=dict)
@@ -60,11 +60,33 @@ class BaseStatement(Generic[T]):
60
60
  @dataclass
61
61
  class Statement(BaseStatement[T]):
62
62
  cursor_: str | None = field(init=False, default=None)
63
+ where_edge_clauses: list[tuple[Column, list[Expression]]] = field(
64
+ init=False, default_factory=list
65
+ )
63
66
 
64
67
  def cursor(self, cursor: str | None) -> Self:
65
68
  self.cursor_ = cursor
66
69
  return self
67
70
 
71
+ def where_edge(
72
+ self, property: str | Column | Any, *expressions: bool | Expression
73
+ ) -> Self:
74
+ if not expressions:
75
+ return self
76
+
77
+ expressions_: list[Expression] = []
78
+ for expression in expressions:
79
+ assert isinstance(expression, Expression)
80
+ expressions_.append(expression)
81
+
82
+ self.where_edge_clauses.append(
83
+ (
84
+ _create_column(property),
85
+ expressions_,
86
+ )
87
+ )
88
+ return self
89
+
68
90
 
69
91
  @dataclass
70
92
  class SearchStatement(BaseStatement[T]):
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "industrial-model"
3
- version = "0.1.30"
3
+ version = "0.1.32"
4
4
  description = "Industrial Model ORM"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"