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.
Files changed (52) hide show
  1. tallyfy/__init__.py +27 -0
  2. tallyfy/__pycache__/__init__.cpython-310.pyc +0 -0
  3. tallyfy/__pycache__/core.cpython-310.pyc +0 -0
  4. tallyfy/__pycache__/form_fields_management.cpython-310.pyc +0 -0
  5. tallyfy/__pycache__/models.cpython-310.pyc +0 -0
  6. tallyfy/__pycache__/task_management.cpython-310.pyc +0 -0
  7. tallyfy/__pycache__/template_management.cpython-310.pyc +0 -0
  8. tallyfy/__pycache__/user_management.cpython-310.pyc +0 -0
  9. tallyfy/core.py +361 -0
  10. tallyfy/form_fields_management/__init__.py +70 -0
  11. tallyfy/form_fields_management/__pycache__/__init__.cpython-310.pyc +0 -0
  12. tallyfy/form_fields_management/__pycache__/base.cpython-310.pyc +0 -0
  13. tallyfy/form_fields_management/__pycache__/crud_operations.cpython-310.pyc +0 -0
  14. tallyfy/form_fields_management/__pycache__/options_management.cpython-310.pyc +0 -0
  15. tallyfy/form_fields_management/__pycache__/suggestions.cpython-310.pyc +0 -0
  16. tallyfy/form_fields_management/base.py +109 -0
  17. tallyfy/form_fields_management/crud_operations.py +234 -0
  18. tallyfy/form_fields_management/options_management.py +222 -0
  19. tallyfy/form_fields_management/suggestions.py +411 -0
  20. tallyfy/models.py +1464 -0
  21. tallyfy/organization_management/__init__.py +26 -0
  22. tallyfy/organization_management/base.py +76 -0
  23. tallyfy/organization_management/retrieval.py +39 -0
  24. tallyfy/task_management/__init__.py +81 -0
  25. tallyfy/task_management/__pycache__/__init__.cpython-310.pyc +0 -0
  26. tallyfy/task_management/__pycache__/base.cpython-310.pyc +0 -0
  27. tallyfy/task_management/__pycache__/creation.cpython-310.pyc +0 -0
  28. tallyfy/task_management/__pycache__/retrieval.cpython-310.pyc +0 -0
  29. tallyfy/task_management/__pycache__/search.cpython-310.pyc +0 -0
  30. tallyfy/task_management/base.py +125 -0
  31. tallyfy/task_management/creation.py +221 -0
  32. tallyfy/task_management/retrieval.py +252 -0
  33. tallyfy/task_management/search.py +198 -0
  34. tallyfy/template_management/__init__.py +85 -0
  35. tallyfy/template_management/analysis.py +1099 -0
  36. tallyfy/template_management/automation.py +469 -0
  37. tallyfy/template_management/base.py +56 -0
  38. tallyfy/template_management/basic_operations.py +479 -0
  39. tallyfy/template_management/health_assessment.py +793 -0
  40. tallyfy/user_management/__init__.py +70 -0
  41. tallyfy/user_management/__pycache__/__init__.cpython-310.pyc +0 -0
  42. tallyfy/user_management/__pycache__/base.cpython-310.pyc +0 -0
  43. tallyfy/user_management/__pycache__/invitation.cpython-310.pyc +0 -0
  44. tallyfy/user_management/__pycache__/retrieval.cpython-310.pyc +0 -0
  45. tallyfy/user_management/base.py +146 -0
  46. tallyfy/user_management/invitation.py +286 -0
  47. tallyfy/user_management/retrieval.py +381 -0
  48. tallyfy-1.0.16.dist-info/METADATA +742 -0
  49. tallyfy-1.0.16.dist-info/RECORD +52 -0
  50. tallyfy-1.0.16.dist-info/WHEEL +5 -0
  51. tallyfy-1.0.16.dist-info/licenses/LICENSE +21 -0
  52. 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
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
+ ]
@@ -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)