tallyfy 1.0.3__py3-none-any.whl → 1.0.5__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/__init__.py +8 -4
- tallyfy/core.py +8 -8
- tallyfy/form_fields_management/__init__.py +70 -0
- tallyfy/form_fields_management/base.py +109 -0
- tallyfy/form_fields_management/crud_operations.py +234 -0
- tallyfy/form_fields_management/options_management.py +222 -0
- tallyfy/form_fields_management/suggestions.py +411 -0
- tallyfy/task_management/__init__.py +81 -0
- tallyfy/task_management/base.py +125 -0
- tallyfy/task_management/creation.py +221 -0
- tallyfy/task_management/retrieval.py +211 -0
- tallyfy/task_management/search.py +196 -0
- tallyfy/template_management/__init__.py +85 -0
- tallyfy/template_management/analysis.py +1093 -0
- tallyfy/template_management/automation.py +469 -0
- tallyfy/template_management/base.py +56 -0
- tallyfy/template_management/basic_operations.py +477 -0
- tallyfy/template_management/health_assessment.py +763 -0
- tallyfy/user_management/__init__.py +69 -0
- tallyfy/user_management/base.py +146 -0
- tallyfy/user_management/invitation.py +286 -0
- tallyfy/user_management/retrieval.py +339 -0
- {tallyfy-1.0.3.dist-info → tallyfy-1.0.5.dist-info}/METADATA +120 -56
- tallyfy-1.0.5.dist-info/RECORD +28 -0
- tallyfy/BUILD.md +0 -5
- tallyfy/README.md +0 -634
- tallyfy/form_fields_management.py +0 -582
- tallyfy/task_management.py +0 -356
- tallyfy/template_management.py +0 -2607
- tallyfy/user_management.py +0 -235
- tallyfy-1.0.3.dist-info/RECORD +0 -14
- {tallyfy-1.0.3.dist-info → tallyfy-1.0.5.dist-info}/WHEEL +0 -0
- {tallyfy-1.0.3.dist-info → tallyfy-1.0.5.dist-info}/licenses/LICENSE +0 -0
- {tallyfy-1.0.3.dist-info → tallyfy-1.0.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AI-powered form field suggestions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List, Dict, Any, Optional
|
|
6
|
+
from .base import FormFieldManagerBase
|
|
7
|
+
from ..models import Step, TallyfyError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FormFieldSuggestions(FormFieldManagerBase):
|
|
11
|
+
"""Handles AI-powered form field suggestions based on step content"""
|
|
12
|
+
|
|
13
|
+
def suggest_form_fields_for_step(self, org_id: str, template_id: str, step_id: str) -> List[Dict[str, Any]]:
|
|
14
|
+
"""
|
|
15
|
+
AI-powered suggestions for relevant form fields based on step content.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
org_id: Organization ID
|
|
19
|
+
template_id: Template ID
|
|
20
|
+
step_id: Step ID to analyze
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
List of suggested form field configurations
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
TallyfyError: If the request fails
|
|
27
|
+
"""
|
|
28
|
+
self._validate_org_id(org_id)
|
|
29
|
+
self._validate_template_id(template_id)
|
|
30
|
+
self._validate_step_id(step_id)
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
# Get the step details to analyze
|
|
34
|
+
step = self._get_step_details(org_id, template_id, step_id)
|
|
35
|
+
if not step:
|
|
36
|
+
raise TallyfyError(f"Step {step_id} not found")
|
|
37
|
+
|
|
38
|
+
# Get template context for better suggestions
|
|
39
|
+
template = self.sdk.get_template(org_id, template_id)
|
|
40
|
+
if not template:
|
|
41
|
+
raise TallyfyError(f"Template {template_id} not found")
|
|
42
|
+
|
|
43
|
+
# Analyze step content for intelligent suggestions
|
|
44
|
+
step_title = step.title.lower() if step.title else ''
|
|
45
|
+
step_summary = step.summary.lower() if step.summary else ''
|
|
46
|
+
step_type = step.step_type or 'task'
|
|
47
|
+
existing_fields = step.captures or []
|
|
48
|
+
|
|
49
|
+
# Combined text for analysis
|
|
50
|
+
step_text = f"{step_title} {step_summary}".strip()
|
|
51
|
+
|
|
52
|
+
suggestions = []
|
|
53
|
+
|
|
54
|
+
# Get field patterns for analysis
|
|
55
|
+
field_patterns = self._get_field_patterns()
|
|
56
|
+
|
|
57
|
+
# Check existing field types to avoid duplicates
|
|
58
|
+
existing_field_types = set()
|
|
59
|
+
existing_field_labels = set()
|
|
60
|
+
for field in existing_fields:
|
|
61
|
+
if hasattr(field, 'field_type'):
|
|
62
|
+
existing_field_types.add(field.field_type)
|
|
63
|
+
if hasattr(field, 'label'):
|
|
64
|
+
existing_field_labels.add(field.label.lower())
|
|
65
|
+
|
|
66
|
+
# Analyze step content against patterns
|
|
67
|
+
matched_patterns = []
|
|
68
|
+
for pattern_name, pattern_data in field_patterns.items():
|
|
69
|
+
keyword_matches = sum(1 for keyword in pattern_data['keywords'] if keyword in step_text)
|
|
70
|
+
if keyword_matches > 0:
|
|
71
|
+
matched_patterns.append((pattern_name, keyword_matches, pattern_data))
|
|
72
|
+
|
|
73
|
+
# Sort by relevance (number of keyword matches)
|
|
74
|
+
matched_patterns.sort(key=lambda x: x[1], reverse=True)
|
|
75
|
+
|
|
76
|
+
# Generate suggestions from matched patterns
|
|
77
|
+
suggested_count = 0
|
|
78
|
+
max_suggestions = 5
|
|
79
|
+
|
|
80
|
+
for pattern_name, matches, pattern_data in matched_patterns:
|
|
81
|
+
if suggested_count >= max_suggestions:
|
|
82
|
+
break
|
|
83
|
+
|
|
84
|
+
for field_config in pattern_data['fields']:
|
|
85
|
+
if suggested_count >= max_suggestions:
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
# Skip if similar field already exists
|
|
89
|
+
field_label_lower = field_config['label'].lower()
|
|
90
|
+
if field_label_lower in existing_field_labels:
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
# Add suggestion with metadata
|
|
94
|
+
suggestion = {
|
|
95
|
+
'field_config': field_config.copy(),
|
|
96
|
+
'confidence': min(0.9, 0.3 + (matches * 0.2)), # Confidence based on keyword matches
|
|
97
|
+
'pattern_matched': pattern_name,
|
|
98
|
+
'keyword_matches': matches,
|
|
99
|
+
'priority': 'high' if matches >= 2 else 'medium' if matches >= 1 else 'low'
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Add position suggestion
|
|
103
|
+
suggestion['field_config']['position'] = len(existing_fields) + suggested_count + 1
|
|
104
|
+
|
|
105
|
+
suggestions.append(suggestion)
|
|
106
|
+
suggested_count += 1
|
|
107
|
+
|
|
108
|
+
# If no specific patterns matched, provide generic useful fields
|
|
109
|
+
if not suggestions:
|
|
110
|
+
suggestions = self._get_generic_suggestions(existing_fields)
|
|
111
|
+
|
|
112
|
+
# Add implementation guidance
|
|
113
|
+
for suggestion in suggestions:
|
|
114
|
+
suggestion['implementation'] = {
|
|
115
|
+
'method': 'add_form_field_to_step',
|
|
116
|
+
'parameters': {
|
|
117
|
+
'org_id': org_id,
|
|
118
|
+
'template_id': template_id,
|
|
119
|
+
'step_id': step_id,
|
|
120
|
+
'field_data': suggestion['field_config']
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return suggestions
|
|
125
|
+
|
|
126
|
+
except TallyfyError:
|
|
127
|
+
raise
|
|
128
|
+
except Exception as e:
|
|
129
|
+
self._handle_api_error(e, "suggest form fields for step", org_id=org_id, template_id=template_id, step_id=step_id)
|
|
130
|
+
|
|
131
|
+
def _get_step_details(self, org_id: str, template_id: str, step_id: str) -> Optional[Step]:
|
|
132
|
+
"""
|
|
133
|
+
Get step details using CRUD operations to avoid circular imports.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
org_id: Organization ID
|
|
137
|
+
template_id: Template ID
|
|
138
|
+
step_id: Step ID
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Step object or None if not found
|
|
142
|
+
"""
|
|
143
|
+
from .crud_operations import FormFieldCRUD
|
|
144
|
+
crud = FormFieldCRUD(self.sdk)
|
|
145
|
+
return crud.get_step(org_id, template_id, step_id)
|
|
146
|
+
|
|
147
|
+
def _get_field_patterns(self) -> Dict[str, Dict[str, Any]]:
|
|
148
|
+
"""
|
|
149
|
+
Get predefined field patterns for content analysis.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Dictionary of field patterns with keywords and suggested fields
|
|
153
|
+
"""
|
|
154
|
+
return {
|
|
155
|
+
# Approval and Review patterns
|
|
156
|
+
'approval': {
|
|
157
|
+
'keywords': ['approve', 'review', 'sign off', 'accept', 'reject', 'confirm'],
|
|
158
|
+
'fields': [
|
|
159
|
+
{
|
|
160
|
+
'field_type': 'dropdown',
|
|
161
|
+
'label': 'Decision',
|
|
162
|
+
'options': [
|
|
163
|
+
{'value': 'approved', 'label': 'Approved'},
|
|
164
|
+
{'value': 'rejected', 'label': 'Rejected'},
|
|
165
|
+
{'value': 'needs_revision', 'label': 'Needs Revision'}
|
|
166
|
+
],
|
|
167
|
+
'required': True,
|
|
168
|
+
'reasoning': 'Approval steps typically need a decision field'
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
'field_type': 'textarea',
|
|
172
|
+
'label': 'Comments',
|
|
173
|
+
'required': False,
|
|
174
|
+
'reasoning': 'Comments are useful for providing feedback'
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
# Contact and Communication patterns
|
|
180
|
+
'contact': {
|
|
181
|
+
'keywords': ['contact', 'call', 'email', 'phone', 'reach out', 'communicate'],
|
|
182
|
+
'fields': [
|
|
183
|
+
{
|
|
184
|
+
'field_type': 'text',
|
|
185
|
+
'label': 'Contact Method',
|
|
186
|
+
'required': True,
|
|
187
|
+
'reasoning': 'Track how contact was made'
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
'field_type': 'date',
|
|
191
|
+
'label': 'Contact Date',
|
|
192
|
+
'required': True,
|
|
193
|
+
'reasoning': 'Record when contact was made'
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
'field_type': 'textarea',
|
|
197
|
+
'label': 'Contact Notes',
|
|
198
|
+
'required': False,
|
|
199
|
+
'reasoning': 'Document the conversation or interaction'
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
# Document and File patterns
|
|
205
|
+
'document': {
|
|
206
|
+
'keywords': ['document', 'file', 'upload', 'attach', 'report', 'contract', 'agreement'],
|
|
207
|
+
'fields': [
|
|
208
|
+
{
|
|
209
|
+
'field_type': 'file',
|
|
210
|
+
'label': 'Document Upload',
|
|
211
|
+
'required': True,
|
|
212
|
+
'reasoning': 'File upload for document-related steps'
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
'field_type': 'text',
|
|
216
|
+
'label': 'Document Title',
|
|
217
|
+
'required': False,
|
|
218
|
+
'reasoning': 'Name or title of the document'
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
'field_type': 'textarea',
|
|
222
|
+
'label': 'Document Description',
|
|
223
|
+
'required': False,
|
|
224
|
+
'reasoning': 'Brief description of the document'
|
|
225
|
+
}
|
|
226
|
+
]
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
# Payment and Financial patterns
|
|
230
|
+
'payment': {
|
|
231
|
+
'keywords': ['payment', 'invoice', 'cost', 'price', 'amount', 'bill', 'expense'],
|
|
232
|
+
'fields': [
|
|
233
|
+
{
|
|
234
|
+
'field_type': 'number',
|
|
235
|
+
'label': 'Amount',
|
|
236
|
+
'required': True,
|
|
237
|
+
'reasoning': 'Financial steps need amount tracking'
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
'field_type': 'dropdown',
|
|
241
|
+
'label': 'Currency',
|
|
242
|
+
'options': [
|
|
243
|
+
{'value': 'USD', 'label': 'USD'},
|
|
244
|
+
{'value': 'EUR', 'label': 'EUR'},
|
|
245
|
+
{'value': 'GBP', 'label': 'GBP'}
|
|
246
|
+
],
|
|
247
|
+
'required': True,
|
|
248
|
+
'reasoning': 'Specify currency for financial transactions'
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
'field_type': 'date',
|
|
252
|
+
'label': 'Payment Date',
|
|
253
|
+
'required': False,
|
|
254
|
+
'reasoning': 'Track when payment was made'
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
# Quality and Testing patterns
|
|
260
|
+
'quality': {
|
|
261
|
+
'keywords': ['test', 'quality', 'check', 'verify', 'validate', 'inspect'],
|
|
262
|
+
'fields': [
|
|
263
|
+
{
|
|
264
|
+
'field_type': 'dropdown',
|
|
265
|
+
'label': 'Test Result',
|
|
266
|
+
'options': [
|
|
267
|
+
{'value': 'pass', 'label': 'Pass'},
|
|
268
|
+
{'value': 'fail', 'label': 'Fail'},
|
|
269
|
+
{'value': 'partial', 'label': 'Partial Pass'}
|
|
270
|
+
],
|
|
271
|
+
'required': True,
|
|
272
|
+
'reasoning': 'Quality steps need result tracking'
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
'field_type': 'textarea',
|
|
276
|
+
'label': 'Test Notes',
|
|
277
|
+
'required': False,
|
|
278
|
+
'reasoning': 'Document test findings and issues'
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
'field_type': 'number',
|
|
282
|
+
'label': 'Score',
|
|
283
|
+
'required': False,
|
|
284
|
+
'reasoning': 'Numerical rating for quality assessment'
|
|
285
|
+
}
|
|
286
|
+
]
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
# Schedule and Time patterns
|
|
290
|
+
'schedule': {
|
|
291
|
+
'keywords': ['schedule', 'meeting', 'appointment', 'deadline', 'due', 'time'],
|
|
292
|
+
'fields': [
|
|
293
|
+
{
|
|
294
|
+
'field_type': 'datetime',
|
|
295
|
+
'label': 'Scheduled Time',
|
|
296
|
+
'required': True,
|
|
297
|
+
'reasoning': 'Scheduling steps need date and time'
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
'field_type': 'text',
|
|
301
|
+
'label': 'Location',
|
|
302
|
+
'required': False,
|
|
303
|
+
'reasoning': 'Meeting location or venue'
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
'field_type': 'textarea',
|
|
307
|
+
'label': 'Agenda',
|
|
308
|
+
'required': False,
|
|
309
|
+
'reasoning': 'Meeting agenda or notes'
|
|
310
|
+
}
|
|
311
|
+
]
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
def _get_generic_suggestions(self, existing_fields: List) -> List[Dict[str, Any]]:
|
|
316
|
+
"""
|
|
317
|
+
Get generic form field suggestions when no specific patterns match.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
existing_fields: List of existing form fields on the step
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
List of generic field suggestions
|
|
324
|
+
"""
|
|
325
|
+
return [
|
|
326
|
+
{
|
|
327
|
+
'field_config': {
|
|
328
|
+
'field_type': 'textarea',
|
|
329
|
+
'label': 'Notes',
|
|
330
|
+
'required': False,
|
|
331
|
+
'position': len(existing_fields) + 1
|
|
332
|
+
},
|
|
333
|
+
'confidence': 0.6,
|
|
334
|
+
'pattern_matched': 'generic',
|
|
335
|
+
'keyword_matches': 0,
|
|
336
|
+
'priority': 'medium',
|
|
337
|
+
'reasoning': 'Notes field is useful for most steps to capture additional information'
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
'field_config': {
|
|
341
|
+
'field_type': 'dropdown',
|
|
342
|
+
'label': 'Status',
|
|
343
|
+
'options': [
|
|
344
|
+
{'value': 'completed', 'label': 'Completed'},
|
|
345
|
+
{'value': 'in_progress', 'label': 'In Progress'},
|
|
346
|
+
{'value': 'blocked', 'label': 'Blocked'}
|
|
347
|
+
],
|
|
348
|
+
'required': False,
|
|
349
|
+
'position': len(existing_fields) + 2
|
|
350
|
+
},
|
|
351
|
+
'confidence': 0.5,
|
|
352
|
+
'pattern_matched': 'generic',
|
|
353
|
+
'keyword_matches': 0,
|
|
354
|
+
'priority': 'low',
|
|
355
|
+
'reasoning': 'Status tracking can be helpful for workflow management'
|
|
356
|
+
}
|
|
357
|
+
]
|
|
358
|
+
|
|
359
|
+
def suggest_field_improvements(self, org_id: str, template_id: str, step_id: str, field_id: str) -> List[Dict[str, Any]]:
|
|
360
|
+
"""
|
|
361
|
+
Suggest improvements for an existing form field.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
org_id: Organization ID
|
|
365
|
+
template_id: Template ID
|
|
366
|
+
step_id: Step ID
|
|
367
|
+
field_id: Form field ID to analyze
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
List of improvement suggestions
|
|
371
|
+
|
|
372
|
+
Raises:
|
|
373
|
+
TallyfyError: If the request fails
|
|
374
|
+
"""
|
|
375
|
+
try:
|
|
376
|
+
# Get current field options using options management
|
|
377
|
+
from .options_management import FormFieldOptions
|
|
378
|
+
options_manager = FormFieldOptions(self.sdk)
|
|
379
|
+
|
|
380
|
+
current_options = options_manager.get_dropdown_options(org_id, template_id, step_id, field_id)
|
|
381
|
+
|
|
382
|
+
improvements = []
|
|
383
|
+
|
|
384
|
+
# Analyze dropdown options if applicable
|
|
385
|
+
if current_options:
|
|
386
|
+
# Check for common improvements
|
|
387
|
+
if len(current_options) > 10:
|
|
388
|
+
improvements.append({
|
|
389
|
+
'type': 'reduce_options',
|
|
390
|
+
'priority': 'medium',
|
|
391
|
+
'description': 'Consider reducing the number of dropdown options for better usability',
|
|
392
|
+
'recommendation': 'Group similar options or use a search-enabled dropdown',
|
|
393
|
+
'current_count': len(current_options)
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
# Check for inconsistent naming
|
|
397
|
+
option_lengths = [len(opt) for opt in current_options]
|
|
398
|
+
if max(option_lengths) - min(option_lengths) > 20:
|
|
399
|
+
improvements.append({
|
|
400
|
+
'type': 'normalize_options',
|
|
401
|
+
'priority': 'low',
|
|
402
|
+
'description': 'Option labels have inconsistent lengths',
|
|
403
|
+
'recommendation': 'Standardize option label lengths for better visual consistency'
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
return improvements
|
|
407
|
+
|
|
408
|
+
except TallyfyError:
|
|
409
|
+
raise
|
|
410
|
+
except Exception as e:
|
|
411
|
+
self._handle_api_error(e, "suggest field improvements", org_id=org_id, template_id=template_id, step_id=step_id, field_id=field_id)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Task Management Package
|
|
3
|
+
|
|
4
|
+
This package provides a refactored, modular approach to task management
|
|
5
|
+
functionality, breaking down the monolithic TaskManagement class into
|
|
6
|
+
specialized components for better maintainability and separation of concerns.
|
|
7
|
+
|
|
8
|
+
Classes:
|
|
9
|
+
TaskRetrieval: Task and process retrieval operations
|
|
10
|
+
TaskSearch: Search operations for tasks, processes, and templates
|
|
11
|
+
TaskCreation: Task creation operations
|
|
12
|
+
TaskManager: Unified interface combining all functionality
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from .base import TaskManagerBase
|
|
16
|
+
from .retrieval import TaskRetrieval
|
|
17
|
+
from .search import TaskSearch
|
|
18
|
+
from .creation import TaskCreation
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TaskManager:
|
|
22
|
+
"""
|
|
23
|
+
Unified interface for task management functionality.
|
|
24
|
+
|
|
25
|
+
This class provides access to all task management capabilities
|
|
26
|
+
through a single interface while maintaining the modular structure
|
|
27
|
+
underneath.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, sdk):
|
|
31
|
+
"""
|
|
32
|
+
Initialize task manager with SDK instance.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
sdk: Main SDK instance
|
|
36
|
+
"""
|
|
37
|
+
self.retrieval = TaskRetrieval(sdk)
|
|
38
|
+
self._search_service = TaskSearch(sdk) # Use private name to avoid conflict
|
|
39
|
+
self.creation = TaskCreation(sdk)
|
|
40
|
+
|
|
41
|
+
# For backward compatibility, expose common methods at the top level
|
|
42
|
+
|
|
43
|
+
# Retrieval methods
|
|
44
|
+
self.get_my_tasks = self.retrieval.get_my_tasks
|
|
45
|
+
self.get_user_tasks = self.retrieval.get_user_tasks
|
|
46
|
+
self.get_tasks_for_process = self.retrieval.get_tasks_for_process
|
|
47
|
+
self.get_organization_runs = self.retrieval.get_organization_runs
|
|
48
|
+
self.get_organization_processes = self.retrieval.get_organization_processes
|
|
49
|
+
|
|
50
|
+
# Search methods
|
|
51
|
+
self.search_processes_by_name = self._search_service.search_processes_by_name
|
|
52
|
+
self.search = self._search_service.search # Main search method for backward compatibility
|
|
53
|
+
self.search_processes = self._search_service.search_processes
|
|
54
|
+
self.search_templates = self._search_service.search_templates
|
|
55
|
+
self.search_tasks = self._search_service.search_tasks
|
|
56
|
+
self.find_process_by_name = self._search_service.find_process_by_name
|
|
57
|
+
|
|
58
|
+
# Creation methods
|
|
59
|
+
self.create_task = self.creation.create_task
|
|
60
|
+
self.create_simple_task = self.creation.create_simple_task
|
|
61
|
+
self.create_user_task = self.creation.create_user_task
|
|
62
|
+
self.create_guest_task = self.creation.create_guest_task
|
|
63
|
+
self.create_group_task = self.creation.create_group_task
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def search_service(self):
|
|
67
|
+
"""Access to the TaskSearch service object for advanced search operations."""
|
|
68
|
+
return self._search_service
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# For backward compatibility, create an alias
|
|
72
|
+
TaskManagement = TaskManager
|
|
73
|
+
|
|
74
|
+
__all__ = [
|
|
75
|
+
'TaskManagerBase',
|
|
76
|
+
'TaskRetrieval',
|
|
77
|
+
'TaskSearch',
|
|
78
|
+
'TaskCreation',
|
|
79
|
+
'TaskManager',
|
|
80
|
+
'TaskManagement' # Backward compatibility alias
|
|
81
|
+
]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base class for task management operations
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, List, Dict, Any
|
|
6
|
+
from ..models import TallyfyError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TaskManagerBase:
|
|
10
|
+
"""Base class providing common functionality for task management"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, sdk):
|
|
13
|
+
"""
|
|
14
|
+
Initialize base task manager.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
sdk: Main SDK instance
|
|
18
|
+
"""
|
|
19
|
+
self.sdk = sdk
|
|
20
|
+
|
|
21
|
+
def _validate_org_id(self, org_id: str) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Validate organization ID parameter.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
org_id: Organization ID to validate
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ValueError: If org_id is invalid
|
|
30
|
+
"""
|
|
31
|
+
if not org_id or not isinstance(org_id, str):
|
|
32
|
+
raise ValueError("Organization ID must be a non-empty string")
|
|
33
|
+
|
|
34
|
+
def _validate_user_id(self, user_id: int) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Validate user ID parameter.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
user_id: User ID to validate
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
ValueError: If user_id is invalid
|
|
43
|
+
"""
|
|
44
|
+
if not isinstance(user_id, int) or user_id <= 0:
|
|
45
|
+
raise ValueError("User ID must be a positive integer")
|
|
46
|
+
|
|
47
|
+
def _validate_process_id(self, process_id: str) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Validate process ID parameter.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
process_id: Process ID to validate
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
ValueError: If process_id is invalid
|
|
56
|
+
"""
|
|
57
|
+
if not process_id or not isinstance(process_id, str):
|
|
58
|
+
raise ValueError("Process ID must be a non-empty string")
|
|
59
|
+
|
|
60
|
+
def _extract_data(self, response_data, data_key: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
61
|
+
"""
|
|
62
|
+
Extract data from API response.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
response_data: Raw response from API
|
|
66
|
+
data_key: Optional key to extract from response (e.g., 'process', 'blueprint')
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Extracted data list or empty list
|
|
70
|
+
"""
|
|
71
|
+
if isinstance(response_data, dict):
|
|
72
|
+
if data_key and data_key in response_data:
|
|
73
|
+
# Handle search responses with specific keys
|
|
74
|
+
nested_data = response_data[data_key]
|
|
75
|
+
if isinstance(nested_data, dict) and 'data' in nested_data:
|
|
76
|
+
return nested_data['data']
|
|
77
|
+
elif isinstance(nested_data, list):
|
|
78
|
+
return nested_data
|
|
79
|
+
elif 'data' in response_data:
|
|
80
|
+
return response_data['data']
|
|
81
|
+
return []
|
|
82
|
+
elif isinstance(response_data, list):
|
|
83
|
+
return response_data
|
|
84
|
+
return []
|
|
85
|
+
|
|
86
|
+
def _handle_api_error(self, error: Exception, operation: str, **context) -> None:
|
|
87
|
+
"""
|
|
88
|
+
Handle API errors with context.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
error: The exception that occurred
|
|
92
|
+
operation: Description of the operation that failed
|
|
93
|
+
**context: Additional context for error logging
|
|
94
|
+
"""
|
|
95
|
+
context_str = ", ".join([f"{k}={v}" for k, v in context.items()])
|
|
96
|
+
error_msg = f"Failed to {operation}"
|
|
97
|
+
if context_str:
|
|
98
|
+
error_msg += f" ({context_str})"
|
|
99
|
+
error_msg += f": {error}"
|
|
100
|
+
|
|
101
|
+
self.sdk.logger.error(error_msg)
|
|
102
|
+
|
|
103
|
+
if isinstance(error, TallyfyError):
|
|
104
|
+
raise error
|
|
105
|
+
else:
|
|
106
|
+
raise TallyfyError(error_msg)
|
|
107
|
+
|
|
108
|
+
def _build_query_params(self, **kwargs) -> Dict[str, Any]:
|
|
109
|
+
"""
|
|
110
|
+
Build query parameters from keyword arguments, filtering out None values.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
**kwargs: Keyword arguments to convert to query parameters
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Dictionary of non-None parameters
|
|
117
|
+
"""
|
|
118
|
+
params = {}
|
|
119
|
+
for key, value in kwargs.items():
|
|
120
|
+
if value is not None:
|
|
121
|
+
if isinstance(value, bool):
|
|
122
|
+
params[key] = 'true' if value else 'false'
|
|
123
|
+
else:
|
|
124
|
+
params[key] = str(value)
|
|
125
|
+
return params
|