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