pygeai 0.6.0b7__py3-none-any.whl → 0.6.0b11__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 (178) hide show
  1. pygeai/_docs/source/conf.py +78 -6
  2. pygeai/_docs/source/content/api_reference/embeddings.rst +31 -1
  3. pygeai/_docs/source/content/api_reference/evaluation.rst +590 -0
  4. pygeai/_docs/source/content/api_reference/feedback.rst +237 -0
  5. pygeai/_docs/source/content/api_reference/files.rst +592 -0
  6. pygeai/_docs/source/content/api_reference/gam.rst +401 -0
  7. pygeai/_docs/source/content/api_reference/proxy.rst +318 -0
  8. pygeai/_docs/source/content/api_reference/secrets.rst +495 -0
  9. pygeai/_docs/source/content/api_reference/usage_limits.rst +390 -0
  10. pygeai/_docs/source/content/api_reference.rst +7 -0
  11. pygeai/_docs/source/content/debugger.rst +376 -83
  12. pygeai/_docs/source/content/migration.rst +528 -0
  13. pygeai/_docs/source/content/modules.rst +1 -1
  14. pygeai/_docs/source/pygeai.cli.rst +8 -0
  15. pygeai/_docs/source/pygeai.tests.cli.rst +16 -0
  16. pygeai/_docs/source/pygeai.tests.core.embeddings.rst +16 -0
  17. pygeai/_docs/source/pygeai.tests.snippets.chat.rst +40 -0
  18. pygeai/_docs/source/pygeai.tests.snippets.dbg.rst +45 -0
  19. pygeai/_docs/source/pygeai.tests.snippets.embeddings.rst +40 -0
  20. pygeai/_docs/source/pygeai.tests.snippets.evaluation.dataset.rst +197 -0
  21. pygeai/_docs/source/pygeai.tests.snippets.evaluation.plan.rst +133 -0
  22. pygeai/_docs/source/pygeai.tests.snippets.evaluation.result.rst +37 -0
  23. pygeai/_docs/source/pygeai.tests.snippets.evaluation.rst +10 -0
  24. pygeai/_docs/source/pygeai.tests.snippets.rst +1 -0
  25. pygeai/admin/clients.py +5 -0
  26. pygeai/assistant/clients.py +7 -0
  27. pygeai/assistant/data_analyst/clients.py +2 -0
  28. pygeai/assistant/rag/clients.py +11 -0
  29. pygeai/chat/clients.py +236 -25
  30. pygeai/chat/endpoints.py +3 -1
  31. pygeai/cli/commands/chat.py +322 -1
  32. pygeai/cli/commands/embeddings.py +56 -8
  33. pygeai/cli/commands/migrate.py +994 -434
  34. pygeai/cli/error_handler.py +116 -0
  35. pygeai/cli/geai.py +28 -10
  36. pygeai/cli/parsers.py +8 -2
  37. pygeai/core/base/clients.py +3 -1
  38. pygeai/core/common/exceptions.py +11 -10
  39. pygeai/core/embeddings/__init__.py +19 -0
  40. pygeai/core/embeddings/clients.py +17 -2
  41. pygeai/core/embeddings/mappers.py +16 -2
  42. pygeai/core/embeddings/responses.py +9 -2
  43. pygeai/core/feedback/clients.py +1 -0
  44. pygeai/core/files/clients.py +5 -7
  45. pygeai/core/files/managers.py +42 -0
  46. pygeai/core/llm/clients.py +4 -0
  47. pygeai/core/plugins/clients.py +1 -0
  48. pygeai/core/rerank/clients.py +1 -0
  49. pygeai/core/secrets/clients.py +6 -0
  50. pygeai/core/services/rest.py +1 -1
  51. pygeai/dbg/__init__.py +3 -0
  52. pygeai/dbg/debugger.py +565 -70
  53. pygeai/evaluation/clients.py +1 -1
  54. pygeai/evaluation/dataset/clients.py +45 -44
  55. pygeai/evaluation/plan/clients.py +27 -26
  56. pygeai/evaluation/result/clients.py +37 -5
  57. pygeai/gam/clients.py +4 -0
  58. pygeai/health/clients.py +1 -0
  59. pygeai/lab/agents/clients.py +8 -1
  60. pygeai/lab/models.py +3 -3
  61. pygeai/lab/processes/clients.py +21 -0
  62. pygeai/lab/strategies/clients.py +4 -0
  63. pygeai/lab/tools/clients.py +1 -0
  64. pygeai/migration/__init__.py +31 -0
  65. pygeai/migration/strategies.py +404 -155
  66. pygeai/migration/tools.py +170 -3
  67. pygeai/organization/clients.py +13 -0
  68. pygeai/organization/limits/clients.py +15 -0
  69. pygeai/proxy/clients.py +3 -1
  70. pygeai/tests/admin/test_clients.py +16 -11
  71. pygeai/tests/assistants/rag/test_clients.py +35 -23
  72. pygeai/tests/assistants/test_clients.py +22 -15
  73. pygeai/tests/auth/test_clients.py +14 -6
  74. pygeai/tests/chat/test_clients.py +211 -1
  75. pygeai/tests/cli/commands/test_embeddings.py +32 -9
  76. pygeai/tests/cli/commands/test_evaluation.py +7 -0
  77. pygeai/tests/cli/commands/test_migrate.py +112 -243
  78. pygeai/tests/cli/test_error_handler.py +225 -0
  79. pygeai/tests/cli/test_geai_driver.py +154 -0
  80. pygeai/tests/cli/test_parsers.py +5 -5
  81. pygeai/tests/core/embeddings/test_clients.py +144 -0
  82. pygeai/tests/core/embeddings/test_managers.py +171 -0
  83. pygeai/tests/core/embeddings/test_mappers.py +142 -0
  84. pygeai/tests/core/feedback/test_clients.py +2 -0
  85. pygeai/tests/core/files/test_clients.py +1 -0
  86. pygeai/tests/core/llm/test_clients.py +14 -9
  87. pygeai/tests/core/plugins/test_clients.py +5 -3
  88. pygeai/tests/core/rerank/test_clients.py +1 -0
  89. pygeai/tests/core/secrets/test_clients.py +19 -13
  90. pygeai/tests/dbg/test_debugger.py +453 -75
  91. pygeai/tests/evaluation/dataset/test_clients.py +3 -1
  92. pygeai/tests/evaluation/plan/test_clients.py +4 -2
  93. pygeai/tests/evaluation/result/test_clients.py +7 -5
  94. pygeai/tests/gam/test_clients.py +1 -1
  95. pygeai/tests/health/test_clients.py +1 -0
  96. pygeai/tests/lab/agents/test_clients.py +9 -0
  97. pygeai/tests/lab/processes/test_clients.py +36 -0
  98. pygeai/tests/lab/processes/test_mappers.py +3 -0
  99. pygeai/tests/lab/strategies/test_clients.py +14 -9
  100. pygeai/tests/migration/test_strategies.py +45 -218
  101. pygeai/tests/migration/test_tools.py +133 -9
  102. pygeai/tests/organization/limits/test_clients.py +17 -0
  103. pygeai/tests/organization/test_clients.py +22 -0
  104. pygeai/tests/proxy/test_clients.py +2 -0
  105. pygeai/tests/proxy/test_integration.py +1 -0
  106. pygeai/tests/snippets/chat/chat_completion_with_reasoning_effort.py +18 -0
  107. pygeai/tests/snippets/chat/get_response.py +15 -0
  108. pygeai/tests/snippets/chat/get_response_streaming.py +20 -0
  109. pygeai/tests/snippets/chat/get_response_with_files.py +16 -0
  110. pygeai/tests/snippets/chat/get_response_with_tools.py +36 -0
  111. pygeai/tests/snippets/dbg/__init__.py +0 -0
  112. pygeai/tests/snippets/dbg/basic_debugging.py +32 -0
  113. pygeai/tests/snippets/dbg/breakpoint_management.py +48 -0
  114. pygeai/tests/snippets/dbg/stack_navigation.py +45 -0
  115. pygeai/tests/snippets/dbg/stepping_example.py +40 -0
  116. pygeai/tests/snippets/embeddings/cache_example.py +31 -0
  117. pygeai/tests/snippets/embeddings/cohere_example.py +41 -0
  118. pygeai/tests/snippets/embeddings/openai_base64_example.py +27 -0
  119. pygeai/tests/snippets/embeddings/openai_example.py +30 -0
  120. pygeai/tests/snippets/embeddings/similarity_example.py +42 -0
  121. pygeai/tests/snippets/evaluation/dataset/__init__.py +0 -0
  122. pygeai/tests/snippets/evaluation/dataset/complete_workflow_example.py +195 -0
  123. pygeai/tests/snippets/evaluation/dataset/create_dataset.py +26 -0
  124. pygeai/tests/snippets/evaluation/dataset/create_dataset_from_file.py +11 -0
  125. pygeai/tests/snippets/evaluation/dataset/create_dataset_row.py +17 -0
  126. pygeai/tests/snippets/evaluation/dataset/create_expected_source.py +18 -0
  127. pygeai/tests/snippets/evaluation/dataset/create_filter_variable.py +19 -0
  128. pygeai/tests/snippets/evaluation/dataset/delete_dataset.py +9 -0
  129. pygeai/tests/snippets/evaluation/dataset/delete_dataset_row.py +10 -0
  130. pygeai/tests/snippets/evaluation/dataset/delete_expected_source.py +15 -0
  131. pygeai/tests/snippets/evaluation/dataset/delete_filter_variable.py +15 -0
  132. pygeai/tests/snippets/evaluation/dataset/get_dataset.py +9 -0
  133. pygeai/tests/snippets/evaluation/dataset/get_dataset_row.py +10 -0
  134. pygeai/tests/snippets/evaluation/dataset/get_expected_source.py +15 -0
  135. pygeai/tests/snippets/evaluation/dataset/get_filter_variable.py +15 -0
  136. pygeai/tests/snippets/evaluation/dataset/list_dataset_rows.py +9 -0
  137. pygeai/tests/snippets/evaluation/dataset/list_datasets.py +6 -0
  138. pygeai/tests/snippets/evaluation/dataset/list_expected_sources.py +10 -0
  139. pygeai/tests/snippets/evaluation/dataset/list_filter_variables.py +10 -0
  140. pygeai/tests/snippets/evaluation/dataset/update_dataset.py +15 -0
  141. pygeai/tests/snippets/evaluation/dataset/update_dataset_row.py +20 -0
  142. pygeai/tests/snippets/evaluation/dataset/update_expected_source.py +18 -0
  143. pygeai/tests/snippets/evaluation/dataset/update_filter_variable.py +19 -0
  144. pygeai/tests/snippets/evaluation/dataset/upload_dataset_rows_file.py +10 -0
  145. pygeai/tests/snippets/evaluation/plan/__init__.py +0 -0
  146. pygeai/tests/snippets/evaluation/plan/add_plan_system_metric.py +13 -0
  147. pygeai/tests/snippets/evaluation/plan/complete_workflow_example.py +136 -0
  148. pygeai/tests/snippets/evaluation/plan/create_evaluation_plan.py +24 -0
  149. pygeai/tests/snippets/evaluation/plan/create_rag_evaluation_plan.py +22 -0
  150. pygeai/tests/snippets/evaluation/plan/delete_evaluation_plan.py +9 -0
  151. pygeai/tests/snippets/evaluation/plan/delete_plan_system_metric.py +13 -0
  152. pygeai/tests/snippets/evaluation/plan/execute_evaluation_plan.py +11 -0
  153. pygeai/tests/snippets/evaluation/plan/get_evaluation_plan.py +9 -0
  154. pygeai/tests/snippets/evaluation/plan/get_plan_system_metric.py +13 -0
  155. pygeai/tests/snippets/evaluation/plan/get_system_metric.py +9 -0
  156. pygeai/tests/snippets/evaluation/plan/list_evaluation_plans.py +7 -0
  157. pygeai/tests/snippets/evaluation/plan/list_plan_system_metrics.py +9 -0
  158. pygeai/tests/snippets/evaluation/plan/list_system_metrics.py +7 -0
  159. pygeai/tests/snippets/evaluation/plan/update_evaluation_plan.py +22 -0
  160. pygeai/tests/snippets/evaluation/plan/update_plan_system_metric.py +14 -0
  161. pygeai/tests/snippets/evaluation/result/__init__.py +0 -0
  162. pygeai/tests/snippets/evaluation/result/complete_workflow_example.py +150 -0
  163. pygeai/tests/snippets/evaluation/result/get_evaluation_result.py +26 -0
  164. pygeai/tests/snippets/evaluation/result/list_evaluation_results.py +17 -0
  165. pygeai/tests/snippets/migrate/__init__.py +45 -0
  166. pygeai/tests/snippets/migrate/agent_migration.py +110 -0
  167. pygeai/tests/snippets/migrate/assistant_migration.py +64 -0
  168. pygeai/tests/snippets/migrate/orchestrator_examples.py +179 -0
  169. pygeai/tests/snippets/migrate/process_migration.py +64 -0
  170. pygeai/tests/snippets/migrate/project_migration.py +42 -0
  171. pygeai/tests/snippets/migrate/tool_migration.py +64 -0
  172. pygeai/tests/snippets/organization/create_project.py +2 -2
  173. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/METADATA +1 -1
  174. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/RECORD +178 -96
  175. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/WHEEL +0 -0
  176. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/entry_points.txt +0 -0
  177. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/licenses/LICENSE +0 -0
  178. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/top_level.txt +0 -0
pygeai/migration/tools.py CHANGED
@@ -1,13 +1,180 @@
1
+ from typing import List, Dict, Optional
2
+ from dataclasses import dataclass, field
3
+
4
+ from pygeai import logger
1
5
  from pygeai.migration.strategies import MigrationStrategy
6
+ from pygeai.core.utils.console import Console
2
7
 
3
8
 
4
9
  class MigrationTool:
10
+ """
11
+ Orchestrates migration operations using configurable strategies.
12
+
13
+ This class provides a flexible way to execute migrations with support for:
14
+ - Batch migrations of multiple resources
15
+ - Dependency ordering
16
+ - Progress tracking
17
+ - Dry-run mode
18
+ - Rollback capabilities
19
+ """
5
20
 
6
21
  def __init__(self, strategy: MigrationStrategy):
7
- self.strategy = strategy
22
+ self._strategy = strategy
8
23
 
9
24
  def set_strategy(self, strategy: MigrationStrategy):
10
- self.strategy = strategy
25
+ """
26
+ Update the migration strategy.
27
+
28
+ :param strategy: The new migration strategy to use
29
+ """
30
+ self._strategy = strategy
11
31
 
12
32
  def run_migration(self):
13
- self.strategy.migrate()
33
+ """
34
+ Execute the configured migration strategy.
35
+
36
+ :return: The result from the migration strategy (if any)
37
+ :raises ValueError: If migration fails
38
+ """
39
+ logger.info(f"Starting migration with strategy: {self._strategy.__class__.__name__}")
40
+ return self._strategy.migrate()
41
+
42
+
43
+ @dataclass
44
+ class MigrationPlan:
45
+ """
46
+ Defines a migration plan with multiple strategies and execution order.
47
+
48
+ :param strategies: List of migration strategies to execute
49
+ :param dependencies: Map of strategy index to list of dependent strategy indices
50
+ :param dry_run: If True, validate without executing migrations
51
+ :param stop_on_error: If True, stop execution on first error
52
+ """
53
+ strategies: List[MigrationStrategy]
54
+ dependencies: Dict[int, List[int]] = field(default_factory=dict)
55
+ dry_run: bool = False
56
+ stop_on_error: bool = True
57
+
58
+
59
+ class MigrationOrchestrator:
60
+ """
61
+ Advanced orchestration for complex migration scenarios.
62
+
63
+ Handles batch migrations, dependency resolution, progress tracking,
64
+ and rollback on failure.
65
+ """
66
+
67
+ def __init__(self, plan: MigrationPlan):
68
+ self._plan = plan
69
+ self._completed: List[int] = []
70
+ self._failed: List[int] = []
71
+
72
+ def execute(self) -> Dict[str, any]:
73
+ """
74
+ Execute the migration plan respecting dependencies.
75
+
76
+ :return: Summary of migration results
77
+ :raises ValueError: If migration fails and stop_on_error is True
78
+ """
79
+ logger.info(f"Executing migration plan with {len(self._plan.strategies)} strategies")
80
+
81
+ if self._plan.dry_run:
82
+ return self._validate_plan()
83
+
84
+ execution_order = self._resolve_dependencies()
85
+ total_strategies = len(execution_order)
86
+
87
+ Console.write_stdout("")
88
+ Console.write_stdout("=" * 60)
89
+ Console.write_stdout(f"Migration Progress: 0/{total_strategies} completed")
90
+ Console.write_stdout("=" * 60)
91
+
92
+ for position, idx in enumerate(execution_order, 1):
93
+ strategy = self._plan.strategies[idx]
94
+ display_info = strategy.get_display_info()
95
+
96
+ Console.write_stdout(f"\n[{position}/{total_strategies}] Migrating {display_info}...")
97
+
98
+ try:
99
+ logger.info(f"Executing strategy {idx + 1}/{len(self._plan.strategies)}: {strategy.__class__.__name__}")
100
+ strategy.migrate()
101
+ self._completed.append(idx)
102
+ Console.write_stdout(f"✓ Successfully migrated {display_info}")
103
+ except Exception as e:
104
+ logger.error(f"Strategy {idx} failed: {e}")
105
+ self._failed.append(idx)
106
+ Console.write_stdout(f"✗ Failed to migrate {display_info}: {e}")
107
+ if self._plan.stop_on_error:
108
+ raise ValueError(f"Migration failed at strategy {idx}: {e}") from e
109
+
110
+ Console.write_stdout("")
111
+ Console.write_stdout("=" * 60)
112
+ Console.write_stdout(f"Migration Complete: {len(self._completed)}/{total_strategies} successful")
113
+ Console.write_stdout("=" * 60)
114
+
115
+ return self._generate_summary()
116
+
117
+ def _resolve_dependencies(self) -> List[int]:
118
+ """
119
+ Resolve strategy execution order based on dependencies.
120
+
121
+ :return: Ordered list of strategy indices
122
+ :raises ValueError: If circular dependencies detected
123
+ """
124
+ visited = set()
125
+ order = []
126
+
127
+ def visit(idx: int, path: set):
128
+ if idx in path:
129
+ raise ValueError(f"Circular dependency detected at strategy {idx}")
130
+ if idx in visited:
131
+ return
132
+
133
+ path.add(idx)
134
+ for dep_idx in self._plan.dependencies.get(idx, []):
135
+ visit(dep_idx, path)
136
+ path.remove(idx)
137
+
138
+ visited.add(idx)
139
+ order.append(idx)
140
+
141
+ for idx in range(len(self._plan.strategies)):
142
+ visit(idx, set())
143
+
144
+ return order
145
+
146
+ def _validate_plan(self) -> Dict[str, any]:
147
+ """
148
+ Validate the migration plan without executing.
149
+
150
+ :return: Validation results
151
+ """
152
+ logger.info("Validating migration plan (dry-run mode)")
153
+ try:
154
+ execution_order = self._resolve_dependencies()
155
+ return {
156
+ "valid": True,
157
+ "execution_order": execution_order,
158
+ "total_strategies": len(self._plan.strategies)
159
+ }
160
+ except Exception as e:
161
+ logger.error(f"Validation failed: {e}")
162
+ return {
163
+ "valid": False,
164
+ "error": str(e)
165
+ }
166
+
167
+ def _generate_summary(self) -> Dict[str, any]:
168
+ """
169
+ Generate a summary of migration results.
170
+
171
+ :return: Summary dictionary with completed, failed, and pending migrations
172
+ """
173
+ return {
174
+ "total": len(self._plan.strategies),
175
+ "completed": len(self._completed),
176
+ "failed": len(self._failed),
177
+ "success_rate": len(self._completed) / len(self._plan.strategies) if self._plan.strategies else 0,
178
+ "completed_indices": self._completed,
179
+ "failed_indices": self._failed
180
+ }
@@ -25,6 +25,7 @@ class OrganizationClient(BaseClient):
25
25
  :return: AssistantListResponse - The API response containing the list of assistants and the project.
26
26
  """
27
27
  response = self.api_service.get(endpoint=GET_ASSISTANT_LIST_V1, params={"detail": detail})
28
+ validate_status_code(response)
28
29
  return parse_json_response(response, "get assistant list")
29
30
 
30
31
  def get_project_list(
@@ -56,6 +57,7 @@ class OrganizationClient(BaseClient):
56
57
  "detail": detail
57
58
  }
58
59
  )
60
+ validate_status_code(response)
59
61
  return parse_json_response(response, "get project list")
60
62
 
61
63
  def get_project_data(
@@ -72,6 +74,7 @@ class OrganizationClient(BaseClient):
72
74
  response = self.api_service.get(
73
75
  endpoint=endpoint
74
76
  )
77
+ validate_status_code(response)
75
78
  return parse_json_response(response, "get project data for ID", project_id=project_id)
76
79
 
77
80
  def create_project(
@@ -109,6 +112,7 @@ class OrganizationClient(BaseClient):
109
112
  "description": description
110
113
  }
111
114
  )
115
+ validate_status_code(response)
112
116
  return parse_json_response(response, "create project with name", name=name)
113
117
 
114
118
  def update_project(
@@ -133,6 +137,7 @@ class OrganizationClient(BaseClient):
133
137
  "description": description
134
138
  }
135
139
  )
140
+ validate_status_code(response)
136
141
  return parse_json_response(response, "update project with ID", project_id=project_id)
137
142
 
138
143
  def delete_project(
@@ -147,6 +152,7 @@ class OrganizationClient(BaseClient):
147
152
  """
148
153
  endpoint = DELETE_PROJECT_V1.format(id=project_id)
149
154
  response = self.api_service.delete(endpoint=endpoint)
155
+ validate_status_code(response)
150
156
  return parse_json_response(response, "delete project with ID", project_id=project_id)
151
157
 
152
158
  def get_project_tokens(
@@ -161,6 +167,7 @@ class OrganizationClient(BaseClient):
161
167
  """
162
168
  endpoint = GET_PROJECT_TOKENS_V1.format(id=project_id)
163
169
  response = self.api_service.get(endpoint=endpoint)
170
+ validate_status_code(response)
164
171
  return parse_json_response(response, "get tokens for project with ID", project_id=project_id)
165
172
 
166
173
  def export_request_data(
@@ -188,6 +195,7 @@ class OrganizationClient(BaseClient):
188
195
  "count": count
189
196
  }
190
197
  )
198
+ validate_status_code(response)
191
199
  return parse_json_response(response, "export request data")
192
200
 
193
201
  def get_memberships(
@@ -223,6 +231,7 @@ class OrganizationClient(BaseClient):
223
231
  params["roleTypes"] = role_types
224
232
 
225
233
  response = self.api_service.get(endpoint=GET_MEMBERSHIPS_V2, params=params)
234
+ validate_status_code(response)
226
235
  return parse_json_response(response, "get memberships")
227
236
 
228
237
  def get_project_memberships(
@@ -258,6 +267,7 @@ class OrganizationClient(BaseClient):
258
267
  params["roleTypes"] = role_types
259
268
 
260
269
  response = self.api_service.get(endpoint=GET_PROJECT_MEMBERSHIPS_V2, params=params)
270
+ validate_status_code(response)
261
271
  return parse_json_response(response, "get project memberships")
262
272
 
263
273
  def get_project_roles(
@@ -271,6 +281,7 @@ class OrganizationClient(BaseClient):
271
281
  :return: dict - The API response containing the list of roles for the project, in JSON format.
272
282
  """
273
283
  response = self.api_service.get(endpoint=GET_PROJECT_ROLES_V2, params={"projectId": project_id})
284
+ validate_status_code(response)
274
285
  return parse_json_response(response, "get project roles for project", project_id=project_id)
275
286
 
276
287
  def get_project_members(
@@ -284,6 +295,7 @@ class OrganizationClient(BaseClient):
284
295
  :return: dict - The API response containing the list of members with their roles, in JSON format.
285
296
  """
286
297
  response = self.api_service.get(endpoint=GET_PROJECT_MEMBERS_V2, params={"projectId": project_id})
298
+ validate_status_code(response)
287
299
  return parse_json_response(response, "get project members for project", project_id=project_id)
288
300
 
289
301
  def get_organization_members(
@@ -297,4 +309,5 @@ class OrganizationClient(BaseClient):
297
309
  :return: dict - The API response containing the list of members with their roles, in JSON format.
298
310
  """
299
311
  response = self.api_service.get(endpoint=GET_ORGANIZATION_MEMBERS_V2, params={"organizationId": organization_id})
312
+ validate_status_code(response)
300
313
  return parse_json_response(response, "get organization members for organization", organization_id=organization_id)
@@ -34,6 +34,7 @@ class UsageLimitClient(BaseClient):
34
34
  endpoint=endpoint,
35
35
  data=usage_limit
36
36
  )
37
+ validate_status_code(response)
37
38
  return parse_json_response(response, f"set usage limit for organization", organization=organization)
38
39
 
39
40
  def get_organization_latest_usage_limit(self, organization: str) -> dict:
@@ -45,6 +46,7 @@ class UsageLimitClient(BaseClient):
45
46
  """
46
47
  endpoint = GET_ORGANIZATION_LATEST_USAGE_LIMIT_V2.format(organization=organization)
47
48
  response = self.api_service.get(endpoint=endpoint)
49
+ validate_status_code(response)
48
50
  return parse_json_response(response, f"get latest usage limit for organization", organization=organization)
49
51
 
50
52
  def get_all_usage_limits_from_organization(self, organization: str) -> dict:
@@ -56,6 +58,7 @@ class UsageLimitClient(BaseClient):
56
58
  """
57
59
  endpoint = GET_ALL_ORGANIZATION_USAGE_LIMITS_V2.format(organization=organization)
58
60
  response = self.api_service.get(endpoint=endpoint)
61
+ validate_status_code(response)
59
62
  return parse_json_response(response, f"get all usage limits for organization", organization=organization)
60
63
 
61
64
  def delete_usage_limit_from_organization(self, organization: str, limit_id: str) -> dict:
@@ -68,6 +71,7 @@ class UsageLimitClient(BaseClient):
68
71
  """
69
72
  endpoint = DELETE_ORGANIZATION_USAGE_LIMIT_V2.format(organization=organization, id=limit_id)
70
73
  response = self.api_service.delete(endpoint=endpoint)
74
+ validate_status_code(response)
71
75
  return parse_json_response(response, f"delete usage limit with ID '{limit_id}' from organization", organization=organization)
72
76
 
73
77
  def set_organization_hard_limit(self, organization: str, limit_id: str, hard_limit: float) -> dict:
@@ -86,6 +90,7 @@ class UsageLimitClient(BaseClient):
86
90
  "hardLimit": hard_limit
87
91
  }
88
92
  )
93
+ validate_status_code(response)
89
94
  return parse_json_response(response, f"set hard limit for usage limit ID '{limit_id}' in organization", organization=organization)
90
95
 
91
96
  def set_organization_soft_limit(self, organization: str, limit_id: str, soft_limit: float) -> dict:
@@ -104,6 +109,7 @@ class UsageLimitClient(BaseClient):
104
109
  "softLimit": soft_limit
105
110
  }
106
111
  )
112
+ validate_status_code(response)
107
113
  return parse_json_response(response, f"set soft limit for usage limit ID '{limit_id}' in organization", organization=organization)
108
114
 
109
115
  def set_organization_renewal_status(self, organization: str, limit_id: str, renewal_status: str) -> dict:
@@ -122,6 +128,7 @@ class UsageLimitClient(BaseClient):
122
128
  "renewalStatus": renewal_status
123
129
  }
124
130
  )
131
+ validate_status_code(response)
125
132
  return parse_json_response(response, f"set renewal status for usage limit ID '{limit_id}' in organization", organization=organization)
126
133
 
127
134
  def set_project_usage_limit(self, organization: str, project: str, usage_limit: dict) -> dict:
@@ -145,6 +152,7 @@ class UsageLimitClient(BaseClient):
145
152
  endpoint=endpoint,
146
153
  data=usage_limit
147
154
  )
155
+ validate_status_code(response)
148
156
  return parse_json_response(response, f"set usage limit for project '{project}' in organization", organization=organization)
149
157
 
150
158
  def get_all_usage_limits_from_project(self, organization: str, project: str) -> dict:
@@ -157,6 +165,7 @@ class UsageLimitClient(BaseClient):
157
165
  """
158
166
  endpoint = GET_ALL_PROJECT_USAGE_LIMIT_V2.format(organization=organization, project=project)
159
167
  response = self.api_service.get(endpoint=endpoint)
168
+ validate_status_code(response)
160
169
  return parse_json_response(response, f"get all usage limits for project '{project}' in organization", organization=organization)
161
170
 
162
171
  def get_latest_usage_limit_from_project(self, organization: str, project: str) -> dict:
@@ -169,6 +178,7 @@ class UsageLimitClient(BaseClient):
169
178
  """
170
179
  endpoint = GET_LATEST_PROJECT_USAGE_LIMIT_V2.format(organization=organization, project=project)
171
180
  response = self.api_service.get(endpoint=endpoint)
181
+ validate_status_code(response)
172
182
  return parse_json_response(response, f"get latest usage limit for project '{project}' in organization", organization=organization)
173
183
 
174
184
  def get_active_usage_limit_from_project(self, organization: str, project: str) -> dict:
@@ -181,6 +191,7 @@ class UsageLimitClient(BaseClient):
181
191
  """
182
192
  endpoint = GET_PROJECT_ACTIVE_USAGE_LIMIT_V2.format(organization=organization, project=project)
183
193
  response = self.api_service.get(endpoint=endpoint)
194
+ validate_status_code(response)
184
195
  return parse_json_response(response, f"get active usage limit for project '{project}' in organization", organization=organization)
185
196
 
186
197
  def delete_usage_limit_from_project(self, organization: str, project: str, limit_id: str) -> dict:
@@ -194,6 +205,7 @@ class UsageLimitClient(BaseClient):
194
205
  """
195
206
  endpoint = DELETE_PROJECT_USAGE_LIMIT_V2.format(organization=organization, project=project, id=limit_id)
196
207
  response = self.api_service.delete(endpoint=endpoint)
208
+ validate_status_code(response)
197
209
  return parse_json_response(response, f"delete usage limit with ID '{limit_id}' for project '{project}' in organization", organization=organization)
198
210
 
199
211
  def set_hard_limit_for_active_usage_limit_from_project(
@@ -219,6 +231,7 @@ class UsageLimitClient(BaseClient):
219
231
  "hardLimit": hard_limit
220
232
  }
221
233
  )
234
+ validate_status_code(response)
222
235
  return parse_json_response(response, f"set hard limit for usage limit ID '{limit_id}' for project '{project}' in organization", organization=organization)
223
236
 
224
237
  def set_soft_limit_for_active_usage_limit_from_project(
@@ -244,6 +257,7 @@ class UsageLimitClient(BaseClient):
244
257
  "softLimit": soft_limit
245
258
  }
246
259
  )
260
+ validate_status_code(response)
247
261
  return parse_json_response(response, f"set soft limit for usage limit ID '{limit_id}' for project '{project}' in organization", organization=organization)
248
262
 
249
263
  def set_project_renewal_status(self, organization: str, project: str, limit_id: str, renewal_status: str) -> dict:
@@ -263,4 +277,5 @@ class UsageLimitClient(BaseClient):
263
277
  "renewalStatus": renewal_status
264
278
  }
265
279
  )
280
+ validate_status_code(response)
266
281
  return parse_json_response(response, f"set renewal status for usage limit ID '{limit_id}' for project '{project}' in organization", organization=organization)
pygeai/proxy/clients.py CHANGED
@@ -5,6 +5,7 @@ import requests
5
5
  from urllib3.exceptions import MaxRetryError
6
6
  from pygeai.proxy.tool import ProxiedTool
7
7
  from pygeai.core.utils.validators import validate_status_code
8
+ from pygeai.core.utils.parsers import parse_json_response
8
9
 
9
10
 
10
11
  @dataclass
@@ -151,7 +152,8 @@ class ProxyClient:
151
152
  response = self.session.request(method, url, **kwargs)
152
153
  response.raise_for_status()
153
154
  try:
154
- return response.json()
155
+ validate_status_code(response)
156
+ return parse_json_response(response, "unknown operation")
155
157
  except ValueError:
156
158
  return response.text
157
159
  except requests.exceptions.Timeout:
@@ -3,7 +3,7 @@ from unittest.mock import patch, MagicMock
3
3
  from json import JSONDecodeError
4
4
 
5
5
  from pygeai.admin.clients import AdminClient
6
- from pygeai.core.common.exceptions import InvalidAPIResponseException
6
+ from pygeai.core.common.exceptions import InvalidAPIResponseException, APIResponseError, APIResponseError
7
7
 
8
8
 
9
9
  class TestAdminClient(unittest.TestCase):
@@ -18,6 +18,7 @@ class TestAdminClient(unittest.TestCase):
18
18
  @patch('pygeai.core.services.rest.ApiService.get')
19
19
  def test_validate_api_token_success(self, mock_get):
20
20
  self.mock_response.json.return_value = {"organizationId": "org-123", "projectId": "proj-123"}
21
+ self.mock_response.status_code = 200
21
22
  mock_get.return_value = self.mock_response
22
23
 
23
24
  result = self.client.validate_api_token()
@@ -32,13 +33,14 @@ class TestAdminClient(unittest.TestCase):
32
33
  self.mock_response.text = "Invalid response"
33
34
  mock_get.return_value = self.mock_response
34
35
 
35
- with self.assertRaises(InvalidAPIResponseException) as context:
36
+ with self.assertRaises(APIResponseError) as context:
36
37
  self.client.validate_api_token()
37
- self.assertIn("Unable to validate API token", str(context.exception))
38
+ self.assertIn("API returned an error", str(context.exception)) # "Unable to validate API token", str(context.exception))
38
39
 
39
40
  @patch('pygeai.core.services.rest.ApiService.get')
40
41
  def test_get_authorized_organizations_success(self, mock_get):
41
42
  self.mock_response.json.return_value = {"organizations": ["org1", "org2"]}
43
+ self.mock_response.status_code = 200
42
44
  mock_get.return_value = self.mock_response
43
45
 
44
46
  result = self.client.get_authorized_organizations()
@@ -53,13 +55,14 @@ class TestAdminClient(unittest.TestCase):
53
55
  self.mock_response.text = "Invalid response"
54
56
  mock_get.return_value = self.mock_response
55
57
 
56
- with self.assertRaises(InvalidAPIResponseException) as context:
58
+ with self.assertRaises(APIResponseError) as context:
57
59
  self.client.get_authorized_organizations()
58
- self.assertIn("Unable to retrieve authorized organizations", str(context.exception))
60
+ self.assertIn("API returned an error", str(context.exception)) # "Unable to retrieve authorized organizations", str(context.exception))
59
61
 
60
62
  @patch('pygeai.core.services.rest.ApiService.get')
61
63
  def test_get_authorized_projects_by_organization_success(self, mock_get):
62
64
  self.mock_response.json.return_value = {"projects": ["proj1", "proj2"]}
65
+ self.mock_response.status_code = 200
63
66
  mock_get.return_value = self.mock_response
64
67
 
65
68
  result = self.client.get_authorized_projects_by_organization("org-123")
@@ -76,13 +79,14 @@ class TestAdminClient(unittest.TestCase):
76
79
  self.mock_response.text = "Invalid response"
77
80
  mock_get.return_value = self.mock_response
78
81
 
79
- with self.assertRaises(InvalidAPIResponseException) as context:
82
+ with self.assertRaises(APIResponseError) as context:
80
83
  self.client.get_authorized_projects_by_organization("org-123")
81
- self.assertIn("Unable to retrieve authorized projects for organization", str(context.exception))
84
+ self.assertIn("API returned an error", str(context.exception)) # "Unable to retrieve authorized projects for organization", str(context.exception))
82
85
 
83
86
  @patch('pygeai.core.services.rest.ApiService.get')
84
87
  def test_get_project_visibility_success(self, mock_get):
85
88
  self.mock_response.json.return_value = {}
89
+ self.mock_response.status_code = 200
86
90
  mock_get.return_value = self.mock_response
87
91
 
88
92
  result = self.client.get_project_visibility(
@@ -105,13 +109,14 @@ class TestAdminClient(unittest.TestCase):
105
109
  self.mock_response.text = "Forbidden"
106
110
  mock_get.return_value = self.mock_response
107
111
 
108
- with self.assertRaises(InvalidAPIResponseException) as context:
112
+ with self.assertRaises(APIResponseError) as context:
109
113
  self.client.get_project_visibility("org-123", "proj-456", "token-789")
110
- self.assertIn("Unable to retrieve project visibility", str(context.exception))
114
+ self.assertIn("API returned an error", str(context.exception)) # "Unable to retrieve project visibility", str(context.exception))
111
115
 
112
116
  @patch('pygeai.core.services.rest.ApiService.get')
113
117
  def test_get_project_api_token_success(self, mock_get):
114
118
  self.mock_response.json.return_value = {"apiToken": "api-token-123"}
119
+ self.mock_response.status_code = 200
115
120
  mock_get.return_value = self.mock_response
116
121
 
117
122
  result = self.client.get_project_api_token(
@@ -134,9 +139,9 @@ class TestAdminClient(unittest.TestCase):
134
139
  self.mock_response.text = "Unauthorized"
135
140
  mock_get.return_value = self.mock_response
136
141
 
137
- with self.assertRaises(InvalidAPIResponseException) as context:
142
+ with self.assertRaises(APIResponseError) as context:
138
143
  self.client.get_project_api_token("org-123", "proj-456", "token-789")
139
- self.assertIn("Unable to retrieve project API token", str(context.exception))
144
+ self.assertIn("API returned an error", str(context.exception)) # "Unable to retrieve project API token", str(context.exception))
140
145
 
141
146
 
142
147
  if __name__ == '__main__':