industrial-model 0.1.10__py3-none-any.whl → 0.1.11__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,7 @@
1
1
  from .config import DataModelId
2
2
  from .engines import AsyncEngine, Engine
3
3
  from .models import (
4
+ AggregationResult,
4
5
  InstanceId,
5
6
  PaginatedResult,
6
7
  TViewInstance,
@@ -10,9 +11,11 @@ from .models import (
10
11
  ViewInstanceConfig,
11
12
  WritableViewInstance,
12
13
  )
13
- from .statements import and_, col, not_, or_, select
14
+ from .statements import aggregate, and_, col, not_, or_, select
14
15
 
15
16
  __all__ = [
17
+ "aggregate",
18
+ "AggregationResult",
16
19
  "and_",
17
20
  "or_",
18
21
  "col",
@@ -11,9 +11,14 @@ from cognite.client.data_classes.data_modeling.query import (
11
11
  )
12
12
 
13
13
  from industrial_model.config import DataModelId
14
- from industrial_model.models import TViewInstance, TWritableViewInstance
15
- from industrial_model.statements import Statement
14
+ from industrial_model.models import (
15
+ AggregationResult,
16
+ TViewInstance,
17
+ TWritableViewInstance,
18
+ )
19
+ from industrial_model.statements import AggregationStatement, Statement
16
20
 
21
+ from .aggregation_mapper import AggregationMapper
17
22
  from .optimizer import QueryOptimizer
18
23
  from .query_mapper import QueryMapper
19
24
  from .query_result_mapper import (
@@ -34,15 +39,12 @@ class CogniteAdapter:
34
39
  ):
35
40
  self._cognite_client = cognite_client
36
41
 
37
- dm = cognite_client.data_modeling.data_models.retrieve(
38
- ids=data_model_id.as_tuple(),
39
- inline_views=True,
40
- ).latest_version()
41
- view_mapper = ViewMapper(dm.views)
42
+ view_mapper = ViewMapper(cognite_client, data_model_id)
43
+ self._optmizer = QueryOptimizer(cognite_client, data_model_id)
42
44
  self._query_mapper = QueryMapper(view_mapper)
43
45
  self._result_mapper = QueryResultMapper(view_mapper)
44
46
  self._upsert_mapper = UpsertMapper(view_mapper)
45
- self._optmizer = QueryOptimizer(cognite_client)
47
+ self._aggregation_mapper = AggregationMapper(view_mapper)
46
48
 
47
49
  def query(
48
50
  self, statement: Statement[TViewInstance], all_pages: bool
@@ -80,6 +82,29 @@ class CogniteAdapter:
80
82
  if not all_pages or last_page:
81
83
  return data, next_cursor_
82
84
 
85
+ def aggregate(
86
+ self, statement: AggregationStatement[TViewInstance]
87
+ ) -> list[AggregationResult]:
88
+ query = self._aggregation_mapper.map(statement)
89
+
90
+ result = self._cognite_client.data_modeling.instances.aggregate(
91
+ view=query.view.as_id(),
92
+ aggregates=query.metric_aggregation,
93
+ filter=query.filters,
94
+ group_by=query.group_by_columns,
95
+ limit=query.limit,
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
+ ]
107
+
83
108
  def upsert(
84
109
  self, entries: list[TWritableViewInstance], replace: bool = False
85
110
  ) -> None:
@@ -0,0 +1,64 @@
1
+ from dataclasses import dataclass
2
+
3
+ import cognite.client.data_classes.filters as filters
4
+ from cognite.client.data_classes.aggregations import Count, MetricAggregation
5
+ from cognite.client.data_classes.data_modeling import (
6
+ View,
7
+ )
8
+
9
+ from industrial_model.models import TViewInstance
10
+ from industrial_model.statements import AggregationStatement
11
+
12
+ from .filter_mapper import (
13
+ FilterMapper,
14
+ )
15
+ from .view_mapper import ViewMapper
16
+
17
+
18
+ @dataclass
19
+ class AggregationQuery:
20
+ view: View
21
+ metric_aggregation: MetricAggregation
22
+ filters: filters.Filter | None
23
+ group_by_columns: list[str]
24
+ limit: int
25
+
26
+
27
+ class AggregationMapper:
28
+ def __init__(self, view_mapper: ViewMapper):
29
+ self._view_mapper = view_mapper
30
+ self._filter_mapper = FilterMapper(view_mapper)
31
+
32
+ def map(
33
+ self, statement: AggregationStatement[TViewInstance]
34
+ ) -> AggregationQuery:
35
+ root_node = statement.entity.get_view_external_id()
36
+
37
+ root_view = self._view_mapper.get_view(root_node)
38
+
39
+ filters_ = (
40
+ filters.And(
41
+ *self._filter_mapper.map(statement.where_clauses, root_view)
42
+ )
43
+ if statement.where_clauses
44
+ else None
45
+ )
46
+
47
+ metric_aggregation = (
48
+ Count(statement.aggregation_property.property)
49
+ if statement.aggregate_ == "count"
50
+ else None
51
+ )
52
+ if metric_aggregation is None:
53
+ raise ValueError(
54
+ f"Unsupported aggregate function: {statement.aggregate_}"
55
+ )
56
+ return AggregationQuery(
57
+ view=root_view,
58
+ metric_aggregation=metric_aggregation,
59
+ filters=filters_,
60
+ group_by_columns=[
61
+ column.property for column in statement.group_by_columns
62
+ ],
63
+ limit=statement.limit_,
64
+ )
@@ -1,5 +1,8 @@
1
+ from threading import Lock
2
+
1
3
  from cognite.client import CogniteClient
2
4
 
5
+ from industrial_model.config import DataModelId
3
6
  from industrial_model.models import TViewInstance
4
7
  from industrial_model.statements import (
5
8
  BoolExpression,
@@ -14,11 +17,11 @@ SPACE_PROPERTY = "space"
14
17
 
15
18
  class QueryOptimizer:
16
19
  def __init__(
17
- self,
18
- cognite_client: CogniteClient,
20
+ self, cognite_client: CogniteClient, data_model_id: DataModelId
19
21
  ):
20
- self._all_spaces: list[str] | None = None
22
+ self._all_spaces = data_model_id.instance_spaces
21
23
  self._cognite_client = cognite_client
24
+ self._lock = Lock()
22
25
 
23
26
  def optimize(self, statement: Statement[TViewInstance]) -> None:
24
27
  instance_spaces = statement.entity.view_config.get("instance_spaces")
@@ -58,7 +61,7 @@ class QueryOptimizer:
58
61
  return False
59
62
 
60
63
  def _find_spaces(self, instance_spaces_prefix: str) -> list[str]:
61
- all_spaces = self._get_all_spaces()
64
+ all_spaces = self._load_spaces()
62
65
 
63
66
  return [
64
67
  space
@@ -66,12 +69,18 @@ class QueryOptimizer:
66
69
  if space.startswith(instance_spaces_prefix)
67
70
  ]
68
71
 
69
- def _get_all_spaces(self) -> list[str]:
72
+ def _load_spaces(self) -> list[str]:
70
73
  all_spaces = self._all_spaces
71
- if all_spaces is None:
74
+ if all_spaces:
75
+ return all_spaces
76
+
77
+ with self._lock:
78
+ if self._all_spaces:
79
+ return self._all_spaces
80
+
72
81
  all_spaces = self._cognite_client.data_modeling.spaces.list(
73
82
  limit=-1
74
83
  ).as_ids()
75
84
 
76
- self._all_spaces = all_spaces
77
- return all_spaces
85
+ self._all_spaces = all_spaces
86
+ return all_spaces
@@ -1,16 +1,50 @@
1
+ from threading import Lock
2
+
3
+ from cognite.client import CogniteClient
1
4
  from cognite.client.data_classes.data_modeling import (
2
5
  View,
3
6
  )
4
7
 
8
+ from industrial_model.config import DataModelId
9
+
5
10
 
6
11
  class ViewMapper:
7
- def __init__(self, views: list[View]):
8
- self._views_as_dict = {view.external_id: view for view in views}
12
+ def __init__(
13
+ self, cognite_client: CogniteClient, data_model_id: DataModelId
14
+ ):
15
+ self._cognite_client = cognite_client
16
+ self._data_model_id = data_model_id
17
+ self._views_as_dict: dict[str, View] | None = None
18
+
19
+ if data_model_id.views:
20
+ self._views_as_dict = {
21
+ view.external_id: view for view in data_model_id.views
22
+ }
23
+
24
+ self._lock = Lock()
9
25
 
10
26
  def get_view(self, view_external_id: str) -> View:
11
- if view_external_id not in self._views_as_dict:
27
+ views = self._load_views()
28
+ if view_external_id not in views:
12
29
  raise ValueError(
13
30
  f"View {view_external_id} is not available in data model"
14
31
  )
15
32
 
16
- return self._views_as_dict[view_external_id]
33
+ return views[view_external_id]
34
+
35
+ def _load_views(self) -> dict[str, View]:
36
+ if self._views_as_dict:
37
+ return self._views_as_dict
38
+
39
+ with self._lock:
40
+ if self._views_as_dict:
41
+ return self._views_as_dict
42
+
43
+ dm = self._cognite_client.data_modeling.data_models.retrieve(
44
+ ids=self._data_model_id.as_tuple(),
45
+ inline_views=True,
46
+ ).latest_version()
47
+
48
+ views = {view.external_id: view for view in dm.views}
49
+ self._views_as_dict = views
50
+ return views
@@ -1,10 +1,19 @@
1
- from industrial_model.models import RootModel
1
+ from dataclasses import dataclass, field
2
2
 
3
+ from cognite.client.data_classes.data_modeling import View
4
+ from pydantic import BaseModel, ConfigDict
5
+
6
+
7
+ @dataclass
8
+ class DataModelId(BaseModel):
9
+ model_config = ConfigDict(arbitrary_types_allowed=True)
3
10
 
4
- class DataModelId(RootModel):
5
11
  external_id: str
6
12
  space: str
7
13
  version: str
8
14
 
15
+ views: list[View] | None = field(default=None)
16
+ instance_spaces: list[str] | None = field(default=None)
17
+
9
18
  def as_tuple(self) -> tuple[str, str, str]:
10
19
  return self.space, self.external_id, self.version
@@ -2,12 +2,13 @@ from cognite.client import CogniteClient
2
2
 
3
3
  from industrial_model.config import DataModelId
4
4
  from industrial_model.models import (
5
+ AggregationResult,
5
6
  PaginatedResult,
6
7
  TViewInstance,
8
+ TWritableViewInstance,
7
9
  ValidationMode,
8
10
  )
9
- from industrial_model.models.entities import TWritableViewInstance
10
- from industrial_model.statements import Statement
11
+ from industrial_model.statements import AggregationStatement, Statement
11
12
  from industrial_model.utils import run_async
12
13
 
13
14
  from .engine import Engine
@@ -37,6 +38,11 @@ class AsyncEngine:
37
38
  self._engine.query_all_pages, statement, validation_mode
38
39
  )
39
40
 
41
+ async def aggregate_async(
42
+ self, statement: AggregationStatement[TViewInstance]
43
+ ) -> list[AggregationResult]:
44
+ return await run_async(self._engine.aggregate, statement)
45
+
40
46
  async def upsert_async(
41
47
  self, entries: list[TWritableViewInstance], replace: bool = False
42
48
  ) -> None:
@@ -5,15 +5,14 @@ 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
+ EdgeContainer,
8
10
  PaginatedResult,
9
11
  TViewInstance,
10
- ValidationMode,
11
- )
12
- from industrial_model.models.entities import (
13
- EdgeContainer,
14
12
  TWritableViewInstance,
13
+ ValidationMode,
15
14
  )
16
- from industrial_model.statements import Statement
15
+ from industrial_model.statements import AggregationStatement, Statement
17
16
 
18
17
 
19
18
  class Engine:
@@ -49,6 +48,11 @@ class Engine:
49
48
 
50
49
  return self._validate_data(statement.entity, data, validation_mode)
51
50
 
51
+ def aggregate(
52
+ self, statement: AggregationStatement[TViewInstance]
53
+ ) -> list[AggregationResult]:
54
+ return self._cognite_adapter.aggregate(statement)
55
+
52
56
  def upsert(
53
57
  self, entries: list[TWritableViewInstance], replace: bool = False
54
58
  ) -> None:
@@ -1,5 +1,6 @@
1
1
  from .base import RootModel
2
2
  from .entities import (
3
+ AggregationResult,
3
4
  EdgeContainer,
4
5
  InstanceId,
5
6
  PaginatedResult,
@@ -13,6 +14,7 @@ from .entities import (
13
14
  from .schemas import get_parent_and_children_nodes, get_schema_properties
14
15
 
15
16
  __all__ = [
17
+ "AggregationResult",
16
18
  "RootModel",
17
19
  "EdgeContainer",
18
20
  "InstanceId",
@@ -90,4 +90,10 @@ class PaginatedResult(RootModel, Generic[TViewInstance]):
90
90
  next_cursor: str | None
91
91
 
92
92
 
93
+ class AggregationResult(RootModel):
94
+ group: dict[str, str | int | float | bool] | None
95
+ value: float
96
+ aggregate: str
97
+
98
+
93
99
  ValidationMode = Literal["raiseOnError", "ignoreOnError"]
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass, field
2
- from typing import Any, Generic, Self, TypeVar
2
+ from typing import Any, Generic, Literal, Self, TypeVar
3
3
 
4
4
  from industrial_model.constants import DEFAULT_LIMIT, SORT_DIRECTION
5
5
 
@@ -17,6 +17,10 @@ from .expressions import (
17
17
  T = TypeVar("T")
18
18
 
19
19
 
20
+ def _create_column(property: str | Column | Any) -> Column:
21
+ return property if isinstance(property, Column) else Column(property)
22
+
23
+
20
24
  @dataclass
21
25
  class Statement(Generic[T]):
22
26
  entity: type[T] = field(init=True)
@@ -33,16 +37,21 @@ class Statement(Generic[T]):
33
37
  self.where_clauses.append(expression)
34
38
  return self
35
39
 
36
- def asc(self, property: Any) -> Self:
37
- self.sort_clauses.append((Column(property), "ascending"))
38
- return self
40
+ def asc(self, property: str | Column | Any) -> Self:
41
+ return self.sort(property, "ascending")
39
42
 
40
- def desc(self, property: Any) -> Self:
41
- self.sort_clauses.append((Column(property), "descending"))
42
- return self
43
+ def desc(self, property: str | Column | Any) -> Self:
44
+ return self.sort(property, "descending")
43
45
 
44
- def sort(self, property: Any, direction: SORT_DIRECTION) -> Self:
45
- self.sort_clauses.append((Column(property), direction))
46
+ def sort(
47
+ self, property: str | Column | Any, direction: SORT_DIRECTION
48
+ ) -> Self:
49
+ self.sort_clauses.append(
50
+ (
51
+ _create_column(property),
52
+ direction,
53
+ )
54
+ )
46
55
  return self
47
56
 
48
57
  def limit(self, limit: int) -> Self:
@@ -54,11 +63,52 @@ class Statement(Generic[T]):
54
63
  return self
55
64
 
56
65
 
66
+ @dataclass
67
+ class AggregationStatement(Generic[T]):
68
+ aggregate_: Literal["count"] = field(init=False, default="count")
69
+ aggregation_property: Column = field(
70
+ init=False, default=Column("externalId")
71
+ )
72
+
73
+ entity: type[T] = field(init=True)
74
+ group_by_columns: list[Column] = field(init=False, default_factory=list)
75
+ where_clauses: list[Expression] = field(init=False, default_factory=list)
76
+ limit_: int = field(init=False, default=-1)
77
+
78
+ def aggregate(self, aggregates: Literal["count"]) -> Self:
79
+ self.aggregate_ = aggregates
80
+ return self
81
+
82
+ def aggregate_by(self, property: str | Column | Any) -> Self:
83
+ self.aggregation_property = _create_column(property)
84
+ return self
85
+
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
+ def where(self, *expressions: bool | Expression) -> Self:
91
+ for expression in expressions:
92
+ assert isinstance(expression, Expression)
93
+ self.where_clauses.append(expression)
94
+ return self
95
+
96
+ def limit(self, limit: int) -> Self:
97
+ self.limit_ = limit
98
+ return self
99
+
100
+
57
101
  def select(entity: type[T]) -> Statement[T]:
58
102
  return Statement(entity)
59
103
 
60
104
 
105
+ def aggregate(entity: type[T]) -> AggregationStatement[T]:
106
+ return AggregationStatement(entity)
107
+
108
+
61
109
  __all__ = [
110
+ "aggregate",
111
+ "AggregationStatement",
62
112
  "Statement",
63
113
  "select",
64
114
  "Column",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: industrial-model
3
- Version: 0.1.10
3
+ Version: 0.1.11
4
4
  Summary: Industrial Model ORM
5
5
  Author-email: Lucas Alves <lucasrosaalves@gmail.com>
6
6
  Classifier: Programming Language :: Python
@@ -1,30 +1,31 @@
1
- industrial_model/__init__.py,sha256=LJRHjaQvFZC-rvDubXvI9VaM9me0fqi4DG2mC0pEzC0,635
2
- industrial_model/config.py,sha256=wzSKVKHIdGlxCRtu0PIeL3SLYvnQBR4Py46CcETxa8U,238
1
+ industrial_model/__init__.py,sha256=13TekQQlw7euJr1pIncV1R1Z8cWtvCPPx5e6s0NY_C0,711
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=xRLmfPctoEvjRiXqD9Nd_e0uwDD_2VrsvjG-aJXmuR0,4658
6
+ industrial_model/cognite_adapters/__init__.py,sha256=a7xPiSkuWkVxvsmVfFD-lOzdOc-tTNo8JmbyL5tC-68,5461
7
+ industrial_model/cognite_adapters/aggregation_mapper.py,sha256=wG8ottGilTAJcpf2kQvhvyRmDBpSKiT4esx0P5kmZBU,1845
7
8
  industrial_model/cognite_adapters/filter_mapper.py,sha256=NqH-OW7_iKFY9POCG8W3KjkwXUgrZP1d_yxDx1J0fXM,3859
8
9
  industrial_model/cognite_adapters/models.py,sha256=2j2IS01uPkQEp9WdVk8seYzEqGcDdWFnpzXhusHB2zk,945
9
- industrial_model/cognite_adapters/optimizer.py,sha256=G8I07jJ9tarE5GZXXUSpTMKUei6ptV-cudJSLsoykX4,2223
10
+ industrial_model/cognite_adapters/optimizer.py,sha256=BbbsA3r9ZKJt6mN1wVRZc046ov-vbml9_ESfurTNRUg,2477
10
11
  industrial_model/cognite_adapters/query_mapper.py,sha256=3fEcaLsGjLKIh-g1BbMcffQ6rp99JeCW555iJo8JW44,6260
11
12
  industrial_model/cognite_adapters/query_result_mapper.py,sha256=lsKz0wiqMOo54MVEw_YqLcHXa_HWYgAbSLnBbbdVupw,8451
12
13
  industrial_model/cognite_adapters/sort_mapper.py,sha256=RJUAYlZGXoYzK0PwX63cibRF_L-MUq9g2ZsC2EeNIF4,696
13
14
  industrial_model/cognite_adapters/upsert_mapper.py,sha256=tWEiBJQeeNz1HDu0AoBIfCw_LL156Zg4h7ORKlZ__uw,4870
14
15
  industrial_model/cognite_adapters/utils.py,sha256=rztCtS10ZTQmXfBv0nLgDiQIMWemhdSFK-SwrbRjVxM,4792
15
- industrial_model/cognite_adapters/view_mapper.py,sha256=lnv64KezSQTAr6XdcExa8d92GU5Ll9K9HfMcQGzhX6k,488
16
+ industrial_model/cognite_adapters/view_mapper.py,sha256=qvT25gwIgPdAqZf-oQEh0GylUi5KEPwjLNmIl5ISQGA,1474
16
17
  industrial_model/engines/__init__.py,sha256=7aGHrUm2MxIq39vR8h0xu3i1zNOuT9H9U-q4lV3nErQ,102
17
- industrial_model/engines/async_engine.py,sha256=jnNGBVoD0xL52UEOZ2SzTU3Eogx_EJQXklPJu22iN0I,1325
18
- industrial_model/engines/engine.py,sha256=J7JlTN0V8H259Aidh7RkBc34UK04J5iTEsWLSHV-hzg,2834
19
- industrial_model/models/__init__.py,sha256=TVlMUoK7IThiZmH9CYe0pSqfJfVJ4-DmG9vFdUILKtU,624
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
20
21
  industrial_model/models/base.py,sha256=jbiaICJ0R1mmxXtDqxxlVdq-tTX4RLdqnLTgs9HLm_4,1279
21
- industrial_model/models/entities.py,sha256=iLZ4gYGPlNCQJ33s0d6TenjMFuCR8TsvQPEpRAhzvlQ,2378
22
+ industrial_model/models/entities.py,sha256=x4c-ch-9cRVVk039dx60kXSDFhsxQ3lIY96tJ4vPjYI,2506
22
23
  industrial_model/models/schemas.py,sha256=LKNPDnUy1jtMyOHDf28En9vThhdzOSswewIcjC_y-6U,4393
23
24
  industrial_model/queries/__init__.py,sha256=7aheTE5qs03rxWm9fmGWptbz_p9OIXXYD8if56cqs18,227
24
25
  industrial_model/queries/models.py,sha256=iiHQ7-cfg0nukEv5PoCx9QPF-w1gVSnoNbXBOK9Mzeo,1185
25
26
  industrial_model/queries/params.py,sha256=ehgCoR5n6E-tkEuoymZ2lkLcSzMaBAx_HnyJ7sWpqz0,964
26
- industrial_model/statements/__init__.py,sha256=9fD-qpNXIkrjoahxC_R6hS4DKSVelehimvRPKbpYfA0,1775
27
+ industrial_model/statements/__init__.py,sha256=Y57G1TJlYxCHg3Zz7ojQoAMVnCMaUCRRWXEm2IcbAWo,3332
27
28
  industrial_model/statements/expressions.py,sha256=Sar1cIvy3sYi7tkWJN3ylHlZ252oN2mZJpZ1TX9jN3s,4940
28
- industrial_model-0.1.10.dist-info/METADATA,sha256=o0eF-e97Jrv_Z5e55btU-7jmfeZ5q-025Yy4H-isGVM,5698
29
- industrial_model-0.1.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
30
- industrial_model-0.1.10.dist-info/RECORD,,
29
+ industrial_model-0.1.11.dist-info/METADATA,sha256=IXdJyysssgu2JDxbSe6IN0Wg3US7Ax3XYu4NXUXU838,5698
30
+ industrial_model-0.1.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
31
+ industrial_model-0.1.11.dist-info/RECORD,,