tallyfy 1.0.16__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.
- tallyfy/__init__.py +27 -0
- tallyfy/__pycache__/__init__.cpython-310.pyc +0 -0
- tallyfy/__pycache__/core.cpython-310.pyc +0 -0
- tallyfy/__pycache__/form_fields_management.cpython-310.pyc +0 -0
- tallyfy/__pycache__/models.cpython-310.pyc +0 -0
- tallyfy/__pycache__/task_management.cpython-310.pyc +0 -0
- tallyfy/__pycache__/template_management.cpython-310.pyc +0 -0
- tallyfy/__pycache__/user_management.cpython-310.pyc +0 -0
- tallyfy/core.py +361 -0
- tallyfy/form_fields_management/__init__.py +70 -0
- tallyfy/form_fields_management/__pycache__/__init__.cpython-310.pyc +0 -0
- tallyfy/form_fields_management/__pycache__/base.cpython-310.pyc +0 -0
- tallyfy/form_fields_management/__pycache__/crud_operations.cpython-310.pyc +0 -0
- tallyfy/form_fields_management/__pycache__/options_management.cpython-310.pyc +0 -0
- tallyfy/form_fields_management/__pycache__/suggestions.cpython-310.pyc +0 -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/models.py +1464 -0
- tallyfy/organization_management/__init__.py +26 -0
- tallyfy/organization_management/base.py +76 -0
- tallyfy/organization_management/retrieval.py +39 -0
- tallyfy/task_management/__init__.py +81 -0
- tallyfy/task_management/__pycache__/__init__.cpython-310.pyc +0 -0
- tallyfy/task_management/__pycache__/base.cpython-310.pyc +0 -0
- tallyfy/task_management/__pycache__/creation.cpython-310.pyc +0 -0
- tallyfy/task_management/__pycache__/retrieval.cpython-310.pyc +0 -0
- tallyfy/task_management/__pycache__/search.cpython-310.pyc +0 -0
- tallyfy/task_management/base.py +125 -0
- tallyfy/task_management/creation.py +221 -0
- tallyfy/task_management/retrieval.py +252 -0
- tallyfy/task_management/search.py +198 -0
- tallyfy/template_management/__init__.py +85 -0
- tallyfy/template_management/analysis.py +1099 -0
- tallyfy/template_management/automation.py +469 -0
- tallyfy/template_management/base.py +56 -0
- tallyfy/template_management/basic_operations.py +479 -0
- tallyfy/template_management/health_assessment.py +793 -0
- tallyfy/user_management/__init__.py +70 -0
- tallyfy/user_management/__pycache__/__init__.cpython-310.pyc +0 -0
- tallyfy/user_management/__pycache__/base.cpython-310.pyc +0 -0
- tallyfy/user_management/__pycache__/invitation.cpython-310.pyc +0 -0
- tallyfy/user_management/__pycache__/retrieval.cpython-310.pyc +0 -0
- tallyfy/user_management/base.py +146 -0
- tallyfy/user_management/invitation.py +286 -0
- tallyfy/user_management/retrieval.py +381 -0
- tallyfy-1.0.16.dist-info/METADATA +742 -0
- tallyfy-1.0.16.dist-info/RECORD +52 -0
- tallyfy-1.0.16.dist-info/WHEEL +5 -0
- tallyfy-1.0.16.dist-info/licenses/LICENSE +21 -0
- tallyfy-1.0.16.dist-info/top_level.txt +1 -0
tallyfy/__init__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tallyfy SDK - A modular Python SDK for Tallyfy API
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .core import TallyfySDK, TallyfyError
|
|
6
|
+
from .models import *
|
|
7
|
+
from .user_management import UserManager, UserManagement
|
|
8
|
+
from .task_management import TaskManager, TaskManagement
|
|
9
|
+
from .template_management import TemplateManager, TemplateManagement
|
|
10
|
+
from .form_fields_management import FormFieldManager, FormFieldManagement
|
|
11
|
+
from .organization_management import OrganizationManager, OrganizationManagement
|
|
12
|
+
|
|
13
|
+
__version__ = "1.0.16"
|
|
14
|
+
__all__ = [
|
|
15
|
+
"TallyfySDK",
|
|
16
|
+
"TallyfyError",
|
|
17
|
+
"UserManager",
|
|
18
|
+
"UserManagement",
|
|
19
|
+
"TaskManager",
|
|
20
|
+
"TaskManagement",
|
|
21
|
+
"TemplateManager",
|
|
22
|
+
"TemplateManagement",
|
|
23
|
+
"FormFieldManager",
|
|
24
|
+
"FormFieldManagement",
|
|
25
|
+
"OrganizationManager",
|
|
26
|
+
"OrganizationManagement"
|
|
27
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
tallyfy/core.py
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core SDK functionality and base classes
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
import time
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Dict, Any, Optional
|
|
9
|
+
|
|
10
|
+
from .models import TallyfyError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaseSDK:
|
|
14
|
+
"""
|
|
15
|
+
Base SDK class with common functionality for HTTP requests and error handling
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, api_key: str, base_url: str = "https://api.tallyfy.com", timeout: int = 30, max_retries: int = 3, retry_delay: float = 1.0):
|
|
19
|
+
self.base_url = base_url.rstrip('/')
|
|
20
|
+
self.timeout = timeout
|
|
21
|
+
self.max_retries = max_retries
|
|
22
|
+
self.retry_delay = retry_delay
|
|
23
|
+
|
|
24
|
+
# Setup default headers
|
|
25
|
+
self.default_headers = {
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
'Accept': 'application/json',
|
|
28
|
+
'X-Tallyfy-Client': 'TallyfySDK',
|
|
29
|
+
'Authorization': f'Bearer {api_key}'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Setup logging
|
|
33
|
+
self.logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
# Setup session for connection pooling
|
|
36
|
+
self.session = requests.Session()
|
|
37
|
+
self.session.headers.update(self.default_headers)
|
|
38
|
+
|
|
39
|
+
def _build_url(self, endpoint: str) -> str:
|
|
40
|
+
"""Build full URL from endpoint"""
|
|
41
|
+
endpoint = endpoint.lstrip('/')
|
|
42
|
+
return f"{self.base_url}/{endpoint}"
|
|
43
|
+
|
|
44
|
+
def _make_request(self, method: str, endpoint: str, params: Optional[Dict[str, Any]] = None, data: Optional[Dict] = None) -> Dict[str, Any]:
|
|
45
|
+
"""
|
|
46
|
+
Make HTTP request with retry logic
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
method: HTTP method (GET, POST, etc.)
|
|
50
|
+
endpoint: API endpoint
|
|
51
|
+
params: URL parameters
|
|
52
|
+
data: Request body data
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Parsed JSON response data
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
TallyfyError: If request fails after all retries
|
|
59
|
+
"""
|
|
60
|
+
url = self._build_url(endpoint)
|
|
61
|
+
|
|
62
|
+
# Prepare request arguments
|
|
63
|
+
request_args = {
|
|
64
|
+
'timeout': self.timeout,
|
|
65
|
+
'params': params
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if data:
|
|
69
|
+
request_args['json'] = data
|
|
70
|
+
|
|
71
|
+
# Retry logic
|
|
72
|
+
last_exception = None
|
|
73
|
+
|
|
74
|
+
for attempt in range(self.max_retries + 1):
|
|
75
|
+
try:
|
|
76
|
+
self.logger.debug(f"Making {method} request to {url} (attempt {attempt + 1})")
|
|
77
|
+
response = self.session.request(method, url, **request_args)
|
|
78
|
+
|
|
79
|
+
# Parse response
|
|
80
|
+
try:
|
|
81
|
+
response_data = response.json()
|
|
82
|
+
except ValueError:
|
|
83
|
+
response_data = response.text
|
|
84
|
+
|
|
85
|
+
# Check if request was successful
|
|
86
|
+
if response.ok:
|
|
87
|
+
return response_data
|
|
88
|
+
else:
|
|
89
|
+
error_msg = f"API request failed with status {response.status_code}"
|
|
90
|
+
if isinstance(response_data, dict) and 'message' in response_data:
|
|
91
|
+
error_msg += f": {response_data['message']}"
|
|
92
|
+
elif isinstance(response_data, str):
|
|
93
|
+
error_msg += f": {response_data}"
|
|
94
|
+
|
|
95
|
+
# Don't retry on client errors (4xx)
|
|
96
|
+
if 400 <= response.status_code < 500:
|
|
97
|
+
raise TallyfyError(
|
|
98
|
+
error_msg,
|
|
99
|
+
status_code=response.status_code,
|
|
100
|
+
response_data=response_data
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Retry on server errors (5xx)
|
|
104
|
+
if attempt < self.max_retries:
|
|
105
|
+
self.logger.warning(f"Request failed, retrying in {self.retry_delay}s...")
|
|
106
|
+
time.sleep(self.retry_delay)
|
|
107
|
+
continue
|
|
108
|
+
else:
|
|
109
|
+
raise TallyfyError(
|
|
110
|
+
error_msg,
|
|
111
|
+
status_code=response.status_code,
|
|
112
|
+
response_data=response_data
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
except requests.exceptions.RequestException as req_error:
|
|
116
|
+
last_exception = req_error
|
|
117
|
+
if attempt < self.max_retries:
|
|
118
|
+
self.logger.warning(f"Request failed with {type(req_error).__name__}, retrying in {self.retry_delay}s...")
|
|
119
|
+
time.sleep(self.retry_delay)
|
|
120
|
+
continue
|
|
121
|
+
else:
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
# If we get here, all retries failed
|
|
125
|
+
raise TallyfyError(f"Request failed after {self.max_retries + 1} attempts: {str(last_exception)}")
|
|
126
|
+
|
|
127
|
+
def close(self):
|
|
128
|
+
"""Close the HTTP session and cleanup resources"""
|
|
129
|
+
if hasattr(self, 'session'):
|
|
130
|
+
self.session.close()
|
|
131
|
+
|
|
132
|
+
def __enter__(self):
|
|
133
|
+
"""Context manager entry"""
|
|
134
|
+
return self
|
|
135
|
+
|
|
136
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
137
|
+
"""Context manager exit"""
|
|
138
|
+
self.close()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class TallyfySDK(BaseSDK):
|
|
142
|
+
"""
|
|
143
|
+
High-level SDK for Tallyfy API endpoints
|
|
144
|
+
Provides typed methods for interacting with Tallyfy's REST API
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
def __init__(self, api_key: str, base_url: str = "https://api.tallyfy.com", timeout: int = 30, max_retries: int = 3, retry_delay: float = 1.0):
|
|
148
|
+
super().__init__(api_key, base_url, timeout, max_retries, retry_delay)
|
|
149
|
+
|
|
150
|
+
# Initialize management modules
|
|
151
|
+
from .user_management import UserManager
|
|
152
|
+
from .task_management import TaskManager
|
|
153
|
+
from .template_management import TemplateManager
|
|
154
|
+
from .form_fields_management import FormFieldManager
|
|
155
|
+
from .organization_management import OrganizationManager
|
|
156
|
+
|
|
157
|
+
self.users = UserManager(self)
|
|
158
|
+
self.tasks = TaskManager(self)
|
|
159
|
+
self.templates = TemplateManager(self)
|
|
160
|
+
self.form_fields = FormFieldManager(self)
|
|
161
|
+
self.organizations = OrganizationManager(self)
|
|
162
|
+
|
|
163
|
+
def get_current_user_info(self, org_id: str):
|
|
164
|
+
"""Get current user information."""
|
|
165
|
+
return self.users.get_current_user_info(org_id)
|
|
166
|
+
|
|
167
|
+
def get_user(self, org_id: str, user_id: int):
|
|
168
|
+
"""Get user information."""
|
|
169
|
+
return self.users.get_user(org_id, user_id)
|
|
170
|
+
|
|
171
|
+
def get_current_user_organizations(self, page: int = 1, per_page: int = 10):
|
|
172
|
+
"""Get all organizations the current member is a part of."""
|
|
173
|
+
return self.organizations.get_current_user_organizations(page, per_page)
|
|
174
|
+
# Backward compatibility methods - delegate to management modules
|
|
175
|
+
def get_organization_users(self, org_id: str, with_groups: bool = False):
|
|
176
|
+
"""Get all organization members with full profile data."""
|
|
177
|
+
return self.users.get_organization_users(org_id, with_groups)
|
|
178
|
+
|
|
179
|
+
def get_organization_users_list(self, org_id: str):
|
|
180
|
+
"""Get organization members with minimal data."""
|
|
181
|
+
return self.users.get_organization_users_list(org_id)
|
|
182
|
+
|
|
183
|
+
def get_organization_guests(self, org_id: str, with_stats: bool = False):
|
|
184
|
+
"""Get organization guests with full profile data."""
|
|
185
|
+
return self.users.get_organization_guests(org_id, with_stats)
|
|
186
|
+
|
|
187
|
+
def get_organization_guests_list(self, org_id: str):
|
|
188
|
+
"""Get organization guests with minimal data."""
|
|
189
|
+
return self.users.get_organization_guests_list(org_id)
|
|
190
|
+
|
|
191
|
+
def invite_user_to_organization(self, org_id: str, email: str, first_name: str, last_name: str, role: str = "light", message: Optional[str] = None):
|
|
192
|
+
"""Invite a member to your organization."""
|
|
193
|
+
return self.users.invite_user_to_organization(org_id, email, first_name, last_name, role, message)
|
|
194
|
+
|
|
195
|
+
def get_my_tasks(self, org_id: str):
|
|
196
|
+
"""Get tasks assigned to the current user."""
|
|
197
|
+
return self.tasks.get_my_tasks(org_id)
|
|
198
|
+
|
|
199
|
+
def get_user_tasks(self, org_id: str, user_id: int):
|
|
200
|
+
"""Get tasks assigned to a specific user."""
|
|
201
|
+
return self.tasks.get_user_tasks(org_id, user_id)
|
|
202
|
+
|
|
203
|
+
def get_tasks_for_process(self, org_id: str, process_id: Optional[str] = None, process_name: Optional[str] = None,
|
|
204
|
+
status: Optional[str] = None, sort: Optional[str] = None, with_: Optional[str] = None,
|
|
205
|
+
owners: Optional[str] = None, groups: Optional[str] = None, guests: Optional[str] = None,
|
|
206
|
+
page: Optional[int] = None, per_page: Optional[int] = None, current_task: Optional[str] = None,
|
|
207
|
+
replace_page: Optional[int] = None, without_pagination: str = "true",
|
|
208
|
+
deadline_start_range: Optional[str] = None, deadline_end_range: Optional[str] = None,
|
|
209
|
+
unassigned: Optional[bool] = None):
|
|
210
|
+
"""Get all tasks for a given process."""
|
|
211
|
+
return self.tasks.get_tasks_for_process(org_id, process_id, process_name,
|
|
212
|
+
status, sort, with_,
|
|
213
|
+
owners, groups, guests,
|
|
214
|
+
page, per_page, current_task,
|
|
215
|
+
replace_page, without_pagination,
|
|
216
|
+
deadline_start_range, deadline_end_range,
|
|
217
|
+
unassigned)
|
|
218
|
+
|
|
219
|
+
def get_organization_runs(self, org_id: str, per_page: int = 25, page: int = 1, with_data: Optional[str] = None,
|
|
220
|
+
form_fields_values: Optional[bool] = None, owners: Optional[str] = None,
|
|
221
|
+
task_status: Optional[str] = None, groups: Optional[str] = None,
|
|
222
|
+
status: Optional[str] = None, folder: Optional[str] = None,
|
|
223
|
+
checklist_id: Optional[str] = None, starred: Optional[bool] = None,
|
|
224
|
+
run_type: Optional[str] = None, tag: Optional[str] = None, sort: Optional[str] = None):
|
|
225
|
+
"""Get all processes (runs) in the organization."""
|
|
226
|
+
return self.tasks.get_organization_runs(org_id, per_page, page, with_data, form_fields_values, owners, task_status, groups, status, folder, checklist_id, starred, run_type, tag, sort)
|
|
227
|
+
|
|
228
|
+
def create_task(self, org_id: str, title: str, deadline: str, description: Optional[str] = None, owners = None, max_assignable: Optional[int] = None, prevent_guest_comment: Optional[bool] = None):
|
|
229
|
+
"""Create a standalone task."""
|
|
230
|
+
return self.tasks.create_task(org_id=org_id, title=title, deadline=deadline, description=description, owners=owners, max_assignable=max_assignable, prevent_guest_comment=prevent_guest_comment)
|
|
231
|
+
|
|
232
|
+
def search_processes_by_name(self, org_id: str, process_name: str):
|
|
233
|
+
"""Search processes by name."""
|
|
234
|
+
return self.tasks.search_processes_by_name(org_id, process_name)
|
|
235
|
+
|
|
236
|
+
def search_templates_by_name(self, org_id: str, template_name: str):
|
|
237
|
+
"""Search templates by name."""
|
|
238
|
+
return self.templates.search_templates_by_name(org_id, template_name)
|
|
239
|
+
|
|
240
|
+
def search(self, org_id: str, search_query: str, search_type: str = "process", per_page: int = 20):
|
|
241
|
+
"""Universal search for processes, templates, or tasks."""
|
|
242
|
+
return self.tasks.search(org_id, search_query, search_type, per_page)
|
|
243
|
+
|
|
244
|
+
def get_template(self, org_id: str, template_id: Optional[str] = None, template_name: Optional[str] = None):
|
|
245
|
+
"""Get template by ID or name."""
|
|
246
|
+
return self.templates.get_template(org_id, template_id, template_name)
|
|
247
|
+
|
|
248
|
+
def get_all_templates(self, org_id: str, per_page: int = 100):
|
|
249
|
+
"""Get all templates by organization."""
|
|
250
|
+
return self.templates.get_all_templates(org_id, per_page)
|
|
251
|
+
|
|
252
|
+
def update_template_metadata(self, org_id: str, template_id: str, **kwargs):
|
|
253
|
+
"""Update template metadata."""
|
|
254
|
+
return self.templates.update_template_metadata(org_id, template_id, **kwargs)
|
|
255
|
+
|
|
256
|
+
def get_template_with_steps(self, org_id: str, template_id: Optional[str] = None, template_name: Optional[str] = None):
|
|
257
|
+
"""Get template with steps."""
|
|
258
|
+
return self.templates.get_template_with_steps(org_id, template_id, template_name)
|
|
259
|
+
|
|
260
|
+
def duplicate_template(self, org_id: str, template_id: str, new_name: str, copy_permissions: bool = False):
|
|
261
|
+
"""Duplicate template."""
|
|
262
|
+
return self.templates.duplicate_template(org_id, template_id, new_name, copy_permissions)
|
|
263
|
+
|
|
264
|
+
def get_template_steps(self, org_id: str, template_id: str):
|
|
265
|
+
"""Get template steps."""
|
|
266
|
+
return self.templates.get_template_steps(org_id, template_id)
|
|
267
|
+
|
|
268
|
+
def get_step_dependencies(self, org_id: str, template_id: str, step_id: str):
|
|
269
|
+
"""Analyze step dependencies."""
|
|
270
|
+
return self.templates.get_step_dependencies(org_id, template_id, step_id)
|
|
271
|
+
|
|
272
|
+
def suggest_step_deadline(self, org_id: str, template_id: str, step_id: str):
|
|
273
|
+
"""Suggest step deadline."""
|
|
274
|
+
return self.templates.suggest_step_deadline(org_id, template_id, step_id)
|
|
275
|
+
|
|
276
|
+
# Form field methods
|
|
277
|
+
def add_form_field_to_step(self, org_id: str, template_id: str, step_id: str, field_data: Dict[str, Any]):
|
|
278
|
+
"""Add form field to step."""
|
|
279
|
+
return self.form_fields.add_form_field_to_step(org_id, template_id, step_id, field_data)
|
|
280
|
+
|
|
281
|
+
def update_form_field(self, org_id: str, template_id: str, step_id: str, field_id: str, **kwargs):
|
|
282
|
+
"""Update form field."""
|
|
283
|
+
return self.form_fields.update_form_field(org_id, template_id, step_id, field_id, **kwargs)
|
|
284
|
+
|
|
285
|
+
def move_form_field(self, org_id: str, template_id: str, from_step: str, field_id: str, to_step: str, position: int = 1):
|
|
286
|
+
"""Move form field between steps."""
|
|
287
|
+
return self.form_fields.move_form_field(org_id, template_id, from_step, field_id, to_step, position)
|
|
288
|
+
|
|
289
|
+
def delete_form_field(self, org_id: str, template_id: str, step_id: str, field_id: str):
|
|
290
|
+
"""Delete form field."""
|
|
291
|
+
return self.form_fields.delete_form_field(org_id, template_id, step_id, field_id)
|
|
292
|
+
|
|
293
|
+
# Automation management methods
|
|
294
|
+
def create_automation_rule(self, org_id: str, template_id: str, automation_data: Dict[str, Any]):
|
|
295
|
+
"""Create conditional automation (if-then rules)."""
|
|
296
|
+
return self.templates.create_automation_rule(org_id, template_id, automation_data)
|
|
297
|
+
|
|
298
|
+
def update_automation_rule(self, org_id: str, template_id: str, automation_id: str, **kwargs):
|
|
299
|
+
"""Modify automation conditions and actions."""
|
|
300
|
+
return self.templates.update_automation_rule(org_id, template_id, automation_id, **kwargs)
|
|
301
|
+
|
|
302
|
+
def delete_automation_rule(self, org_id: str, template_id: str, automation_id: str):
|
|
303
|
+
"""Remove an automation rule."""
|
|
304
|
+
return self.templates.delete_automation_rule(org_id, template_id, automation_id)
|
|
305
|
+
|
|
306
|
+
def analyze_template_automations(self, org_id: str, template_id: str):
|
|
307
|
+
"""Analyze all automations for conflicts, redundancies, and optimization opportunities."""
|
|
308
|
+
return self.templates.analyze_template_automations(org_id, template_id)
|
|
309
|
+
|
|
310
|
+
def consolidate_automation_rules(self, org_id: str, template_id: str, preview: bool = True):
|
|
311
|
+
"""Suggest and optionally implement automation consolidation."""
|
|
312
|
+
return self.templates.consolidate_automation_rules(org_id, template_id, preview)
|
|
313
|
+
|
|
314
|
+
def get_step_visibility_conditions(self, org_id: str, template_id: str, step_id: str):
|
|
315
|
+
"""Analyze when/how a step becomes visible based on all automations."""
|
|
316
|
+
return self.templates.get_step_visibility_conditions(org_id, template_id, step_id)
|
|
317
|
+
|
|
318
|
+
def suggest_automation_consolidation(self, org_id: str, template_id: str):
|
|
319
|
+
"""AI analysis of automation rules with consolidation recommendations."""
|
|
320
|
+
return self.templates.suggest_automation_consolidation(org_id, template_id)
|
|
321
|
+
|
|
322
|
+
# Kickoff field management methods
|
|
323
|
+
# def add_kickoff_field(self, org_id: str, template_id: str, field_data: Dict[str, Any]):
|
|
324
|
+
# """Add kickoff/prerun fields to template."""
|
|
325
|
+
# return self.templates.add_kickoff_field(org_id, template_id, field_data)
|
|
326
|
+
|
|
327
|
+
# def update_kickoff_field(self, org_id: str, template_id: str, field_id: str, **kwargs):
|
|
328
|
+
# """Update kickoff field properties."""
|
|
329
|
+
# return self.templates.update_kickoff_field(org_id, template_id, field_id, **kwargs)
|
|
330
|
+
|
|
331
|
+
def suggest_kickoff_fields(self, org_id: str, template_id: str):
|
|
332
|
+
"""Suggest relevant kickoff fields based on template analysis."""
|
|
333
|
+
return self.templates.suggest_kickoff_fields(org_id, template_id)
|
|
334
|
+
|
|
335
|
+
def get_dropdown_options(self, org_id: str, template_id: str, step_id: str, field_id: str):
|
|
336
|
+
"""Get dropdown options."""
|
|
337
|
+
return self.form_fields.get_dropdown_options(org_id, template_id, step_id, field_id)
|
|
338
|
+
|
|
339
|
+
def update_dropdown_options(self, org_id: str, template_id: str, step_id: str, field_id: str, options):
|
|
340
|
+
"""Update dropdown options."""
|
|
341
|
+
return self.form_fields.update_dropdown_options(org_id, template_id, step_id, field_id, options)
|
|
342
|
+
|
|
343
|
+
def suggest_form_fields_for_step(self, org_id: str, template_id: str, step_id: str):
|
|
344
|
+
"""AI-powered form field suggestions."""
|
|
345
|
+
return self.form_fields.suggest_form_fields_for_step(org_id, template_id, step_id)
|
|
346
|
+
|
|
347
|
+
def assess_template_health(self, org_id: str, template_id: str):
|
|
348
|
+
"""Comprehensive template health check analyzing multiple aspects."""
|
|
349
|
+
return self.templates.assess_template_health(org_id, template_id)
|
|
350
|
+
|
|
351
|
+
def add_assignees_to_step(self, org_id: str, template_id: str, step_id: str, assignees: Dict[str, Any]):
|
|
352
|
+
"""Add assignees to a specific step in a template."""
|
|
353
|
+
return self.templates.add_assignees_to_step(org_id, template_id, step_id, assignees)
|
|
354
|
+
|
|
355
|
+
def edit_description_on_step(self, org_id: str, template_id: str, step_id: str, description: str):
|
|
356
|
+
"""Edit the description/summary of a specific step in a template."""
|
|
357
|
+
return self.templates.edit_description_on_step(org_id, template_id, step_id, description)
|
|
358
|
+
|
|
359
|
+
def add_step_to_template(self, org_id: str, template_id: str, step_data: Dict[str, Any]):
|
|
360
|
+
"""Add a new step to a template."""
|
|
361
|
+
return self.templates.add_step_to_template(org_id, template_id, step_data)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Form Fields Management Package
|
|
3
|
+
|
|
4
|
+
This package provides a refactored, modular approach to form field management
|
|
5
|
+
functionality, breaking down the monolithic FormFieldManagement class into
|
|
6
|
+
specialized components for better maintainability and separation of concerns.
|
|
7
|
+
|
|
8
|
+
Classes:
|
|
9
|
+
FormFieldCRUD: Basic CRUD operations for form fields
|
|
10
|
+
FormFieldOptions: Dropdown options management
|
|
11
|
+
FormFieldSuggestions: AI-powered form field suggestions
|
|
12
|
+
FormFieldManager: Unified interface combining all functionality
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from .base import FormFieldManagerBase
|
|
16
|
+
from .crud_operations import FormFieldCRUD
|
|
17
|
+
from .options_management import FormFieldOptions
|
|
18
|
+
from .suggestions import FormFieldSuggestions
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FormFieldManager:
|
|
22
|
+
"""
|
|
23
|
+
Unified interface for form field management functionality.
|
|
24
|
+
|
|
25
|
+
This class provides access to all form field management capabilities
|
|
26
|
+
through a single interface while maintaining the modular structure
|
|
27
|
+
underneath.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, sdk):
|
|
31
|
+
"""
|
|
32
|
+
Initialize form field manager with SDK instance.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
sdk: Main SDK instance
|
|
36
|
+
"""
|
|
37
|
+
self.crud = FormFieldCRUD(sdk)
|
|
38
|
+
self.options = FormFieldOptions(sdk)
|
|
39
|
+
self.suggestions = FormFieldSuggestions(sdk)
|
|
40
|
+
|
|
41
|
+
# For backward compatibility, expose common methods at the top level
|
|
42
|
+
self.add_form_field_to_step = self.crud.add_form_field_to_step
|
|
43
|
+
self.update_form_field = self.crud.update_form_field
|
|
44
|
+
self.move_form_field = self.crud.move_form_field
|
|
45
|
+
self.delete_form_field = self.crud.delete_form_field
|
|
46
|
+
self.get_step = self.crud.get_step
|
|
47
|
+
|
|
48
|
+
# Options management methods
|
|
49
|
+
self.get_dropdown_options = self.options.get_dropdown_options
|
|
50
|
+
self.update_dropdown_options = self.options.update_dropdown_options
|
|
51
|
+
self.add_dropdown_option = self.options.add_dropdown_option
|
|
52
|
+
self.remove_dropdown_option = self.options.remove_dropdown_option
|
|
53
|
+
self.reorder_dropdown_options = self.options.reorder_dropdown_options
|
|
54
|
+
|
|
55
|
+
# Suggestions methods
|
|
56
|
+
self.suggest_form_fields_for_step = self.suggestions.suggest_form_fields_for_step
|
|
57
|
+
self.suggest_field_improvements = self.suggestions.suggest_field_improvements
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# For backward compatibility, create an alias
|
|
61
|
+
FormFieldManagement = FormFieldManager
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
'FormFieldManagerBase',
|
|
65
|
+
'FormFieldCRUD',
|
|
66
|
+
'FormFieldOptions',
|
|
67
|
+
'FormFieldSuggestions',
|
|
68
|
+
'FormFieldManager',
|
|
69
|
+
'FormFieldManagement' # Backward compatibility alias
|
|
70
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base class for form field management operations
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from ..models import TallyfyError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FormFieldManagerBase:
|
|
10
|
+
"""Base class providing common functionality for form field management"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, sdk):
|
|
13
|
+
"""
|
|
14
|
+
Initialize base form field 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_template_id(self, template_id: str) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Validate template ID parameter.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
template_id: Template ID to validate
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
ValueError: If template_id is invalid
|
|
43
|
+
"""
|
|
44
|
+
if not template_id or not isinstance(template_id, str):
|
|
45
|
+
raise ValueError("Template ID must be a non-empty string")
|
|
46
|
+
|
|
47
|
+
def _validate_step_id(self, step_id: str) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Validate step ID parameter.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
step_id: Step ID to validate
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
ValueError: If step_id is invalid
|
|
56
|
+
"""
|
|
57
|
+
if not step_id or not isinstance(step_id, str):
|
|
58
|
+
raise ValueError("Step ID must be a non-empty string")
|
|
59
|
+
|
|
60
|
+
def _validate_field_id(self, field_id: str) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Validate field ID parameter.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
field_id: Field ID to validate
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
ValueError: If field_id is invalid
|
|
69
|
+
"""
|
|
70
|
+
if not field_id or not isinstance(field_id, str):
|
|
71
|
+
raise ValueError("Field ID must be a non-empty string")
|
|
72
|
+
|
|
73
|
+
def _extract_data(self, response_data) -> Optional[dict]:
|
|
74
|
+
"""
|
|
75
|
+
Extract data from API response.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
response_data: Raw response from API
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Extracted data dictionary or None
|
|
82
|
+
"""
|
|
83
|
+
if isinstance(response_data, dict):
|
|
84
|
+
if 'data' in response_data:
|
|
85
|
+
return response_data['data']
|
|
86
|
+
return response_data
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
def _handle_api_error(self, error: Exception, operation: str, **context) -> None:
|
|
90
|
+
"""
|
|
91
|
+
Handle API errors with context.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
error: The exception that occurred
|
|
95
|
+
operation: Description of the operation that failed
|
|
96
|
+
**context: Additional context for error logging
|
|
97
|
+
"""
|
|
98
|
+
context_str = ", ".join([f"{k}={v}" for k, v in context.items()])
|
|
99
|
+
error_msg = f"Failed to {operation}"
|
|
100
|
+
if context_str:
|
|
101
|
+
error_msg += f" ({context_str})"
|
|
102
|
+
error_msg += f": {error}"
|
|
103
|
+
|
|
104
|
+
self.sdk.logger.error(error_msg)
|
|
105
|
+
|
|
106
|
+
if isinstance(error, TallyfyError):
|
|
107
|
+
raise error
|
|
108
|
+
else:
|
|
109
|
+
raise TallyfyError(error_msg)
|