codemie-sdk-python 0.1.349__tar.gz → 0.1.351__tar.gz

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 (58) hide show
  1. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/PKG-INFO +1 -1
  2. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/pyproject.toml +1 -1
  3. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/__init__.py +20 -0
  4. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/client/client.py +5 -0
  5. codemie_sdk_python-0.1.351/src/codemie_sdk/models/skill.py +125 -0
  6. codemie_sdk_python-0.1.351/src/codemie_sdk/services/skill.py +302 -0
  7. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/utils/http.py +12 -0
  8. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/README.md +0 -0
  9. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/auth/__init__.py +0 -0
  10. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/auth/credentials.py +0 -0
  11. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/client/__init__.py +0 -0
  12. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/exceptions.py +0 -0
  13. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/__init__.py +0 -0
  14. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/admin.py +0 -0
  15. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/analytics.py +0 -0
  16. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/assistant.py +0 -0
  17. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/categories.py +0 -0
  18. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/common.py +0 -0
  19. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/conversation.py +0 -0
  20. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/datasource.py +0 -0
  21. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/errors.py +0 -0
  22. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/file_operation.py +0 -0
  23. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/guardrails.py +0 -0
  24. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/integration.py +0 -0
  25. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/llm.py +0 -0
  26. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/mermaid.py +0 -0
  27. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/task.py +0 -0
  28. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/user.py +0 -0
  29. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/vendor_assistant.py +0 -0
  30. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/vendor_guardrail.py +0 -0
  31. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/vendor_knowledgebase.py +0 -0
  32. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/vendor_workflow.py +0 -0
  33. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/workflow.py +0 -0
  34. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/workflow_execution_payload.py +0 -0
  35. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/workflow_state.py +0 -0
  36. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/models/workflow_thoughts.py +0 -0
  37. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/admin.py +0 -0
  38. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/analytics.py +0 -0
  39. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/assistant.py +0 -0
  40. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/categories.py +0 -0
  41. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/codemie_guardrails.py +0 -0
  42. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/conversation.py +0 -0
  43. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/datasource.py +0 -0
  44. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/files.py +0 -0
  45. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/integration.py +0 -0
  46. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/llm.py +0 -0
  47. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/mermaid.py +0 -0
  48. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/task.py +0 -0
  49. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/user.py +0 -0
  50. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/vendor_assistant.py +0 -0
  51. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/vendor_guardrail.py +0 -0
  52. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/vendor_knowledgebase.py +0 -0
  53. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/vendor_workflow.py +0 -0
  54. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/webhook.py +0 -0
  55. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/workflow.py +0 -0
  56. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/workflow_execution.py +0 -0
  57. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/services/workflow_execution_state.py +0 -0
  58. {codemie_sdk_python-0.1.349 → codemie_sdk_python-0.1.351}/src/codemie_sdk/utils/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: codemie-sdk-python
3
- Version: 0.1.349
3
+ Version: 0.1.351
4
4
  Summary: CodeMie SDK for Python
5
5
  Author: Vadym Vlasenko
6
6
  Author-email: vadym_vlasenko@epam.com
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "codemie-sdk-python"
3
- version = "0.1.349"
3
+ version = "0.1.351"
4
4
  description = "CodeMie SDK for Python"
5
5
  authors = [
6
6
  "Vadym Vlasenko <vadym_vlasenko@epam.com>",
@@ -116,6 +116,17 @@ from .models.categories import (
116
116
  CategoryListMetadata,
117
117
  )
118
118
  from .services.categories import CategoryService
119
+ from .models.skill import (
120
+ SkillVisibility,
121
+ SkillCategory,
122
+ SkillCreateRequest,
123
+ SkillUpdateRequest,
124
+ SkillImportRequest,
125
+ SkillDetailResponse,
126
+ SkillListResponse,
127
+ SkillListPaginatedResponse,
128
+ )
129
+ from .services.skill import SkillService
119
130
 
120
131
  __version__ = "0.2.12"
121
132
  __all__ = [
@@ -202,4 +213,13 @@ __all__ = [
202
213
  "CategoryListResponse",
203
214
  "CategoryListMetadata",
204
215
  "CategoryService",
216
+ "SkillVisibility",
217
+ "SkillCategory",
218
+ "SkillCreateRequest",
219
+ "SkillUpdateRequest",
220
+ "SkillImportRequest",
221
+ "SkillDetailResponse",
222
+ "SkillListResponse",
223
+ "SkillListPaginatedResponse",
224
+ "SkillService",
205
225
  ]
@@ -12,6 +12,7 @@ from ..services.datasource import DatasourceService
12
12
  from ..services.llm import LLMService
13
13
  from ..services.integration import IntegrationService
14
14
  from ..services.mermaid import MermaidService
15
+ from ..services.skill import SkillService
15
16
  from ..services.task import TaskService
16
17
  from ..services.user import UserService
17
18
  from ..services.workflow import WorkflowService
@@ -91,6 +92,7 @@ class CodeMieClient:
91
92
  self.integrations = IntegrationService(
92
93
  self._api_domain, self._token, verify_ssl=verify_ssl
93
94
  )
95
+ self.skills = SkillService(self._api_domain, self._token, verify_ssl=verify_ssl)
94
96
  self.tasks = TaskService(self._api_domain, self._token, verify_ssl=verify_ssl)
95
97
  self.users = UserService(self._api_domain, self._token, verify_ssl=verify_ssl)
96
98
  self.datasources = DatasourceService(
@@ -167,6 +169,9 @@ class CodeMieClient:
167
169
  self.integrations = IntegrationService(
168
170
  self._api_domain, self._token, verify_ssl=self._verify_ssl
169
171
  )
172
+ self.skills = SkillService(
173
+ self._api_domain, self._token, verify_ssl=self._verify_ssl
174
+ )
170
175
  self.tasks = TaskService(
171
176
  self._api_domain, self._token, verify_ssl=self._verify_ssl
172
177
  )
@@ -0,0 +1,125 @@
1
+ """Models for skill-related data structures."""
2
+
3
+ from datetime import datetime
4
+ from enum import Enum
5
+ from typing import List, Optional
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+ from .common import User
10
+
11
+
12
+ class SkillVisibility(str, Enum):
13
+ """Skill visibility options."""
14
+
15
+ PROJECT = "project"
16
+ PRIVATE = "private"
17
+ PUBLIC = "public"
18
+
19
+
20
+ class SkillCategory(BaseModel):
21
+ """Skill category model."""
22
+
23
+ model_config = ConfigDict(extra="ignore")
24
+
25
+ value: str # Category value (e.g., "development", "testing")
26
+ label: str # Human-readable label (e.g., "Development", "Testing")
27
+ description: Optional[str] = None
28
+
29
+
30
+ class SkillBase(BaseModel):
31
+ """Base skill model."""
32
+
33
+ model_config = ConfigDict(extra="ignore")
34
+
35
+ id: str
36
+ name: str
37
+ description: str
38
+ project: str
39
+ visibility: SkillVisibility
40
+ categories: List[str] = Field(default_factory=list)
41
+ created_date: datetime = Field(alias="createdDate")
42
+
43
+
44
+ class SkillListResponse(SkillBase):
45
+ """Skill list item response."""
46
+
47
+ created_by: Optional[User] = Field(None, alias="created_by")
48
+ updated_date: Optional[datetime] = Field(None, alias="updatedDate")
49
+ is_attached: Optional[bool] = Field(None, alias="is_attached")
50
+ reactions_count: Optional[int] = Field(None, alias="reactionsCount")
51
+ assistants_count: Optional[int] = Field(None, alias="assistantsCount")
52
+
53
+
54
+ class SkillDetailResponse(SkillListResponse):
55
+ """Full skill details response."""
56
+
57
+ content: str
58
+ slug: Optional[str] = None
59
+ icon_url: Optional[str] = Field(None, alias="iconUrl")
60
+
61
+
62
+ class SkillCreateRequest(BaseModel):
63
+ """Request to create a skill."""
64
+
65
+ name: str
66
+ description: str
67
+ content: str
68
+ project: str
69
+ visibility: Optional[SkillVisibility] = SkillVisibility.PROJECT
70
+ categories: Optional[List[str]] = Field(default_factory=list)
71
+
72
+
73
+ class SkillUpdateRequest(BaseModel):
74
+ """Request to update a skill."""
75
+
76
+ name: Optional[str] = None
77
+ description: Optional[str] = None
78
+ content: Optional[str] = None
79
+ project: Optional[str] = None
80
+ visibility: Optional[SkillVisibility] = None
81
+ categories: Optional[List[str]] = None
82
+
83
+
84
+ class SkillImportRequest(BaseModel):
85
+ """Request to import a skill from .md file."""
86
+
87
+ file_content: str
88
+ filename: str
89
+ project: str
90
+ visibility: Optional[SkillVisibility] = SkillVisibility.PROJECT
91
+
92
+
93
+ class SkillAttachRequest(BaseModel):
94
+ """Request to attach skill to assistant."""
95
+
96
+ skill_id: str
97
+
98
+
99
+ class SkillBulkAttachRequest(BaseModel):
100
+ """Request to bulk attach skill to assistants."""
101
+
102
+ assistant_ids: List[str]
103
+
104
+
105
+ class SkillListPaginatedResponse(BaseModel):
106
+ """Paginated list of skills."""
107
+
108
+ model_config = ConfigDict(extra="ignore")
109
+
110
+ skills: List[SkillListResponse]
111
+ page: int
112
+ per_page: int = Field(alias="perPage")
113
+ total: int
114
+ pages: int
115
+
116
+
117
+ class SkillReactionResponse(BaseModel):
118
+ """Skill reaction response."""
119
+
120
+ model_config = ConfigDict(extra="ignore")
121
+
122
+ resource_id: str = Field(alias="resourceId")
123
+ reaction: str
124
+ reaction_at: str = Field(alias="reactionAt")
125
+ resource_type: Optional[str] = Field(None, alias="resourceType")
@@ -0,0 +1,302 @@
1
+ """Skill service implementation."""
2
+
3
+ import json
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ from ..models.skill import (
7
+ SkillAttachRequest,
8
+ SkillBulkAttachRequest,
9
+ SkillCategory,
10
+ SkillCreateRequest,
11
+ SkillDetailResponse,
12
+ SkillImportRequest,
13
+ SkillListPaginatedResponse,
14
+ SkillListResponse,
15
+ SkillUpdateRequest,
16
+ )
17
+ from ..utils.http import ApiRequestHandler
18
+
19
+
20
+ class SkillService:
21
+ """Service for managing CodeMie skills."""
22
+
23
+ def __init__(self, api_domain: str, token: str, verify_ssl: bool = True):
24
+ """Initialize the skill service.
25
+
26
+ Args:
27
+ api_domain: Base URL for the CodeMie API
28
+ token: Authentication token
29
+ verify_ssl: Whether to verify SSL certificates. Default: True
30
+ """
31
+ self._api = ApiRequestHandler(api_domain, token, verify_ssl)
32
+
33
+ # Core CRUD Operations
34
+
35
+ def list(
36
+ self,
37
+ page: int = 0,
38
+ per_page: int = 12,
39
+ filters: Optional[Dict[str, Any]] = None,
40
+ ) -> SkillListPaginatedResponse:
41
+ """List skills with pagination.
42
+
43
+ Args:
44
+ page: Page number (0-based)
45
+ per_page: Number of items per page
46
+ filters: Optional filters for skills
47
+
48
+ Returns:
49
+ Paginated list of skills
50
+ """
51
+ params = {
52
+ "page": page,
53
+ "perPage": per_page,
54
+ }
55
+ if filters:
56
+ params["filters"] = json.dumps(filters)
57
+ return self._api.get("/v1/skills", SkillListPaginatedResponse, params=params)
58
+
59
+ def get(self, skill_id: str) -> SkillDetailResponse:
60
+ """Get skill by ID.
61
+
62
+ Args:
63
+ skill_id: Skill identifier
64
+
65
+ Returns:
66
+ Full skill details
67
+ """
68
+ return self._api.get(f"/v1/skills/{skill_id}", SkillDetailResponse)
69
+
70
+ def create(self, request: SkillCreateRequest) -> SkillDetailResponse:
71
+ """Create a new skill.
72
+
73
+ Args:
74
+ request: Skill creation request
75
+
76
+ Returns:
77
+ Created skill details
78
+ """
79
+ return self._api.post(
80
+ "/v1/skills",
81
+ SkillDetailResponse,
82
+ json_data=request.model_dump(exclude_none=True),
83
+ )
84
+
85
+ def update(self, skill_id: str, request: SkillUpdateRequest) -> SkillDetailResponse:
86
+ """Update an existing skill.
87
+
88
+ Args:
89
+ skill_id: Skill identifier
90
+ request: Skill update request
91
+
92
+ Returns:
93
+ Updated skill details
94
+ """
95
+ return self._api.put(
96
+ f"/v1/skills/{skill_id}",
97
+ SkillDetailResponse,
98
+ json_data=request.model_dump(exclude_none=True),
99
+ )
100
+
101
+ def delete(self, skill_id: str) -> dict:
102
+ """Delete a skill.
103
+
104
+ Args:
105
+ skill_id: Skill identifier
106
+
107
+ Returns:
108
+ Deletion confirmation
109
+ """
110
+ return self._api.delete(f"/v1/skills/{skill_id}", dict)
111
+
112
+ # Import/Export Operations
113
+
114
+ def import_skill(self, request: SkillImportRequest) -> SkillDetailResponse:
115
+ """Import skill from .md file content.
116
+
117
+ Args:
118
+ request: Skill import request with file content
119
+
120
+ Returns:
121
+ Imported skill details
122
+ """
123
+ return self._api.post(
124
+ "/v1/skills/import",
125
+ SkillDetailResponse,
126
+ json_data=request.model_dump(exclude_none=True),
127
+ )
128
+
129
+ def export(self, skill_id: str) -> str:
130
+ """Export skill.
131
+
132
+ Args:
133
+ skill_id: Skill identifier
134
+
135
+ Returns:
136
+ Exported skill markdown content
137
+ """
138
+ # Export returns raw markdown content, not JSON
139
+ response = self._api.get(
140
+ f"/v1/skills/{skill_id}/export", None, wrap_response=False
141
+ )
142
+ return response.text
143
+
144
+ # Assistant Integration
145
+
146
+ def attach_to_assistant(self, assistant_id: str, skill_id: str) -> dict:
147
+ """Attach skill to an assistant.
148
+
149
+ Args:
150
+ assistant_id: Assistant identifier
151
+ skill_id: Skill identifier
152
+
153
+ Returns:
154
+ Attachment confirmation
155
+ """
156
+ request = SkillAttachRequest(skill_id=skill_id)
157
+ return self._api.post(
158
+ f"/v1/assistants/{assistant_id}/skills",
159
+ dict,
160
+ json_data=request.model_dump(),
161
+ )
162
+
163
+ def detach_from_assistant(self, assistant_id: str, skill_id: str) -> dict:
164
+ """Detach skill from an assistant.
165
+
166
+ Args:
167
+ assistant_id: Assistant identifier
168
+ skill_id: Skill identifier
169
+
170
+ Returns:
171
+ Detachment confirmation
172
+ """
173
+ return self._api.delete(
174
+ f"/v1/assistants/{assistant_id}/skills/{skill_id}", dict
175
+ )
176
+
177
+ def get_assistant_skills(self, assistant_id: str) -> List[SkillListResponse]:
178
+ """Get all skills attached to an assistant.
179
+
180
+ Args:
181
+ assistant_id: Assistant identifier
182
+
183
+ Returns:
184
+ List of skills attached to the assistant
185
+ """
186
+ return self._api.get(
187
+ f"/v1/assistants/{assistant_id}/skills",
188
+ List[SkillListResponse],
189
+ )
190
+
191
+ def bulk_attach_to_assistants(
192
+ self, skill_id: str, assistant_ids: List[str]
193
+ ) -> dict:
194
+ """Bulk attach skill to multiple assistants.
195
+
196
+ Args:
197
+ skill_id: Skill identifier
198
+ assistant_ids: List of assistant identifiers
199
+
200
+ Returns:
201
+ Bulk attachment confirmation
202
+ """
203
+ request = SkillBulkAttachRequest(assistant_ids=assistant_ids)
204
+ return self._api.post(
205
+ f"/v1/skills/{skill_id}/assistants/bulk-attach",
206
+ dict,
207
+ json_data=request.model_dump(),
208
+ )
209
+
210
+ def get_skill_assistants(self, skill_id: str) -> List[dict]:
211
+ """Get all assistants using this skill.
212
+
213
+ Args:
214
+ skill_id: Skill identifier
215
+
216
+ Returns:
217
+ List of assistants using the skill
218
+ """
219
+ return self._api.get(f"/v1/skills/{skill_id}/assistants", List[dict])
220
+
221
+ # Marketplace Operations
222
+
223
+ def publish(
224
+ self,
225
+ skill_id: str,
226
+ categories: Optional[List[str]] = None,
227
+ ) -> dict:
228
+ """Publish skill to marketplace.
229
+
230
+ Args:
231
+ skill_id: Skill identifier
232
+ categories: Optional list of category IDs
233
+
234
+ Returns:
235
+ Publication confirmation
236
+ """
237
+ body = {}
238
+ if categories:
239
+ body["categories"] = categories
240
+ return self._api.post(
241
+ f"/v1/skills/{skill_id}/marketplace/publish",
242
+ dict,
243
+ json_data=body if body else None,
244
+ )
245
+
246
+ def unpublish(self, skill_id: str) -> dict:
247
+ """Unpublish skill from marketplace.
248
+
249
+ Args:
250
+ skill_id: Skill identifier
251
+
252
+ Returns:
253
+ Unpublication confirmation
254
+ """
255
+ return self._api.post(f"/v1/skills/{skill_id}/marketplace/unpublish", dict)
256
+
257
+ # Utility Methods
258
+
259
+ def list_categories(self) -> List[SkillCategory]:
260
+ """Get list of available skill categories.
261
+
262
+ Returns:
263
+ List of skill categories
264
+ """
265
+ return self._api.get("/v1/skills/categories", List[SkillCategory])
266
+
267
+ def get_users(self) -> List[dict]:
268
+ """Get skill users.
269
+
270
+ Returns:
271
+ List of users with access to skills
272
+ """
273
+ return self._api.get("/v1/skills/users", List[dict])
274
+
275
+ # Reactions
276
+
277
+ def react(self, skill_id: str, reaction: str) -> dict:
278
+ """React to a skill.
279
+
280
+ Args:
281
+ skill_id: Skill identifier
282
+ reaction: Reaction type (e.g., "like", "dislike")
283
+
284
+ Returns:
285
+ Reaction confirmation
286
+ """
287
+ return self._api.post(
288
+ f"/v1/skills/{skill_id}/reactions",
289
+ dict,
290
+ json_data={"reaction": reaction},
291
+ )
292
+
293
+ def remove_reactions(self, skill_id: str) -> dict:
294
+ """Remove reactions from a skill.
295
+
296
+ Args:
297
+ skill_id: Skill identifier
298
+
299
+ Returns:
300
+ Removal confirmation
301
+ """
302
+ return self._api.delete(f"/v1/skills/{skill_id}/reactions", dict)
@@ -90,6 +90,13 @@ class ApiRequestHandler:
90
90
  Parsed response object or list of objects
91
91
  """
92
92
  try:
93
+ # Handle empty responses (e.g., 204 No Content for DELETE operations)
94
+ if not response.content or response.status_code == 204:
95
+ logger.debug(f"Empty response with status {response.status_code}")
96
+ if response_model is dict:
97
+ return {}
98
+ return None
99
+
93
100
  response_data = response.json()
94
101
  logger.debug(f"Received response with status {response.status_code}")
95
102
  logger.debug(f"Response datasource_type: {type(response_data)}")
@@ -109,6 +116,11 @@ class ApiRequestHandler:
109
116
  model_class = get_args(response_model)[0]
110
117
  if not isinstance(response_data, list):
111
118
  response_data = [response_data]
119
+
120
+ # If List[dict], return raw data without validation
121
+ if model_class is dict:
122
+ return response_data
123
+
112
124
  return [model_class.model_validate(item) for item in response_data]
113
125
  else:
114
126
  # Handle single model