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.

Files changed (34) hide show
  1. tallyfy/__init__.py +8 -4
  2. tallyfy/core.py +8 -8
  3. tallyfy/form_fields_management/__init__.py +70 -0
  4. tallyfy/form_fields_management/base.py +109 -0
  5. tallyfy/form_fields_management/crud_operations.py +234 -0
  6. tallyfy/form_fields_management/options_management.py +222 -0
  7. tallyfy/form_fields_management/suggestions.py +411 -0
  8. tallyfy/task_management/__init__.py +81 -0
  9. tallyfy/task_management/base.py +125 -0
  10. tallyfy/task_management/creation.py +221 -0
  11. tallyfy/task_management/retrieval.py +211 -0
  12. tallyfy/task_management/search.py +196 -0
  13. tallyfy/template_management/__init__.py +85 -0
  14. tallyfy/template_management/analysis.py +1093 -0
  15. tallyfy/template_management/automation.py +469 -0
  16. tallyfy/template_management/base.py +56 -0
  17. tallyfy/template_management/basic_operations.py +477 -0
  18. tallyfy/template_management/health_assessment.py +763 -0
  19. tallyfy/user_management/__init__.py +69 -0
  20. tallyfy/user_management/base.py +146 -0
  21. tallyfy/user_management/invitation.py +286 -0
  22. tallyfy/user_management/retrieval.py +339 -0
  23. {tallyfy-1.0.3.dist-info → tallyfy-1.0.5.dist-info}/METADATA +120 -56
  24. tallyfy-1.0.5.dist-info/RECORD +28 -0
  25. tallyfy/BUILD.md +0 -5
  26. tallyfy/README.md +0 -634
  27. tallyfy/form_fields_management.py +0 -582
  28. tallyfy/task_management.py +0 -356
  29. tallyfy/template_management.py +0 -2607
  30. tallyfy/user_management.py +0 -235
  31. tallyfy-1.0.3.dist-info/RECORD +0 -14
  32. {tallyfy-1.0.3.dist-info → tallyfy-1.0.5.dist-info}/WHEEL +0 -0
  33. {tallyfy-1.0.3.dist-info → tallyfy-1.0.5.dist-info}/licenses/LICENSE +0 -0
  34. {tallyfy-1.0.3.dist-info → tallyfy-1.0.5.dist-info}/top_level.txt +0 -0
tallyfy/__init__.py CHANGED
@@ -4,17 +4,21 @@ Tallyfy SDK - A modular Python SDK for Tallyfy API
4
4
 
5
5
  from .core import TallyfySDK, TallyfyError
6
6
  from .models import *
7
- from .user_management import UserManagement
8
- from .task_management import TaskManagement
9
- from .template_management import TemplateManagement
10
- from .form_fields_management import FormFieldManagement
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
11
 
12
12
  __version__ = "1.0.0"
13
13
  __all__ = [
14
14
  "TallyfySDK",
15
15
  "TallyfyError",
16
+ "UserManager",
16
17
  "UserManagement",
18
+ "TaskManager",
17
19
  "TaskManagement",
20
+ "TemplateManager",
18
21
  "TemplateManagement",
22
+ "FormFieldManager",
19
23
  "FormFieldManagement"
20
24
  ]
tallyfy/core.py CHANGED
@@ -148,15 +148,15 @@ class TallyfySDK(BaseSDK):
148
148
  super().__init__(api_key, base_url, timeout, max_retries, retry_delay)
149
149
 
150
150
  # Initialize management modules
151
- from .user_management import UserManagement
152
- from .task_management import TaskManagement
153
- from .template_management import TemplateManagement
154
- from .form_fields_management import FormFieldManagement
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
155
 
156
- self.users = UserManagement(self)
157
- self.tasks = TaskManagement(self)
158
- self.templates = TemplateManagement(self)
159
- self.form_fields = FormFieldManagement(self)
156
+ self.users = UserManager(self)
157
+ self.tasks = TaskManager(self)
158
+ self.templates = TemplateManager(self)
159
+ self.form_fields = FormFieldManager(self)
160
160
 
161
161
  # Backward compatibility methods - delegate to management modules
162
162
  def get_organization_users(self, org_id: str, with_groups: bool = False):
@@ -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)
@@ -0,0 +1,234 @@
1
+ """
2
+ CRUD operations for form field management
3
+ """
4
+
5
+ from typing import Dict, Any, Optional
6
+ from .base import FormFieldManagerBase
7
+ from ..models import Capture, Step, TallyfyError
8
+
9
+
10
+ class FormFieldCRUD(FormFieldManagerBase):
11
+ """Handles basic CRUD operations for form fields"""
12
+
13
+ def add_form_field_to_step(self, org_id: str, template_id: str, step_id: str, field_data: Dict[str, Any]) -> Optional[Capture]:
14
+ """
15
+ Add form fields (text, dropdown, date, etc.) to a step.
16
+
17
+ Args:
18
+ org_id: Organization ID
19
+ template_id: Template ID
20
+ step_id: Step ID
21
+ field_data: Form field creation data including field_type, label, required, etc.
22
+
23
+ Returns:
24
+ Created Capture object or None if creation failed
25
+
26
+ Raises:
27
+ TallyfyError: If the request fails
28
+ """
29
+ self._validate_org_id(org_id)
30
+ self._validate_template_id(template_id)
31
+ self._validate_step_id(step_id)
32
+
33
+ try:
34
+ endpoint = f"organizations/{org_id}/checklists/{template_id}/steps/{step_id}/captures"
35
+
36
+ # Validate required fields
37
+ required_fields = ['field_type', 'label']
38
+ for field in required_fields:
39
+ if field not in field_data:
40
+ raise ValueError(f"Missing required field: {field}")
41
+
42
+ # Set defaults for optional fields
43
+ capture_data = {
44
+ 'field_type': field_data['field_type'],
45
+ 'label': field_data['label'],
46
+ 'required': field_data.get('required', True),
47
+ 'position': field_data.get('position', 1)
48
+ }
49
+
50
+ # Add optional fields if provided
51
+ optional_fields = ['guidance', 'options', 'default_value', 'default_value_enabled']
52
+ for field in optional_fields:
53
+ if field in field_data:
54
+ capture_data[field] = field_data[field]
55
+
56
+ response_data = self.sdk._make_request('POST', endpoint, data=capture_data)
57
+
58
+ extracted_data = self._extract_data(response_data)
59
+ if extracted_data:
60
+ return Capture.from_dict(extracted_data)
61
+ else:
62
+ self.sdk.logger.warning("Unexpected response format for form field creation")
63
+ return None
64
+
65
+ except TallyfyError:
66
+ raise
67
+ except Exception as e:
68
+ self._handle_api_error(e, "add form field to step", org_id=org_id, template_id=template_id, step_id=step_id)
69
+
70
+ def update_form_field(self, org_id: str, template_id: str, step_id: str, field_id: str, **kwargs) -> Optional[Capture]:
71
+ """
72
+ Update form field properties, validation, options.
73
+
74
+ Args:
75
+ org_id: Organization ID
76
+ template_id: Template ID
77
+ step_id: Step ID
78
+ field_id: Form field ID
79
+ **kwargs: Form field properties to update
80
+
81
+ Returns:
82
+ Updated Capture object or None if update failed
83
+
84
+ Raises:
85
+ TallyfyError: If the request fails
86
+ """
87
+ self._validate_org_id(org_id)
88
+ self._validate_template_id(template_id)
89
+ self._validate_step_id(step_id)
90
+ self._validate_field_id(field_id)
91
+
92
+ try:
93
+ endpoint = f"organizations/{org_id}/checklists/{template_id}/steps/{step_id}/captures/{field_id}"
94
+
95
+ # Build update data from kwargs
96
+ update_data = {}
97
+ allowed_fields = [
98
+ 'field_type', 'label', 'guidance', 'position', 'required',
99
+ 'options', 'default_value', 'default_value_enabled'
100
+ ]
101
+
102
+ for field, value in kwargs.items():
103
+ if field in allowed_fields:
104
+ update_data[field] = value
105
+ else:
106
+ self.sdk.logger.warning(f"Ignoring unknown form field: {field}")
107
+
108
+ if not update_data:
109
+ raise ValueError("No valid form field properties provided for update")
110
+
111
+ response_data = self.sdk._make_request('PUT', endpoint, data=update_data)
112
+
113
+ extracted_data = self._extract_data(response_data)
114
+ if extracted_data:
115
+ return Capture.from_dict(extracted_data)
116
+ else:
117
+ self.sdk.logger.warning("Unexpected response format for form field update")
118
+ return None
119
+
120
+ except TallyfyError:
121
+ raise
122
+ except Exception as e:
123
+ self._handle_api_error(e, "update form field", org_id=org_id, template_id=template_id, step_id=step_id, field_id=field_id)
124
+
125
+ def move_form_field(self, org_id: str, template_id: str, from_step: str, field_id: str, to_step: str, position: int = 1) -> bool:
126
+ """
127
+ Move form field between steps.
128
+
129
+ Args:
130
+ org_id: Organization ID
131
+ template_id: Template ID
132
+ from_step: Source step ID
133
+ field_id: Form field ID to move
134
+ to_step: Target step ID
135
+ position: Position in target step (default: 1)
136
+
137
+ Returns:
138
+ True if move was successful
139
+
140
+ Raises:
141
+ TallyfyError: If the request fails
142
+ """
143
+ self._validate_org_id(org_id)
144
+ self._validate_template_id(template_id)
145
+ self._validate_step_id(from_step)
146
+ self._validate_field_id(field_id)
147
+ self._validate_step_id(to_step)
148
+
149
+ try:
150
+ endpoint = f"organizations/{org_id}/checklists/{template_id}/steps/{from_step}/captures/{field_id}/move"
151
+
152
+ move_data = {
153
+ 'to_step_id': to_step,
154
+ 'position': position
155
+ }
156
+
157
+ response_data = self.sdk._make_request('POST', endpoint, data=move_data)
158
+
159
+ # Check if move was successful
160
+ return isinstance(response_data, dict) and response_data.get('success', False)
161
+
162
+ except TallyfyError:
163
+ raise
164
+ except Exception as e:
165
+ self._handle_api_error(e, "move form field", org_id=org_id, template_id=template_id, from_step=from_step, field_id=field_id, to_step=to_step)
166
+
167
+ def delete_form_field(self, org_id: str, template_id: str, step_id: str, field_id: str) -> bool:
168
+ """
169
+ Delete a form field from a step.
170
+
171
+ Args:
172
+ org_id: Organization ID
173
+ template_id: Template ID
174
+ step_id: Step ID
175
+ field_id: Form field ID
176
+
177
+ Returns:
178
+ True if deletion was successful
179
+
180
+ Raises:
181
+ TallyfyError: If the request fails
182
+ """
183
+ self._validate_org_id(org_id)
184
+ self._validate_template_id(template_id)
185
+ self._validate_step_id(step_id)
186
+ self._validate_field_id(field_id)
187
+
188
+ try:
189
+ endpoint = f"organizations/{org_id}/checklists/{template_id}/steps/{step_id}/captures/{field_id}"
190
+
191
+ response_data = self.sdk._make_request('DELETE', endpoint)
192
+
193
+ # Check if deletion was successful
194
+ return isinstance(response_data, dict) and response_data.get('success', False)
195
+
196
+ except TallyfyError:
197
+ raise
198
+ except Exception as e:
199
+ self._handle_api_error(e, "delete form field", org_id=org_id, template_id=template_id, step_id=step_id, field_id=field_id)
200
+
201
+ def get_step(self, org_id: str, template_id: str, step_id: str) -> Optional[Step]:
202
+ """
203
+ Get a specific step with its details.
204
+
205
+ Args:
206
+ org_id: Organization ID
207
+ template_id: Template ID
208
+ step_id: Step ID
209
+
210
+ Returns:
211
+ Step object or None if not found
212
+
213
+ Raises:
214
+ TallyfyError: If the request fails
215
+ """
216
+ self._validate_org_id(org_id)
217
+ self._validate_template_id(template_id)
218
+ self._validate_step_id(step_id)
219
+
220
+ try:
221
+ endpoint = f"organizations/{org_id}/checklists/{template_id}/steps/{step_id}"
222
+ response_data = self.sdk._make_request('GET', endpoint)
223
+
224
+ extracted_data = self._extract_data(response_data)
225
+ if extracted_data:
226
+ return Step.from_dict(extracted_data)
227
+ else:
228
+ self.sdk.logger.warning("Unexpected response format for step")
229
+ return None
230
+
231
+ except TallyfyError:
232
+ raise
233
+ except Exception as e:
234
+ self._handle_api_error(e, "get step", org_id=org_id, template_id=template_id, step_id=step_id)
@@ -0,0 +1,222 @@
1
+ """
2
+ Dropdown options management for form fields
3
+ """
4
+
5
+ from typing import List, Dict, Any, Optional
6
+ from .base import FormFieldManagerBase
7
+ from ..models import TallyfyError
8
+
9
+
10
+ class FormFieldOptions(FormFieldManagerBase):
11
+ """Handles dropdown options management for form fields"""
12
+
13
+ def get_dropdown_options(self, org_id: str, template_id: str, step_id: str, field_id: str) -> List[str]:
14
+ """
15
+ Get current dropdown options for analysis.
16
+
17
+ Args:
18
+ org_id: Organization ID
19
+ template_id: Template ID
20
+ step_id: Step ID
21
+ field_id: Form field ID
22
+
23
+ Returns:
24
+ List of dropdown option strings
25
+
26
+ Raises:
27
+ TallyfyError: If the request fails
28
+ """
29
+ self._validate_org_id(org_id)
30
+ self._validate_template_id(template_id)
31
+ self._validate_step_id(step_id)
32
+ self._validate_field_id(field_id)
33
+
34
+ try:
35
+ endpoint = f"organizations/{org_id}/checklists/{template_id}/steps/{step_id}/captures/{field_id}"
36
+
37
+ response_data = self.sdk._make_request('GET', endpoint)
38
+
39
+ extracted_data = self._extract_data(response_data)
40
+ if extracted_data:
41
+ options = extracted_data.get('options', [])
42
+
43
+ # Extract option values/labels
44
+ if isinstance(options, list):
45
+ return [
46
+ opt.get('label', opt.get('value', str(opt))) if isinstance(opt, dict) else str(opt)
47
+ for opt in options
48
+ ]
49
+ else:
50
+ return []
51
+ else:
52
+ self.sdk.logger.warning("Unexpected response format for form field options")
53
+ return []
54
+
55
+ except TallyfyError:
56
+ raise
57
+ except Exception as e:
58
+ self._handle_api_error(e, "get dropdown options", org_id=org_id, template_id=template_id, step_id=step_id, field_id=field_id)
59
+
60
+ def update_dropdown_options(self, org_id: str, template_id: str, step_id: str, field_id: str, options: List[str]) -> bool:
61
+ """
62
+ Update dropdown options (for external data integration).
63
+
64
+ Args:
65
+ org_id: Organization ID
66
+ template_id: Template ID
67
+ step_id: Step ID
68
+ field_id: Form field ID
69
+ options: List of new option strings
70
+
71
+ Returns:
72
+ True if update was successful
73
+
74
+ Raises:
75
+ TallyfyError: If the request fails
76
+ """
77
+ self._validate_org_id(org_id)
78
+ self._validate_template_id(template_id)
79
+ self._validate_step_id(step_id)
80
+ self._validate_field_id(field_id)
81
+
82
+ try:
83
+ # Format options for API
84
+ formatted_options = []
85
+ for i, option in enumerate(options):
86
+ if isinstance(option, str):
87
+ formatted_options.append({
88
+ 'value': option.lower().replace(' ', '_'),
89
+ 'label': option,
90
+ 'position': i + 1
91
+ })
92
+ elif isinstance(option, dict):
93
+ formatted_options.append(option)
94
+ else:
95
+ formatted_options.append({
96
+ 'value': str(option),
97
+ 'label': str(option),
98
+ 'position': i + 1
99
+ })
100
+
101
+ # Update the field with new options using CRUD operations
102
+ # We need to import FormFieldCRUD here to avoid circular imports
103
+ from .crud_operations import FormFieldCRUD
104
+ crud = FormFieldCRUD(self.sdk)
105
+
106
+ updated_capture = crud.update_form_field(
107
+ org_id, template_id, step_id, field_id,
108
+ options=formatted_options
109
+ )
110
+
111
+ return updated_capture is not None
112
+
113
+ except TallyfyError:
114
+ raise
115
+ except Exception as e:
116
+ self._handle_api_error(e, "update dropdown options", org_id=org_id, template_id=template_id, step_id=step_id, field_id=field_id)
117
+
118
+ def add_dropdown_option(self, org_id: str, template_id: str, step_id: str, field_id: str, new_option: str, position: Optional[int] = None) -> bool:
119
+ """
120
+ Add a single new option to an existing dropdown field.
121
+
122
+ Args:
123
+ org_id: Organization ID
124
+ template_id: Template ID
125
+ step_id: Step ID
126
+ field_id: Form field ID
127
+ new_option: New option to add
128
+ position: Position to insert the option (None = append to end)
129
+
130
+ Returns:
131
+ True if addition was successful
132
+
133
+ Raises:
134
+ TallyfyError: If the request fails
135
+ """
136
+ try:
137
+ # Get current options
138
+ current_options = self.get_dropdown_options(org_id, template_id, step_id, field_id)
139
+
140
+ # Add new option at specified position or end
141
+ if position is not None and 0 <= position <= len(current_options):
142
+ current_options.insert(position, new_option)
143
+ else:
144
+ current_options.append(new_option)
145
+
146
+ # Update with new options list
147
+ return self.update_dropdown_options(org_id, template_id, step_id, field_id, current_options)
148
+
149
+ except TallyfyError:
150
+ raise
151
+ except Exception as e:
152
+ self._handle_api_error(e, "add dropdown option", org_id=org_id, template_id=template_id, step_id=step_id, field_id=field_id)
153
+
154
+ def remove_dropdown_option(self, org_id: str, template_id: str, step_id: str, field_id: str, option_to_remove: str) -> bool:
155
+ """
156
+ Remove a specific option from a dropdown field.
157
+
158
+ Args:
159
+ org_id: Organization ID
160
+ template_id: Template ID
161
+ step_id: Step ID
162
+ field_id: Form field ID
163
+ option_to_remove: Option to remove
164
+
165
+ Returns:
166
+ True if removal was successful
167
+
168
+ Raises:
169
+ TallyfyError: If the request fails
170
+ """
171
+ try:
172
+ # Get current options
173
+ current_options = self.get_dropdown_options(org_id, template_id, step_id, field_id)
174
+
175
+ # Remove the specified option if it exists
176
+ if option_to_remove in current_options:
177
+ current_options.remove(option_to_remove)
178
+
179
+ # Update with modified options list
180
+ return self.update_dropdown_options(org_id, template_id, step_id, field_id, current_options)
181
+ else:
182
+ self.sdk.logger.warning(f"Option '{option_to_remove}' not found in dropdown field {field_id}")
183
+ return False
184
+
185
+ except TallyfyError:
186
+ raise
187
+ except Exception as e:
188
+ self._handle_api_error(e, "remove dropdown option", org_id=org_id, template_id=template_id, step_id=step_id, field_id=field_id)
189
+
190
+ def reorder_dropdown_options(self, org_id: str, template_id: str, step_id: str, field_id: str, ordered_options: List[str]) -> bool:
191
+ """
192
+ Reorder dropdown options to match the provided list.
193
+
194
+ Args:
195
+ org_id: Organization ID
196
+ template_id: Template ID
197
+ step_id: Step ID
198
+ field_id: Form field ID
199
+ ordered_options: List of options in desired order
200
+
201
+ Returns:
202
+ True if reordering was successful
203
+
204
+ Raises:
205
+ TallyfyError: If the request fails
206
+ """
207
+ try:
208
+ # Get current options to validate
209
+ current_options = self.get_dropdown_options(org_id, template_id, step_id, field_id)
210
+
211
+ # Validate that all provided options exist in current options
212
+ missing_options = set(ordered_options) - set(current_options)
213
+ if missing_options:
214
+ raise ValueError(f"Cannot reorder: options not found in field: {missing_options}")
215
+
216
+ # Update with reordered options
217
+ return self.update_dropdown_options(org_id, template_id, step_id, field_id, ordered_options)
218
+
219
+ except TallyfyError:
220
+ raise
221
+ except Exception as e:
222
+ self._handle_api_error(e, "reorder dropdown options", org_id=org_id, template_id=template_id, step_id=step_id, field_id=field_id)