robosystems-client 0.1.18__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of robosystems-client might be problematic. Click here for more details.

Files changed (145) hide show
  1. robosystems_client/api/agent/auto_select_agent.py +10 -4
  2. robosystems_client/api/agent/batch_process_queries.py +9 -4
  3. robosystems_client/api/agent/execute_specific_agent.py +11 -4
  4. robosystems_client/api/agent/get_agent_metadata.py +4 -1
  5. robosystems_client/api/agent/list_agents.py +4 -1
  6. robosystems_client/api/agent/recommend_agent.py +4 -1
  7. robosystems_client/api/auth/check_password_strength.py +2 -0
  8. robosystems_client/api/auth/complete_sso_auth.py +3 -0
  9. robosystems_client/api/auth/forgot_password.py +6 -3
  10. robosystems_client/api/auth/generate_sso_token.py +3 -0
  11. robosystems_client/api/auth/get_captcha_config.py +1 -0
  12. robosystems_client/api/auth/get_current_auth_user.py +3 -0
  13. robosystems_client/api/auth/get_password_policy.py +1 -0
  14. robosystems_client/api/auth/login_user.py +7 -3
  15. robosystems_client/api/auth/logout_user.py +2 -0
  16. robosystems_client/api/auth/refresh_auth_session.py +3 -0
  17. robosystems_client/api/auth/register_user.py +11 -6
  18. robosystems_client/api/auth/resend_verification_email.py +8 -3
  19. robosystems_client/api/auth/reset_password.py +3 -0
  20. robosystems_client/api/auth/sso_token_exchange.py +7 -3
  21. robosystems_client/api/auth/validate_reset_token.py +2 -0
  22. robosystems_client/api/auth/verify_email.py +3 -0
  23. robosystems_client/api/backup/create_backup.py +14 -8
  24. robosystems_client/api/backup/get_backup_download_url.py +9 -4
  25. robosystems_client/api/backup/get_backup_stats.py +3 -1
  26. robosystems_client/api/backup/list_backups.py +7 -5
  27. robosystems_client/api/backup/restore_backup.py +27 -8
  28. robosystems_client/api/connections/create_connection.py +14 -8
  29. robosystems_client/api/connections/create_link_token.py +9 -4
  30. robosystems_client/api/connections/delete_connection.py +13 -8
  31. robosystems_client/api/connections/exchange_link_token.py +9 -4
  32. robosystems_client/api/connections/get_connection.py +9 -4
  33. robosystems_client/api/connections/get_connection_options.py +8 -4
  34. robosystems_client/api/connections/init_o_auth.py +3 -1
  35. robosystems_client/api/connections/list_connections.py +8 -4
  36. robosystems_client/api/connections/oauth_callback.py +10 -4
  37. robosystems_client/api/connections/sync_connection.py +13 -8
  38. robosystems_client/api/graph_analytics/get_graph_metrics.py +13 -8
  39. robosystems_client/api/graph_analytics/get_graph_usage_stats.py +12 -8
  40. robosystems_client/api/graph_billing/get_current_graph_bill.py +9 -4
  41. robosystems_client/api/graph_billing/get_graph_billing_history.py +9 -4
  42. robosystems_client/api/graph_billing/get_graph_monthly_bill.py +10 -4
  43. robosystems_client/api/graph_billing/get_graph_usage_details.py +10 -4
  44. robosystems_client/api/graph_credits/check_credit_balance.py +9 -4
  45. robosystems_client/api/graph_credits/check_storage_limits.py +9 -4
  46. robosystems_client/api/graph_credits/get_credit_summary.py +9 -4
  47. robosystems_client/api/graph_credits/get_storage_usage.py +8 -4
  48. robosystems_client/api/graph_credits/list_credit_transactions.py +9 -4
  49. robosystems_client/api/graph_health/get_database_health.py +9 -4
  50. robosystems_client/api/graph_info/get_database_info.py +9 -4
  51. robosystems_client/api/graph_limits/get_graph_limits.py +9 -4
  52. robosystems_client/api/{create → graphs}/create_graph.py +7 -5
  53. robosystems_client/api/{create → graphs}/get_available_extensions.py +2 -1
  54. robosystems_client/api/{user/get_user_graphs.py → graphs/get_graphs.py} +3 -1
  55. robosystems_client/api/{user/select_user_graph.py → graphs/select_graph.py} +13 -8
  56. robosystems_client/api/mcp/call_mcp_tool.py +18 -8
  57. robosystems_client/api/mcp/list_mcp_tools.py +12 -8
  58. robosystems_client/api/operations/cancel_operation.py +9 -3
  59. robosystems_client/api/operations/get_operation_status.py +8 -3
  60. robosystems_client/api/operations/stream_operation_events.py +8 -3
  61. robosystems_client/api/query/execute_cypher_query.py +49 -16
  62. robosystems_client/api/schema/export_graph_schema.py +3 -1
  63. robosystems_client/api/schema/{get_graph_schema_info.py → get_graph_schema.py} +37 -47
  64. robosystems_client/api/schema/validate_schema.py +10 -5
  65. robosystems_client/api/service_offerings/get_service_offerings.py +2 -0
  66. robosystems_client/api/status/get_service_status.py +1 -0
  67. robosystems_client/api/subgraphs/create_subgraph.py +3 -1
  68. robosystems_client/api/subgraphs/delete_subgraph.py +15 -7
  69. robosystems_client/api/subgraphs/get_subgraph_info.py +14 -7
  70. robosystems_client/api/subgraphs/get_subgraph_quota.py +10 -4
  71. robosystems_client/api/subgraphs/list_subgraphs.py +3 -1
  72. robosystems_client/api/tables/delete_file_v1_graphs_graph_id_tables_files_file_id_delete.py +287 -0
  73. robosystems_client/api/tables/get_file_info_v1_graphs_graph_id_tables_files_file_id_get.py +283 -0
  74. robosystems_client/api/tables/get_upload_url_v1_graphs_graph_id_tables_table_name_files_post.py +260 -0
  75. robosystems_client/api/tables/ingest_tables_v1_graphs_graph_id_tables_ingest_post.py +251 -0
  76. robosystems_client/api/tables/list_table_files_v1_graphs_graph_id_tables_table_name_files_get.py +283 -0
  77. robosystems_client/api/{backup/export_backup.py → tables/list_tables_v1_graphs_graph_id_tables_get.py} +36 -36
  78. robosystems_client/api/{schema/list_schema_extensions.py → tables/query_tables_v1_graphs_graph_id_tables_query_post.py} +67 -43
  79. robosystems_client/api/tables/update_file_v1_graphs_graph_id_tables_files_file_id_patch.py +306 -0
  80. robosystems_client/api/user/create_user_api_key.py +2 -0
  81. robosystems_client/api/user/get_all_credit_summaries.py +6 -3
  82. robosystems_client/api/user/get_current_user.py +2 -0
  83. robosystems_client/api/user/list_user_api_keys.py +2 -0
  84. robosystems_client/api/user/revoke_user_api_key.py +7 -3
  85. robosystems_client/api/user/update_user.py +2 -0
  86. robosystems_client/api/user/update_user_api_key.py +2 -0
  87. robosystems_client/api/user/update_user_password.py +8 -3
  88. robosystems_client/api/user_analytics/get_detailed_user_analytics.py +2 -0
  89. robosystems_client/api/user_analytics/get_user_usage_overview.py +2 -0
  90. robosystems_client/api/user_limits/get_all_shared_repository_limits.py +2 -0
  91. robosystems_client/api/user_limits/get_shared_repository_limits.py +6 -4
  92. robosystems_client/api/user_limits/get_user_limits.py +3 -0
  93. robosystems_client/api/user_limits/get_user_usage.py +2 -0
  94. robosystems_client/api/user_subscriptions/cancel_shared_repository_subscription.py +11 -6
  95. robosystems_client/api/user_subscriptions/get_repository_credits.py +7 -3
  96. robosystems_client/api/user_subscriptions/get_shared_repository_credits.py +7 -3
  97. robosystems_client/api/user_subscriptions/get_user_shared_subscriptions.py +7 -3
  98. robosystems_client/api/user_subscriptions/subscribe_to_shared_repository.py +8 -3
  99. robosystems_client/api/user_subscriptions/upgrade_shared_repository_subscription.py +12 -6
  100. robosystems_client/extensions/README.md +1 -212
  101. robosystems_client/extensions/__init__.py +12 -28
  102. robosystems_client/extensions/extensions.py +3 -17
  103. robosystems_client/extensions/operation_client.py +12 -4
  104. robosystems_client/extensions/query_client.py +38 -24
  105. robosystems_client/extensions/sse_client.py +11 -0
  106. robosystems_client/extensions/table_ingest_client.py +466 -0
  107. robosystems_client/models/__init__.py +39 -31
  108. robosystems_client/models/api_key_info.py +20 -0
  109. robosystems_client/models/backup_restore_request.py +1 -12
  110. robosystems_client/models/bulk_ingest_request.py +50 -0
  111. robosystems_client/models/bulk_ingest_response.py +137 -0
  112. robosystems_client/models/create_api_key_request.py +20 -0
  113. robosystems_client/models/create_graph_request.py +4 -3
  114. robosystems_client/models/{sso_login_request.py → delete_file_v1_graphs_graph_id_tables_files_file_id_delete_response_delete_file_v1_graphs_graph_id_tables_files_file_id_delete.py} +9 -22
  115. robosystems_client/models/file_update_request.py +62 -0
  116. robosystems_client/models/file_upload_request.py +51 -0
  117. robosystems_client/models/file_upload_response.py +83 -0
  118. robosystems_client/models/{get_graph_schema_info_response_getgraphschemainfo.py → get_file_info_v1_graphs_graph_id_tables_files_file_id_get_response_get_file_info_v1_graphs_graph_id_tables_files_file_id_get.py} +8 -5
  119. robosystems_client/models/{copy_response_error_details_type_0.py → get_graph_schema_response_getgraphschema.py} +5 -5
  120. robosystems_client/models/list_table_files_v1_graphs_graph_id_tables_table_name_files_get_response_list_table_files_v1_graphs_graph_id_tables_table_name_files_get.py +47 -0
  121. robosystems_client/models/table_info.py +107 -0
  122. robosystems_client/models/table_ingest_result.py +107 -0
  123. robosystems_client/models/table_list_response.py +81 -0
  124. robosystems_client/models/table_query_request.py +40 -0
  125. robosystems_client/models/table_query_response.py +92 -0
  126. robosystems_client/models/{list_schema_extensions_response_listschemaextensions.py → update_file_v1_graphs_graph_id_tables_files_file_id_patch_response_update_file_v1_graphs_graph_id_tables_files_file_id_patch.py} +8 -5
  127. {robosystems_client-0.1.18.dist-info → robosystems_client-0.2.0.dist-info}/METADATA +15 -3
  128. {robosystems_client-0.1.18.dist-info → robosystems_client-0.2.0.dist-info}/RECORD +132 -127
  129. robosystems_client/api/auth/sso_login.py +0 -177
  130. robosystems_client/api/copy/copy_data_to_graph.py +0 -486
  131. robosystems_client/extensions/copy_client.py +0 -479
  132. robosystems_client/models/copy_response.py +0 -275
  133. robosystems_client/models/copy_response_status.py +0 -11
  134. robosystems_client/models/data_frame_copy_request.py +0 -125
  135. robosystems_client/models/data_frame_copy_request_format.py +0 -10
  136. robosystems_client/models/s3_copy_request.py +0 -378
  137. robosystems_client/models/s3_copy_request_file_format.py +0 -12
  138. robosystems_client/models/s3_copy_request_s3_url_style_type_0.py +0 -9
  139. robosystems_client/models/url_copy_request.py +0 -157
  140. robosystems_client/models/url_copy_request_file_format.py +0 -10
  141. robosystems_client/models/url_copy_request_headers_type_0.py +0 -44
  142. /robosystems_client/api/{copy → graphs}/__init__.py +0 -0
  143. /robosystems_client/api/{create → tables}/__init__.py +0 -0
  144. {robosystems_client-0.1.18.dist-info → robosystems_client-0.2.0.dist-info}/WHEEL +0 -0
  145. {robosystems_client-0.1.18.dist-info → robosystems_client-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,479 +0,0 @@
1
- """Enhanced Copy Client with SSE support
2
-
3
- Provides intelligent data copy operations with progress monitoring.
4
- """
5
-
6
- from dataclasses import dataclass
7
- from typing import Dict, Any, Optional, Callable, Union, List
8
- from enum import Enum
9
- import time
10
- import logging
11
-
12
- from ..api.copy.copy_data_to_graph import sync_detailed as copy_data_to_graph
13
- from ..models.s3_copy_request import S3CopyRequest
14
- from ..models.url_copy_request import URLCopyRequest
15
- from ..models.data_frame_copy_request import DataFrameCopyRequest
16
- from ..models.copy_response import CopyResponse
17
- from ..models.copy_response_status import CopyResponseStatus
18
- from ..models.s3_copy_request_file_format import S3CopyRequestFileFormat
19
- from .sse_client import SSEClient, AsyncSSEClient, SSEConfig, EventType
20
-
21
- logger = logging.getLogger(__name__)
22
-
23
-
24
- class CopySourceType(Enum):
25
- """Types of copy sources"""
26
-
27
- S3 = "s3"
28
- URL = "url"
29
- DATAFRAME = "dataframe"
30
-
31
-
32
- @dataclass
33
- class CopyOptions:
34
- """Options for copy operations"""
35
-
36
- on_progress: Optional[Callable[[str, Optional[float]], None]] = None
37
- on_queue_update: Optional[Callable[[int, int], None]] = None
38
- on_warning: Optional[Callable[[str], None]] = None
39
- timeout: Optional[int] = None
40
- test_mode: Optional[bool] = None
41
-
42
-
43
- @dataclass
44
- class CopyResult:
45
- """Result from copy operation"""
46
-
47
- status: str # 'completed', 'failed', 'partial', 'accepted'
48
- rows_imported: Optional[int] = None
49
- rows_skipped: Optional[int] = None
50
- bytes_processed: Optional[int] = None
51
- execution_time_ms: Optional[float] = None
52
- warnings: Optional[List[str]] = None
53
- error: Optional[str] = None
54
- operation_id: Optional[str] = None
55
- sse_url: Optional[str] = None
56
- message: Optional[str] = None
57
-
58
-
59
- @dataclass
60
- class CopyStatistics:
61
- """Statistics from copy operation"""
62
-
63
- total_rows: int
64
- imported_rows: int
65
- skipped_rows: int
66
- bytes_processed: int
67
- duration: float # seconds
68
- throughput: float # rows per second
69
-
70
-
71
- class CopyClient:
72
- """Enhanced copy client with SSE streaming support"""
73
-
74
- def __init__(self, config: Dict[str, Any]):
75
- self.config = config
76
- self.base_url = config["base_url"]
77
- self.headers = config.get("headers", {})
78
- # Get token from config if passed by parent
79
- self.token = config.get("token")
80
- self.sse_client: Optional[SSEClient] = None
81
-
82
- def copy_from_s3(
83
- self, graph_id: str, request: S3CopyRequest, options: Optional[CopyOptions] = None
84
- ) -> CopyResult:
85
- """Copy data from S3 to graph database"""
86
- return self._execute_copy(graph_id, request, CopySourceType.S3, options)
87
-
88
- def copy_from_url(
89
- self, graph_id: str, request: URLCopyRequest, options: Optional[CopyOptions] = None
90
- ) -> CopyResult:
91
- """Copy data from URL to graph database (when available)"""
92
- return self._execute_copy(graph_id, request, CopySourceType.URL, options)
93
-
94
- def copy_from_dataframe(
95
- self,
96
- graph_id: str,
97
- request: DataFrameCopyRequest,
98
- options: Optional[CopyOptions] = None,
99
- ) -> CopyResult:
100
- """Copy data from DataFrame to graph database (when available)"""
101
- return self._execute_copy(graph_id, request, CopySourceType.DATAFRAME, options)
102
-
103
- def _execute_copy(
104
- self,
105
- graph_id: str,
106
- request: Union[S3CopyRequest, URLCopyRequest, DataFrameCopyRequest],
107
- source_type: CopySourceType,
108
- options: Optional[CopyOptions] = None,
109
- ) -> CopyResult:
110
- """Execute copy operation with automatic SSE monitoring for long-running operations"""
111
- if options is None:
112
- options = CopyOptions()
113
-
114
- start_time = time.time()
115
-
116
- # Import client here to avoid circular imports
117
- from ..client import Client
118
-
119
- # Create client with headers
120
- client = Client(base_url=self.base_url, headers=self.headers)
121
-
122
- try:
123
- # Execute the copy request with token if available
124
- kwargs = {"graph_id": graph_id, "client": client, "body": request}
125
- # Only add token if it's a valid string
126
- if self.token and isinstance(self.token, str) and self.token.strip():
127
- kwargs["token"] = self.token
128
- response = copy_data_to_graph(**kwargs)
129
-
130
- if response.parsed:
131
- response_data: CopyResponse = response.parsed
132
-
133
- # Check if this is an accepted (async) operation
134
- if (
135
- response_data.status == CopyResponseStatus.ACCEPTED
136
- and response_data.operation_id
137
- ):
138
- # This is a long-running operation with SSE monitoring
139
- if options.on_progress:
140
- options.on_progress("Copy operation started. Monitoring progress...", None)
141
-
142
- # If SSE URL is provided, use it for monitoring
143
- if response_data.sse_url:
144
- return self._monitor_copy_operation(
145
- response_data.operation_id, options, start_time
146
- )
147
-
148
- # Otherwise return the accepted response
149
- return CopyResult(
150
- status="accepted",
151
- operation_id=response_data.operation_id,
152
- sse_url=response_data.sse_url,
153
- message=response_data.message,
154
- )
155
-
156
- # This is a synchronous response - operation completed immediately
157
- return self._build_copy_result(response_data, time.time() - start_time)
158
- else:
159
- return CopyResult(
160
- status="failed",
161
- error="No response data received",
162
- execution_time_ms=(time.time() - start_time) * 1000,
163
- )
164
-
165
- except Exception as e:
166
- error_msg = str(e)
167
- # Check for authentication errors
168
- if (
169
- "401" in error_msg or "403" in error_msg or "unauthorized" in error_msg.lower()
170
- ):
171
- logger.error(f"Authentication failed during copy operation: {e}")
172
- return CopyResult(
173
- status="failed",
174
- error=f"Authentication failed: {error_msg}",
175
- execution_time_ms=(time.time() - start_time) * 1000,
176
- )
177
- else:
178
- logger.error(f"Copy operation failed: {e}")
179
- return CopyResult(
180
- status="failed",
181
- error=error_msg,
182
- execution_time_ms=(time.time() - start_time) * 1000,
183
- )
184
-
185
- def _monitor_copy_operation(
186
- self, operation_id: str, options: CopyOptions, start_time: float
187
- ) -> CopyResult:
188
- """Monitor a copy operation using SSE"""
189
- timeout_ms = options.timeout or 3600000 # Default 1 hour for copy operations
190
- timeout_time = time.time() + (timeout_ms / 1000)
191
-
192
- result = CopyResult(status="failed")
193
- warnings: List[str] = []
194
-
195
- # Set up SSE connection
196
- sse_config = SSEConfig(base_url=self.base_url, timeout=timeout_ms // 1000)
197
- sse_client = SSEClient(sse_config)
198
-
199
- try:
200
- sse_client.connect(operation_id)
201
-
202
- # Set up event handlers
203
- def on_queue_update(data):
204
- if options.on_queue_update:
205
- position = data.get("position", data.get("queue_position", 0))
206
- wait_time = data.get("estimated_wait_seconds", 0)
207
- options.on_queue_update(position, wait_time)
208
-
209
- def on_progress(data):
210
- if options.on_progress:
211
- message = data.get("message", data.get("status", "Processing..."))
212
- progress_percent = data.get("progress_percent", data.get("progress"))
213
- options.on_progress(message, progress_percent)
214
-
215
- # Check for warnings in progress updates
216
- if "warnings" in data and data["warnings"]:
217
- warnings.extend(data["warnings"])
218
- if options.on_warning:
219
- for warning in data["warnings"]:
220
- options.on_warning(warning)
221
-
222
- def on_completed(data):
223
- nonlocal result
224
- completion_data = data.get("result", data)
225
- result = CopyResult(
226
- status=completion_data.get("status", "completed"),
227
- rows_imported=completion_data.get("rows_imported"),
228
- rows_skipped=completion_data.get("rows_skipped"),
229
- bytes_processed=completion_data.get("bytes_processed"),
230
- execution_time_ms=(time.time() - start_time) * 1000,
231
- warnings=warnings if warnings else completion_data.get("warnings"),
232
- message=completion_data.get("message"),
233
- )
234
-
235
- def on_error(data):
236
- nonlocal result
237
- result = CopyResult(
238
- status="failed",
239
- error=data.get("message", data.get("error", "Copy operation failed")),
240
- execution_time_ms=(time.time() - start_time) * 1000,
241
- warnings=warnings if warnings else None,
242
- )
243
-
244
- def on_cancelled(data):
245
- nonlocal result
246
- result = CopyResult(
247
- status="failed",
248
- error="Copy operation cancelled",
249
- execution_time_ms=(time.time() - start_time) * 1000,
250
- warnings=warnings if warnings else None,
251
- )
252
-
253
- # Register event handlers
254
- sse_client.on(EventType.QUEUE_UPDATE.value, on_queue_update)
255
- sse_client.on(EventType.OPERATION_PROGRESS.value, on_progress)
256
- sse_client.on(EventType.OPERATION_COMPLETED.value, on_completed)
257
- sse_client.on(EventType.OPERATION_ERROR.value, on_error)
258
- sse_client.on(EventType.OPERATION_CANCELLED.value, on_cancelled)
259
-
260
- # Listen for events until completion or timeout
261
- while time.time() < timeout_time:
262
- sse_client.listen(timeout=1) # Process events for 1 second
263
-
264
- # Check if operation is complete
265
- if result.status in ["completed", "failed", "partial"]:
266
- break
267
-
268
- if time.time() >= timeout_time:
269
- result = CopyResult(
270
- status="failed",
271
- error=f"Copy operation timeout after {timeout_ms}ms",
272
- execution_time_ms=(time.time() - start_time) * 1000,
273
- )
274
-
275
- finally:
276
- sse_client.close()
277
-
278
- return result
279
-
280
- def _build_copy_result(
281
- self, response_data: CopyResponse, execution_time: float
282
- ) -> CopyResult:
283
- """Build copy result from response data"""
284
- return CopyResult(
285
- status=response_data.status.value,
286
- rows_imported=response_data.rows_imported,
287
- rows_skipped=response_data.rows_skipped,
288
- bytes_processed=response_data.bytes_processed,
289
- execution_time_ms=response_data.execution_time_ms or (execution_time * 1000),
290
- warnings=response_data.warnings,
291
- message=response_data.message,
292
- error=str(response_data.error_details) if response_data.error_details else None,
293
- )
294
-
295
- def calculate_statistics(self, result: CopyResult) -> Optional[CopyStatistics]:
296
- """Calculate copy statistics from result"""
297
- if result.status == "failed" or not result.rows_imported:
298
- return None
299
-
300
- total_rows = (result.rows_imported or 0) + (result.rows_skipped or 0)
301
- duration = (result.execution_time_ms or 0) / 1000 # Convert to seconds
302
- throughput = (result.rows_imported or 0) / duration if duration > 0 else 0
303
-
304
- return CopyStatistics(
305
- total_rows=total_rows,
306
- imported_rows=result.rows_imported or 0,
307
- skipped_rows=result.rows_skipped or 0,
308
- bytes_processed=result.bytes_processed or 0,
309
- duration=duration,
310
- throughput=throughput,
311
- )
312
-
313
- def copy_s3(
314
- self,
315
- graph_id: str,
316
- table_name: str,
317
- s3_path: str,
318
- access_key_id: str,
319
- secret_access_key: str,
320
- region: str = "us-east-1",
321
- file_format: Optional[str] = None,
322
- ignore_errors: bool = False,
323
- ) -> CopyResult:
324
- """Convenience method for simple S3 copy with default options"""
325
-
326
- # Map string format to enum
327
- format_enum = S3CopyRequestFileFormat.PARQUET
328
- if file_format:
329
- format_map = {
330
- "csv": S3CopyRequestFileFormat.CSV,
331
- "parquet": S3CopyRequestFileFormat.PARQUET,
332
- "json": S3CopyRequestFileFormat.JSON,
333
- "delta": S3CopyRequestFileFormat.DELTA,
334
- "iceberg": S3CopyRequestFileFormat.ICEBERG,
335
- }
336
- format_enum = format_map.get(file_format.lower(), S3CopyRequestFileFormat.PARQUET)
337
-
338
- request = S3CopyRequest(
339
- table_name=table_name,
340
- s3_path=s3_path,
341
- s3_access_key_id=access_key_id,
342
- s3_secret_access_key=secret_access_key,
343
- s3_region=region,
344
- file_format=format_enum,
345
- ignore_errors=ignore_errors,
346
- )
347
-
348
- return self.copy_from_s3(graph_id, request)
349
-
350
- def monitor_multiple_copies(
351
- self, operation_ids: List[str], options: Optional[CopyOptions] = None
352
- ) -> Dict[str, CopyResult]:
353
- """Monitor multiple copy operations concurrently"""
354
- results = {}
355
- for operation_id in operation_ids:
356
- result = self._monitor_copy_operation(
357
- operation_id, options or CopyOptions(), time.time()
358
- )
359
- results[operation_id] = result
360
- return results
361
-
362
- def batch_copy_from_s3(
363
- self, graph_id: str, copies: List[Dict[str, Any]]
364
- ) -> List[CopyResult]:
365
- """Batch copy multiple tables from S3"""
366
- results = []
367
- for copy_config in copies:
368
- request = copy_config.get("request")
369
- options = copy_config.get("options")
370
- if request:
371
- result = self.copy_from_s3(graph_id, request, options)
372
- results.append(result)
373
- return results
374
-
375
- def copy_with_retry(
376
- self,
377
- graph_id: str,
378
- request: Union[S3CopyRequest, URLCopyRequest, DataFrameCopyRequest],
379
- source_type: CopySourceType,
380
- max_retries: int = 3,
381
- options: Optional[CopyOptions] = None,
382
- ) -> CopyResult:
383
- """Copy with retry logic for transient failures"""
384
- if options is None:
385
- options = CopyOptions()
386
-
387
- last_error: Optional[Exception] = None
388
- attempt = 0
389
-
390
- while attempt < max_retries:
391
- attempt += 1
392
-
393
- try:
394
- result = self._execute_copy(graph_id, request, source_type, options)
395
-
396
- # If successful or partially successful, return
397
- if result.status in ["completed", "partial"]:
398
- return result
399
-
400
- # If failed, check if it's retryable
401
- if result.status == "failed":
402
- is_retryable = self._is_retryable_error(result.error)
403
- if not is_retryable or attempt == max_retries:
404
- return result
405
-
406
- # Wait before retry with exponential backoff
407
- wait_time = min(1000 * (2 ** (attempt - 1)), 30000) / 1000
408
- if options.on_progress:
409
- options.on_progress(
410
- f"Retrying copy operation (attempt {attempt}/{max_retries}) in {wait_time}s...",
411
- None,
412
- )
413
- time.sleep(wait_time)
414
-
415
- except Exception as e:
416
- last_error = e
417
-
418
- if attempt == max_retries:
419
- raise last_error
420
-
421
- # Wait before retry
422
- wait_time = min(1000 * (2 ** (attempt - 1)), 30000) / 1000
423
- if options.on_progress:
424
- options.on_progress(
425
- f"Retrying after error (attempt {attempt}/{max_retries}) in {wait_time}s...",
426
- None,
427
- )
428
- time.sleep(wait_time)
429
-
430
- raise last_error or Exception("Copy operation failed after all retries")
431
-
432
- def _is_retryable_error(self, error: Optional[str]) -> bool:
433
- """Check if an error is retryable"""
434
- if not error:
435
- return False
436
-
437
- retryable_patterns = [
438
- "timeout",
439
- "network",
440
- "connection",
441
- "temporary",
442
- "unavailable",
443
- "rate limit",
444
- "throttl",
445
- ]
446
-
447
- lower_error = error.lower()
448
- return any(pattern in lower_error for pattern in retryable_patterns)
449
-
450
- def close(self):
451
- """Cancel any active SSE connections"""
452
- if self.sse_client:
453
- self.sse_client.close()
454
- self.sse_client = None
455
-
456
-
457
- class AsyncCopyClient:
458
- """Async version of CopyClient for async/await usage"""
459
-
460
- def __init__(self, config: Dict[str, Any]):
461
- self.config = config
462
- self.base_url = config["base_url"]
463
- self.sse_client: Optional[AsyncSSEClient] = None
464
- # Get token from config if passed by parent
465
- self.token = config.get("token")
466
-
467
- async def copy_from_s3(
468
- self, graph_id: str, request: S3CopyRequest, options: Optional[CopyOptions] = None
469
- ) -> CopyResult:
470
- """Copy data from S3 to graph database asynchronously"""
471
- # Async implementation would go here
472
- # For now, this is a placeholder
473
- raise NotImplementedError("Async copy client not yet implemented")
474
-
475
- async def close(self):
476
- """Close any active connections"""
477
- if self.sse_client:
478
- await self.sse_client.close()
479
- self.sse_client = None