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
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Organization management module for Tallyfy SDK
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .base import OrganizationManagerBase
|
|
6
|
+
from .retrieval import OrganizationRetrieval
|
|
7
|
+
|
|
8
|
+
class OrganizationManager(OrganizationRetrieval):
|
|
9
|
+
"""
|
|
10
|
+
Complete organization management interface combining all functionality.
|
|
11
|
+
|
|
12
|
+
This class provides access to:
|
|
13
|
+
- Organization retrieval operations
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
>>> from tallyfy import TallyfySDK
|
|
17
|
+
>>> sdk = TallyfySDK(api_key="your_api_key")
|
|
18
|
+
>>> orgs = sdk.organizations.get_current_user_organizations()
|
|
19
|
+
>>> print(f"User belongs to {len(orgs.data)} organizations")
|
|
20
|
+
"""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
# Maintain backward compatibility
|
|
24
|
+
OrganizationManagement = OrganizationManager
|
|
25
|
+
|
|
26
|
+
__all__ = ['OrganizationManager', 'OrganizationManagement', 'OrganizationRetrieval', 'OrganizationManagerBase']
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base class for organization management operations
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Dict, Any, List, Optional
|
|
7
|
+
from ..models import TallyfyError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OrganizationManagerBase:
|
|
11
|
+
"""Base class providing common functionality for organization management operations"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, sdk):
|
|
14
|
+
"""
|
|
15
|
+
Initialize the organization manager base.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
sdk: The main Tallyfy SDK instance
|
|
19
|
+
"""
|
|
20
|
+
self.sdk = sdk
|
|
21
|
+
self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
|
|
22
|
+
|
|
23
|
+
def _extract_data(self, response_data: Dict[str, Any], default_empty: bool = True) -> Any:
|
|
24
|
+
"""
|
|
25
|
+
Extract data from API response.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
response_data: Raw API response
|
|
29
|
+
default_empty: Return empty list if no data found
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Extracted data
|
|
33
|
+
"""
|
|
34
|
+
if not response_data:
|
|
35
|
+
return [] if default_empty else None
|
|
36
|
+
|
|
37
|
+
# Handle direct data responses
|
|
38
|
+
if 'data' in response_data:
|
|
39
|
+
return response_data['data']
|
|
40
|
+
|
|
41
|
+
# Handle responses where the main content is directly available
|
|
42
|
+
# This is common for single item responses
|
|
43
|
+
return response_data if not default_empty else []
|
|
44
|
+
|
|
45
|
+
def _handle_api_error(self, error: Exception, operation: str, **context) -> None:
|
|
46
|
+
"""
|
|
47
|
+
Handle and log API errors consistently.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
error: The exception that occurred
|
|
51
|
+
operation: Description of the operation that failed
|
|
52
|
+
**context: Additional context for debugging
|
|
53
|
+
"""
|
|
54
|
+
context_str = ", ".join([f"{k}={v}" for k, v in context.items()])
|
|
55
|
+
error_message = f"Failed to {operation}"
|
|
56
|
+
if context_str:
|
|
57
|
+
error_message += f" ({context_str})"
|
|
58
|
+
|
|
59
|
+
self.logger.error(f"{error_message}: {str(error)}")
|
|
60
|
+
|
|
61
|
+
if isinstance(error, TallyfyError):
|
|
62
|
+
raise error
|
|
63
|
+
else:
|
|
64
|
+
raise TallyfyError(f"{error_message}: {str(error)}")
|
|
65
|
+
|
|
66
|
+
def _build_query_params(self, **kwargs) -> Dict[str, Any]:
|
|
67
|
+
"""
|
|
68
|
+
Build query parameters for API requests.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
**kwargs: Parameters to include in the query string
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Dictionary of query parameters with None values filtered out
|
|
75
|
+
"""
|
|
76
|
+
return {key: value for key, value in kwargs.items() if value is not None}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Organization retrieval operations
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
from .base import OrganizationManagerBase
|
|
7
|
+
from ..models import Organization, OrganizationsList, TallyfyError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OrganizationRetrieval(OrganizationManagerBase):
|
|
11
|
+
"""Handles organization retrieval operations"""
|
|
12
|
+
|
|
13
|
+
def get_current_user_organizations(self, page: int = 1, per_page: int = 10) -> OrganizationsList:
|
|
14
|
+
"""
|
|
15
|
+
Get all organizations the current member is a part of.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
page: Page number (default: 1)
|
|
19
|
+
per_page: Number of results per page (default: 10, max: 100)
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
OrganizationsList object containing organizations and pagination metadata
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
TallyfyError: If the request fails
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
endpoint = "me/organizations"
|
|
29
|
+
params = self._build_query_params(page=page, per_page=min(per_page, 100))
|
|
30
|
+
|
|
31
|
+
response_data = self.sdk._make_request('GET', endpoint, params=params)
|
|
32
|
+
|
|
33
|
+
# Return the structured response with pagination
|
|
34
|
+
return OrganizationsList.from_dict(response_data)
|
|
35
|
+
|
|
36
|
+
except TallyfyError:
|
|
37
|
+
raise
|
|
38
|
+
except Exception as e:
|
|
39
|
+
self._handle_api_error(e, "get my organizations", page=page, per_page=per_page)
|
|
@@ -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
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Task creation operations
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from .base import TaskManagerBase
|
|
7
|
+
from ..models import Task, TaskOwners, TallyfyError
|
|
8
|
+
from email_validator import validate_email, EmailNotValidError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TaskCreation(TaskManagerBase):
|
|
12
|
+
"""Handles task creation operations"""
|
|
13
|
+
|
|
14
|
+
def create_task(self, org_id: str, title: str, deadline: str,
|
|
15
|
+
owners: TaskOwners, description: Optional[str] = None,
|
|
16
|
+
max_assignable: Optional[int] = None, prevent_guest_comment: Optional[bool] = None) -> Optional[Task]:
|
|
17
|
+
"""
|
|
18
|
+
Create a standalone task in the organization.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
org_id: Organization ID
|
|
22
|
+
title: Task name (required)
|
|
23
|
+
deadline: Task deadline in "YYYY-mm-dd HH:ii:ss" format
|
|
24
|
+
owners: TaskOwners object with users, guests, and groups
|
|
25
|
+
description: Task description (optional)
|
|
26
|
+
max_assignable: Maximum number of assignees (optional)
|
|
27
|
+
prevent_guest_comment: Prevent guests from commenting (optional)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Task object for the created task
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
TallyfyError: If the request fails
|
|
34
|
+
ValueError: If required parameters are missing or invalid
|
|
35
|
+
"""
|
|
36
|
+
self._validate_org_id(org_id)
|
|
37
|
+
|
|
38
|
+
if not title or not isinstance(title, str):
|
|
39
|
+
raise ValueError("Task title must be a non-empty string")
|
|
40
|
+
|
|
41
|
+
if not deadline or not isinstance(deadline, str):
|
|
42
|
+
raise ValueError("Task deadline must be a non-empty string in 'YYYY-mm-dd HH:ii:ss' format")
|
|
43
|
+
|
|
44
|
+
# Validate that at least one assignee is provided
|
|
45
|
+
if not owners or not isinstance(owners, TaskOwners):
|
|
46
|
+
raise ValueError("TaskOwners object is required")
|
|
47
|
+
|
|
48
|
+
if not owners.users and not owners.guests and not owners.groups:
|
|
49
|
+
raise ValueError("At least one assignee is required (users, guests, or groups)")
|
|
50
|
+
|
|
51
|
+
# Validate max_assignable if provided
|
|
52
|
+
if max_assignable is not None and (not isinstance(max_assignable, int) or max_assignable <= 0):
|
|
53
|
+
raise ValueError("max_assignable must be a positive integer")
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
endpoint = f"organizations/{org_id}/tasks"
|
|
57
|
+
|
|
58
|
+
task_data = {
|
|
59
|
+
"title": title,
|
|
60
|
+
"deadline": deadline,
|
|
61
|
+
"owners": {
|
|
62
|
+
"users": owners.users or [],
|
|
63
|
+
"guests": owners.guests or [],
|
|
64
|
+
"groups": owners.groups or []
|
|
65
|
+
},
|
|
66
|
+
"task_type": "task",
|
|
67
|
+
"separate_task_for_each_assignee": True,
|
|
68
|
+
"status": "not-started",
|
|
69
|
+
"everyone_must_complete": False,
|
|
70
|
+
"is_soft_start_date": True
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Add optional fields
|
|
74
|
+
if description:
|
|
75
|
+
task_data["description"] = description
|
|
76
|
+
if max_assignable is not None:
|
|
77
|
+
task_data["max_assignable"] = max_assignable
|
|
78
|
+
if prevent_guest_comment is not None:
|
|
79
|
+
task_data["prevent_guest_comment"] = prevent_guest_comment
|
|
80
|
+
|
|
81
|
+
response_data = self.sdk._make_request('POST', endpoint, data=task_data)
|
|
82
|
+
|
|
83
|
+
task_response_data = self._extract_data(response_data)
|
|
84
|
+
if task_response_data:
|
|
85
|
+
# Handle both single task and list responses
|
|
86
|
+
if isinstance(task_response_data, list) and task_response_data:
|
|
87
|
+
return Task.from_dict(task_response_data[0])
|
|
88
|
+
elif isinstance(task_response_data, dict):
|
|
89
|
+
return Task.from_dict(task_response_data)
|
|
90
|
+
|
|
91
|
+
# Check if response_data itself is the task data
|
|
92
|
+
if isinstance(response_data, dict) and 'data' in response_data:
|
|
93
|
+
task_data_response = response_data['data']
|
|
94
|
+
if isinstance(task_data_response, dict):
|
|
95
|
+
return Task.from_dict(task_data_response)
|
|
96
|
+
|
|
97
|
+
self.sdk.logger.warning("Unexpected response format for task creation")
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
except TallyfyError:
|
|
101
|
+
raise
|
|
102
|
+
except Exception as e:
|
|
103
|
+
self._handle_api_error(e, "create task", org_id=org_id, title=title)
|
|
104
|
+
|
|
105
|
+
def create_simple_task(self, org_id: str, title: str, deadline: str, user_ids: Optional[list] = None,
|
|
106
|
+
guest_emails: Optional[list] = None, group_ids: Optional[list] = None,
|
|
107
|
+
description: Optional[str] = None) -> Optional[Task]:
|
|
108
|
+
"""
|
|
109
|
+
Create a simple task with basic assignee information.
|
|
110
|
+
|
|
111
|
+
This is a convenience method that creates TaskOwners internally.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
org_id: Organization ID
|
|
115
|
+
title: Task name (required)
|
|
116
|
+
deadline: Task deadline in "YYYY-mm-dd HH:ii:ss" format
|
|
117
|
+
user_ids: List of user IDs to assign (optional)
|
|
118
|
+
guest_emails: List of guest email addresses to assign (optional)
|
|
119
|
+
group_ids: List of group IDs to assign (optional)
|
|
120
|
+
description: Task description (optional)
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Task object for the created task
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
TallyfyError: If the request fails
|
|
127
|
+
ValueError: If required parameters are missing or no assignees provided
|
|
128
|
+
"""
|
|
129
|
+
# Validate that at least one assignee type is provided
|
|
130
|
+
if not user_ids and not guest_emails and not group_ids:
|
|
131
|
+
raise ValueError("At least one assignee is required (user_ids, guest_emails, or group_ids)")
|
|
132
|
+
|
|
133
|
+
# Create TaskOwners object
|
|
134
|
+
owners = TaskOwners(
|
|
135
|
+
users=user_ids or [],
|
|
136
|
+
guests=guest_emails or [],
|
|
137
|
+
groups=group_ids or []
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return self.create_task(org_id, title, deadline, owners, description)
|
|
141
|
+
|
|
142
|
+
def create_user_task(self, org_id: str, title: str, deadline: str, user_ids: list,
|
|
143
|
+
description: Optional[str] = None) -> Optional[Task]:
|
|
144
|
+
"""
|
|
145
|
+
Create a task assigned to specific users only.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
org_id: Organization ID
|
|
149
|
+
title: Task name (required)
|
|
150
|
+
deadline: Task deadline in "YYYY-mm-dd HH:ii:ss" format
|
|
151
|
+
user_ids: List of user IDs to assign (required)
|
|
152
|
+
description: Task description (optional)
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Task object for the created task
|
|
156
|
+
|
|
157
|
+
Raises:
|
|
158
|
+
TallyfyError: If the request fails
|
|
159
|
+
ValueError: If required parameters are missing
|
|
160
|
+
"""
|
|
161
|
+
if not user_ids or not isinstance(user_ids, list):
|
|
162
|
+
raise ValueError("user_ids must be a non-empty list")
|
|
163
|
+
|
|
164
|
+
return self.create_simple_task(org_id, title, deadline, user_ids=user_ids, description=description)
|
|
165
|
+
|
|
166
|
+
def create_guest_task(self, org_id: str, title: str, deadline: str, guest_emails: list,
|
|
167
|
+
description: Optional[str] = None) -> Optional[Task]:
|
|
168
|
+
"""
|
|
169
|
+
Create a task assigned to guests only.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
org_id: Organization ID
|
|
173
|
+
title: Task name (required)
|
|
174
|
+
deadline: Task deadline in "YYYY-mm-dd HH:ii:ss" format
|
|
175
|
+
guest_emails: List of guest email addresses to assign (required)
|
|
176
|
+
description: Task description (optional)
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Task object for the created task
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
TallyfyError: If the request fails
|
|
183
|
+
ValueError: If required parameters are missing
|
|
184
|
+
"""
|
|
185
|
+
if not guest_emails or not isinstance(guest_emails, list):
|
|
186
|
+
raise ValueError("guest_emails must be a non-empty list")
|
|
187
|
+
|
|
188
|
+
# Basic email validation
|
|
189
|
+
for email in guest_emails:
|
|
190
|
+
try:
|
|
191
|
+
validation = validate_email(email)
|
|
192
|
+
# The validated email address
|
|
193
|
+
email = validation.normalized
|
|
194
|
+
except EmailNotValidError as e:
|
|
195
|
+
raise ValueError(f"Invalid email address: {str(e)}")
|
|
196
|
+
|
|
197
|
+
return self.create_simple_task(org_id, title, deadline, guest_emails=guest_emails, description=description)
|
|
198
|
+
|
|
199
|
+
def create_group_task(self, org_id: str, title: str, deadline: str, group_ids: list,
|
|
200
|
+
description: Optional[str] = None) -> Optional[Task]:
|
|
201
|
+
"""
|
|
202
|
+
Create a task assigned to groups only.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
org_id: Organization ID
|
|
206
|
+
title: Task name (required)
|
|
207
|
+
deadline: Task deadline in "YYYY-mm-dd HH:ii:ss" format
|
|
208
|
+
group_ids: List of group IDs to assign (required)
|
|
209
|
+
description: Task description (optional)
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Task object for the created task
|
|
213
|
+
|
|
214
|
+
Raises:
|
|
215
|
+
TallyfyError: If the request fails
|
|
216
|
+
ValueError: If required parameters are missing
|
|
217
|
+
"""
|
|
218
|
+
if not group_ids or not isinstance(group_ids, list):
|
|
219
|
+
raise ValueError("group_ids must be a non-empty list")
|
|
220
|
+
|
|
221
|
+
return self.create_simple_task(org_id, title, deadline, group_ids=group_ids, description=description)
|