industrial-model 0.1.12__py3-none-any.whl → 0.1.13__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,9 +1,10 @@
1
1
  from .config import DataModelId
2
2
  from .engines import AsyncEngine, Engine
3
3
  from .models import (
4
- AggregationResult,
4
+ AggregatedViewInstance,
5
5
  InstanceId,
6
6
  PaginatedResult,
7
+ TAggregatedViewInstance,
7
8
  TViewInstance,
8
9
  TWritableViewInstance,
9
10
  ValidationMode,
@@ -15,7 +16,7 @@ from .statements import aggregate, and_, col, not_, or_, select
15
16
 
16
17
  __all__ = [
17
18
  "aggregate",
18
- "AggregationResult",
19
+ "AggregatedViewInstance",
19
20
  "and_",
20
21
  "or_",
21
22
  "col",
@@ -25,6 +26,7 @@ __all__ = [
25
26
  "InstanceId",
26
27
  "TViewInstance",
27
28
  "DataModelId",
29
+ "TAggregatedViewInstance",
28
30
  "TWritableViewInstance",
29
31
  "ValidationMode",
30
32
  "Engine",
@@ -12,7 +12,7 @@ from cognite.client.data_classes.data_modeling.query import (
12
12
 
13
13
  from industrial_model.config import DataModelId
14
14
  from industrial_model.models import (
15
- AggregationResult,
15
+ TAggregatedViewInstance,
16
16
  TViewInstance,
17
17
  TWritableViewInstance,
18
18
  )
@@ -83,8 +83,8 @@ class CogniteAdapter:
83
83
  return data, next_cursor_
84
84
 
85
85
  def aggregate(
86
- self, statement: AggregationStatement[TViewInstance]
87
- ) -> list[AggregationResult]:
86
+ self, statement: AggregationStatement[TAggregatedViewInstance]
87
+ ) -> list[dict[str, Any]]:
88
88
  query = self._aggregation_mapper.map(statement)
89
89
 
90
90
  result = self._cognite_client.data_modeling.instances.aggregate(
@@ -94,16 +94,15 @@ class CogniteAdapter:
94
94
  group_by=query.group_by_columns,
95
95
  limit=query.limit,
96
96
  )
97
-
98
- return [
99
- AggregationResult(
100
- group=item.group,
101
- value=item.aggregates[0].value,
102
- aggregate=statement.aggregate_,
103
- )
104
- for item in result
105
- if item.aggregates and item.aggregates[0].value is not None
106
- ]
97
+ data: list[dict[str, Any]] = []
98
+ for item in result:
99
+ if not item.aggregates or item.aggregates[0].value is None:
100
+ continue
101
+
102
+ entry = item.group if item.group else {}
103
+ entry["value"] = item.aggregates[0].value
104
+ data.append(entry)
105
+ return data
107
106
 
108
107
  def upsert(
109
108
  self, entries: list[TWritableViewInstance], replace: bool = False
@@ -1,12 +1,19 @@
1
1
  from dataclasses import dataclass
2
2
 
3
3
  import cognite.client.data_classes.filters as filters
4
- from cognite.client.data_classes.aggregations import Count, MetricAggregation
4
+ from cognite.client.data_classes.aggregations import (
5
+ Avg,
6
+ Count,
7
+ Max,
8
+ MetricAggregation,
9
+ Min,
10
+ Sum,
11
+ )
5
12
  from cognite.client.data_classes.data_modeling import (
6
13
  View,
7
14
  )
8
15
 
9
- from industrial_model.models import TViewInstance
16
+ from industrial_model.models import TAggregatedViewInstance
10
17
  from industrial_model.statements import AggregationStatement
11
18
 
12
19
  from .filter_mapper import (
@@ -30,7 +37,7 @@ class AggregationMapper:
30
37
  self._filter_mapper = FilterMapper(view_mapper)
31
38
 
32
39
  def map(
33
- self, statement: AggregationStatement[TViewInstance]
40
+ self, statement: AggregationStatement[TAggregatedViewInstance]
34
41
  ) -> AggregationQuery:
35
42
  root_node = statement.entity.get_view_external_id()
36
43
 
@@ -43,12 +50,18 @@ class AggregationMapper:
43
50
  if statement.where_clauses
44
51
  else None
45
52
  )
53
+ metric_aggregation: MetricAggregation | None = None
54
+ if statement.aggregate == "avg":
55
+ metric_aggregation = Avg(statement.aggregation_property.property)
56
+ elif statement.aggregate == "min":
57
+ metric_aggregation = Min(statement.aggregation_property.property)
58
+ elif statement.aggregate == "max":
59
+ metric_aggregation = Max(statement.aggregation_property.property)
60
+ elif statement.aggregate == "sum":
61
+ metric_aggregation = Sum(statement.aggregation_property.property)
62
+ elif statement.aggregate == "count":
63
+ metric_aggregation = Count(statement.aggregation_property.property)
46
64
 
47
- metric_aggregation = (
48
- Count(statement.aggregation_property.property)
49
- if statement.aggregate_ == "count"
50
- else None
51
- )
52
65
  if metric_aggregation is None:
53
66
  raise ValueError(
54
67
  f"Unsupported aggregate function: {statement.aggregate_}"
@@ -57,8 +70,6 @@ class AggregationMapper:
57
70
  view=root_view,
58
71
  metric_aggregation=metric_aggregation,
59
72
  filters=filters_,
60
- group_by_columns=[
61
- column.property for column in statement.group_by_columns
62
- ],
73
+ group_by_columns=statement.entity.get_group_by_fields(),
63
74
  limit=statement.limit_,
64
75
  )
@@ -1,4 +1,4 @@
1
- from datetime import datetime
1
+ from datetime import date, datetime
2
2
 
3
3
  import cognite.client.data_classes.filters as cdf_filters
4
4
  from cognite.client.data_classes.data_modeling import MappedProperty, View
@@ -57,6 +57,9 @@ class FilterMapper:
57
57
  if isinstance(value_, datetime):
58
58
  value_ = datetime_to_ms_iso_timestamp(value_)
59
59
 
60
+ if isinstance(value_, date):
61
+ value_ = value_.strftime("%Y-%m-%d")
62
+
60
63
  if expression.operator == "==":
61
64
  return cdf_filters.Equals(property_ref, value_)
62
65
  elif expression.operator == "in":
@@ -2,8 +2,8 @@ from cognite.client import CogniteClient
2
2
 
3
3
  from industrial_model.config import DataModelId
4
4
  from industrial_model.models import (
5
- AggregationResult,
6
5
  PaginatedResult,
6
+ TAggregatedViewInstance,
7
7
  TViewInstance,
8
8
  TWritableViewInstance,
9
9
  ValidationMode,
@@ -39,8 +39,8 @@ class AsyncEngine:
39
39
  )
40
40
 
41
41
  async def aggregate_async(
42
- self, statement: AggregationStatement[TViewInstance]
43
- ) -> list[AggregationResult]:
42
+ self, statement: AggregationStatement[TAggregatedViewInstance]
43
+ ) -> list[TAggregatedViewInstance]:
44
44
  return await run_async(self._engine.aggregate, statement)
45
45
 
46
46
  async def upsert_async(
@@ -5,9 +5,9 @@ from cognite.client import CogniteClient
5
5
  from industrial_model.cognite_adapters import CogniteAdapter
6
6
  from industrial_model.config import DataModelId
7
7
  from industrial_model.models import (
8
- AggregationResult,
9
8
  EdgeContainer,
10
9
  PaginatedResult,
10
+ TAggregatedViewInstance,
11
11
  TViewInstance,
12
12
  TWritableViewInstance,
13
13
  ValidationMode,
@@ -49,9 +49,11 @@ class Engine:
49
49
  return self._validate_data(statement.entity, data, validation_mode)
50
50
 
51
51
  def aggregate(
52
- self, statement: AggregationStatement[TViewInstance]
53
- ) -> list[AggregationResult]:
54
- return self._cognite_adapter.aggregate(statement)
52
+ self, statement: AggregationStatement[TAggregatedViewInstance]
53
+ ) -> list[TAggregatedViewInstance]:
54
+ data = self._cognite_adapter.aggregate(statement)
55
+
56
+ return [statement.entity.model_validate(item) for item in data]
55
57
 
56
58
  def upsert(
57
59
  self, entries: list[TWritableViewInstance], replace: bool = False
@@ -1,9 +1,10 @@
1
1
  from .base import RootModel
2
2
  from .entities import (
3
- AggregationResult,
3
+ AggregatedViewInstance,
4
4
  EdgeContainer,
5
5
  InstanceId,
6
6
  PaginatedResult,
7
+ TAggregatedViewInstance,
7
8
  TViewInstance,
8
9
  TWritableViewInstance,
9
10
  ValidationMode,
@@ -14,10 +15,11 @@ from .entities import (
14
15
  from .schemas import get_parent_and_children_nodes, get_schema_properties
15
16
 
16
17
  __all__ = [
17
- "AggregationResult",
18
+ "AggregatedViewInstance",
18
19
  "RootModel",
19
20
  "EdgeContainer",
20
21
  "InstanceId",
22
+ "TAggregatedViewInstance",
21
23
  "TViewInstance",
22
24
  "TWritableViewInstance",
23
25
  "ViewInstance",
@@ -44,3 +44,14 @@ class RootModel(BaseModel):
44
44
  populate_by_name=True,
45
45
  from_attributes=True,
46
46
  )
47
+
48
+ def get_field_name(self, field_name_or_alias: str) -> str | None:
49
+ entry = self.__class__.model_fields.get(field_name_or_alias)
50
+ if entry:
51
+ return field_name_or_alias
52
+
53
+ for key, field_info in self.__class__.model_fields.items():
54
+ if field_info.alias == field_name_or_alias:
55
+ return key
56
+
57
+ return None
@@ -56,17 +56,6 @@ class ViewInstance(InstanceId, metaclass=DBModelMetaclass):
56
56
  def get_view_external_id(cls) -> str:
57
57
  return cls.view_config.get("view_external_id") or cls.__name__
58
58
 
59
- def get_field_name(self, field_name_or_alias: str) -> str | None:
60
- entry = self.__class__.model_fields.get(field_name_or_alias)
61
- if entry:
62
- return field_name_or_alias
63
-
64
- for key, field_info in self.__class__.model_fields.items():
65
- if field_info.alias == field_name_or_alias:
66
- return key
67
-
68
- return None
69
-
70
59
 
71
60
  class WritableViewInstance(ViewInstance):
72
61
  @abstractmethod
@@ -78,10 +67,33 @@ class WritableViewInstance(ViewInstance):
78
67
  )
79
68
 
80
69
 
70
+ class AggregatedViewInstance(RootModel, metaclass=DBModelMetaclass):
71
+ view_config: ClassVar[ViewInstanceConfig] = ViewInstanceConfig()
72
+
73
+ value: float
74
+
75
+ @classmethod
76
+ def get_view_external_id(cls) -> str:
77
+ return cls.view_config.get("view_external_id") or cls.__name__
78
+
79
+ @classmethod
80
+ def get_group_by_fields(cls) -> list[str]:
81
+ group_by_fields: set[str] = set()
82
+ for key, field_info in cls.model_fields.items():
83
+ if key == "value":
84
+ continue
85
+ group_by_fields.add(field_info.alias or key)
86
+
87
+ return list(group_by_fields)
88
+
89
+
81
90
  TViewInstance = TypeVar("TViewInstance", bound=ViewInstance)
82
91
  TWritableViewInstance = TypeVar(
83
92
  "TWritableViewInstance", bound=WritableViewInstance
84
93
  )
94
+ TAggregatedViewInstance = TypeVar(
95
+ "TAggregatedViewInstance", bound=AggregatedViewInstance
96
+ )
85
97
 
86
98
 
87
99
  class PaginatedResult(RootModel, Generic[TViewInstance]):
@@ -90,10 +102,4 @@ class PaginatedResult(RootModel, Generic[TViewInstance]):
90
102
  next_cursor: str | None
91
103
 
92
104
 
93
- class AggregationResult(RootModel):
94
- group: dict[str, str | int | float | bool | InstanceId] | None
95
- value: float
96
- aggregate: str
97
-
98
-
99
105
  ValidationMode = Literal["raiseOnError", "ignoreOnError"]
@@ -63,30 +63,24 @@ class Statement(Generic[T]):
63
63
  return self
64
64
 
65
65
 
66
+ AggregateTypes = Literal["count", "avg", "min", "max", "sum"]
67
+
68
+
66
69
  @dataclass
67
70
  class AggregationStatement(Generic[T]):
68
- aggregate_: Literal["count"] = field(init=False, default="count")
71
+ entity: type[T] = field(init=True)
72
+ aggregate: AggregateTypes = field(init=True)
73
+
69
74
  aggregation_property: Column = field(
70
75
  init=False, default=Column("externalId")
71
76
  )
72
-
73
- entity: type[T] = field(init=True)
74
- group_by_columns: list[Column] = field(init=False, default_factory=list)
75
77
  where_clauses: list[Expression] = field(init=False, default_factory=list)
76
78
  limit_: int = field(init=False, default=-1)
77
79
 
78
- def aggregate(self, aggregates: Literal["count"]) -> Self:
79
- self.aggregate_ = aggregates
80
- return self
81
-
82
80
  def aggregate_by(self, property: str | Column | Any) -> Self:
83
81
  self.aggregation_property = _create_column(property)
84
82
  return self
85
83
 
86
- def group_by(self, *property: str | Column | Any) -> Self:
87
- self.group_by_columns.extend(_create_column(p) for p in property)
88
- return self
89
-
90
84
  def where(self, *expressions: bool | Expression) -> Self:
91
85
  for expression in expressions:
92
86
  assert isinstance(expression, Expression)
@@ -102,8 +96,11 @@ def select(entity: type[T]) -> Statement[T]:
102
96
  return Statement(entity)
103
97
 
104
98
 
105
- def aggregate(entity: type[T]) -> AggregationStatement[T]:
106
- return AggregationStatement(entity)
99
+ def aggregate(
100
+ entity: type[T],
101
+ aggregate: AggregateTypes = "count",
102
+ ) -> AggregationStatement[T]:
103
+ return AggregationStatement(entity=entity, aggregate=aggregate)
107
104
 
108
105
 
109
106
  __all__ = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: industrial-model
3
- Version: 0.1.12
3
+ Version: 0.1.13
4
4
  Summary: Industrial Model ORM
5
5
  Author-email: Lucas Alves <lucasrosaalves@gmail.com>
6
6
  Classifier: Programming Language :: Python
@@ -50,14 +50,19 @@ from cognite.client import CogniteClient
50
50
  from pydantic import Field
51
51
 
52
52
  from industrial_model import (
53
+ aggregate,
54
+ AggregatedViewInstance,
53
55
  AsyncEngine,
54
56
  DataModelId,
55
57
  Engine,
58
+ InstanceId,
56
59
  ViewInstance,
57
- select,
58
- col,
60
+ ViewInstanceConfig,
61
+ WritableViewInstance,
59
62
  and_,
63
+ col,
60
64
  or_,
65
+ select,
61
66
  )
62
67
 
63
68
  # Define entities (view instances)
@@ -184,12 +189,10 @@ all_results = engine.query_all_pages(statement)
184
189
 
185
190
  # 7. Data Ingestion
186
191
 
187
- from industrial_model import (
188
- WritableViewInstance # necessary for data ingestion
189
- )
190
-
191
-
192
192
  class WritablePerson(WritableViewInstance):
193
+ view_config = ViewInstanceConfig(
194
+ view_external_id="Person" # Maps this class to the 'Person' view
195
+ )
193
196
  name: str
194
197
  lives_in: InstanceId
195
198
  cars: list[InstanceId]
@@ -203,10 +206,7 @@ class WritablePerson(WritableViewInstance):
203
206
  space=self.space,
204
207
  )
205
208
 
206
- statement = select(WritablePerson).where(
207
- (WritablePerson.external_id == "Lucas")
208
- )
209
-
209
+ statement = select(WritablePerson).where(WritablePerson.external_id == "Lucas")
210
210
  person = engine.query_all_pages(statement)[0]
211
211
 
212
212
  person.lives_in = InstanceId(external_id="br", space="data-space")
@@ -214,7 +214,21 @@ person.cars.clear() # Gonna remove all car edges from the person
214
214
 
215
215
  engine.upsert([person])
216
216
 
217
- ```
218
217
 
219
218
 
219
+ # 8. Aggregate
220
+
221
+ class AggregateByNamePerson(AggregatedViewInstance):
222
+ view_config = ViewInstanceConfig(
223
+ view_external_id="Person" # Maps this class to the 'Person' view
224
+ )
225
+
226
+ name: str # group by name
227
+
228
+
229
+ aggregate_result = engine.aggregate(aggregate(AggregateByNamePerson, "count"))
230
+
231
+
232
+ ```
233
+
220
234
  ---
@@ -1,11 +1,11 @@
1
- industrial_model/__init__.py,sha256=13TekQQlw7euJr1pIncV1R1Z8cWtvCPPx5e6s0NY_C0,711
1
+ industrial_model/__init__.py,sha256=JyQbC-Qz4yI2lH0vfD1QXx2EJwe73frLIsXU_gfbMIU,781
2
2
  industrial_model/config.py,sha256=bNEfjgD2vth4RPGKDwjGmh0edleQ95g__UJGHV4vix0,520
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=oh4AxwxXaWgIC2uolkCbvkgo0ququHB6yAPVIXy45Ts,663
6
- industrial_model/cognite_adapters/__init__.py,sha256=a7xPiSkuWkVxvsmVfFD-lOzdOc-tTNo8JmbyL5tC-68,5461
7
- industrial_model/cognite_adapters/aggregation_mapper.py,sha256=wG8ottGilTAJcpf2kQvhvyRmDBpSKiT4esx0P5kmZBU,1845
8
- industrial_model/cognite_adapters/filter_mapper.py,sha256=NqH-OW7_iKFY9POCG8W3KjkwXUgrZP1d_yxDx1J0fXM,3859
6
+ industrial_model/cognite_adapters/__init__.py,sha256=MxX7Z6VntqmP8luG1CzbMBFu_K0ls8iU2diLsrEjLCk,5492
7
+ industrial_model/cognite_adapters/aggregation_mapper.py,sha256=rXt2BZ2jS3afD9d3B2t8aizL9TWnMnqCCNdzTn_XR3Y,2362
8
+ industrial_model/cognite_adapters/filter_mapper.py,sha256=X2BVp305v1Wt00DTZEnCooOG_g6Jo7bkj-NyfRtECAE,3952
9
9
  industrial_model/cognite_adapters/models.py,sha256=2j2IS01uPkQEp9WdVk8seYzEqGcDdWFnpzXhusHB2zk,945
10
10
  industrial_model/cognite_adapters/optimizer.py,sha256=BbbsA3r9ZKJt6mN1wVRZc046ov-vbml9_ESfurTNRUg,2477
11
11
  industrial_model/cognite_adapters/query_mapper.py,sha256=3fEcaLsGjLKIh-g1BbMcffQ6rp99JeCW555iJo8JW44,6260
@@ -15,17 +15,17 @@ industrial_model/cognite_adapters/upsert_mapper.py,sha256=tWEiBJQeeNz1HDu0AoBIfC
15
15
  industrial_model/cognite_adapters/utils.py,sha256=rztCtS10ZTQmXfBv0nLgDiQIMWemhdSFK-SwrbRjVxM,4792
16
16
  industrial_model/cognite_adapters/view_mapper.py,sha256=qvT25gwIgPdAqZf-oQEh0GylUi5KEPwjLNmIl5ISQGA,1474
17
17
  industrial_model/engines/__init__.py,sha256=7aGHrUm2MxIq39vR8h0xu3i1zNOuT9H9U-q4lV3nErQ,102
18
- industrial_model/engines/async_engine.py,sha256=qILOHTBnq8DOwHoGnBQ6qrRh2zv2-0rjBd36hqWXM0w,1523
19
- industrial_model/engines/engine.py,sha256=fSJL0i7TwJsVP_-uNrN_WoxToCpwidIik58KREEw-oQ,3003
20
- industrial_model/models/__init__.py,sha256=klwPCssjGx-0rGi8aBLzP6GWO9kgR7zDAo93el9hQ1I,672
21
- industrial_model/models/base.py,sha256=jbiaICJ0R1mmxXtDqxxlVdq-tTX4RLdqnLTgs9HLm_4,1279
22
- industrial_model/models/entities.py,sha256=xWvkGp0tCuI5wMAG-rfnh8bmsiJXtc7uNEXJ1RJL4GU,2519
18
+ industrial_model/engines/async_engine.py,sha256=XgDKCHh4gi2mPBknp7MM4mRsuA_224NesIZ7xq1YQic,1545
19
+ industrial_model/engines/engine.py,sha256=DaA34P1aL_qvL691OG_-g_yCC-U2ZJr9tF32Pl5vpP4,3098
20
+ industrial_model/models/__init__.py,sha256=AzJ0CyPK5PvUCX45FFtybl13tkukUvk2UAF_90s_LQ8,742
21
+ industrial_model/models/base.py,sha256=iGhDjXqA5ULEQIFHtkMi7WYJl0nQq1wi8_zqOr-Ep78,1649
22
+ industrial_model/models/entities.py,sha256=GZu-Y5ZalhXM8EHTzBe4hCKA2Dryk8J99p_FTVNX2jc,2709
23
23
  industrial_model/models/schemas.py,sha256=LKNPDnUy1jtMyOHDf28En9vThhdzOSswewIcjC_y-6U,4393
24
24
  industrial_model/queries/__init__.py,sha256=7aheTE5qs03rxWm9fmGWptbz_p9OIXXYD8if56cqs18,227
25
25
  industrial_model/queries/models.py,sha256=iiHQ7-cfg0nukEv5PoCx9QPF-w1gVSnoNbXBOK9Mzeo,1185
26
26
  industrial_model/queries/params.py,sha256=ehgCoR5n6E-tkEuoymZ2lkLcSzMaBAx_HnyJ7sWpqz0,964
27
- industrial_model/statements/__init__.py,sha256=Y57G1TJlYxCHg3Zz7ojQoAMVnCMaUCRRWXEm2IcbAWo,3332
27
+ industrial_model/statements/__init__.py,sha256=15LI9POLUdh6i3Zw0Aek0fVFfcTj8P8ZEa1Cli3pgXQ,3095
28
28
  industrial_model/statements/expressions.py,sha256=Sar1cIvy3sYi7tkWJN3ylHlZ252oN2mZJpZ1TX9jN3s,4940
29
- industrial_model-0.1.12.dist-info/METADATA,sha256=NK9n4THFdg-uVxO24RZiB-OOp0TfTQxra-YMHO30VxM,5698
30
- industrial_model-0.1.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
31
- industrial_model-0.1.12.dist-info/RECORD,,
29
+ industrial_model-0.1.13.dist-info/METADATA,sha256=SVjWvzWjDcTnXYVMMs1zIqJTZjadxGrydjjoHVlPo7w,6144
30
+ industrial_model-0.1.13.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
31
+ industrial_model-0.1.13.dist-info/RECORD,,