tallyfy 1.0.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.

Potentially problematic release.


This version of tallyfy might be problematic. Click here for more details.

tallyfy/models.py ADDED
@@ -0,0 +1,1013 @@
1
+ """
2
+ Data models for Tallyfy API responses
3
+ """
4
+
5
+ from typing import Dict, Any, Optional, List
6
+ from dataclasses import dataclass
7
+
8
+
9
+ class TallyfyError(Exception):
10
+ """Custom exception for Tallyfy API errors"""
11
+
12
+ def __init__(self, message: str, status_code: Optional[int] = None, response_data: Optional[Any] = None):
13
+ super().__init__(message)
14
+ self.status_code = status_code
15
+ self.response_data = response_data
16
+
17
+
18
+ @dataclass
19
+ class Country:
20
+ """Country data model"""
21
+ id: int
22
+ name: str
23
+ phone_code: str
24
+ iso2: str
25
+
26
+ @classmethod
27
+ def from_dict(cls, data: Dict[str, Any]) -> 'Country':
28
+ """Create Country instance from dictionary"""
29
+ return cls(
30
+ id=data.get('id', 0),
31
+ name=data.get('name', ''),
32
+ phone_code=data.get('phone_code', ''),
33
+ iso2=data.get('iso2', '')
34
+ )
35
+
36
+
37
+ @dataclass
38
+ class User:
39
+ """User data model"""
40
+ id: int
41
+ email: str
42
+ username: str
43
+ first_name: str
44
+ last_name: str
45
+ full_name: str
46
+ profile_pic: Optional[str] = None
47
+ locale: Optional[str] = None
48
+ active: bool = True
49
+ is_suspended: bool = False
50
+ created_at: Optional[str] = None
51
+ last_updated: Optional[str] = None
52
+ last_login_at: Optional[str] = None
53
+ activated_at: Optional[str] = None
54
+ support_user: bool = False
55
+ country: Optional[Country] = None
56
+ phone: Optional[str] = None
57
+ job_title: Optional[str] = None
58
+ job_description: Optional[str] = None
59
+ team: Optional[str] = None
60
+ timezone: Optional[str] = None
61
+ UTC_offset: Optional[str] = None
62
+ last_accessed_at: Optional[str] = None
63
+ approved_at: Optional[str] = None
64
+ invited_by: Optional[int] = None
65
+ disabled_at: Optional[str] = None
66
+ disabled_by: Optional[int] = None
67
+ reactivated_at: Optional[str] = None
68
+ reactivated_by: Optional[int] = None
69
+ status: Optional[str] = None
70
+ date_format: Optional[str] = None
71
+ last_known_ip: Optional[str] = None
72
+ last_known_country: Optional[str] = None
73
+
74
+ @classmethod
75
+ def from_dict(cls, data: Dict[str, Any]) -> 'User':
76
+ """Create User instance from dictionary"""
77
+ country_data = data.get('country')
78
+ country = Country.from_dict(country_data) if country_data else None
79
+
80
+ return cls(
81
+ id=data.get('id', 0),
82
+ email=data.get('email', ''),
83
+ username=data.get('username', ''),
84
+ first_name=data.get('first_name', ''),
85
+ last_name=data.get('last_name', ''),
86
+ full_name=data.get('full_name', ''),
87
+ profile_pic=data.get('profile_pic'),
88
+ locale=data.get('locale'),
89
+ active=data.get('active', True),
90
+ is_suspended=data.get('is_suspended', False),
91
+ created_at=data.get('created_at'),
92
+ last_updated=data.get('last_updated'),
93
+ last_login_at=data.get('last_login_at'),
94
+ activated_at=data.get('activated_at'),
95
+ support_user=data.get('support_user', False),
96
+ country=country,
97
+ phone=data.get('phone'),
98
+ job_title=data.get('job_title'),
99
+ job_description=data.get('job_description'),
100
+ team=data.get('team'),
101
+ timezone=data.get('timezone'),
102
+ UTC_offset=data.get('UTC_offset'),
103
+ last_accessed_at=data.get('last_accessed_at'),
104
+ approved_at=data.get('approved_at'),
105
+ invited_by=data.get('invited_by'),
106
+ disabled_at=data.get('disabled_at'),
107
+ disabled_by=data.get('disabled_by'),
108
+ reactivated_at=data.get('reactivated_at'),
109
+ reactivated_by=data.get('reactivated_by'),
110
+ status=data.get('status'),
111
+ date_format=data.get('date_format'),
112
+ last_known_ip=data.get('last_known_ip'),
113
+ last_known_country=data.get('last_known_country')
114
+ )
115
+
116
+
117
+ @dataclass
118
+ class GuestDetails:
119
+ """Guest details data model"""
120
+ first_name: Optional[str] = None
121
+ last_name: Optional[str] = None
122
+ status: Optional[str] = None
123
+ phone_1: Optional[str] = None
124
+ phone_2: Optional[str] = None
125
+ image_url: Optional[str] = None
126
+ contact_url: Optional[str] = None
127
+ company_url: Optional[str] = None
128
+ opportunity_url: Optional[str] = None
129
+ company_name: Optional[str] = None
130
+ opportunity_name: Optional[str] = None
131
+ external_sync_source: Optional[str] = None
132
+ external_date_creation: Optional[str] = None
133
+ timezone: Optional[str] = None
134
+ reactivated_at: Optional[str] = None
135
+ reactivated_by: Optional[int] = None
136
+ disabled_on: Optional[str] = None
137
+ disabled_by: Optional[int] = None
138
+
139
+ @classmethod
140
+ def from_dict(cls, data: Dict[str, Any]) -> 'GuestDetails':
141
+ """Create GuestDetails instance from dictionary"""
142
+ return cls(
143
+ first_name=data.get('first_name'),
144
+ last_name=data.get('last_name'),
145
+ status=data.get('status'),
146
+ phone_1=data.get('phone_1'),
147
+ phone_2=data.get('phone_2'),
148
+ image_url=data.get('image_url'),
149
+ contact_url=data.get('contact_url'),
150
+ company_url=data.get('company_url'),
151
+ opportunity_url=data.get('opportunity_url'),
152
+ company_name=data.get('company_name'),
153
+ opportunity_name=data.get('opportunity_name'),
154
+ external_sync_source=data.get('external_sync_source'),
155
+ external_date_creation=data.get('external_date_creation'),
156
+ timezone=data.get('timezone'),
157
+ reactivated_at=data.get('reactivated_at'),
158
+ reactivated_by=data.get('reactivated_by'),
159
+ disabled_on=data.get('disabled_on'),
160
+ disabled_by=data.get('disabled_by')
161
+ )
162
+
163
+
164
+ @dataclass
165
+ class Guest:
166
+ """Guest data model"""
167
+ email: str
168
+ last_accessed_at: Optional[str] = None
169
+ last_known_ip: Optional[str] = None
170
+ last_known_country: Optional[str] = None
171
+ details: Optional[GuestDetails] = None
172
+
173
+ @classmethod
174
+ def from_dict(cls, data: Dict[str, Any]) -> 'Guest':
175
+ """Create Guest instance from dictionary"""
176
+ details_data = data.get('details')
177
+ details = GuestDetails.from_dict(details_data) if details_data else None
178
+
179
+ return cls(
180
+ email=data.get('email', ''),
181
+ last_accessed_at=data.get('last_accessed_at'),
182
+ last_known_ip=data.get('last_known_ip'),
183
+ last_known_country=data.get('last_known_country'),
184
+ details=details
185
+ )
186
+
187
+
188
+ @dataclass
189
+ class TaskOwners:
190
+ """Task owners data model"""
191
+ users: List[int] = None
192
+ guests: List[str] = None
193
+ groups: List[int] = None
194
+ task_urls: List[str] = None
195
+
196
+ def __post_init__(self):
197
+ """Initialize empty lists if None"""
198
+ if self.users is None:
199
+ self.users = []
200
+ if self.guests is None:
201
+ self.guests = []
202
+ if self.groups is None:
203
+ self.groups = []
204
+ if self.task_urls is None:
205
+ self.task_urls = []
206
+
207
+ @classmethod
208
+ def from_dict(cls, data: Dict[str, Any]) -> 'TaskOwners':
209
+ """Create TaskOwners instance from dictionary"""
210
+ return cls(
211
+ users=data.get('users', []),
212
+ guests=data.get('guests', []),
213
+ groups=data.get('groups', []),
214
+ task_urls=data.get('taskUrls', [])
215
+ )
216
+
217
+
218
+ @dataclass
219
+ class Task:
220
+ """Task data model"""
221
+ id: str
222
+ increment_id: int
223
+ title: str
224
+ original_title: str
225
+ allow_guest_owners: bool
226
+ run_id: Optional[str] = None
227
+ checklist_id: Optional[str] = None
228
+ linked_step_id: Optional[str] = None
229
+ step_id: Optional[str] = None
230
+ alias: Optional[str] = None
231
+ taskdata: Optional[Dict[str, Any]] = None
232
+ owners: Optional[TaskOwners] = None
233
+ is_completable: bool = True
234
+ status: str = "not-started"
235
+ status_label: str = "not-started"
236
+ task_type: str = "task"
237
+ is_approved: Optional[bool] = None
238
+ position: int = 0
239
+ started_at: Optional[str] = None
240
+ deadline: Optional[str] = None
241
+ created_at: Optional[str] = None
242
+ last_updated: Optional[str] = None
243
+ archived_at: Optional[str] = None
244
+ completed_at: Optional[str] = None
245
+ starter_id: Optional[int] = None
246
+ completer_id: Optional[int] = None
247
+ is_oneoff_task: bool = False
248
+ everyone_must_complete: bool = False
249
+ completion_progress: Optional[float] = None
250
+ has_deadline_dependent_child_tasks: bool = False
251
+ can_complete_only_assignees: bool = False
252
+ max_assignable: int = 0
253
+ webhook: Optional[str] = None
254
+ prevent_guest_comment: bool = False
255
+ stage_id: Optional[str] = None
256
+ problem: bool = False
257
+ blueprint_position: Optional[int] = None
258
+ is_soft_start_date: bool = False
259
+ send_chromeless: Optional[bool] = None
260
+ run_status: Optional[str] = None
261
+ completer_guest: Optional[str] = None
262
+
263
+ @classmethod
264
+ def from_dict(cls, data: Dict[str, Any]) -> 'Task':
265
+ """Create Task instance from dictionary"""
266
+ owners_data = data.get('owners')
267
+ owners = TaskOwners.from_dict(owners_data) if owners_data else None
268
+
269
+ return cls(
270
+ id=data.get('id', ''),
271
+ increment_id=data.get('increment_id', 0),
272
+ title=data.get('title', ''),
273
+ original_title=data.get('original_title', ''),
274
+ allow_guest_owners=data.get('allow_guest_owners', False),
275
+ run_id=data.get('run_id'),
276
+ checklist_id=data.get('checklist_id'),
277
+ linked_step_id=data.get('linked_step_id'),
278
+ step_id=data.get('step_id'),
279
+ alias=data.get('alias'),
280
+ taskdata=data.get('taskdata', {}),
281
+ owners=owners,
282
+ is_completable=data.get('is_completable', True),
283
+ status=data.get('status', 'not-started'),
284
+ status_label=data.get('status_label', 'not-started'),
285
+ task_type=data.get('task_type', 'task'),
286
+ is_approved=data.get('is_approved'),
287
+ position=data.get('position', 0),
288
+ started_at=data.get('started_at'),
289
+ deadline=data.get('deadline'),
290
+ created_at=data.get('created_at'),
291
+ last_updated=data.get('last_updated'),
292
+ archived_at=data.get('archived_at'),
293
+ completed_at=data.get('completed_at'),
294
+ starter_id=data.get('starter_id'),
295
+ completer_id=data.get('completer_id'),
296
+ is_oneoff_task=data.get('is_oneoff_task', False),
297
+ everyone_must_complete=data.get('everyone_must_complete', False),
298
+ completion_progress=data.get('completion_progress'),
299
+ has_deadline_dependent_child_tasks=data.get('has_deadline_dependent_child_tasks', False),
300
+ can_complete_only_assignees=data.get('can_complete_only_assignees', False),
301
+ max_assignable=data.get('max_assignable', 0),
302
+ webhook=data.get('webhook'),
303
+ prevent_guest_comment=data.get('prevent_guest_comment', False),
304
+ stage_id=data.get('stage_id'),
305
+ problem=data.get('problem', False),
306
+ blueprint_position=data.get('blueprint_position'),
307
+ is_soft_start_date=data.get('is_soft_start_date', False),
308
+ send_chromeless=data.get('send_chromeless'),
309
+ run_status=data.get('run_status'),
310
+ completer_guest=data.get('completer_guest')
311
+ )
312
+
313
+
314
+ @dataclass
315
+ class RunProgress:
316
+ """Run progress data model"""
317
+ complete: int = 0
318
+ total: int = 0
319
+ percent: float = 0.0
320
+
321
+ @classmethod
322
+ def from_dict(cls, data: Dict[str, Any]) -> 'RunProgress':
323
+ """Create RunProgress instance from dictionary"""
324
+ return cls(
325
+ complete=data.get('complete', 0),
326
+ total=data.get('total', 0),
327
+ percent=data.get('percent', 0.0)
328
+ )
329
+
330
+
331
+ @dataclass
332
+ class Run:
333
+ """Run (Process) data model"""
334
+ id: str
335
+ increment_id: int
336
+ checklist_id: str
337
+ checklist_title: str
338
+ assign_someone_else: bool
339
+ name: str
340
+ summary: Optional[str] = None
341
+ status: str = "active"
342
+ progress: Optional[RunProgress] = None
343
+ whole_progress: Optional[RunProgress] = None
344
+ started_by: Optional[int] = None
345
+ prerun: Optional[Dict[str, Any]] = None
346
+ prerun_completed_at: Optional[str] = None
347
+ prerun_completed_by: Optional[int] = None
348
+ prerun_length: Optional[int] = None
349
+ starred: bool = False
350
+ created_at: Optional[str] = None
351
+ due_date: Optional[str] = None
352
+ owner_id: Optional[int] = None
353
+ started_at: Optional[str] = None
354
+ last_updated: Optional[str] = None
355
+ completed_at: Optional[str] = None
356
+ late_tasks: Optional[int] = None
357
+ archived_at: Optional[str] = None
358
+ due_date_passed: bool = False
359
+ collaborators: List[int] = None
360
+ due_soon: bool = False
361
+ max_task_deadline: Optional[str] = None
362
+
363
+ def __post_init__(self):
364
+ """Initialize empty lists if None"""
365
+ if self.collaborators is None:
366
+ self.collaborators = []
367
+
368
+ @classmethod
369
+ def from_dict(cls, data: Dict[str, Any]) -> 'Run':
370
+ """Create Run instance from dictionary"""
371
+ progress_data = data.get('progress')
372
+ progress = RunProgress.from_dict(progress_data) if progress_data else None
373
+
374
+ whole_progress_data = data.get('whole_progress')
375
+ whole_progress = RunProgress.from_dict(whole_progress_data) if whole_progress_data else None
376
+
377
+ return cls(
378
+ id=data.get('id', ''),
379
+ increment_id=data.get('increment_id', 0),
380
+ checklist_id=data.get('checklist_id', ''),
381
+ checklist_title=data.get('checklist_title', ''),
382
+ assign_someone_else=data.get('assign_someone_else', False),
383
+ name=data.get('name', ''),
384
+ summary=data.get('summary'),
385
+ status=data.get('status', 'active'),
386
+ progress=progress,
387
+ whole_progress=whole_progress,
388
+ started_by=data.get('started_by'),
389
+ prerun=data.get('prerun'),
390
+ prerun_completed_at=data.get('prerun_completed_at'),
391
+ prerun_completed_by=data.get('prerun_completed_by'),
392
+ prerun_length=data.get('prerun_length'),
393
+ starred=data.get('starred', False),
394
+ created_at=data.get('created_at'),
395
+ due_date=data.get('due_date'),
396
+ owner_id=data.get('owner_id'),
397
+ started_at=data.get('started_at'),
398
+ last_updated=data.get('last_updated'),
399
+ completed_at=data.get('completed_at'),
400
+ late_tasks=data.get('late_tasks'),
401
+ archived_at=data.get('archived_at'),
402
+ due_date_passed=data.get('due_date_passed', False),
403
+ collaborators=data.get('collaborators', []),
404
+ due_soon=data.get('due_soon', False),
405
+ max_task_deadline=data.get('max_task_deadline')
406
+ )
407
+
408
+
409
+ @dataclass
410
+ class Folder:
411
+ """Folder data model for search results"""
412
+ id: str
413
+ parent: Optional[str]
414
+ name: str
415
+
416
+ @classmethod
417
+ def from_dict(cls, data: Dict[str, Any]) -> 'Folder':
418
+ """Create Folder instance from dictionary"""
419
+ return cls(
420
+ id=data.get('id', ''),
421
+ parent=data.get('parent'),
422
+ name=data.get('name', '')
423
+ )
424
+
425
+
426
+ @dataclass
427
+ class Tag:
428
+ """Tag data model for industry and topic tags"""
429
+ id: str
430
+ title: str
431
+ type: str
432
+ color: str
433
+ created_at: Optional[str] = None
434
+ deleted_at: Optional[str] = None
435
+
436
+ @classmethod
437
+ def from_dict(cls, data: Dict[str, Any]) -> 'Tag':
438
+ """Create Tag instance from dictionary"""
439
+ return cls(
440
+ id=data.get('id', ''),
441
+ title=data.get('title', ''),
442
+ type=data.get('type', ''),
443
+ color=data.get('color', ''),
444
+ created_at=data.get('created_at'),
445
+ deleted_at=data.get('deleted_at')
446
+ )
447
+
448
+
449
+ @dataclass
450
+ class PrerunField:
451
+ """Prerun field data model"""
452
+ id: str
453
+ checklist_id: str
454
+ alias: str
455
+ field_type: str
456
+ guidance: Optional[str] = None
457
+ position: int = 1
458
+ required: bool = True
459
+ use_wysiwyg_editor: bool = True
460
+ collect_time: bool = True
461
+ options: List[Dict[str, Any]] = None
462
+ field_validation: List[str] = None
463
+ created_at: Optional[str] = None
464
+ last_updated: Optional[str] = None
465
+
466
+ def __post_init__(self):
467
+ """Initialize empty lists if None"""
468
+ if self.options is None:
469
+ self.options = []
470
+ if self.field_validation is None:
471
+ self.field_validation = []
472
+
473
+ @classmethod
474
+ def from_dict(cls, data: Dict[str, Any]) -> 'PrerunField':
475
+ """Create PrerunField instance from dictionary"""
476
+ return cls(
477
+ id=data.get('id', ''),
478
+ checklist_id=data.get('checklist_id', ''),
479
+ alias=data.get('alias', ''),
480
+ field_type=data.get('field_type', 'text'),
481
+ guidance=data.get('guidance'),
482
+ position=data.get('position', 1),
483
+ required=data.get('required', True),
484
+ use_wysiwyg_editor=data.get('use_wysiwyg_editor', True),
485
+ collect_time=data.get('collect_time', True),
486
+ options=data.get('options', []),
487
+ field_validation=data.get('field_validation', []),
488
+ created_at=data.get('created_at'),
489
+ last_updated=data.get('last_updated')
490
+ )
491
+
492
+
493
+ @dataclass
494
+ class AutomationCondition:
495
+ """Automation condition data model"""
496
+ id: str
497
+ conditionable_id: str
498
+ conditionable_type: str
499
+ operation: str
500
+ statement: str
501
+ logic: str
502
+ position: int
503
+ column_contains_name: Optional[str] = None
504
+
505
+ @classmethod
506
+ def from_dict(cls, data: Dict[str, Any]) -> 'AutomationCondition':
507
+ """Create AutomationCondition instance from dictionary"""
508
+ return cls(
509
+ id=data.get('id', ''),
510
+ conditionable_id=data.get('conditionable_id', ''),
511
+ conditionable_type=data.get('conditionable_type', ''),
512
+ operation=data.get('operation', ''),
513
+ statement=data.get('statement', ''),
514
+ logic=data.get('logic', ''),
515
+ position=data.get('position', 0),
516
+ column_contains_name=data.get('column_contains_name')
517
+ )
518
+
519
+
520
+ @dataclass
521
+ class AutomationDeadline:
522
+ """Automation deadline data model"""
523
+ value: int
524
+ unit: str
525
+
526
+ @classmethod
527
+ def from_dict(cls, data: Dict[str, Any]) -> 'AutomationDeadline':
528
+ """Create AutomationDeadline instance from dictionary"""
529
+ return cls(
530
+ value=data.get('value', 0),
531
+ unit=data.get('unit', 'days')
532
+ )
533
+
534
+
535
+ @dataclass
536
+ class AutomationAssignees:
537
+ """Automation assignees data model"""
538
+ users: List[int] = None
539
+ guests: List[str] = None
540
+ groups: List[str] = None
541
+
542
+ def __post_init__(self):
543
+ """Initialize empty lists if None"""
544
+ if self.users is None:
545
+ self.users = []
546
+ if self.guests is None:
547
+ self.guests = []
548
+ if self.groups is None:
549
+ self.groups = []
550
+
551
+ @classmethod
552
+ def from_dict(cls, data: Dict[str, Any]) -> 'AutomationAssignees':
553
+ """Create AutomationAssignees instance from dictionary"""
554
+ return cls(
555
+ users=data.get('users', []),
556
+ guests=data.get('guests', []),
557
+ groups=data.get('groups', [])
558
+ )
559
+
560
+
561
+ @dataclass
562
+ class AutomationAction:
563
+ """Automation action data model"""
564
+ id: str
565
+ action_type: str
566
+ action_verb: str
567
+ target_step_id: Optional[str] = None
568
+ position: int = 0
569
+ actionable_id: Optional[str] = None
570
+ actionable_type: Optional[str] = None
571
+ deadline: Optional[AutomationDeadline] = None
572
+ assignees: Optional[AutomationAssignees] = None
573
+
574
+ @classmethod
575
+ def from_dict(cls, data: Dict[str, Any]) -> 'AutomationAction':
576
+ """Create AutomationAction instance from dictionary"""
577
+ deadline_data = data.get('deadline')
578
+ deadline = AutomationDeadline.from_dict(deadline_data) if deadline_data else None
579
+
580
+ assignees_data = data.get('assignees')
581
+ assignees = AutomationAssignees.from_dict(assignees_data) if assignees_data else None
582
+
583
+ return cls(
584
+ id=data.get('id', ''),
585
+ action_type=data.get('action_type', ''),
586
+ action_verb=data.get('action_verb', ''),
587
+ target_step_id=data.get('target_step_id'),
588
+ position=data.get('position', 0),
589
+ actionable_id=data.get('actionable_id'),
590
+ actionable_type=data.get('actionable_type'),
591
+ deadline=deadline,
592
+ assignees=assignees
593
+ )
594
+
595
+
596
+ @dataclass
597
+ class AutomatedAction:
598
+ """Automated action data model"""
599
+ id: str
600
+ automated_alias: str
601
+ conditions: List[AutomationCondition] = None
602
+ then_actions: List[AutomationAction] = None
603
+ created_at: Optional[str] = None
604
+ last_updated: Optional[str] = None
605
+ archived_at: Optional[str] = None
606
+
607
+ def __post_init__(self):
608
+ """Initialize empty lists if None"""
609
+ if self.conditions is None:
610
+ self.conditions = []
611
+ if self.then_actions is None:
612
+ self.then_actions = []
613
+
614
+ @classmethod
615
+ def from_dict(cls, data: Dict[str, Any]) -> 'AutomatedAction':
616
+ """Create AutomatedAction instance from dictionary"""
617
+ conditions_data = data.get('conditions', [])
618
+ conditions = [AutomationCondition.from_dict(cond_data) for cond_data in conditions_data] if conditions_data else []
619
+
620
+ actions_data = data.get('then_actions', [])
621
+ then_actions = [AutomationAction.from_dict(action_data) for action_data in actions_data] if actions_data else []
622
+
623
+ return cls(
624
+ id=data.get('id', ''),
625
+ automated_alias=data.get('automated_alias', ''),
626
+ conditions=conditions,
627
+ then_actions=then_actions,
628
+ created_at=data.get('created_at'),
629
+ last_updated=data.get('last_updated'),
630
+ archived_at=data.get('archived_at')
631
+ )
632
+
633
+
634
+ @dataclass
635
+ class Template:
636
+ """Template data model"""
637
+ id: str
638
+ title: str
639
+ summary: Optional[str] = None
640
+ starred: bool = True
641
+ webhook: Optional[str] = None
642
+ explanation_video: Optional[str] = None
643
+ guidance: Optional[str] = None
644
+ icon: Optional[str] = None
645
+ alias: Optional[str] = None
646
+ prerun: List[PrerunField] = None
647
+ automated_actions: List[AutomatedAction] = None
648
+ created_by: Optional[int] = None
649
+ owner_id: Optional[int] = None
650
+ started_processes: int = 0
651
+ kickoff_title: Optional[str] = None
652
+ kickoff_description: Optional[str] = None
653
+ created_at: Optional[str] = None
654
+ last_updated: Optional[str] = None
655
+ archived_at: Optional[str] = None
656
+ is_public: bool = True
657
+ is_featured: bool = True
658
+ users: List[int] = None
659
+ groups: List[str] = None
660
+ public_cover: Optional[str] = None
661
+ industry_tags: List[Tag] = None
662
+ topic_tags: List[Tag] = None
663
+ type: Optional[str] = None
664
+ default_process_name_format: Optional[str] = None
665
+ is_public_kickoff: bool = True
666
+ dual_version_enabled: bool = True
667
+ is_published_state: bool = True
668
+ auto_naming: bool = True
669
+ last_updated_by: Optional[str] = None
670
+ linked_tasks: List[Task] = None
671
+ folderize_process: bool = True
672
+ tag_process: bool = True
673
+ allow_launcher_change_name: bool = True
674
+ is_pinned: bool = True
675
+ ko_form_blueprint_id: Optional[str] = None
676
+ default_folder: Optional[str] = None
677
+ folder_changeable_by_launcher: bool = True
678
+ kickoff_sharing_user_id: Optional[int] = None
679
+
680
+ def __post_init__(self):
681
+ """Initialize empty lists if None"""
682
+ if self.prerun is None:
683
+ self.prerun = []
684
+ if self.automated_actions is None:
685
+ self.automated_actions = []
686
+ if self.users is None:
687
+ self.users = []
688
+ if self.groups is None:
689
+ self.groups = []
690
+ if self.industry_tags is None:
691
+ self.industry_tags = []
692
+ if self.topic_tags is None:
693
+ self.topic_tags = []
694
+ if self.linked_tasks is None:
695
+ self.linked_tasks = []
696
+
697
+ @classmethod
698
+ def from_dict(cls, data: Dict[str, Any]) -> 'Template':
699
+ """Create Template instance from dictionary"""
700
+ prerun_data = data.get('prerun', [])
701
+ prerun = [PrerunField.from_dict(field_data) for field_data in prerun_data] if prerun_data else []
702
+
703
+ automated_actions_data = data.get('automated_actions', [])
704
+ automated_actions = [AutomatedAction.from_dict(action_data) for action_data in automated_actions_data] if automated_actions_data else []
705
+
706
+ industry_tags_data = data.get('industry_tags', [])
707
+ industry_tags = [Tag.from_dict(tag_data) for tag_data in industry_tags_data] if industry_tags_data else []
708
+
709
+ topic_tags_data = data.get('topic_tags', [])
710
+ topic_tags = [Tag.from_dict(tag_data) for tag_data in topic_tags_data] if topic_tags_data else []
711
+
712
+ linked_tasks_data = data.get('linked_tasks', [])
713
+ linked_tasks = [Task.from_dict(task_data) for task_data in linked_tasks_data] if linked_tasks_data else []
714
+
715
+ return cls(
716
+ id=data.get('id', ''),
717
+ title=data.get('title', ''),
718
+ summary=data.get('summary'),
719
+ starred=data.get('starred', True),
720
+ webhook=data.get('webhook'),
721
+ explanation_video=data.get('explanation_video'),
722
+ guidance=data.get('guidance'),
723
+ icon=data.get('icon'),
724
+ alias=data.get('alias'),
725
+ prerun=prerun,
726
+ automated_actions=automated_actions,
727
+ created_by=data.get('created_by'),
728
+ owner_id=data.get('owner_id'),
729
+ started_processes=data.get('started_processes', 0),
730
+ kickoff_title=data.get('kickoff_title'),
731
+ kickoff_description=data.get('kickoff_description'),
732
+ created_at=data.get('created_at'),
733
+ last_updated=data.get('last_updated'),
734
+ archived_at=data.get('archived_at'),
735
+ is_public=data.get('is_public', True),
736
+ is_featured=data.get('is_featured', True),
737
+ users=data.get('users', []),
738
+ groups=data.get('groups', []),
739
+ public_cover=data.get('public_cover'),
740
+ industry_tags=industry_tags,
741
+ topic_tags=topic_tags,
742
+ type=data.get('type'),
743
+ default_process_name_format=data.get('default_process_name_format'),
744
+ is_public_kickoff=data.get('is_public_kickoff', True),
745
+ dual_version_enabled=data.get('dual_version_enabled', True),
746
+ is_published_state=data.get('is_published_state', True),
747
+ auto_naming=data.get('auto_naming', True),
748
+ last_updated_by=data.get('last_updated_by'),
749
+ linked_tasks=linked_tasks,
750
+ folderize_process=data.get('folderize_process', True),
751
+ tag_process=data.get('tag_process', True),
752
+ allow_launcher_change_name=data.get('allow_launcher_change_name', True),
753
+ is_pinned=data.get('is_pinned', True),
754
+ ko_form_blueprint_id=data.get('ko_form_blueprint_id'),
755
+ default_folder=data.get('default_folder'),
756
+ folder_changeable_by_launcher=data.get('folder_changeable_by_launcher', True),
757
+ kickoff_sharing_user_id=data.get('kickoff_sharing_user_id')
758
+ )
759
+
760
+
761
+ @dataclass
762
+ class StepStartDate:
763
+ """Step start date data model"""
764
+ value: int = 0
765
+ unit: str = "days"
766
+
767
+ @classmethod
768
+ def from_dict(cls, data: Dict[str, Any]) -> 'StepStartDate':
769
+ """Create StepStartDate instance from dictionary"""
770
+ return cls(
771
+ value=data.get('value', 0),
772
+ unit=data.get('unit', 'days')
773
+ )
774
+
775
+
776
+ @dataclass
777
+ class StepDeadline:
778
+ """Step deadline data model"""
779
+ value: int = 0
780
+ unit: str = "days"
781
+ option: str = "from"
782
+ step: str = "start_run"
783
+
784
+ @classmethod
785
+ def from_dict(cls, data: Dict[str, Any]) -> 'StepDeadline':
786
+ """Create StepDeadline instance from dictionary"""
787
+ return cls(
788
+ value=data.get('value', 0),
789
+ unit=data.get('unit', 'days'),
790
+ option=data.get('option', 'from'),
791
+ step=data.get('step', 'start_run')
792
+ )
793
+
794
+
795
+ @dataclass
796
+ class StepBpToLaunch:
797
+ """Step blueprint to launch data model"""
798
+ id: Optional[str] = None
799
+ default_name_format: Optional[str] = None
800
+ tasks_within_process: bool = True
801
+
802
+ @classmethod
803
+ def from_dict(cls, data: Dict[str, Any]) -> 'StepBpToLaunch':
804
+ """Create StepBpToLaunch instance from dictionary"""
805
+ return cls(
806
+ id=data.get('id'),
807
+ default_name_format=data.get('default_name_format'),
808
+ tasks_within_process=data.get('tasks_within_process', True)
809
+ )
810
+
811
+
812
+ @dataclass
813
+ class Capture:
814
+ """Capture (form field) data model"""
815
+ id: str
816
+ step_id: str
817
+ field_type: str
818
+ label: str
819
+ guidance: Optional[str] = None
820
+ position: int = 1
821
+ required: bool = True
822
+ options: List[Dict[str, Any]] = None
823
+ columns: List[Dict[str, Any]] = None
824
+ default_value_enabled: bool = False
825
+ default_value: Optional[str] = None
826
+ created_at: Optional[str] = None
827
+ last_updated: Optional[str] = None
828
+
829
+ def __post_init__(self):
830
+ """Initialize empty lists if None"""
831
+ if self.options is None:
832
+ self.options = []
833
+ if self.columns is None:
834
+ self.columns = []
835
+
836
+ @classmethod
837
+ def from_dict(cls, data: Dict[str, Any]) -> 'Capture':
838
+ """Create Capture instance from dictionary"""
839
+ return cls(
840
+ id=data.get('id', ''),
841
+ step_id=data.get('step_id', ''),
842
+ field_type=data.get('field_type', 'text'),
843
+ label=data.get('label', ''),
844
+ guidance=data.get('guidance'),
845
+ position=data.get('position', 1),
846
+ required=data.get('required', True),
847
+ options=data.get('options', []),
848
+ columns=data.get('columns', []),
849
+ default_value_enabled=data.get('default_value_enabled', False),
850
+ default_value=data.get('default_value'),
851
+ created_at=data.get('created_at'),
852
+ last_updated=data.get('last_updated')
853
+ )
854
+
855
+
856
+ @dataclass
857
+ class Step:
858
+ """Step data model"""
859
+ id: str
860
+ checklist_id: str
861
+ title: str
862
+ alias: Optional[str] = None
863
+ summary: Optional[str] = None
864
+ step_type: Optional[str] = None
865
+ position: int = 1
866
+ allow_guest_owners: bool = False
867
+ max_assignable: int = 1
868
+ skip_start_process: bool = False
869
+ can_complete_only_assignees: bool = False
870
+ everyone_must_complete: bool = False
871
+ webhook: Optional[str] = None
872
+ start_date: Optional[StepStartDate] = None
873
+ is_soft_start_date: bool = False
874
+ deadline: Optional[StepDeadline] = None
875
+ bp_to_launch: Optional[StepBpToLaunch] = None
876
+ assignees: List[int] = None
877
+ guests: List[str] = None
878
+ captures: List[Capture] = None
879
+ prevent_guest_comment: bool = False
880
+ roles: List[str] = None
881
+ role_changes_every_time: bool = False
882
+ created_at: Optional[str] = None
883
+ last_updated: Optional[str] = None
884
+ archived_at: Optional[str] = None
885
+
886
+ def __post_init__(self):
887
+ """Initialize empty lists if None"""
888
+ if self.assignees is None:
889
+ self.assignees = []
890
+ if self.guests is None:
891
+ self.guests = []
892
+ if self.captures is None:
893
+ self.captures = []
894
+ if self.roles is None:
895
+ self.roles = []
896
+
897
+ @classmethod
898
+ def from_dict(cls, data: Dict[str, Any]) -> 'Step':
899
+ """Create Step instance from dictionary"""
900
+ start_date_data = data.get('start_date')
901
+ start_date = StepStartDate.from_dict(start_date_data) if start_date_data else None
902
+
903
+ deadline_data = data.get('deadline')
904
+ deadline = StepDeadline.from_dict(deadline_data) if deadline_data else None
905
+
906
+ bp_to_launch_data = data.get('bp_to_launch')
907
+ bp_to_launch = StepBpToLaunch.from_dict(bp_to_launch_data) if bp_to_launch_data else None
908
+
909
+ captures_data = data.get('captures', [])
910
+ captures = [Capture.from_dict(capture_data) for capture_data in captures_data] if captures_data else []
911
+
912
+ return cls(
913
+ id=data.get('id', ''),
914
+ checklist_id=data.get('checklist_id', ''),
915
+ title=data.get('title', ''),
916
+ alias=data.get('alias'),
917
+ summary=data.get('summary'),
918
+ step_type=data.get('step_type'),
919
+ position=data.get('position', 1),
920
+ allow_guest_owners=data.get('allow_guest_owners', False),
921
+ max_assignable=data.get('max_assignable', 1),
922
+ skip_start_process=data.get('skip_start_process', False),
923
+ can_complete_only_assignees=data.get('can_complete_only_assignees', False),
924
+ everyone_must_complete=data.get('everyone_must_complete', False),
925
+ webhook=data.get('webhook'),
926
+ start_date=start_date,
927
+ is_soft_start_date=data.get('is_soft_start_date', False),
928
+ deadline=deadline,
929
+ bp_to_launch=bp_to_launch,
930
+ assignees=data.get('assignees', []),
931
+ guests=data.get('guests', []),
932
+ captures=captures,
933
+ prevent_guest_comment=data.get('prevent_guest_comment', False),
934
+ roles=data.get('roles', []),
935
+ role_changes_every_time=data.get('role_changes_every_time', False),
936
+ created_at=data.get('created_at'),
937
+ last_updated=data.get('last_updated'),
938
+ archived_at=data.get('archived_at')
939
+ )
940
+
941
+
942
+ @dataclass
943
+ class SearchResult:
944
+ """Search result data model for templates, processes, and tasks"""
945
+ id: str
946
+ increment_id: int
947
+ search_type: str
948
+
949
+ # Common fields
950
+ name: Optional[str] = None
951
+ title: Optional[str] = None
952
+ status: Optional[str] = None
953
+ type: Optional[str] = None
954
+
955
+ # Template-specific fields
956
+ steps_count: Optional[int] = None
957
+ icon: Optional[str] = None
958
+ is_public: Optional[bool] = None
959
+ is_featured: Optional[bool] = None
960
+ organization_id: Optional[str] = None
961
+ folders: Optional[List[Folder]] = None
962
+
963
+ # Process-specific fields
964
+ due_date_passed: Optional[bool] = None
965
+ due_soon: Optional[bool] = None
966
+
967
+ # Task-specific fields
968
+ status_label: Optional[str] = None
969
+ position: Optional[int] = None
970
+ deadline: Optional[str] = None
971
+ created_at: Optional[str] = None
972
+ starter_id: Optional[int] = None
973
+ is_oneoff_task: Optional[bool] = None
974
+ owners: Optional[TaskOwners] = None
975
+
976
+ def __post_init__(self):
977
+ """Initialize empty lists if None"""
978
+ if self.folders is None:
979
+ self.folders = []
980
+
981
+ @classmethod
982
+ def from_dict(cls, data: Dict[str, Any], search_type: str) -> 'SearchResult':
983
+ """Create SearchResult instance from dictionary"""
984
+ folders_data = data.get('folders', [])
985
+ folders = [Folder.from_dict(folder_data) for folder_data in folders_data] if folders_data else []
986
+
987
+ owners_data = data.get('owners')
988
+ owners = TaskOwners.from_dict(owners_data) if owners_data else None
989
+
990
+ return cls(
991
+ id=data.get('id', ''),
992
+ increment_id=data.get('increment_id', 0),
993
+ search_type=search_type,
994
+ name=data.get('name'),
995
+ title=data.get('title'),
996
+ status=data.get('status'),
997
+ type=data.get('type'),
998
+ steps_count=data.get('steps_count'),
999
+ icon=data.get('icon'),
1000
+ is_public=data.get('is_public'),
1001
+ is_featured=data.get('is_featured'),
1002
+ organization_id=data.get('organization_id'),
1003
+ folders=folders,
1004
+ due_date_passed=data.get('due_date_passed'),
1005
+ due_soon=data.get('due_soon'),
1006
+ status_label=data.get('status_label'),
1007
+ position=data.get('position'),
1008
+ deadline=data.get('deadline'),
1009
+ created_at=data.get('created_at'),
1010
+ starter_id=data.get('starter_id'),
1011
+ is_oneoff_task=data.get('is_oneoff_task'),
1012
+ owners=owners
1013
+ )