binalyze-air-sdk 1.0.1__py3-none-any.whl → 1.0.3__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 (142) hide show
  1. binalyze_air/__init__.py +77 -77
  2. binalyze_air/apis/__init__.py +67 -27
  3. binalyze_air/apis/acquisitions.py +107 -0
  4. binalyze_air/apis/api_tokens.py +49 -0
  5. binalyze_air/apis/assets.py +161 -0
  6. binalyze_air/apis/audit_logs.py +26 -0
  7. binalyze_air/apis/{authentication.py → auth.py} +29 -27
  8. binalyze_air/apis/auto_asset_tags.py +79 -75
  9. binalyze_air/apis/backup.py +177 -0
  10. binalyze_air/apis/baseline.py +46 -0
  11. binalyze_air/apis/cases.py +225 -0
  12. binalyze_air/apis/cloud_forensics.py +116 -0
  13. binalyze_air/apis/event_subscription.py +96 -96
  14. binalyze_air/apis/evidence.py +249 -53
  15. binalyze_air/apis/interact.py +153 -36
  16. binalyze_air/apis/investigation_hub.py +234 -0
  17. binalyze_air/apis/license.py +104 -0
  18. binalyze_air/apis/logger.py +83 -0
  19. binalyze_air/apis/multipart_upload.py +201 -0
  20. binalyze_air/apis/notifications.py +115 -0
  21. binalyze_air/apis/organizations.py +267 -0
  22. binalyze_air/apis/params.py +44 -39
  23. binalyze_air/apis/policies.py +186 -0
  24. binalyze_air/apis/preset_filters.py +79 -0
  25. binalyze_air/apis/recent_activities.py +71 -0
  26. binalyze_air/apis/relay_server.py +104 -0
  27. binalyze_air/apis/settings.py +395 -27
  28. binalyze_air/apis/tasks.py +80 -0
  29. binalyze_air/apis/triage.py +197 -0
  30. binalyze_air/apis/user_management.py +183 -74
  31. binalyze_air/apis/webhook_executions.py +50 -0
  32. binalyze_air/apis/webhooks.py +322 -230
  33. binalyze_air/base.py +207 -133
  34. binalyze_air/client.py +217 -1337
  35. binalyze_air/commands/__init__.py +175 -145
  36. binalyze_air/commands/acquisitions.py +661 -387
  37. binalyze_air/commands/api_tokens.py +55 -0
  38. binalyze_air/commands/assets.py +324 -362
  39. binalyze_air/commands/{authentication.py → auth.py} +36 -36
  40. binalyze_air/commands/auto_asset_tags.py +230 -230
  41. binalyze_air/commands/backup.py +47 -0
  42. binalyze_air/commands/baseline.py +32 -396
  43. binalyze_air/commands/cases.py +609 -602
  44. binalyze_air/commands/cloud_forensics.py +88 -0
  45. binalyze_air/commands/event_subscription.py +101 -101
  46. binalyze_air/commands/evidences.py +918 -988
  47. binalyze_air/commands/interact.py +172 -58
  48. binalyze_air/commands/investigation_hub.py +315 -0
  49. binalyze_air/commands/license.py +183 -0
  50. binalyze_air/commands/logger.py +126 -0
  51. binalyze_air/commands/multipart_upload.py +363 -0
  52. binalyze_air/commands/notifications.py +45 -0
  53. binalyze_air/commands/organizations.py +200 -221
  54. binalyze_air/commands/policies.py +175 -203
  55. binalyze_air/commands/preset_filters.py +55 -0
  56. binalyze_air/commands/recent_activities.py +32 -0
  57. binalyze_air/commands/relay_server.py +144 -0
  58. binalyze_air/commands/settings.py +431 -29
  59. binalyze_air/commands/tasks.py +95 -56
  60. binalyze_air/commands/triage.py +224 -360
  61. binalyze_air/commands/user_management.py +351 -126
  62. binalyze_air/commands/webhook_executions.py +77 -0
  63. binalyze_air/config.py +244 -244
  64. binalyze_air/exceptions.py +49 -49
  65. binalyze_air/http_client.py +426 -305
  66. binalyze_air/models/__init__.py +287 -285
  67. binalyze_air/models/acquisitions.py +365 -250
  68. binalyze_air/models/api_tokens.py +73 -0
  69. binalyze_air/models/assets.py +438 -438
  70. binalyze_air/models/audit.py +247 -272
  71. binalyze_air/models/audit_logs.py +14 -0
  72. binalyze_air/models/{authentication.py → auth.py} +69 -69
  73. binalyze_air/models/auto_asset_tags.py +227 -116
  74. binalyze_air/models/backup.py +138 -0
  75. binalyze_air/models/baseline.py +231 -231
  76. binalyze_air/models/cases.py +275 -275
  77. binalyze_air/models/cloud_forensics.py +145 -0
  78. binalyze_air/models/event_subscription.py +170 -171
  79. binalyze_air/models/evidence.py +65 -65
  80. binalyze_air/models/evidences.py +367 -348
  81. binalyze_air/models/interact.py +266 -135
  82. binalyze_air/models/investigation_hub.py +265 -0
  83. binalyze_air/models/license.py +150 -0
  84. binalyze_air/models/logger.py +83 -0
  85. binalyze_air/models/multipart_upload.py +352 -0
  86. binalyze_air/models/notifications.py +138 -0
  87. binalyze_air/models/organizations.py +293 -293
  88. binalyze_air/models/params.py +153 -127
  89. binalyze_air/models/policies.py +260 -249
  90. binalyze_air/models/preset_filters.py +79 -0
  91. binalyze_air/models/recent_activities.py +70 -0
  92. binalyze_air/models/relay_server.py +121 -0
  93. binalyze_air/models/settings.py +538 -84
  94. binalyze_air/models/tasks.py +215 -149
  95. binalyze_air/models/triage.py +141 -142
  96. binalyze_air/models/user_management.py +200 -97
  97. binalyze_air/models/webhook_executions.py +33 -0
  98. binalyze_air/queries/__init__.py +121 -133
  99. binalyze_air/queries/acquisitions.py +155 -155
  100. binalyze_air/queries/api_tokens.py +46 -0
  101. binalyze_air/queries/assets.py +186 -105
  102. binalyze_air/queries/audit.py +400 -416
  103. binalyze_air/queries/{authentication.py → auth.py} +55 -55
  104. binalyze_air/queries/auto_asset_tags.py +59 -59
  105. binalyze_air/queries/backup.py +66 -0
  106. binalyze_air/queries/baseline.py +21 -185
  107. binalyze_air/queries/cases.py +292 -292
  108. binalyze_air/queries/cloud_forensics.py +137 -0
  109. binalyze_air/queries/event_subscription.py +54 -54
  110. binalyze_air/queries/evidence.py +139 -139
  111. binalyze_air/queries/evidences.py +279 -279
  112. binalyze_air/queries/interact.py +140 -28
  113. binalyze_air/queries/investigation_hub.py +329 -0
  114. binalyze_air/queries/license.py +85 -0
  115. binalyze_air/queries/logger.py +58 -0
  116. binalyze_air/queries/multipart_upload.py +180 -0
  117. binalyze_air/queries/notifications.py +71 -0
  118. binalyze_air/queries/organizations.py +222 -222
  119. binalyze_air/queries/params.py +154 -115
  120. binalyze_air/queries/policies.py +149 -149
  121. binalyze_air/queries/preset_filters.py +60 -0
  122. binalyze_air/queries/recent_activities.py +44 -0
  123. binalyze_air/queries/relay_server.py +42 -0
  124. binalyze_air/queries/settings.py +533 -20
  125. binalyze_air/queries/tasks.py +125 -81
  126. binalyze_air/queries/triage.py +230 -230
  127. binalyze_air/queries/user_management.py +193 -83
  128. binalyze_air/queries/webhook_executions.py +39 -0
  129. binalyze_air_sdk-1.0.3.dist-info/METADATA +752 -0
  130. binalyze_air_sdk-1.0.3.dist-info/RECORD +132 -0
  131. {binalyze_air_sdk-1.0.1.dist-info → binalyze_air_sdk-1.0.3.dist-info}/WHEEL +1 -1
  132. binalyze_air/apis/endpoints.py +0 -22
  133. binalyze_air/apis/evidences.py +0 -216
  134. binalyze_air/apis/users.py +0 -68
  135. binalyze_air/commands/users.py +0 -101
  136. binalyze_air/models/endpoints.py +0 -76
  137. binalyze_air/models/users.py +0 -82
  138. binalyze_air/queries/endpoints.py +0 -25
  139. binalyze_air/queries/users.py +0 -69
  140. binalyze_air_sdk-1.0.1.dist-info/METADATA +0 -635
  141. binalyze_air_sdk-1.0.1.dist-info/RECORD +0 -82
  142. {binalyze_air_sdk-1.0.1.dist-info → binalyze_air_sdk-1.0.3.dist-info}/top_level.txt +0 -0
@@ -1,231 +1,231 @@
1
- """
2
- Triage-related queries for the Binalyze AIR SDK.
3
- """
4
-
5
- from typing import List, Optional, Dict, Any
6
-
7
- from ..base import Query
8
- from ..models.triage import (
9
- TriageRule, TriageTag, TriageFilter, TriageRuleType, TriageSeverity, TriageStatus
10
- )
11
- from ..http_client import HTTPClient
12
-
13
-
14
- class ListTriageRulesQuery(Query[List[TriageRule]]):
15
- """Query to list triage rules with optional filtering."""
16
-
17
- def __init__(self, http_client: HTTPClient, filter_params: Optional[TriageFilter] = None, organization_ids: Optional[List[int]] = None):
18
- self.http_client = http_client
19
- self.filter_params = filter_params or TriageFilter()
20
- self.organization_ids = organization_ids or [0]
21
-
22
- def execute(self) -> List[TriageRule]:
23
- """Execute the query to list triage rules."""
24
- params = self.filter_params.to_params()
25
-
26
- # Add organization IDs
27
- params["filter[organizationIds]"] = ",".join(map(str, self.organization_ids))
28
-
29
- response = self.http_client.get("triages/rules", params=params)
30
-
31
- entities = response.get("result", {}).get("entities", [])
32
-
33
- rules = []
34
- for entity_data in entities:
35
- mapped_data = {
36
- "id": entity_data.get("_id"),
37
- "name": entity_data.get("description", ""), # Use description as name
38
- "description": entity_data.get("description"),
39
- "type": entity_data.get("engine", "yara"), # Use engine as type
40
- "rule_content": entity_data.get("rule", ""), # Try "rule" field
41
- "enabled": entity_data.get("enabled", True),
42
- "severity": entity_data.get("severity", "medium"),
43
- "tags": entity_data.get("tags", []),
44
- "search_in": entity_data.get("searchIn"), # Map searchIn field
45
- "organization_id": entity_data.get("organizationId", 0),
46
- "organization_ids": entity_data.get("organizationIds", []), # Map organizationIds array
47
- "created_at": entity_data.get("createdAt"),
48
- "updated_at": entity_data.get("updatedAt"),
49
- "created_by": entity_data.get("createdBy", "Unknown"),
50
- "updated_by": entity_data.get("updatedBy"),
51
- "match_count": entity_data.get("matchCount", 0),
52
- "last_match": entity_data.get("lastMatch"),
53
- "deletable": entity_data.get("deletable"), # Map deletable field
54
- }
55
-
56
- # Remove None values
57
- mapped_data = {k: v for k, v in mapped_data.items() if v is not None}
58
-
59
- rules.append(TriageRule(**mapped_data))
60
-
61
- return rules
62
-
63
-
64
- class GetTriageRuleQuery(Query[TriageRule]):
65
- """Query to get a specific triage rule by ID."""
66
-
67
- def __init__(self, http_client: HTTPClient, rule_id: str):
68
- self.http_client = http_client
69
- self.rule_id = rule_id
70
-
71
- def execute(self) -> TriageRule:
72
- """Execute the query to get triage rule details."""
73
- response = self.http_client.get(f"triages/rules/{self.rule_id}")
74
-
75
- entity_data = response.get("result", {})
76
-
77
- mapped_data = {
78
- "id": entity_data.get("_id"),
79
- "name": entity_data.get("description", ""), # Use description as name
80
- "description": entity_data.get("description"),
81
- "type": entity_data.get("engine", "yara"), # Use engine as type
82
- "rule_content": entity_data.get("rule", ""), # Try "rule" field
83
- "enabled": entity_data.get("enabled", True),
84
- "severity": entity_data.get("severity", "medium"),
85
- "tags": entity_data.get("tags", []),
86
- "search_in": entity_data.get("searchIn"), # Map searchIn field
87
- "organization_id": entity_data.get("organizationId", 0),
88
- "organization_ids": entity_data.get("organizationIds", []), # Map organizationIds array
89
- "created_at": entity_data.get("createdAt"),
90
- "updated_at": entity_data.get("updatedAt"),
91
- "created_by": entity_data.get("createdBy", "Unknown"),
92
- "updated_by": entity_data.get("updatedBy"),
93
- "match_count": entity_data.get("matchCount", 0),
94
- "last_match": entity_data.get("lastMatch"),
95
- "deletable": entity_data.get("deletable"), # Map deletable field
96
- }
97
-
98
- # Remove None values
99
- mapped_data = {k: v for k, v in mapped_data.items() if v is not None}
100
-
101
- return TriageRule(**mapped_data)
102
-
103
-
104
- class GetTriageResultsQuery(Query[Dict[str, Any]]):
105
- """Query to get triage results for a specific task or rule."""
106
-
107
- def __init__(self, http_client: HTTPClient, task_id: Optional[str] = None, rule_id: Optional[str] = None):
108
- self.http_client = http_client
109
- self.task_id = task_id
110
- self.rule_id = rule_id
111
-
112
- def execute(self) -> Dict[str, Any]:
113
- """Execute the query to get triage results."""
114
- params = {}
115
-
116
- if self.task_id:
117
- params["taskId"] = self.task_id
118
- if self.rule_id:
119
- params["ruleId"] = self.rule_id
120
-
121
- response = self.http_client.get("triages/results", params=params)
122
- return response.get("result", {})
123
-
124
-
125
- class GetTriageMatchesQuery(Query[Dict[str, Any]]):
126
- """Query to get triage matches for analysis."""
127
-
128
- def __init__(self, http_client: HTTPClient, endpoint_id: str, task_id: str):
129
- self.http_client = http_client
130
- self.endpoint_id = endpoint_id
131
- self.task_id = task_id
132
-
133
- def execute(self) -> Dict[str, Any]:
134
- """Execute the query to get triage matches."""
135
- params = {
136
- "endpointId": self.endpoint_id,
137
- "taskId": self.task_id
138
- }
139
-
140
- response = self.http_client.get("triages/matches", params=params)
141
- return response.get("result", {})
142
-
143
-
144
- class ListTriageTagsQuery(Query[List[TriageTag]]):
145
- """Query to list triage tags."""
146
-
147
- def __init__(self, http_client: HTTPClient, organization_id: Optional[int] = None):
148
- self.http_client = http_client
149
- self.organization_id = organization_id or 0
150
-
151
- def execute(self) -> List[TriageTag]:
152
- """Execute the query to list triage tags."""
153
- # Use singular organizationId as per API documentation
154
- params = {"filter[organizationId]": str(self.organization_id)}
155
-
156
- response = self.http_client.get("triages/tags", params=params)
157
-
158
- # Handle different response formats: direct list or wrapped in result
159
- if isinstance(response, list):
160
- # Direct list response format
161
- entities = response
162
- elif isinstance(response, dict):
163
- # Wrapped response format - check if result is list or dict
164
- result_data = response.get("result", [])
165
- if isinstance(result_data, list):
166
- # result is a direct list of entities
167
- entities = result_data
168
- elif isinstance(result_data, dict):
169
- # result is a dict with entities inside
170
- entities = result_data.get("entities", [])
171
- else:
172
- entities = []
173
-
174
- # Fallback: if no entities found, try direct entities field
175
- if not entities and "entities" in response:
176
- entities = response.get("entities", [])
177
- else:
178
- # Unknown format
179
- entities = []
180
-
181
- tags = []
182
- for entity_data in entities:
183
- # Ensure entity_data is a dictionary
184
- if not isinstance(entity_data, dict):
185
- continue
186
-
187
- mapped_data = {
188
- "id": entity_data.get("id") or entity_data.get("_id"), # Handle both id and _id
189
- "name": entity_data.get("name"),
190
- "description": entity_data.get("description"),
191
- "color": entity_data.get("color", "#3498db"),
192
- "created_at": entity_data.get("createdAt"),
193
- "created_by": entity_data.get("createdBy", "Unknown"), # Provide default for required field
194
- "organization_id": entity_data.get("organizationId", 0),
195
- "usage_count": entity_data.get("usageCount") or entity_data.get("count", 0), # Handle both count and usageCount
196
- }
197
-
198
- # Remove None values but keep defaults for required fields
199
- mapped_data = {k: v for k, v in mapped_data.items() if v is not None}
200
-
201
- tags.append(TriageTag(**mapped_data))
202
-
203
- return tags
204
-
205
-
206
- class ListTriageProfilesQuery(Query[List[Dict[str, Any]]]):
207
- """Query to list triage profiles."""
208
-
209
- def __init__(self, http_client: HTTPClient, organization_id: Optional[int] = None):
210
- self.http_client = http_client
211
- self.organization_id = organization_id or 0
212
-
213
- def execute(self) -> List[Dict[str, Any]]:
214
- """Execute the query to list triage profiles."""
215
- params = {"filter[organizationId]": str(self.organization_id)}
216
-
217
- response = self.http_client.get("triages/profiles", params=params)
218
- return response.get("result", {}).get("entities", [])
219
-
220
-
221
- class GetTriageProfileQuery(Query[Dict[str, Any]]):
222
- """Query to get a specific triage profile by ID."""
223
-
224
- def __init__(self, http_client: HTTPClient, profile_id: str):
225
- self.http_client = http_client
226
- self.profile_id = profile_id
227
-
228
- def execute(self) -> Dict[str, Any]:
229
- """Execute the query to get triage profile details."""
230
- response = self.http_client.get(f"triages/profiles/{self.profile_id}")
1
+ """
2
+ Triage-related queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List, Optional, Dict, Any
6
+
7
+ from ..base import Query
8
+ from ..models.triage import (
9
+ TriageRule, TriageTag, TriageFilter, TriageRuleType, TriageSeverity, TriageStatus
10
+ )
11
+ from ..http_client import HTTPClient
12
+
13
+
14
+ class ListTriageRulesQuery(Query[List[TriageRule]]):
15
+ """Query to list triage rules with optional filtering."""
16
+
17
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[TriageFilter] = None, organization_ids: Optional[List[int]] = None):
18
+ self.http_client = http_client
19
+ self.filter_params = filter_params or TriageFilter()
20
+ self.organization_ids = organization_ids or [0]
21
+
22
+ def execute(self) -> List[TriageRule]:
23
+ """Execute the query to list triage rules."""
24
+ params = self.filter_params.to_params()
25
+
26
+ # Add organization IDs
27
+ params["filter[organizationIds]"] = ",".join(map(str, self.organization_ids))
28
+
29
+ response = self.http_client.get("triages/rules", params=params)
30
+
31
+ entities = response.get("result", {}).get("entities", [])
32
+
33
+ rules = []
34
+ for entity_data in entities:
35
+ mapped_data = {
36
+ "id": entity_data.get("_id"),
37
+ "name": entity_data.get("description", ""), # Use description as name
38
+ "description": entity_data.get("description"),
39
+ "type": entity_data.get("engine", "yara"), # Use engine as type
40
+ "rule_content": entity_data.get("rule", ""), # Try "rule" field
41
+ "enabled": entity_data.get("enabled", True),
42
+ "severity": entity_data.get("severity", "medium"),
43
+ "tags": entity_data.get("tags", []),
44
+ "search_in": entity_data.get("searchIn"), # Map searchIn field
45
+ "organization_id": entity_data.get("organizationId", 0),
46
+ "organization_ids": entity_data.get("organizationIds", []), # Map organizationIds array
47
+ "created_at": entity_data.get("createdAt"),
48
+ "updated_at": entity_data.get("updatedAt"),
49
+ "created_by": entity_data.get("createdBy", "Unknown"),
50
+ "updated_by": entity_data.get("updatedBy"),
51
+ "match_count": entity_data.get("matchCount", 0),
52
+ "last_match": entity_data.get("lastMatch"),
53
+ "deletable": entity_data.get("deletable"), # Map deletable field
54
+ }
55
+
56
+ # Remove None values
57
+ mapped_data = {k: v for k, v in mapped_data.items() if v is not None}
58
+
59
+ rules.append(TriageRule(**mapped_data))
60
+
61
+ return rules
62
+
63
+
64
+ class GetTriageRuleQuery(Query[TriageRule]):
65
+ """Query to get a specific triage rule by ID."""
66
+
67
+ def __init__(self, http_client: HTTPClient, rule_id: str):
68
+ self.http_client = http_client
69
+ self.rule_id = rule_id
70
+
71
+ def execute(self) -> TriageRule:
72
+ """Execute the query to get triage rule details."""
73
+ response = self.http_client.get(f"triages/rules/{self.rule_id}")
74
+
75
+ entity_data = response.get("result", {})
76
+
77
+ mapped_data = {
78
+ "id": entity_data.get("_id"),
79
+ "name": entity_data.get("description", ""), # Use description as name
80
+ "description": entity_data.get("description"),
81
+ "type": entity_data.get("engine", "yara"), # Use engine as type
82
+ "rule_content": entity_data.get("rule", ""), # Try "rule" field
83
+ "enabled": entity_data.get("enabled", True),
84
+ "severity": entity_data.get("severity", "medium"),
85
+ "tags": entity_data.get("tags", []),
86
+ "search_in": entity_data.get("searchIn"), # Map searchIn field
87
+ "organization_id": entity_data.get("organizationId", 0),
88
+ "organization_ids": entity_data.get("organizationIds", []), # Map organizationIds array
89
+ "created_at": entity_data.get("createdAt"),
90
+ "updated_at": entity_data.get("updatedAt"),
91
+ "created_by": entity_data.get("createdBy", "Unknown"),
92
+ "updated_by": entity_data.get("updatedBy"),
93
+ "match_count": entity_data.get("matchCount", 0),
94
+ "last_match": entity_data.get("lastMatch"),
95
+ "deletable": entity_data.get("deletable"), # Map deletable field
96
+ }
97
+
98
+ # Remove None values
99
+ mapped_data = {k: v for k, v in mapped_data.items() if v is not None}
100
+
101
+ return TriageRule(**mapped_data)
102
+
103
+
104
+ class GetTriageResultsQuery(Query[Dict[str, Any]]):
105
+ """Query to get triage results for a specific task or rule."""
106
+
107
+ def __init__(self, http_client: HTTPClient, task_id: Optional[str] = None, rule_id: Optional[str] = None):
108
+ self.http_client = http_client
109
+ self.task_id = task_id
110
+ self.rule_id = rule_id
111
+
112
+ def execute(self) -> Dict[str, Any]:
113
+ """Execute the query to get triage results."""
114
+ params = {}
115
+
116
+ if self.task_id:
117
+ params["taskId"] = self.task_id
118
+ if self.rule_id:
119
+ params["ruleId"] = self.rule_id
120
+
121
+ response = self.http_client.get("triages/results", params=params)
122
+ return response.get("result", {})
123
+
124
+
125
+ class GetTriageMatchesQuery(Query[Dict[str, Any]]):
126
+ """Query to get triage matches for analysis."""
127
+
128
+ def __init__(self, http_client: HTTPClient, endpoint_id: str, task_id: str):
129
+ self.http_client = http_client
130
+ self.endpoint_id = endpoint_id
131
+ self.task_id = task_id
132
+
133
+ def execute(self) -> Dict[str, Any]:
134
+ """Execute the query to get triage matches."""
135
+ params = {
136
+ "endpointId": self.endpoint_id,
137
+ "taskId": self.task_id
138
+ }
139
+
140
+ response = self.http_client.get("triages/matches", params=params)
141
+ return response.get("result", {})
142
+
143
+
144
+ class ListTriageTagsQuery(Query[List[TriageTag]]):
145
+ """Query to list triage tags."""
146
+
147
+ def __init__(self, http_client: HTTPClient, organization_id: Optional[int] = None):
148
+ self.http_client = http_client
149
+ self.organization_id = organization_id or 0
150
+
151
+ def execute(self) -> List[TriageTag]:
152
+ """Execute the query to list triage tags."""
153
+ # Use singular organizationId as per API documentation
154
+ params = {"filter[organizationId]": str(self.organization_id)}
155
+
156
+ response = self.http_client.get("triages/tags", params=params)
157
+
158
+ # Handle different response formats: direct list or wrapped in result
159
+ if isinstance(response, list):
160
+ # Direct list response format
161
+ entities = response
162
+ elif isinstance(response, dict):
163
+ # Wrapped response format - check if result is list or dict
164
+ result_data = response.get("result", [])
165
+ if isinstance(result_data, list):
166
+ # result is a direct list of entities
167
+ entities = result_data
168
+ elif isinstance(result_data, dict):
169
+ # result is a dict with entities inside
170
+ entities = result_data.get("entities", [])
171
+ else:
172
+ entities = []
173
+
174
+ # Fallback: if no entities found, try direct entities field
175
+ if not entities and "entities" in response:
176
+ entities = response.get("entities", [])
177
+ else:
178
+ # Unknown format
179
+ entities = []
180
+
181
+ tags = []
182
+ for entity_data in entities:
183
+ # Ensure entity_data is a dictionary
184
+ if not isinstance(entity_data, dict):
185
+ continue
186
+
187
+ mapped_data = {
188
+ "id": entity_data.get("id") or entity_data.get("_id"), # Handle both id and _id
189
+ "name": entity_data.get("name"),
190
+ "description": entity_data.get("description"),
191
+ "color": entity_data.get("color", "#3498db"),
192
+ "created_at": entity_data.get("createdAt"),
193
+ "created_by": entity_data.get("createdBy", "Unknown"), # Provide default for required field
194
+ "organization_id": entity_data.get("organizationId", 0),
195
+ "usage_count": entity_data.get("usageCount") or entity_data.get("count", 0), # Handle both count and usageCount
196
+ }
197
+
198
+ # Remove None values but keep defaults for required fields
199
+ mapped_data = {k: v for k, v in mapped_data.items() if v is not None}
200
+
201
+ tags.append(TriageTag(**mapped_data))
202
+
203
+ return tags
204
+
205
+
206
+ class ListTriageProfilesQuery(Query[List[Dict[str, Any]]]):
207
+ """Query to list triage profiles."""
208
+
209
+ def __init__(self, http_client: HTTPClient, organization_id: Optional[int] = None):
210
+ self.http_client = http_client
211
+ self.organization_id = organization_id or 0
212
+
213
+ def execute(self) -> List[Dict[str, Any]]:
214
+ """Execute the query to list triage profiles."""
215
+ params = {"filter[organizationId]": str(self.organization_id)}
216
+
217
+ response = self.http_client.get("triages/profiles", params=params)
218
+ return response.get("result", {}).get("entities", [])
219
+
220
+
221
+ class GetTriageProfileQuery(Query[Dict[str, Any]]):
222
+ """Query to get a specific triage profile by ID."""
223
+
224
+ def __init__(self, http_client: HTTPClient, profile_id: str):
225
+ self.http_client = http_client
226
+ self.profile_id = profile_id
227
+
228
+ def execute(self) -> Dict[str, Any]:
229
+ """Execute the query to get triage profile details."""
230
+ response = self.http_client.get(f"triages/profiles/{self.profile_id}")
231
231
  return response.get("result", {})