industrial-model 1.2.0__tar.gz → 1.2.2__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.
- industrial_model-1.2.2/PKG-INFO +1086 -0
- industrial_model-1.2.2/README.md +1062 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/pyproject.toml +1 -1
- industrial_model-1.2.0/PKG-INFO +0 -334
- industrial_model-1.2.0/README.md +0 -310
- {industrial_model-1.2.0 → industrial_model-1.2.2}/.gitignore +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/LICENSE +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/__init__.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/cognite_adapters/__init__.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/cognite_adapters/aggregation_mapper.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/cognite_adapters/filter_mapper.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/cognite_adapters/models.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/cognite_adapters/optimizer.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/cognite_adapters/query_mapper.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/cognite_adapters/query_result_mapper.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/cognite_adapters/search_mapper.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/cognite_adapters/sort_mapper.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/cognite_adapters/upsert_mapper.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/cognite_adapters/utils.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/cognite_adapters/view_mapper.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/config.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/constants.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/engines/__init__.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/engines/_internal.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/engines/async_engine.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/engines/engine.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/models/__init__.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/models/base.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/models/entities.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/models/schemas.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/models/utils.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/py.typed +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/queries/__init__.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/queries/models.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/queries/params.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/queries/utils.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/statements/__init__.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/statements/expressions.py +0 -0
- {industrial_model-1.2.0 → industrial_model-1.2.2}/industrial_model/utils.py +0 -0
|
@@ -0,0 +1,1086 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: industrial-model
|
|
3
|
+
Version: 1.2.2
|
|
4
|
+
Summary: Industrial Model ORM
|
|
5
|
+
Project-URL: Homepage, https://github.com/lucasrosaalves/industrial-model
|
|
6
|
+
Project-URL: Source, https://github.com/lucasrosaalves/industrial-model
|
|
7
|
+
Author-email: Lucas Alves <lucasrosaalves@gmail.com>
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Programming Language :: Python
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Database
|
|
16
|
+
Classifier: Topic :: Database :: Database Engines/Servers
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: anyio>=4.9.0
|
|
20
|
+
Requires-Dist: cognite-sdk>=7.87.0
|
|
21
|
+
Requires-Dist: pydantic>=2.11.4
|
|
22
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# 📦 industrial-model
|
|
26
|
+
|
|
27
|
+
`industrial-model` is a Python ORM-style abstraction for querying views in Cognite Data Fusion (CDF). It provides a declarative and type-safe way to model CDF views using `pydantic`, build queries, and interact with the CDF API in a Pythonic fashion.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## ✨ Features
|
|
32
|
+
|
|
33
|
+
- **Declarative Models**: Define CDF views using Pydantic-style classes with type hints
|
|
34
|
+
- **Type-Safe Queries**: Build complex queries using fluent and composable filters
|
|
35
|
+
- **Flexible Querying**: Support for standard queries, paginated queries, and full page retrieval
|
|
36
|
+
- **Advanced Filtering**: Rich set of filter operators including nested queries, edge filtering, and boolean logic
|
|
37
|
+
- **Search Capabilities**: Full-text fuzzy search with configurable operators
|
|
38
|
+
- **Aggregations**: Count, sum, average, min, max with grouping support
|
|
39
|
+
- **Write Operations**: Upsert and delete instances with edge relationship support
|
|
40
|
+
- **Automatic Aliasing**: Built-in support for field aliases and camelCase transformation
|
|
41
|
+
- **Async Support**: All operations have async equivalents
|
|
42
|
+
- **Validation Modes**: Configurable error handling for data validation
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 📦 Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install industrial-model
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 📚 Table of Contents
|
|
55
|
+
|
|
56
|
+
1. [Getting Started](#-getting-started)
|
|
57
|
+
2. [Model Definition](#-model-definition)
|
|
58
|
+
3. [Engine Setup](#-engine-setup)
|
|
59
|
+
4. [Querying Data](#-querying-data)
|
|
60
|
+
5. [Filtering](#-filtering)
|
|
61
|
+
6. [Search](#-search)
|
|
62
|
+
7. [Aggregations](#-aggregations)
|
|
63
|
+
8. [Write Operations](#-write-operations)
|
|
64
|
+
9. [Advanced Features](#-advanced-features)
|
|
65
|
+
10. [Async Operations](#-async-operations)
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 🚀 Getting Started
|
|
70
|
+
|
|
71
|
+
This guide uses the `CogniteAsset` view from the `CogniteCore` data model (version `v1`) as an example.
|
|
72
|
+
|
|
73
|
+
### Sample GraphQL Schema
|
|
74
|
+
|
|
75
|
+
```graphql
|
|
76
|
+
type CogniteAsset {
|
|
77
|
+
name: String
|
|
78
|
+
description: String
|
|
79
|
+
tags: [String]
|
|
80
|
+
aliases: [String]
|
|
81
|
+
parent: CogniteAsset
|
|
82
|
+
root: CogniteAsset
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 🏗️ Model Definition
|
|
89
|
+
|
|
90
|
+
### Basic Model
|
|
91
|
+
|
|
92
|
+
Define your model by inheriting from `ViewInstance` and adding only the properties you need:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from industrial_model import ViewInstance
|
|
96
|
+
|
|
97
|
+
class CogniteAsset(ViewInstance):
|
|
98
|
+
name: str
|
|
99
|
+
description: str
|
|
100
|
+
aliases: list[str]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Model with Relationships
|
|
104
|
+
|
|
105
|
+
Include nested relationships by referencing other models:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from industrial_model import ViewInstance
|
|
109
|
+
|
|
110
|
+
class CogniteAsset(ViewInstance):
|
|
111
|
+
name: str
|
|
112
|
+
description: str
|
|
113
|
+
aliases: list[str]
|
|
114
|
+
parent: CogniteAsset | None = None
|
|
115
|
+
root: CogniteAsset | None = None
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Field Aliases
|
|
119
|
+
|
|
120
|
+
Use Pydantic's `Field` to map properties to different names in CDF:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from pydantic import Field
|
|
124
|
+
from industrial_model import ViewInstance
|
|
125
|
+
|
|
126
|
+
class CogniteAsset(ViewInstance):
|
|
127
|
+
asset_name: str = Field(alias="name") # Maps to "name" in CDF
|
|
128
|
+
asset_description: str = Field(alias="description")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### View Configuration
|
|
132
|
+
|
|
133
|
+
Configure view mapping and space filtering:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from industrial_model import ViewInstance, ViewInstanceConfig
|
|
137
|
+
|
|
138
|
+
class CogniteAsset(ViewInstance):
|
|
139
|
+
view_config = ViewInstanceConfig(
|
|
140
|
+
view_external_id="CogniteAsset", # Maps this class to the 'CogniteAsset' view
|
|
141
|
+
instance_spaces_prefix="Industr-", # Filters queries to spaces with this prefix
|
|
142
|
+
# OR use explicit spaces:
|
|
143
|
+
# instance_spaces=["Industrial-Data", "Industrial-Production"],
|
|
144
|
+
view_code="ASSET", # Optional: prefix for ID generation
|
|
145
|
+
)
|
|
146
|
+
name: str
|
|
147
|
+
description: str
|
|
148
|
+
aliases: list[str]
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Writable Models
|
|
152
|
+
|
|
153
|
+
For write operations, inherit from `WritableViewInstance` and implement `edge_id_factory`:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from industrial_model import WritableViewInstance, InstanceId, ViewInstanceConfig
|
|
157
|
+
|
|
158
|
+
class CogniteAsset(WritableViewInstance):
|
|
159
|
+
view_config = ViewInstanceConfig(view_external_id="CogniteAsset")
|
|
160
|
+
name: str
|
|
161
|
+
aliases: list[str]
|
|
162
|
+
parent: CogniteAsset | None = None
|
|
163
|
+
|
|
164
|
+
def edge_id_factory(self, target_node: InstanceId, edge_type: InstanceId) -> InstanceId:
|
|
165
|
+
"""Generate edge IDs for relationships."""
|
|
166
|
+
return InstanceId(
|
|
167
|
+
external_id=f"{self.external_id}-{target_node.external_id}-{edge_type.external_id}",
|
|
168
|
+
space=self.space,
|
|
169
|
+
)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Aggregated Models
|
|
173
|
+
|
|
174
|
+
For aggregation queries, use `AggregatedViewInstance`:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from industrial_model import AggregatedViewInstance, ViewInstanceConfig
|
|
178
|
+
|
|
179
|
+
class CogniteAssetByName(AggregatedViewInstance):
|
|
180
|
+
view_config = ViewInstanceConfig(view_external_id="CogniteAsset")
|
|
181
|
+
name: str
|
|
182
|
+
# The 'value' field is automatically included for aggregation results
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## ⚙️ Engine Setup
|
|
188
|
+
|
|
189
|
+
### Option A: From Configuration File
|
|
190
|
+
|
|
191
|
+
Create a `cognite-sdk-config.yaml` file:
|
|
192
|
+
|
|
193
|
+
```yaml
|
|
194
|
+
cognite:
|
|
195
|
+
project: "${CDF_PROJECT}"
|
|
196
|
+
client_name: "${CDF_CLIENT_NAME}"
|
|
197
|
+
base_url: "https://${CDF_CLUSTER}.cognitedata.com"
|
|
198
|
+
credentials:
|
|
199
|
+
client_credentials:
|
|
200
|
+
token_url: "${CDF_TOKEN_URL}"
|
|
201
|
+
client_id: "${CDF_CLIENT_ID}"
|
|
202
|
+
client_secret: "${CDF_CLIENT_SECRET}"
|
|
203
|
+
scopes: ["https://${CDF_CLUSTER}.cognitedata.com/.default"]
|
|
204
|
+
|
|
205
|
+
data_model:
|
|
206
|
+
external_id: "CogniteCore"
|
|
207
|
+
space: "cdf_cdm"
|
|
208
|
+
version: "v1"
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from industrial_model import Engine
|
|
213
|
+
from pathlib import Path
|
|
214
|
+
|
|
215
|
+
engine = Engine.from_config_file(Path("cognite-sdk-config.yaml"))
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Option B: Manual Setup
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from cognite.client import CogniteClient
|
|
222
|
+
from industrial_model import Engine, DataModelId
|
|
223
|
+
|
|
224
|
+
# Create your CogniteClient with appropriate authentication
|
|
225
|
+
cognite_client = CogniteClient(
|
|
226
|
+
# ... your client configuration
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
engine = Engine(
|
|
230
|
+
cognite_client=cognite_client,
|
|
231
|
+
data_model_id=DataModelId(
|
|
232
|
+
external_id="CogniteCore",
|
|
233
|
+
space="cdf_cdm",
|
|
234
|
+
version="v1"
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Async Engine
|
|
240
|
+
|
|
241
|
+
For async operations, use `AsyncEngine`:
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
from industrial_model import AsyncEngine
|
|
245
|
+
from pathlib import Path
|
|
246
|
+
|
|
247
|
+
async_engine = AsyncEngine.from_config_file(Path("cognite-sdk-config.yaml"))
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## 🔎 Querying Data
|
|
253
|
+
|
|
254
|
+
### Basic Query
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
from industrial_model import select
|
|
258
|
+
|
|
259
|
+
statement = select(CogniteAsset).limit(100)
|
|
260
|
+
results = engine.query(statement)
|
|
261
|
+
|
|
262
|
+
# results is a PaginatedResult with:
|
|
263
|
+
# - results.data: list of instances
|
|
264
|
+
# - results.has_next_page: bool
|
|
265
|
+
# - results.next_cursor: str | None
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Query All Pages
|
|
269
|
+
|
|
270
|
+
Fetch all results across multiple pages:
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
statement = select(CogniteAsset).limit(1000)
|
|
274
|
+
all_results = engine.query_all_pages(statement) # Returns list[TViewInstance]
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Pagination with Cursor
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
# First page
|
|
281
|
+
statement = select(CogniteAsset).limit(100)
|
|
282
|
+
page1 = engine.query(statement)
|
|
283
|
+
|
|
284
|
+
# Next page using cursor
|
|
285
|
+
if page1.has_next_page:
|
|
286
|
+
statement = select(CogniteAsset).limit(100).cursor(page1.next_cursor)
|
|
287
|
+
page2 = engine.query(statement)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Sorting
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
from industrial_model import select
|
|
294
|
+
|
|
295
|
+
# Ascending order
|
|
296
|
+
statement = select(CogniteAsset).asc(CogniteAsset.name)
|
|
297
|
+
|
|
298
|
+
# Descending order
|
|
299
|
+
statement = select(CogniteAsset).desc(CogniteAsset.name)
|
|
300
|
+
|
|
301
|
+
# Multiple sort fields
|
|
302
|
+
statement = (
|
|
303
|
+
select(CogniteAsset)
|
|
304
|
+
.asc(CogniteAsset.name)
|
|
305
|
+
.desc(CogniteAsset.external_id)
|
|
306
|
+
)
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Validation Modes
|
|
310
|
+
|
|
311
|
+
Control how validation errors are handled:
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
# Raise on error (default)
|
|
315
|
+
results = engine.query(statement, validation_mode="raiseOnError")
|
|
316
|
+
|
|
317
|
+
# Ignore validation errors
|
|
318
|
+
results = engine.query(statement, validation_mode="ignoreOnError")
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## 🔍 Filtering
|
|
324
|
+
|
|
325
|
+
### Comparison Operators
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
from industrial_model import select, col
|
|
329
|
+
|
|
330
|
+
# Equality
|
|
331
|
+
statement = select(CogniteAsset).where(CogniteAsset.name == "My Asset")
|
|
332
|
+
# or
|
|
333
|
+
statement = select(CogniteAsset).where(col(CogniteAsset.name).equals_("My Asset"))
|
|
334
|
+
|
|
335
|
+
# Inequality
|
|
336
|
+
statement = select(CogniteAsset).where(CogniteAsset.name != "My Asset")
|
|
337
|
+
|
|
338
|
+
# Less than / Less than or equal
|
|
339
|
+
statement = select(CogniteAsset).where(col(CogniteAsset.external_id).lt_("Z"))
|
|
340
|
+
statement = select(CogniteAsset).where(col(CogniteAsset.external_id).lte_("Z"))
|
|
341
|
+
|
|
342
|
+
# Greater than / Greater than or equal
|
|
343
|
+
statement = select(CogniteAsset).where(col(CogniteAsset.external_id).gt_("A"))
|
|
344
|
+
statement = select(CogniteAsset).where(col(CogniteAsset.external_id).gte_("A"))
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### List Operators
|
|
348
|
+
|
|
349
|
+
```python
|
|
350
|
+
from industrial_model import select, col
|
|
351
|
+
|
|
352
|
+
# In (matches any value in list)
|
|
353
|
+
statement = select(CogniteAsset).where(
|
|
354
|
+
col(CogniteAsset.external_id).in_(["asset-1", "asset-2", "asset-3"])
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
# Contains any (for array fields)
|
|
358
|
+
statement = select(CogniteAsset).where(
|
|
359
|
+
col(CogniteAsset.aliases).contains_any_(["alias1", "alias2"])
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# Contains all (for array fields)
|
|
363
|
+
statement = select(CogniteAsset).where(
|
|
364
|
+
col(CogniteAsset.tags).contains_all_(["tag1", "tag2"])
|
|
365
|
+
)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### String Operators
|
|
369
|
+
|
|
370
|
+
```python
|
|
371
|
+
from industrial_model import select, col
|
|
372
|
+
|
|
373
|
+
# Prefix matching
|
|
374
|
+
statement = select(CogniteAsset).where(
|
|
375
|
+
col(CogniteAsset.name).prefix("Pump-")
|
|
376
|
+
)
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Existence Operators
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
from industrial_model import select, col
|
|
383
|
+
|
|
384
|
+
# Field exists
|
|
385
|
+
statement = select(CogniteAsset).where(
|
|
386
|
+
col(CogniteAsset.description).exists_()
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
# Field does not exist
|
|
390
|
+
statement = select(CogniteAsset).where(
|
|
391
|
+
col(CogniteAsset.description).not_exists_()
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# Using == and != with None
|
|
395
|
+
statement = select(CogniteAsset).where(
|
|
396
|
+
CogniteAsset.parent == None # Field is null
|
|
397
|
+
)
|
|
398
|
+
statement = select(CogniteAsset).where(
|
|
399
|
+
CogniteAsset.parent != None # Field is not null
|
|
400
|
+
)
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Nested Queries
|
|
404
|
+
|
|
405
|
+
Filter by properties of related instances:
|
|
406
|
+
|
|
407
|
+
```python
|
|
408
|
+
from industrial_model import select, col
|
|
409
|
+
|
|
410
|
+
# Filter by parent's name
|
|
411
|
+
statement = select(CogniteAsset).where(
|
|
412
|
+
col(CogniteAsset.parent).nested_(
|
|
413
|
+
col(CogniteAsset.name) == "Parent Asset Name"
|
|
414
|
+
)
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
# Multiple nested conditions
|
|
418
|
+
statement = select(CogniteAsset).where(
|
|
419
|
+
col(CogniteAsset.parent).nested_(
|
|
420
|
+
(col(CogniteAsset.name) == "Parent Asset") &
|
|
421
|
+
(col(CogniteAsset.external_id).prefix("PARENT-"))
|
|
422
|
+
)
|
|
423
|
+
)
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Boolean Operators
|
|
427
|
+
|
|
428
|
+
Combine filters using `&`, `|`, and boolean functions:
|
|
429
|
+
|
|
430
|
+
```python
|
|
431
|
+
from industrial_model import select, col, and_, or_, not_
|
|
432
|
+
|
|
433
|
+
# Using & (AND) operator
|
|
434
|
+
statement = select(CogniteAsset).where(
|
|
435
|
+
(col(CogniteAsset.name).prefix("Pump-")) &
|
|
436
|
+
(col(CogniteAsset.aliases).contains_any_(["pump"]))
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
# Using | (OR) operator
|
|
440
|
+
statement = select(CogniteAsset).where(
|
|
441
|
+
(col(CogniteAsset.name) == "Asset 1") |
|
|
442
|
+
(col(CogniteAsset.name) == "Asset 2")
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
# Using and_() function
|
|
446
|
+
statement = select(CogniteAsset).where(
|
|
447
|
+
and_(
|
|
448
|
+
col(CogniteAsset.aliases).contains_any_(["my_alias"]),
|
|
449
|
+
col(CogniteAsset.description).exists_(),
|
|
450
|
+
)
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
# Using or_() function
|
|
454
|
+
statement = select(CogniteAsset).where(
|
|
455
|
+
or_(
|
|
456
|
+
col(CogniteAsset.name) == "Asset 1",
|
|
457
|
+
col(CogniteAsset.name) == "Asset 2",
|
|
458
|
+
col(CogniteAsset.name) == "Asset 3",
|
|
459
|
+
)
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
# Using not_() function
|
|
463
|
+
statement = select(CogniteAsset).where(
|
|
464
|
+
not_(col(CogniteAsset.name).prefix("Test-"))
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# Complex combinations
|
|
468
|
+
statement = select(CogniteAsset).where(
|
|
469
|
+
and_(
|
|
470
|
+
col(CogniteAsset.aliases).contains_any_(["my_alias"]),
|
|
471
|
+
or_(
|
|
472
|
+
col(CogniteAsset.parent).nested_(
|
|
473
|
+
col(CogniteAsset.name) == "Parent Asset Name 1"
|
|
474
|
+
),
|
|
475
|
+
col(CogniteAsset.parent).nested_(
|
|
476
|
+
col(CogniteAsset.name) == "Parent Asset Name 2"
|
|
477
|
+
),
|
|
478
|
+
),
|
|
479
|
+
)
|
|
480
|
+
)
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Edge Filtering
|
|
484
|
+
|
|
485
|
+
Filter on edge properties using `where_edge`:
|
|
486
|
+
|
|
487
|
+
```python
|
|
488
|
+
from industrial_model import select, col
|
|
489
|
+
|
|
490
|
+
# Filter by edge properties
|
|
491
|
+
statement = (
|
|
492
|
+
select(CogniteAsset)
|
|
493
|
+
.where_edge(
|
|
494
|
+
CogniteAsset.parent,
|
|
495
|
+
col(CogniteAsset.external_id) == "PARENT-123"
|
|
496
|
+
)
|
|
497
|
+
.limit(100)
|
|
498
|
+
)
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### Date/Time Filtering
|
|
502
|
+
|
|
503
|
+
```python
|
|
504
|
+
from datetime import datetime
|
|
505
|
+
from industrial_model import select, col
|
|
506
|
+
|
|
507
|
+
# Filter by datetime
|
|
508
|
+
cutoff_date = datetime(2024, 1, 1)
|
|
509
|
+
statement = select(CogniteAsset).where(
|
|
510
|
+
col(CogniteAsset.created_time).gte_(cutoff_date)
|
|
511
|
+
)
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### InstanceId Filtering
|
|
515
|
+
|
|
516
|
+
Filter using InstanceId objects:
|
|
517
|
+
|
|
518
|
+
```python
|
|
519
|
+
from industrial_model import select, col, InstanceId
|
|
520
|
+
|
|
521
|
+
parent_id = InstanceId(external_id="PARENT-123", space="cdf_cdm")
|
|
522
|
+
statement = select(CogniteAsset).where(
|
|
523
|
+
col(CogniteAsset.parent) == parent_id
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
# Or using nested queries
|
|
527
|
+
statement = select(CogniteAsset).where(
|
|
528
|
+
col(CogniteAsset.parent).nested_(
|
|
529
|
+
col(CogniteAsset.external_id) == "PARENT-123"
|
|
530
|
+
)
|
|
531
|
+
)
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## 🔍 Search
|
|
537
|
+
|
|
538
|
+
### Search with Filters
|
|
539
|
+
|
|
540
|
+
```python
|
|
541
|
+
from industrial_model import search, col
|
|
542
|
+
|
|
543
|
+
search_statement = (
|
|
544
|
+
search(CogniteAsset)
|
|
545
|
+
.where(col(CogniteAsset.aliases).contains_any_(["my_alias"]))
|
|
546
|
+
.query_by(
|
|
547
|
+
query="pump equipment",
|
|
548
|
+
query_properties=[CogniteAsset.name, CogniteAsset.description],
|
|
549
|
+
)
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
results = engine.search(search_statement)
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Search Operators
|
|
556
|
+
|
|
557
|
+
```python
|
|
558
|
+
from industrial_model import search, col
|
|
559
|
+
|
|
560
|
+
# AND operator (all terms must match)
|
|
561
|
+
search_statement = (
|
|
562
|
+
search(CogniteAsset)
|
|
563
|
+
.query_by(
|
|
564
|
+
query="pump equipment",
|
|
565
|
+
query_properties=[CogniteAsset.name],
|
|
566
|
+
operation="AND",
|
|
567
|
+
)
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
# OR operator (any term can match) - default
|
|
571
|
+
search_statement = (
|
|
572
|
+
search(CogniteAsset)
|
|
573
|
+
.query_by(
|
|
574
|
+
query="pump equipment",
|
|
575
|
+
query_properties=[CogniteAsset.name],
|
|
576
|
+
operation="OR",
|
|
577
|
+
)
|
|
578
|
+
)
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### Search with Multiple Properties
|
|
582
|
+
|
|
583
|
+
```python
|
|
584
|
+
from industrial_model import search, col
|
|
585
|
+
|
|
586
|
+
search_statement = (
|
|
587
|
+
search(CogniteAsset)
|
|
588
|
+
.query_by(
|
|
589
|
+
query="industrial pump",
|
|
590
|
+
query_properties=[
|
|
591
|
+
CogniteAsset.name,
|
|
592
|
+
CogniteAsset.description,
|
|
593
|
+
CogniteAsset.external_id,
|
|
594
|
+
],
|
|
595
|
+
operation="AND",
|
|
596
|
+
)
|
|
597
|
+
.limit(50)
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
results = engine.search(search_statement)
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## 📊 Aggregations
|
|
606
|
+
|
|
607
|
+
### Count Aggregation
|
|
608
|
+
|
|
609
|
+
```python
|
|
610
|
+
from industrial_model import aggregate, AggregatedViewInstance, ViewInstanceConfig, col
|
|
611
|
+
|
|
612
|
+
class CogniteAssetCount(AggregatedViewInstance):
|
|
613
|
+
view_config = ViewInstanceConfig(view_external_id="CogniteAsset")
|
|
614
|
+
|
|
615
|
+
# Simple count
|
|
616
|
+
statement = aggregate(CogniteAssetCount, "count")
|
|
617
|
+
results = engine.aggregate(statement)
|
|
618
|
+
# Each result has a 'value' field with the count
|
|
619
|
+
|
|
620
|
+
# Count with grouping
|
|
621
|
+
class CogniteAssetByName(AggregatedViewInstance):
|
|
622
|
+
view_config = ViewInstanceConfig(view_external_id="CogniteAsset")
|
|
623
|
+
name: str
|
|
624
|
+
|
|
625
|
+
statement = aggregate(CogniteAssetByName, "count").group_by(
|
|
626
|
+
col(CogniteAssetByName.name)
|
|
627
|
+
)
|
|
628
|
+
results = engine.aggregate(statement)
|
|
629
|
+
# Results grouped by name, each with a count value
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### Sum Aggregation
|
|
633
|
+
|
|
634
|
+
```python
|
|
635
|
+
from industrial_model import aggregate, AggregatedViewInstance, ViewInstanceConfig, col
|
|
636
|
+
|
|
637
|
+
class CogniteAssetWithValue(AggregatedViewInstance):
|
|
638
|
+
view_config = ViewInstanceConfig(view_external_id="CogniteAsset")
|
|
639
|
+
name: str
|
|
640
|
+
# Assume there's a 'value' property in the view
|
|
641
|
+
|
|
642
|
+
statement = (
|
|
643
|
+
aggregate(CogniteAssetWithValue, "sum")
|
|
644
|
+
.aggregate_by(CogniteAssetWithValue.value)
|
|
645
|
+
.group_by(col(CogniteAssetWithValue.name))
|
|
646
|
+
)
|
|
647
|
+
results = engine.aggregate(statement)
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
### Average, Min, Max Aggregations
|
|
651
|
+
|
|
652
|
+
```python
|
|
653
|
+
from industrial_model import aggregate, AggregatedViewInstance, ViewInstanceConfig, col
|
|
654
|
+
|
|
655
|
+
class CogniteAssetStats(AggregatedViewInstance):
|
|
656
|
+
view_config = ViewInstanceConfig(view_external_id="CogniteAsset")
|
|
657
|
+
name: str
|
|
658
|
+
|
|
659
|
+
# Average
|
|
660
|
+
statement = (
|
|
661
|
+
aggregate(CogniteAssetStats, "avg")
|
|
662
|
+
.aggregate_by(CogniteAssetStats.value)
|
|
663
|
+
.group_by(col(CogniteAssetStats.name))
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
# Minimum
|
|
667
|
+
statement = (
|
|
668
|
+
aggregate(CogniteAssetStats, "min")
|
|
669
|
+
.aggregate_by(CogniteAssetStats.value)
|
|
670
|
+
.group_by(col(CogniteAssetStats.name))
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
# Maximum
|
|
674
|
+
statement = (
|
|
675
|
+
aggregate(CogniteAssetStats, "max")
|
|
676
|
+
.aggregate_by(CogniteAssetStats.value)
|
|
677
|
+
.group_by(col(CogniteAssetStats.name))
|
|
678
|
+
)
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
### Aggregation with Filters
|
|
682
|
+
|
|
683
|
+
```python
|
|
684
|
+
from industrial_model import aggregate, AggregatedViewInstance, ViewInstanceConfig, col
|
|
685
|
+
|
|
686
|
+
class CogniteAssetByName(AggregatedViewInstance):
|
|
687
|
+
view_config = ViewInstanceConfig(view_external_id="CogniteAsset")
|
|
688
|
+
name: str
|
|
689
|
+
|
|
690
|
+
statement = (
|
|
691
|
+
aggregate(CogniteAssetByName, "count")
|
|
692
|
+
.where(col("description").exists_())
|
|
693
|
+
.group_by(col(CogniteAssetByName.name))
|
|
694
|
+
.limit(100)
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
results = engine.aggregate(statement)
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
### Multiple Group By Fields
|
|
701
|
+
|
|
702
|
+
```python
|
|
703
|
+
from industrial_model import aggregate, AggregatedViewInstance, ViewInstanceConfig, col
|
|
704
|
+
|
|
705
|
+
class CogniteAssetGrouped(AggregatedViewInstance):
|
|
706
|
+
view_config = ViewInstanceConfig(view_external_id="CogniteAsset")
|
|
707
|
+
name: str
|
|
708
|
+
space: str
|
|
709
|
+
|
|
710
|
+
statement = (
|
|
711
|
+
aggregate(CogniteAssetGrouped, "count")
|
|
712
|
+
.group_by(
|
|
713
|
+
col(CogniteAssetGrouped.name),
|
|
714
|
+
col(CogniteAssetGrouped.space),
|
|
715
|
+
)
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
results = engine.aggregate(statement)
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
---
|
|
722
|
+
|
|
723
|
+
## ✏️ Write Operations
|
|
724
|
+
|
|
725
|
+
### Upsert Instances
|
|
726
|
+
|
|
727
|
+
```python
|
|
728
|
+
from industrial_model import WritableViewInstance, InstanceId, ViewInstanceConfig, select, col
|
|
729
|
+
|
|
730
|
+
class CogniteAsset(WritableViewInstance):
|
|
731
|
+
view_config = ViewInstanceConfig(view_external_id="CogniteAsset")
|
|
732
|
+
name: str
|
|
733
|
+
aliases: list[str]
|
|
734
|
+
parent: CogniteAsset | None = None
|
|
735
|
+
|
|
736
|
+
def edge_id_factory(self, target_node: InstanceId, edge_type: InstanceId) -> InstanceId:
|
|
737
|
+
return InstanceId(
|
|
738
|
+
external_id=f"{self.external_id}-{target_node.external_id}-{edge_type.external_id}",
|
|
739
|
+
space=self.space,
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
# Update existing instances
|
|
743
|
+
instances = engine.query_all_pages(
|
|
744
|
+
select(CogniteAsset).where(col(CogniteAsset.aliases).contains_any_(["my_alias"]))
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
for instance in instances:
|
|
748
|
+
instance.aliases.append("new_alias")
|
|
749
|
+
|
|
750
|
+
# Upsert with default options (merge, keep unset fields)
|
|
751
|
+
engine.upsert(instances)
|
|
752
|
+
|
|
753
|
+
# Upsert with replace=True (replace entire instance)
|
|
754
|
+
engine.upsert(instances, replace=True)
|
|
755
|
+
|
|
756
|
+
# Upsert with remove_unset=True (remove fields not set in model)
|
|
757
|
+
engine.upsert(instances, remove_unset=True)
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
### Create New Instances
|
|
761
|
+
|
|
762
|
+
```python
|
|
763
|
+
from industrial_model import WritableViewInstance, InstanceId, ViewInstanceConfig
|
|
764
|
+
|
|
765
|
+
class CogniteAsset(WritableViewInstance):
|
|
766
|
+
view_config = ViewInstanceConfig(view_external_id="CogniteAsset")
|
|
767
|
+
name: str
|
|
768
|
+
aliases: list[str]
|
|
769
|
+
|
|
770
|
+
def edge_id_factory(self, target_node: InstanceId, edge_type: InstanceId) -> InstanceId:
|
|
771
|
+
return InstanceId(
|
|
772
|
+
external_id=f"{self.external_id}-{target_node.external_id}-{edge_type.external_id}",
|
|
773
|
+
space=self.space,
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
# Create new instances
|
|
777
|
+
new_asset = CogniteAsset(
|
|
778
|
+
external_id="NEW-ASSET-001",
|
|
779
|
+
space="cdf_cdm",
|
|
780
|
+
name="New Asset",
|
|
781
|
+
aliases=["alias1", "alias2"],
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
engine.upsert([new_asset])
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
### Delete Instances
|
|
788
|
+
|
|
789
|
+
```python
|
|
790
|
+
from industrial_model import search, col
|
|
791
|
+
|
|
792
|
+
# Find instances to delete
|
|
793
|
+
instances_to_delete = engine.search(
|
|
794
|
+
search(CogniteAsset)
|
|
795
|
+
.where(col(CogniteAsset.aliases).contains_any_(["old_alias"]))
|
|
796
|
+
.query_by("obsolete", [CogniteAsset.name])
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
# Delete them
|
|
800
|
+
engine.delete(instances_to_delete)
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
---
|
|
804
|
+
|
|
805
|
+
## 🚀 Advanced Features
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
### Generate Model IDs
|
|
809
|
+
|
|
810
|
+
Generate IDs from model fields:
|
|
811
|
+
|
|
812
|
+
```python
|
|
813
|
+
from industrial_model import ViewInstance, ViewInstanceConfig
|
|
814
|
+
|
|
815
|
+
class CogniteAsset(ViewInstance):
|
|
816
|
+
view_config = ViewInstanceConfig(
|
|
817
|
+
view_external_id="CogniteAsset",
|
|
818
|
+
view_code="ASSET",
|
|
819
|
+
)
|
|
820
|
+
name: str
|
|
821
|
+
space: str
|
|
822
|
+
|
|
823
|
+
asset = CogniteAsset(
|
|
824
|
+
external_id="",
|
|
825
|
+
space="cdf_cdm",
|
|
826
|
+
name="Pump-001",
|
|
827
|
+
space="Industrial-Data",
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
# Generate ID from name
|
|
831
|
+
id_from_name = asset.generate_model_id(["name"])
|
|
832
|
+
# Result: "ASSET-Pump-001"
|
|
833
|
+
|
|
834
|
+
# Generate ID from multiple fields
|
|
835
|
+
id_from_fields = asset.generate_model_id(["space", "name"])
|
|
836
|
+
# Result: "ASSET-Industrial-Data-Pump-001"
|
|
837
|
+
|
|
838
|
+
# Without view_code prefix
|
|
839
|
+
id_no_prefix = asset.generate_model_id(["name"], view_code_as_prefix=False)
|
|
840
|
+
# Result: "Pump-001"
|
|
841
|
+
|
|
842
|
+
# Custom separator
|
|
843
|
+
id_custom = asset.generate_model_id(["space", "name"], separator="_")
|
|
844
|
+
# Result: "ASSET-Industrial-Data_Pump-001"
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
### InstanceId Operations
|
|
848
|
+
|
|
849
|
+
```python
|
|
850
|
+
from industrial_model import InstanceId
|
|
851
|
+
|
|
852
|
+
# Create InstanceId
|
|
853
|
+
asset_id = InstanceId(external_id="ASSET-001", space="cdf_cdm")
|
|
854
|
+
|
|
855
|
+
# Convert to tuple
|
|
856
|
+
space, external_id = asset_id.as_tuple()
|
|
857
|
+
|
|
858
|
+
# Use in comparisons
|
|
859
|
+
other_id = InstanceId(external_id="ASSET-001", space="cdf_cdm")
|
|
860
|
+
assert asset_id == other_id
|
|
861
|
+
|
|
862
|
+
# Use as dictionary key (InstanceId is hashable)
|
|
863
|
+
id_map = {asset_id: "some_value"}
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
### PaginatedResult Utilities
|
|
867
|
+
|
|
868
|
+
```python
|
|
869
|
+
from industrial_model import select
|
|
870
|
+
|
|
871
|
+
statement = select(CogniteAsset).limit(100)
|
|
872
|
+
result = engine.query(statement)
|
|
873
|
+
|
|
874
|
+
# Get first item or None
|
|
875
|
+
first_asset = result.first_or_default()
|
|
876
|
+
|
|
877
|
+
# Check if there are more pages
|
|
878
|
+
if result.has_next_page:
|
|
879
|
+
next_cursor = result.next_cursor
|
|
880
|
+
# Use cursor for next page
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
---
|
|
884
|
+
|
|
885
|
+
## ⚡ Async Operations
|
|
886
|
+
|
|
887
|
+
All engine methods have async equivalents:
|
|
888
|
+
|
|
889
|
+
### AsyncEngine Setup
|
|
890
|
+
|
|
891
|
+
```python
|
|
892
|
+
from industrial_model import AsyncEngine
|
|
893
|
+
from pathlib import Path
|
|
894
|
+
|
|
895
|
+
async_engine = AsyncEngine.from_config_file(Path("cognite-sdk-config.yaml"))
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
### Async Query Operations
|
|
899
|
+
|
|
900
|
+
```python
|
|
901
|
+
from industrial_model import select, col
|
|
902
|
+
|
|
903
|
+
# Async query
|
|
904
|
+
statement = select(CogniteAsset).where(col(CogniteAsset.name).prefix("Pump-"))
|
|
905
|
+
result = await async_engine.query_async(statement)
|
|
906
|
+
|
|
907
|
+
# Async query all pages
|
|
908
|
+
all_results = await async_engine.query_all_pages_async(statement)
|
|
909
|
+
|
|
910
|
+
# Async search
|
|
911
|
+
search_statement = search(CogniteAsset).query_by("pump")
|
|
912
|
+
results = await async_engine.search_async(search_statement)
|
|
913
|
+
|
|
914
|
+
# Async aggregate
|
|
915
|
+
aggregate_statement = aggregate(CogniteAssetByName, "count")
|
|
916
|
+
results = await async_engine.aggregate_async(aggregate_statement)
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
### Async Write Operations
|
|
920
|
+
|
|
921
|
+
```python
|
|
922
|
+
# Async upsert
|
|
923
|
+
instances = [new_asset1, new_asset2]
|
|
924
|
+
await async_engine.upsert_async(instances, replace=False, remove_unset=False)
|
|
925
|
+
|
|
926
|
+
# Async delete
|
|
927
|
+
await async_engine.delete_async(instances_to_delete)
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
### Complete Async Example
|
|
931
|
+
|
|
932
|
+
```python
|
|
933
|
+
import asyncio
|
|
934
|
+
from industrial_model import AsyncEngine, select, col
|
|
935
|
+
from pathlib import Path
|
|
936
|
+
|
|
937
|
+
async def main():
|
|
938
|
+
engine = AsyncEngine.from_config_file(Path("cognite-sdk-config.yaml"))
|
|
939
|
+
|
|
940
|
+
# Run multiple queries concurrently
|
|
941
|
+
statement1 = select(CogniteAsset).where(col(CogniteAsset.name).prefix("Pump-"))
|
|
942
|
+
statement2 = select(CogniteAsset).where(col(CogniteAsset.name).prefix("Valve-"))
|
|
943
|
+
|
|
944
|
+
results1, results2 = await asyncio.gather(
|
|
945
|
+
engine.query_all_pages_async(statement1),
|
|
946
|
+
engine.query_all_pages_async(statement2),
|
|
947
|
+
)
|
|
948
|
+
|
|
949
|
+
print(f"Found {len(results1)} pumps and {len(results2)} valves")
|
|
950
|
+
|
|
951
|
+
asyncio.run(main())
|
|
952
|
+
```
|
|
953
|
+
|
|
954
|
+
---
|
|
955
|
+
|
|
956
|
+
## 📝 Complete Example
|
|
957
|
+
|
|
958
|
+
Here's a complete example demonstrating multiple features:
|
|
959
|
+
|
|
960
|
+
```python
|
|
961
|
+
from industrial_model import (
|
|
962
|
+
Engine,
|
|
963
|
+
ViewInstance,
|
|
964
|
+
WritableViewInstance,
|
|
965
|
+
ViewInstanceConfig,
|
|
966
|
+
InstanceId,
|
|
967
|
+
select,
|
|
968
|
+
search,
|
|
969
|
+
aggregate,
|
|
970
|
+
AggregatedViewInstance,
|
|
971
|
+
col,
|
|
972
|
+
and_,
|
|
973
|
+
or_,
|
|
974
|
+
)
|
|
975
|
+
from pathlib import Path
|
|
976
|
+
|
|
977
|
+
# Define models
|
|
978
|
+
class CogniteAsset(WritableViewInstance):
|
|
979
|
+
view_config = ViewInstanceConfig(
|
|
980
|
+
view_external_id="CogniteAsset",
|
|
981
|
+
instance_spaces_prefix="Industrial-",
|
|
982
|
+
)
|
|
983
|
+
name: str
|
|
984
|
+
description: str | None = None
|
|
985
|
+
aliases: list[str] = []
|
|
986
|
+
parent: CogniteAsset | None = None
|
|
987
|
+
|
|
988
|
+
def edge_id_factory(self, target_node: InstanceId, edge_type: InstanceId) -> InstanceId:
|
|
989
|
+
return InstanceId(
|
|
990
|
+
external_id=f"{self.external_id}-{target_node.external_id}-{edge_type.external_id}",
|
|
991
|
+
space=self.space,
|
|
992
|
+
)
|
|
993
|
+
|
|
994
|
+
class AssetCountByParent(AggregatedViewInstance):
|
|
995
|
+
view_config = ViewInstanceConfig(view_external_id="CogniteAsset")
|
|
996
|
+
parent: InstanceId | None = None
|
|
997
|
+
|
|
998
|
+
# Setup engine
|
|
999
|
+
engine = Engine.from_config_file(Path("cognite-sdk-config.yaml"))
|
|
1000
|
+
|
|
1001
|
+
# 1. Query with complex filters
|
|
1002
|
+
statement = (
|
|
1003
|
+
select(CogniteAsset)
|
|
1004
|
+
.where(
|
|
1005
|
+
and_(
|
|
1006
|
+
col(CogniteAsset.aliases).contains_any_(["pump", "equipment"]),
|
|
1007
|
+
col(CogniteAsset.description).exists_(),
|
|
1008
|
+
or_(
|
|
1009
|
+
col(CogniteAsset.parent).nested_(col(CogniteAsset.name) == "Root Asset"),
|
|
1010
|
+
col(CogniteAsset.name).prefix("Pump-"),
|
|
1011
|
+
),
|
|
1012
|
+
)
|
|
1013
|
+
)
|
|
1014
|
+
.asc(CogniteAsset.name)
|
|
1015
|
+
.limit(100)
|
|
1016
|
+
)
|
|
1017
|
+
|
|
1018
|
+
results = engine.query(statement)
|
|
1019
|
+
print(f"Found {len(results.data)} assets")
|
|
1020
|
+
|
|
1021
|
+
# 2. Search with filters
|
|
1022
|
+
search_results = engine.search(
|
|
1023
|
+
search(CogniteAsset)
|
|
1024
|
+
.where(col(CogniteAsset.aliases).contains_any_(["pump"]))
|
|
1025
|
+
.query_by("industrial equipment", [CogniteAsset.name, CogniteAsset.description])
|
|
1026
|
+
)
|
|
1027
|
+
|
|
1028
|
+
# 3. Aggregate
|
|
1029
|
+
aggregate_results = engine.aggregate(
|
|
1030
|
+
aggregate(AssetCountByParent, "count")
|
|
1031
|
+
.where(col(CogniteAsset.description).exists_())
|
|
1032
|
+
.group_by(col(AssetCountByParent.parent))
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
for result in aggregate_results:
|
|
1036
|
+
print(f"Parent: {result.parent}, Count: {result.value}")
|
|
1037
|
+
|
|
1038
|
+
# 4. Update instances
|
|
1039
|
+
assets = engine.query_all_pages(
|
|
1040
|
+
select(CogniteAsset).where(col(CogniteAsset.name).prefix("Pump-"))
|
|
1041
|
+
)
|
|
1042
|
+
|
|
1043
|
+
for asset in assets:
|
|
1044
|
+
if "legacy" not in asset.aliases:
|
|
1045
|
+
asset.aliases.append("legacy")
|
|
1046
|
+
|
|
1047
|
+
engine.upsert(assets, replace=False)
|
|
1048
|
+
|
|
1049
|
+
# 5. Delete obsolete assets
|
|
1050
|
+
obsolete = engine.search(
|
|
1051
|
+
search(CogniteAsset)
|
|
1052
|
+
.query_by("obsolete", [CogniteAsset.name])
|
|
1053
|
+
)
|
|
1054
|
+
engine.delete(obsolete)
|
|
1055
|
+
```
|
|
1056
|
+
|
|
1057
|
+
---
|
|
1058
|
+
|
|
1059
|
+
## 🎯 Best Practices
|
|
1060
|
+
|
|
1061
|
+
1. **Model Definition**: Only include fields you actually need in your models
|
|
1062
|
+
2. **View Configuration**: Use `instance_spaces` or `instance_spaces_prefix` to optimize queries
|
|
1063
|
+
3. **Pagination**: Use `query_all_pages()` for small datasets, `query()` with cursors for large datasets
|
|
1064
|
+
4. **Validation**: Use `ignoreOnError` mode when dealing with potentially inconsistent data
|
|
1065
|
+
5. **Edge Relationships**: Always implement `edge_id_factory` for writable models with relationships
|
|
1066
|
+
6. **Async Operations**: Use async methods when making multiple concurrent queries
|
|
1067
|
+
7. **Filtering**: Use specific filters to reduce query size and improve performance
|
|
1068
|
+
|
|
1069
|
+
---
|
|
1070
|
+
|
|
1071
|
+
## 📚 Additional Resources
|
|
1072
|
+
|
|
1073
|
+
- [Cognite Data Fusion Documentation](https://docs.cognite.com/)
|
|
1074
|
+
- [Pydantic Documentation](https://docs.pydantic.dev/)
|
|
1075
|
+
|
|
1076
|
+
---
|
|
1077
|
+
|
|
1078
|
+
## 🤝 Contributing
|
|
1079
|
+
|
|
1080
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
1081
|
+
|
|
1082
|
+
---
|
|
1083
|
+
|
|
1084
|
+
## 📄 License
|
|
1085
|
+
|
|
1086
|
+
See LICENSE file for details.
|