collibra-connector 1.0.19__py3-none-any.whl → 1.1.0__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,897 @@
1
+ """
2
+ Pydantic models for Collibra API responses.
3
+
4
+ This module provides typed models for all Collibra resources, enabling
5
+ full IDE autocompletion, validation, and type safety.
6
+
7
+ Example:
8
+ >>> asset = conn.asset.get_asset("uuid") # Returns AssetModel
9
+ >>> print(asset.name)
10
+ >>> print(asset.status.name)
11
+ >>> print(asset.type.id)
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from datetime import datetime
16
+ from enum import Enum
17
+ from typing import Any, Dict, Generic, Iterator, List, Optional, TypeVar, Union
18
+
19
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
20
+
21
+
22
+ # Type variable for generic paginated responses
23
+ T = TypeVar('T', bound='BaseCollibraModel')
24
+
25
+
26
+ class ResourceType(str, Enum):
27
+ """Enum for Collibra resource types."""
28
+ ASSET = "Asset"
29
+ DOMAIN = "Domain"
30
+ COMMUNITY = "Community"
31
+ USER = "User"
32
+ ATTRIBUTE = "Attribute"
33
+ RELATION = "Relation"
34
+ COMMENT = "Comment"
35
+ RESPONSIBILITY = "Responsibility"
36
+ WORKFLOW = "Workflow"
37
+ STATUS = "Status"
38
+ ASSET_TYPE = "AssetType"
39
+ DOMAIN_TYPE = "DomainType"
40
+ ATTRIBUTE_TYPE = "AttributeType"
41
+ RELATION_TYPE = "RelationType"
42
+
43
+
44
+ class BaseCollibraModel(BaseModel):
45
+ """Base class for all Collibra models with common configuration."""
46
+ model_config = ConfigDict(
47
+ populate_by_name=True,
48
+ extra="allow", # Allow extra fields from API
49
+ str_strip_whitespace=True,
50
+ )
51
+
52
+ def to_dict(self) -> Dict[str, Any]:
53
+ """Convert model to dictionary with camelCase keys."""
54
+ return self.model_dump(by_alias=True, exclude_none=True)
55
+
56
+ def to_json(self) -> str:
57
+ """Convert model to JSON string."""
58
+ return self.model_dump_json(by_alias=True, exclude_none=True)
59
+
60
+
61
+ class ResourceReference(BaseCollibraModel):
62
+ """Reference to another resource (lightweight representation)."""
63
+ id: str
64
+ resource_type: Optional[str] = Field(default=None, alias="resourceType")
65
+ name: Optional[str] = None
66
+
67
+ def __str__(self) -> str:
68
+ return self.name or self.id
69
+
70
+ def __repr__(self) -> str:
71
+ return f"ResourceReference(id={self.id!r}, name={self.name!r})"
72
+
73
+
74
+ class TypedResourceReference(ResourceReference):
75
+ """Resource reference with additional type information."""
76
+ public_id: Optional[str] = Field(default=None, alias="publicId")
77
+ description: Optional[str] = None
78
+
79
+
80
+ class NamedResource(BaseCollibraModel):
81
+ """Base class for resources with id, name, and description."""
82
+ id: str
83
+ resource_type: Optional[str] = Field(default=None, alias="resourceType")
84
+ name: str
85
+ description: Optional[str] = None
86
+
87
+ def __str__(self) -> str:
88
+ return self.name
89
+
90
+ def __repr__(self) -> str:
91
+ return f"{self.__class__.__name__}(id={self.id!r}, name={self.name!r})"
92
+
93
+
94
+ class TimestampMixin:
95
+ """Mixin for resources with created/modified timestamps (fields only)."""
96
+ pass
97
+
98
+
99
+ def _get_created_datetime(self: Any) -> Optional[datetime]:
100
+ """Get created_on as datetime object."""
101
+ created_on = getattr(self, 'created_on', None)
102
+ if created_on:
103
+ return datetime.fromtimestamp(created_on / 1000)
104
+ return None
105
+
106
+
107
+ def _get_last_modified_datetime(self: Any) -> Optional[datetime]:
108
+ """Get last_modified_on as datetime object."""
109
+ last_modified_on = getattr(self, 'last_modified_on', None)
110
+ if last_modified_on:
111
+ return datetime.fromtimestamp(last_modified_on / 1000)
112
+ return None
113
+
114
+
115
+ # ============================================================================
116
+ # Status Model
117
+ # ============================================================================
118
+
119
+ class StatusModel(NamedResource):
120
+ """Collibra Status Model."""
121
+ created_on: Optional[int] = Field(default=None, alias="createdOn")
122
+ last_modified_on: Optional[int] = Field(default=None, alias="lastModifiedOn")
123
+ created_by: Optional[str] = Field(default=None, alias="createdBy")
124
+ last_modified_by: Optional[str] = Field(default=None, alias="lastModifiedBy")
125
+ signifies_approval: Optional[bool] = Field(default=None, alias="signifiesApproval")
126
+
127
+ @property
128
+ def created_datetime(self) -> Optional[datetime]:
129
+ return _get_created_datetime(self)
130
+
131
+ @property
132
+ def last_modified_datetime(self) -> Optional[datetime]:
133
+ return _get_last_modified_datetime(self)
134
+
135
+
136
+ # ============================================================================
137
+ # Asset Type Model
138
+ # ============================================================================
139
+
140
+ class AssetTypeModel(NamedResource):
141
+ """Collibra Asset Type Model."""
142
+ created_on: Optional[int] = Field(default=None, alias="createdOn")
143
+ last_modified_on: Optional[int] = Field(default=None, alias="lastModifiedOn")
144
+ created_by: Optional[str] = Field(default=None, alias="createdBy")
145
+ last_modified_by: Optional[str] = Field(default=None, alias="lastModifiedBy")
146
+ public_id: Optional[str] = Field(default=None, alias="publicId")
147
+ symbol_type: Optional[str] = Field(default=None, alias="symbolType")
148
+ acronym_code: Optional[str] = Field(default=None, alias="acronymCode")
149
+ parent: Optional[ResourceReference] = None
150
+ is_meta: Optional[bool] = Field(default=False, alias="isMeta")
151
+ display_name_enabled: Optional[bool] = Field(default=False, alias="displayNameEnabled")
152
+ rating_enabled: Optional[bool] = Field(default=False, alias="ratingEnabled")
153
+
154
+ @property
155
+ def created_datetime(self) -> Optional[datetime]:
156
+ return _get_created_datetime(self)
157
+
158
+ @property
159
+ def last_modified_datetime(self) -> Optional[datetime]:
160
+ return _get_last_modified_datetime(self)
161
+
162
+
163
+ # ============================================================================
164
+ # Community Model
165
+ # ============================================================================
166
+
167
+ class CommunityModel(NamedResource):
168
+ """Collibra Community Model."""
169
+ created_on: Optional[int] = Field(default=None, alias="createdOn")
170
+ last_modified_on: Optional[int] = Field(default=None, alias="lastModifiedOn")
171
+ created_by: Optional[str] = Field(default=None, alias="createdBy")
172
+ last_modified_by: Optional[str] = Field(default=None, alias="lastModifiedBy")
173
+ parent: Optional[ResourceReference] = None
174
+
175
+ def is_root(self) -> bool:
176
+ """Check if this is a root community (no parent)."""
177
+ return self.parent is None
178
+
179
+ @property
180
+ def created_datetime(self) -> Optional[datetime]:
181
+ return _get_created_datetime(self)
182
+
183
+ @property
184
+ def last_modified_datetime(self) -> Optional[datetime]:
185
+ return _get_last_modified_datetime(self)
186
+
187
+
188
+ # ============================================================================
189
+ # Domain Model
190
+ # ============================================================================
191
+
192
+ class DomainTypeModel(NamedResource):
193
+ """Collibra Domain Type Model."""
194
+ created_on: Optional[int] = Field(default=None, alias="createdOn")
195
+ last_modified_on: Optional[int] = Field(default=None, alias="lastModifiedOn")
196
+ public_id: Optional[str] = Field(default=None, alias="publicId")
197
+
198
+ @property
199
+ def created_datetime(self) -> Optional[datetime]:
200
+ return _get_created_datetime(self)
201
+
202
+ @property
203
+ def last_modified_datetime(self) -> Optional[datetime]:
204
+ return _get_last_modified_datetime(self)
205
+
206
+
207
+ class DomainModel(NamedResource):
208
+ """Collibra Domain Model."""
209
+ created_on: Optional[int] = Field(default=None, alias="createdOn")
210
+ last_modified_on: Optional[int] = Field(default=None, alias="lastModifiedOn")
211
+ created_by: Optional[str] = Field(default=None, alias="createdBy")
212
+ last_modified_by: Optional[str] = Field(default=None, alias="lastModifiedBy")
213
+ type: ResourceReference
214
+ community: ResourceReference
215
+ excluded_from_auto_hyperlinking: bool = Field(
216
+ default=False,
217
+ alias="excludedFromAutoHyperlinking"
218
+ )
219
+
220
+ @property
221
+ def created_datetime(self) -> Optional[datetime]:
222
+ return _get_created_datetime(self)
223
+
224
+ @property
225
+ def last_modified_datetime(self) -> Optional[datetime]:
226
+ return _get_last_modified_datetime(self)
227
+
228
+
229
+ # ============================================================================
230
+ # Asset Model (Core)
231
+ # ============================================================================
232
+
233
+ class AssetModel(NamedResource):
234
+ """
235
+ Collibra Asset Model - the core data object.
236
+
237
+ This model represents a Collibra asset with full type information
238
+ and convenient property accessors.
239
+
240
+ Example:
241
+ >>> asset = conn.asset.get_asset("uuid")
242
+ >>> print(f"Name: {asset.name}")
243
+ >>> print(f"Type: {asset.type.name}")
244
+ >>> print(f"Status: {asset.status.name}")
245
+ >>> print(f"Domain: {asset.domain.name}")
246
+ >>> if asset.is_approved:
247
+ ... print("Asset is approved!")
248
+ """
249
+ created_on: Optional[int] = Field(default=None, alias="createdOn")
250
+ last_modified_on: Optional[int] = Field(default=None, alias="lastModifiedOn")
251
+ created_by: Optional[str] = Field(default=None, alias="createdBy")
252
+ last_modified_by: Optional[str] = Field(default=None, alias="lastModifiedBy")
253
+ display_name: Optional[str] = Field(default=None, alias="displayName")
254
+ type: ResourceReference
255
+ status: ResourceReference
256
+ domain: ResourceReference
257
+ avg_rating: float = Field(default=0.0, alias="avgRating")
258
+ ratings_count: int = Field(default=0, alias="ratingsCount")
259
+ excluded_from_auto_hyperlinking: bool = Field(
260
+ default=False,
261
+ alias="excludedFromAutoHyperlinking"
262
+ )
263
+ articulation_score: Optional[float] = Field(default=None, alias="articulationScore")
264
+
265
+ @property
266
+ def created_datetime(self) -> Optional[datetime]:
267
+ return _get_created_datetime(self)
268
+
269
+ @property
270
+ def last_modified_datetime(self) -> Optional[datetime]:
271
+ return _get_last_modified_datetime(self)
272
+
273
+ @property
274
+ def effective_name(self) -> str:
275
+ """Get display_name if available, otherwise name."""
276
+ return self.display_name or self.name
277
+
278
+ @property
279
+ def type_name(self) -> str:
280
+ """Convenience accessor for type.name."""
281
+ return self.type.name or "Unknown"
282
+
283
+ @property
284
+ def status_name(self) -> str:
285
+ """Convenience accessor for status.name."""
286
+ return self.status.name or "Unknown"
287
+
288
+ @property
289
+ def domain_name(self) -> str:
290
+ """Convenience accessor for domain.name."""
291
+ return self.domain.name or "Unknown"
292
+
293
+ @property
294
+ def has_rating(self) -> bool:
295
+ """Check if asset has any ratings."""
296
+ return self.ratings_count > 0
297
+
298
+
299
+ # ============================================================================
300
+ # User Model
301
+ # ============================================================================
302
+
303
+ class UserModel(NamedResource):
304
+ """Collibra User Model."""
305
+ created_on: Optional[int] = Field(default=None, alias="createdOn")
306
+ last_modified_on: Optional[int] = Field(default=None, alias="lastModifiedOn")
307
+ created_by: Optional[str] = Field(default=None, alias="createdBy")
308
+ last_modified_by: Optional[str] = Field(default=None, alias="lastModifiedBy")
309
+ first_name: Optional[str] = Field(default=None, alias="firstName")
310
+ last_name: Optional[str] = Field(default=None, alias="lastName")
311
+ email_address: Optional[str] = Field(default=None, alias="emailAddress")
312
+ gender: Optional[str] = None
313
+ language: Optional[str] = None
314
+ activated: bool = False
315
+ enabled: bool = False
316
+ ldap_user: Optional[bool] = Field(default=None, alias="ldapUser")
317
+ guest_user: Optional[bool] = Field(default=None, alias="guestUser")
318
+ api_user: Optional[bool] = Field(default=None, alias="apiUser")
319
+
320
+ @property
321
+ def created_datetime(self) -> Optional[datetime]:
322
+ return _get_created_datetime(self)
323
+
324
+ @property
325
+ def last_modified_datetime(self) -> Optional[datetime]:
326
+ return _get_last_modified_datetime(self)
327
+
328
+ @property
329
+ def full_name(self) -> str:
330
+ """Get user's full name."""
331
+ parts = [self.first_name, self.last_name]
332
+ return " ".join(p for p in parts if p) or self.name
333
+
334
+ @property
335
+ def is_active(self) -> bool:
336
+ """Check if user is both activated and enabled."""
337
+ return self.activated and self.enabled
338
+
339
+
340
+ # ============================================================================
341
+ # Attribute Model
342
+ # ============================================================================
343
+
344
+ class AttributeTypeModel(NamedResource):
345
+ """Collibra Attribute Type Model."""
346
+ created_on: Optional[int] = Field(default=None, alias="createdOn")
347
+ last_modified_on: Optional[int] = Field(default=None, alias="lastModifiedOn")
348
+ public_id: Optional[str] = Field(default=None, alias="publicId")
349
+ kind: Optional[str] = None
350
+ statistics_enabled: Optional[bool] = Field(default=False, alias="statisticsEnabled")
351
+ is_integer: Optional[bool] = Field(default=False, alias="isInteger")
352
+ allowed_values: Optional[List[str]] = Field(default=None, alias="allowedValues")
353
+
354
+ @property
355
+ def created_datetime(self) -> Optional[datetime]:
356
+ return _get_created_datetime(self)
357
+
358
+ @property
359
+ def last_modified_datetime(self) -> Optional[datetime]:
360
+ return _get_last_modified_datetime(self)
361
+
362
+
363
+ class AttributeModel(BaseCollibraModel):
364
+ """Collibra Attribute Model."""
365
+ id: str
366
+ resource_type: Optional[str] = Field(default=None, alias="resourceType")
367
+ created_on: Optional[int] = Field(default=None, alias="createdOn")
368
+ last_modified_on: Optional[int] = Field(default=None, alias="lastModifiedOn")
369
+ type: ResourceReference
370
+ value: Any
371
+ asset: Optional[ResourceReference] = None
372
+
373
+ @property
374
+ def created_datetime(self) -> Optional[datetime]:
375
+ return _get_created_datetime(self)
376
+
377
+ @property
378
+ def last_modified_datetime(self) -> Optional[datetime]:
379
+ return _get_last_modified_datetime(self)
380
+
381
+ @property
382
+ def type_name(self) -> str:
383
+ """Convenience accessor for type.name."""
384
+ return self.type.name or "Unknown"
385
+
386
+ @property
387
+ def string_value(self) -> str:
388
+ """Get value as string."""
389
+ if self.value is None:
390
+ return ""
391
+ return str(self.value)
392
+
393
+
394
+ # ============================================================================
395
+ # Relation Model
396
+ # ============================================================================
397
+
398
+ class RelationTypeModel(NamedResource):
399
+ """Collibra Relation Type Model."""
400
+ created_on: Optional[int] = Field(default=None, alias="createdOn")
401
+ last_modified_on: Optional[int] = Field(default=None, alias="lastModifiedOn")
402
+ public_id: Optional[str] = Field(default=None, alias="publicId")
403
+ role: Optional[str] = None
404
+ co_role: Optional[str] = Field(default=None, alias="coRole")
405
+ source_type: Optional[ResourceReference] = Field(default=None, alias="sourceType")
406
+ target_type: Optional[ResourceReference] = Field(default=None, alias="targetType")
407
+
408
+ @property
409
+ def created_datetime(self) -> Optional[datetime]:
410
+ return _get_created_datetime(self)
411
+
412
+ @property
413
+ def last_modified_datetime(self) -> Optional[datetime]:
414
+ return _get_last_modified_datetime(self)
415
+
416
+
417
+ class RelationModel(BaseCollibraModel):
418
+ """Collibra Relation Model."""
419
+ id: str
420
+ resource_type: Optional[str] = Field(default=None, alias="resourceType")
421
+ created_on: Optional[int] = Field(default=None, alias="createdOn")
422
+ last_modified_on: Optional[int] = Field(default=None, alias="lastModifiedOn")
423
+ type: ResourceReference
424
+ source: ResourceReference
425
+ target: ResourceReference
426
+ starting_date: Optional[int] = Field(default=None, alias="startingDate")
427
+ ending_date: Optional[int] = Field(default=None, alias="endingDate")
428
+
429
+ @property
430
+ def created_datetime(self) -> Optional[datetime]:
431
+ return _get_created_datetime(self)
432
+
433
+ @property
434
+ def last_modified_datetime(self) -> Optional[datetime]:
435
+ return _get_last_modified_datetime(self)
436
+
437
+ @property
438
+ def type_name(self) -> str:
439
+ """Convenience accessor for type.name."""
440
+ return self.type.name or "Unknown"
441
+
442
+
443
+ # ============================================================================
444
+ # Responsibility Model
445
+ # ============================================================================
446
+
447
+ class RoleModel(NamedResource):
448
+ """Collibra Role Model."""
449
+ created_on: Optional[int] = Field(default=None, alias="createdOn")
450
+ last_modified_on: Optional[int] = Field(default=None, alias="lastModifiedOn")
451
+ global_role: Optional[bool] = Field(default=False, alias="global")
452
+
453
+ @property
454
+ def created_datetime(self) -> Optional[datetime]:
455
+ return _get_created_datetime(self)
456
+
457
+ @property
458
+ def last_modified_datetime(self) -> Optional[datetime]:
459
+ return _get_last_modified_datetime(self)
460
+
461
+
462
+ class ResponsibilityModel(BaseCollibraModel):
463
+ """Collibra Responsibility Model."""
464
+ id: str
465
+ resource_type: Optional[str] = Field(default=None, alias="resourceType")
466
+ created_on: Optional[int] = Field(default=None, alias="createdOn")
467
+ last_modified_on: Optional[int] = Field(default=None, alias="lastModifiedOn")
468
+ role: ResourceReference
469
+ owner: ResourceReference
470
+ resource: Optional[ResourceReference] = None
471
+ base_resource: Optional[ResourceReference] = Field(default=None, alias="baseResource")
472
+
473
+ @property
474
+ def created_datetime(self) -> Optional[datetime]:
475
+ return _get_created_datetime(self)
476
+
477
+ @property
478
+ def last_modified_datetime(self) -> Optional[datetime]:
479
+ return _get_last_modified_datetime(self)
480
+
481
+ @property
482
+ def role_name(self) -> str:
483
+ """Convenience accessor for role.name."""
484
+ return self.role.name or "Unknown"
485
+
486
+ @property
487
+ def owner_name(self) -> str:
488
+ """Convenience accessor for owner.name."""
489
+ return self.owner.name or "Unknown"
490
+
491
+
492
+ # ============================================================================
493
+ # Comment Model
494
+ # ============================================================================
495
+
496
+ class CommentModel(BaseCollibraModel):
497
+ """Collibra Comment Model."""
498
+ id: str
499
+ resource_type: Optional[str] = Field(default=None, alias="resourceType")
500
+ created_on: Optional[int] = Field(default=None, alias="createdOn")
501
+ last_modified_on: Optional[int] = Field(default=None, alias="lastModifiedOn")
502
+ created_by: Optional[str] = Field(default=None, alias="createdBy")
503
+ content: str
504
+ base_resource: Optional[ResourceReference] = Field(default=None, alias="baseResource")
505
+ parent: Optional[ResourceReference] = None
506
+
507
+ @property
508
+ def created_datetime(self) -> Optional[datetime]:
509
+ return _get_created_datetime(self)
510
+
511
+ @property
512
+ def last_modified_datetime(self) -> Optional[datetime]:
513
+ return _get_last_modified_datetime(self)
514
+
515
+
516
+ # ============================================================================
517
+ # Search Models
518
+ # ============================================================================
519
+
520
+ class SearchHighlight(BaseCollibraModel):
521
+ """Search result highlight."""
522
+ field: Optional[str] = None
523
+ values: Optional[List[str]] = None
524
+
525
+
526
+ class SearchResource(BaseCollibraModel):
527
+ """The resource found in search."""
528
+ id: str
529
+ resource_type: Optional[str] = Field(default=None, alias="resourceType")
530
+ name: Optional[str] = None
531
+ display_name: Optional[str] = Field(default=None, alias="displayName")
532
+ description: Optional[str] = None
533
+
534
+ @property
535
+ def effective_name(self) -> str:
536
+ """Get display_name if available, otherwise name."""
537
+ return self.display_name or self.name or "Unnamed"
538
+
539
+
540
+ class SearchResultModel(BaseCollibraModel):
541
+ """Model for a single search result."""
542
+ resource: SearchResource
543
+ highlights: Optional[List[SearchHighlight]] = None
544
+ score: Optional[float] = 0.0
545
+
546
+ @property
547
+ def id(self) -> str:
548
+ """Convenience accessor for resource.id."""
549
+ return self.resource.id
550
+
551
+ @property
552
+ def name(self) -> str:
553
+ """Convenience accessor for resource name."""
554
+ return self.resource.effective_name
555
+
556
+
557
+ # ============================================================================
558
+ # Workflow Models
559
+ # ============================================================================
560
+
561
+ class WorkflowDefinitionModel(NamedResource):
562
+ """Collibra Workflow Definition Model."""
563
+ created_on: Optional[int] = Field(default=None, alias="createdOn")
564
+ last_modified_on: Optional[int] = Field(default=None, alias="lastModifiedOn")
565
+ enabled: bool = False
566
+ start_events: Optional[List[str]] = Field(default=None, alias="startEvents")
567
+ process_id: Optional[str] = Field(default=None, alias="processId")
568
+ guest_user_accessible: Optional[bool] = Field(default=False, alias="guestUserAccessible")
569
+ registered_user_accessible: Optional[bool] = Field(default=False, alias="registeredUserAccessible")
570
+ candidate_user_check_disabled: Optional[bool] = Field(default=False, alias="candidateUserCheckDisabled")
571
+ global_create: Optional[bool] = Field(default=False, alias="globalCreate")
572
+ start_label: Optional[str] = Field(default=None, alias="startLabel")
573
+ domain_type: Optional[ResourceReference] = Field(default=None, alias="domainType")
574
+ asset_type: Optional[ResourceReference] = Field(default=None, alias="assetType")
575
+ community: Optional[ResourceReference] = None
576
+ domain: Optional[ResourceReference] = None
577
+ form_required: Optional[bool] = Field(default=False, alias="formRequired")
578
+ business_item_resource_type: Optional[str] = Field(default=None, alias="businessItemResourceType")
579
+ exclusivity: Optional[str] = None
580
+
581
+ @property
582
+ def created_datetime(self) -> Optional[datetime]:
583
+ return _get_created_datetime(self)
584
+
585
+ @property
586
+ def last_modified_datetime(self) -> Optional[datetime]:
587
+ return _get_last_modified_datetime(self)
588
+
589
+
590
+ class WorkflowInstanceModel(BaseCollibraModel):
591
+ """Collibra Workflow Instance Model."""
592
+ id: str
593
+ resource_type: Optional[str] = Field(default=None, alias="resourceType")
594
+ created_on: Optional[int] = Field(default=None, alias="createdOn")
595
+ last_modified_on: Optional[int] = Field(default=None, alias="lastModifiedOn")
596
+ workflow_definition: Optional[ResourceReference] = Field(default=None, alias="workflowDefinition")
597
+ business_item: Optional[ResourceReference] = Field(default=None, alias="businessItem")
598
+ start_date: Optional[int] = Field(default=None, alias="startDate")
599
+ ended: bool = False
600
+ in_error: bool = Field(default=False, alias="inError")
601
+ sub_process_instances_count: int = Field(default=0, alias="subProcessInstancesCount")
602
+
603
+ @property
604
+ def created_datetime(self) -> Optional[datetime]:
605
+ return _get_created_datetime(self)
606
+
607
+ @property
608
+ def last_modified_datetime(self) -> Optional[datetime]:
609
+ return _get_last_modified_datetime(self)
610
+
611
+
612
+ class WorkflowTaskModel(BaseCollibraModel):
613
+ """Collibra Workflow Task Model."""
614
+ id: str
615
+ resource_type: Optional[str] = Field(default=None, alias="resourceType")
616
+ created_on: Optional[int] = Field(default=None, alias="createdOn")
617
+ last_modified_on: Optional[int] = Field(default=None, alias="lastModifiedOn")
618
+ key: Optional[str] = None
619
+ type: Optional[str] = None
620
+ title: Optional[str] = None
621
+ description: Optional[str] = None
622
+ due_date: Optional[int] = Field(default=None, alias="dueDate")
623
+ priority: Optional[int] = None
624
+ cancelable: bool = False
625
+ reassignable: bool = False
626
+ form_required: Optional[bool] = Field(default=False, alias="formRequired")
627
+ owner: Optional[ResourceReference] = None
628
+ candidate_users: Optional[List[ResourceReference]] = Field(default=None, alias="candidateUsers")
629
+ workflow_instance: Optional[ResourceReference] = Field(default=None, alias="workflowInstance")
630
+ workflow_definition: Optional[ResourceReference] = Field(default=None, alias="workflowDefinition")
631
+ business_item: Optional[ResourceReference] = Field(default=None, alias="businessItem")
632
+
633
+ @property
634
+ def created_datetime(self) -> Optional[datetime]:
635
+ return _get_created_datetime(self)
636
+
637
+ @property
638
+ def last_modified_datetime(self) -> Optional[datetime]:
639
+ return _get_last_modified_datetime(self)
640
+
641
+
642
+ # ============================================================================
643
+ # Paginated Response Model
644
+ # ============================================================================
645
+
646
+ class PaginatedResponseModel(BaseCollibraModel, Generic[T]):
647
+ """
648
+ Generic paginated response model.
649
+
650
+ This model wraps API responses that return paginated results,
651
+ providing convenient iteration and pagination helpers.
652
+
653
+ Example:
654
+ >>> response = conn.asset.find_assets()
655
+ >>> print(f"Total: {response.total}")
656
+ >>> for asset in response:
657
+ ... print(asset.name)
658
+ >>> if response.has_more:
659
+ ... next_page = conn.asset.find_assets(offset=response.next_offset)
660
+ """
661
+ results: List[T] = Field(default_factory=list)
662
+ total: int = 0
663
+ offset: int = 0
664
+ limit: int = 0
665
+
666
+ @property
667
+ def has_more(self) -> bool:
668
+ """Check if there are more results available."""
669
+ if self.limit <= 0:
670
+ return False
671
+ return self.offset + self.limit < self.total
672
+
673
+ @property
674
+ def next_offset(self) -> int:
675
+ """Get the offset for the next page."""
676
+ return self.offset + len(self.results)
677
+
678
+ @property
679
+ def page_count(self) -> int:
680
+ """Get the total number of pages."""
681
+ if self.limit <= 0:
682
+ return 1
683
+ return (self.total + self.limit - 1) // self.limit
684
+
685
+ @property
686
+ def current_page(self) -> int:
687
+ """Get the current page number (1-indexed)."""
688
+ if self.limit <= 0:
689
+ return 1
690
+ return (self.offset // self.limit) + 1
691
+
692
+ def __len__(self) -> int:
693
+ """Return the number of results in this page."""
694
+ return len(self.results)
695
+
696
+ def __iter__(self) -> Iterator[T]:
697
+ """Iterate over results in this page."""
698
+ return iter(self.results)
699
+
700
+ def __getitem__(self, index: int) -> T:
701
+ """Get a result by index."""
702
+ return self.results[index]
703
+
704
+ def __bool__(self) -> bool:
705
+ """Check if there are any results."""
706
+ return len(self.results) > 0
707
+
708
+
709
+ # ============================================================================
710
+ # Full Asset Profile Model
711
+ # ============================================================================
712
+
713
+ class RelationSummary(BaseCollibraModel):
714
+ """Summary of a relation for an asset."""
715
+ id: str
716
+ target_id: Optional[str] = None
717
+ target_name: Optional[str] = None
718
+ target_type: Optional[str] = None
719
+ target_status: Optional[str] = None
720
+ source_id: Optional[str] = None
721
+ source_name: Optional[str] = None
722
+ source_type: Optional[str] = None
723
+ source_status: Optional[str] = None
724
+
725
+
726
+ class RelationsGrouped(BaseCollibraModel):
727
+ """Relations grouped by direction and type."""
728
+ outgoing: Dict[str, List[RelationSummary]] = Field(default_factory=dict)
729
+ incoming: Dict[str, List[RelationSummary]] = Field(default_factory=dict)
730
+ outgoing_count: int = 0
731
+ incoming_count: int = 0
732
+
733
+
734
+ class ResponsibilitySummary(BaseCollibraModel):
735
+ """Summary of a responsibility assignment."""
736
+ role: str
737
+ owner: str
738
+ owner_id: Optional[str] = None
739
+
740
+
741
+ class AssetProfileModel(BaseCollibraModel):
742
+ """
743
+ Complete asset profile with all related information.
744
+
745
+ This model provides a comprehensive view of an asset including
746
+ its attributes, relations, responsibilities, and more.
747
+
748
+ Example:
749
+ >>> profile = conn.asset.get_full_profile("uuid")
750
+ >>> print(profile.asset.name)
751
+ >>> print(profile.attributes.get("Description"))
752
+ >>> for rel_type, targets in profile.relations.outgoing.items():
753
+ ... print(f"{rel_type}: {len(targets)} targets")
754
+ """
755
+ asset: AssetModel
756
+ attributes: Dict[str, Any] = Field(default_factory=dict)
757
+ relations: RelationsGrouped = Field(default_factory=RelationsGrouped)
758
+ responsibilities: List[ResponsibilitySummary] = Field(default_factory=list)
759
+ comments: List[CommentModel] = Field(default_factory=list)
760
+ activities: List[Dict[str, Any]] = Field(default_factory=list)
761
+
762
+ @property
763
+ def description(self) -> Optional[str]:
764
+ """Get the Description attribute if available."""
765
+ return self.attributes.get("Description")
766
+
767
+ @property
768
+ def data_steward(self) -> Optional[str]:
769
+ """Get the Data Steward if assigned."""
770
+ for resp in self.responsibilities:
771
+ if "steward" in resp.role.lower():
772
+ return resp.owner
773
+ return None
774
+
775
+
776
+ # ============================================================================
777
+ # Type Aliases for common patterns
778
+ # ============================================================================
779
+
780
+ AssetList = PaginatedResponseModel[AssetModel]
781
+ DomainList = PaginatedResponseModel[DomainModel]
782
+ CommunityList = PaginatedResponseModel[CommunityModel]
783
+ UserList = PaginatedResponseModel[UserModel]
784
+ AttributeList = PaginatedResponseModel[AttributeModel]
785
+ RelationList = PaginatedResponseModel[RelationModel]
786
+ SearchResults = PaginatedResponseModel[SearchResultModel]
787
+
788
+
789
+ # ============================================================================
790
+ # Model Factory Functions
791
+ # ============================================================================
792
+
793
+ def parse_asset(data: Dict[str, Any]) -> AssetModel:
794
+ """Parse a dictionary into an AssetModel."""
795
+ return AssetModel.model_validate(data)
796
+
797
+
798
+ def parse_assets(data: Dict[str, Any]) -> AssetList:
799
+ """Parse a paginated response into AssetList."""
800
+ results = [AssetModel.model_validate(item) for item in data.get("results", [])]
801
+ return PaginatedResponseModel[AssetModel](
802
+ results=results,
803
+ total=data.get("total", 0),
804
+ offset=data.get("offset", 0),
805
+ limit=data.get("limit", 0)
806
+ )
807
+
808
+
809
+ def parse_domain(data: Dict[str, Any]) -> DomainModel:
810
+ """Parse a dictionary into a DomainModel."""
811
+ return DomainModel.model_validate(data)
812
+
813
+
814
+ def parse_domains(data: Dict[str, Any]) -> DomainList:
815
+ """Parse a paginated response into DomainList."""
816
+ results = [DomainModel.model_validate(item) for item in data.get("results", [])]
817
+ return PaginatedResponseModel[DomainModel](
818
+ results=results,
819
+ total=data.get("total", 0),
820
+ offset=data.get("offset", 0),
821
+ limit=data.get("limit", 0)
822
+ )
823
+
824
+
825
+ def parse_community(data: Dict[str, Any]) -> CommunityModel:
826
+ """Parse a dictionary into a CommunityModel."""
827
+ return CommunityModel.model_validate(data)
828
+
829
+
830
+ def parse_communities(data: Dict[str, Any]) -> CommunityList:
831
+ """Parse a paginated response into CommunityList."""
832
+ results = [CommunityModel.model_validate(item) for item in data.get("results", [])]
833
+ return PaginatedResponseModel[CommunityModel](
834
+ results=results,
835
+ total=data.get("total", 0),
836
+ offset=data.get("offset", 0),
837
+ limit=data.get("limit", 0)
838
+ )
839
+
840
+
841
+ def parse_user(data: Dict[str, Any]) -> UserModel:
842
+ """Parse a dictionary into a UserModel."""
843
+ return UserModel.model_validate(data)
844
+
845
+
846
+ def parse_users(data: Dict[str, Any]) -> UserList:
847
+ """Parse a paginated response into UserList."""
848
+ results = [UserModel.model_validate(item) for item in data.get("results", [])]
849
+ return PaginatedResponseModel[UserModel](
850
+ results=results,
851
+ total=data.get("total", 0),
852
+ offset=data.get("offset", 0),
853
+ limit=data.get("limit", 0)
854
+ )
855
+
856
+
857
+ def parse_attribute(data: Dict[str, Any]) -> AttributeModel:
858
+ """Parse a dictionary into an AttributeModel."""
859
+ return AttributeModel.model_validate(data)
860
+
861
+
862
+ def parse_attributes(data: Dict[str, Any]) -> AttributeList:
863
+ """Parse a paginated response into AttributeList."""
864
+ results = [AttributeModel.model_validate(item) for item in data.get("results", [])]
865
+ return PaginatedResponseModel[AttributeModel](
866
+ results=results,
867
+ total=data.get("total", 0),
868
+ offset=data.get("offset", 0),
869
+ limit=data.get("limit", 0)
870
+ )
871
+
872
+
873
+ def parse_relation(data: Dict[str, Any]) -> RelationModel:
874
+ """Parse a dictionary into a RelationModel."""
875
+ return RelationModel.model_validate(data)
876
+
877
+
878
+ def parse_relations(data: Dict[str, Any]) -> RelationList:
879
+ """Parse a paginated response into RelationList."""
880
+ results = [RelationModel.model_validate(item) for item in data.get("results", [])]
881
+ return PaginatedResponseModel[RelationModel](
882
+ results=results,
883
+ total=data.get("total", 0),
884
+ offset=data.get("offset", 0),
885
+ limit=data.get("limit", 0)
886
+ )
887
+
888
+
889
+ def parse_search_results(data: Dict[str, Any]) -> SearchResults:
890
+ """Parse a paginated response into SearchResults."""
891
+ results = [SearchResultModel.model_validate(item) for item in data.get("results", [])]
892
+ return PaginatedResponseModel[SearchResultModel](
893
+ results=results,
894
+ total=data.get("total", 0),
895
+ offset=data.get("offset", 0),
896
+ limit=data.get("limit", 0)
897
+ )