industrial-model 1.2.1__py3-none-any.whl → 1.2.2__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.
@@ -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.
@@ -30,7 +30,7 @@ industrial_model/queries/params.py,sha256=50qY5BO5onLsXorhcv-7qCKhJaMO94UzhKLCmZ
30
30
  industrial_model/queries/utils.py,sha256=uP6PLh9IVHDK6J8x444zHWPmyV4PkxdLO-PMc6qWItc,1505
31
31
  industrial_model/statements/__init__.py,sha256=xazAVHvN8HKsWcXR2Hp1aGxd59stg13JXO6tgpJnsC4,5660
32
32
  industrial_model/statements/expressions.py,sha256=4ZZOcZroI5-4xRw4PXIRlufi0ARndE5zSbbxLDpR2Ec,4816
33
- industrial_model-1.2.1.dist-info/METADATA,sha256=Ic7qCi1S_QRM_6tonlKbj4UYFGxqW0NHcaT3iQuDmrU,7770
34
- industrial_model-1.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
35
- industrial_model-1.2.1.dist-info/licenses/LICENSE,sha256=BCHCZ1Qo7m4YvAEIQqtVBI3NebFJdZ8_7m_cxInIlN0,4934
36
- industrial_model-1.2.1.dist-info/RECORD,,
33
+ industrial_model-1.2.2.dist-info/METADATA,sha256=X_mVQqlQjoqff94wACeDJ11KPCgJ3gJk6Jxx5EtXDEY,26780
34
+ industrial_model-1.2.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
35
+ industrial_model-1.2.2.dist-info/licenses/LICENSE,sha256=BCHCZ1Qo7m4YvAEIQqtVBI3NebFJdZ8_7m_cxInIlN0,4934
36
+ industrial_model-1.2.2.dist-info/RECORD,,
@@ -1,334 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: industrial-model
3
- Version: 1.2.1
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
- - Define CDF views using Pydantic-style classes.
34
- - Build complex queries using fluent and composable filters.
35
- - Easily fetch data using standard or paginated query execution.
36
- - Automatic alias and field transformation support.
37
-
38
- ---
39
-
40
- ## 📦 Installation
41
-
42
- ```bash
43
- pip install industrial-model
44
- ```
45
-
46
- ---
47
-
48
- # Usage Example
49
-
50
- This section shows how to interact with **Cognite Data Fusion (CDF)** using the `industrial_model` package.
51
- We use the simplified version of the `CogniteAsset`view in the `CogniteCore` data model (version `v1`) as a sample for all the examples below.
52
-
53
- ---
54
-
55
- ```graphql
56
- type CogniteAsset {
57
- name: String
58
- description: String
59
- tags: [String]
60
- parent: CogniteAsset
61
- root: CogniteAsset
62
- }
63
- ```
64
-
65
- ---
66
-
67
- ## 🚀 Getting Started
68
-
69
- ### 1. Define Your Model (You only need to add the properties that you want to retrieve)
70
-
71
- ```python
72
- from industrial_model import ViewInstance
73
-
74
- class CogniteAsset(ViewInstance):
75
- name: str
76
- description: str
77
- aliases: list[str]
78
- ```
79
-
80
- ### 2. Create the Engine
81
-
82
- #### Option A: From Configuration File
83
-
84
- Create a `cognite-sdk-config.yaml` file with your credentials and model configuration:
85
-
86
- ```yaml
87
- cognite:
88
- project: "${CDF_PROJECT}"
89
- client_name: "${CDF_CLIENT_NAME}"
90
- base_url: "https://${CDF_CLUSTER}.cognitedata.com"
91
- credentials:
92
- client_credentials:
93
- token_url: "${CDF_TOKEN_URL}"
94
- client_id: "${CDF_CLIENT_ID}"
95
- client_secret: "${CDF_CLIENT_SECRET}"
96
- scopes: ["https://${CDF_CLUSTER}.cognitedata.com/.default"]
97
-
98
- data_model:
99
- external_id: "CogniteCore"
100
- space: "cdf_cdm"
101
- version: "v1"
102
- ```
103
-
104
- ```python
105
- from industrial_model import Engine
106
- from pathlib import Path
107
-
108
- engine = Engine.from_config_file(Path("cognite-sdk-config.yaml"))
109
- ```
110
-
111
- #### Option B: Manually
112
-
113
- ```python
114
- from cognite.client import CogniteClient
115
- from industrial_model import Engine, DataModelId
116
-
117
- engine = Engine(
118
- cognite_client=CogniteClient(), # you need to create a valid cognite client
119
- data_model_id=DataModelId(external_id="CogniteCore", space="cdf_cdm", version="v1")
120
- )
121
- ```
122
-
123
- ---
124
-
125
- ## 🔎 Querying Assets by Alias
126
-
127
- ```python
128
- from industrial_model import select, col
129
-
130
- statement = (
131
- select(CogniteAsset)
132
- .where(col(CogniteAsset.aliases).contains_any_(["my_alias"]))
133
- .limit(1000)
134
- )
135
-
136
- results = engine.query_all_pages(statement)
137
- ```
138
-
139
- ---
140
-
141
- ## 🔗 Filtering by Parent Name
142
-
143
- ```python
144
- class CogniteAsset(ViewInstance):
145
- name: str
146
- description: str
147
- aliases: list[str]
148
- parent: CogniteAsset | None = None
149
- ```
150
-
151
- ```python
152
- statement = (
153
- select(CogniteAsset)
154
- .where(
155
- col(CogniteAsset.aliases).contains_any_(["my_alias"]) &
156
- col(CogniteAsset.parent).nested_(col(CogniteAsset.name) == "Parent Asset Name")
157
- )
158
- )
159
-
160
- results = engine.query(statement)
161
- ```
162
-
163
- ---
164
-
165
- ## 🔗 Filtering by Parent Name with bool operators
166
-
167
- ```python
168
- from industrial_model import select, col, or_, and_
169
-
170
- statement = select(CogniteAsset).where(
171
- and_(
172
- col(CogniteAsset.aliases).contains_any_(["my_alias"]),
173
- or_(
174
- col(CogniteAsset.parent).nested_(
175
- col(CogniteAsset.name) == "Parent Asset Name 1"
176
- ),
177
- col(CogniteAsset.parent).nested_(
178
- col(CogniteAsset.name) == "Parent Asset Name 2"
179
- ),
180
- ),
181
- )
182
- )
183
-
184
- results = engine.query(statement)
185
- ```
186
-
187
- ---
188
-
189
- ## 🔗 Paginating with cursor and sort by name
190
-
191
- ```python
192
- class CogniteAsset(ViewInstance):
193
- name: str
194
- description: str
195
- aliases: list[str]
196
- parent: CogniteAsset | None = None
197
- ```
198
-
199
- ```python
200
- statement = select(CogniteAsset).asc(CogniteAsset.name).cursor("NEXT_CURSOR")
201
-
202
- results = engine.query(statement)
203
- ```
204
-
205
- ---
206
-
207
- ## 🔗 Proving an alias for a property
208
-
209
- ```python
210
- from pydantic import Field
211
-
212
- class CogniteAsset(ViewInstance):
213
- another_name: str = Field(alias="name")
214
- ```
215
-
216
- ---
217
-
218
- ## 🎯 Optimize Query with View Config - The spaces will be appended in every query
219
-
220
- ```python
221
- from industrial_model import ViewInstanceConfig
222
-
223
- class CogniteAsset(ViewInstance):
224
- view_config = ViewInstanceConfig(
225
- view_external_id="CogniteAsset", # Maps this class to the 'CogniteAsset' view
226
- instance_spaces_prefix="Industr-", # Filters queries to spaces with this prefix
227
- instance_spaces=[
228
- "Industrial-Data"
229
- ], # Alternatively, explicitly filter by these spaces
230
- )
231
- name: str
232
- description: str
233
- aliases: list[str]
234
- parent: CogniteAsset | None = None
235
- ```
236
-
237
- ---
238
-
239
- ## 🔍 Search by Fuzzy Name
240
-
241
- ```python
242
- from industrial_model import search
243
-
244
- search_statement = (
245
- search(CogniteAsset)
246
- .where(col(CogniteAsset.aliases).contains_any_(["my_alias"]))
247
- .query_by(
248
- query="my fuzzy name",
249
- query_properties=[CogniteAsset.name],
250
- operator="AND",
251
- )
252
- )
253
-
254
- search_result = engine.search(search_statement)
255
- ```
256
-
257
- ---
258
-
259
- ## 📊 Aggregating Data
260
-
261
- ```python
262
- from industrial_model import aggregate, AggregatedViewInstance
263
-
264
- class CogniteAssetByName(AggregatedViewInstance):
265
- view_config = ViewInstanceConfig(view_external_id="CogniteAsset")
266
- name: str
267
-
268
- aggregate_statement = aggregate(CogniteAssetByName, "count").group_by(
269
- col(CogniteAssetByName.name)
270
- )
271
-
272
- aggregate_result = engine.aggregate(aggregate_statement)
273
- ```
274
-
275
- ---
276
-
277
- ## 🗑️ Deleting Instances
278
-
279
- ```python
280
- instances_to_delete = engine.search(
281
- search(CogniteAsset)
282
- .where(col(CogniteAsset.aliases).contains_any_(["my_alias"]))
283
- .query_by("my fuzzy name", [CogniteAsset.name])
284
- )
285
-
286
- engine.delete(instances_to_delete)
287
- ```
288
-
289
- ---
290
-
291
- ## ✏️ Upserting Instances
292
-
293
- ```python
294
- from industrial_model import WritableViewInstance, InstanceId
295
-
296
- class CogniteAsset(WritableViewInstance):
297
- view_config = ViewInstanceConfig(view_external_id="CogniteAsset")
298
- name: str
299
- aliases: list[str]
300
-
301
- def edge_id_factory(self, target_node: InstanceId, edge_type: InstanceId) -> InstanceId:
302
- return InstanceId(
303
- external_id=f"{self.external_id}-{target_node.external_id}-{edge_type.external_id}",
304
- space=self.space,
305
- )
306
- ```
307
-
308
- ```python
309
- instances = engine.query_all_pages(
310
- select(CogniteAsset).where(col(CogniteAsset.aliases).contains_any_(["my_alias"]))
311
- )
312
-
313
- for instance in instances:
314
- instance.aliases.append("new_alias")
315
-
316
- engine.upsert(instances, replace=False, remove_unset=False)
317
- ```
318
-
319
- ---
320
-
321
- ## ✏️ Async version
322
-
323
- All methods have a async equivalent version
324
-
325
- ```python
326
- await engine.query_async(...)
327
- await engine.query_all_pages_async(...)
328
- await engine.search_async(...)
329
- await engine.aggregate_async(...)
330
- await engine.delete_async(...)
331
- await engine.upsert_async(...)
332
- ```
333
-
334
- ---