robosystems-client 0.2.22__py3-none-any.whl → 0.2.23__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.
- robosystems_client/api/agent/auto_select_agent.py +4 -4
- robosystems_client/api/agent/execute_specific_agent.py +4 -4
- robosystems_client/api/files/update_file.py +4 -4
- robosystems_client/extensions/README.md +20 -24
- robosystems_client/extensions/agent_client.py +1 -1
- robosystems_client/extensions/materialization_client.py +73 -19
- robosystems_client/extensions/sse_client.py +20 -25
- robosystems_client/extensions/subgraph_workspace_client.py +3 -1
- robosystems_client/models/materialize_response.py +20 -58
- {robosystems_client-0.2.22.dist-info → robosystems_client-0.2.23.dist-info}/METADATA +1 -1
- {robosystems_client-0.2.22.dist-info → robosystems_client-0.2.23.dist-info}/RECORD +13 -13
- {robosystems_client-0.2.22.dist-info → robosystems_client-0.2.23.dist-info}/WHEEL +0 -0
- {robosystems_client-0.2.22.dist-info → robosystems_client-0.2.23.dist-info}/licenses/LICENSE +0 -0
|
@@ -134,7 +134,7 @@ def sync_detailed(
|
|
|
134
134
|
**Execution Strategies (automatic):**
|
|
135
135
|
- Fast operations (<5s): Immediate synchronous response
|
|
136
136
|
- Medium operations (5-30s): SSE streaming with progress updates
|
|
137
|
-
- Long operations (>30s):
|
|
137
|
+
- Long operations (>30s): Background queue with operation tracking
|
|
138
138
|
|
|
139
139
|
**Response Mode Override:**
|
|
140
140
|
Use query parameter `?mode=sync|async` to override automatic strategy selection.
|
|
@@ -228,7 +228,7 @@ def sync(
|
|
|
228
228
|
**Execution Strategies (automatic):**
|
|
229
229
|
- Fast operations (<5s): Immediate synchronous response
|
|
230
230
|
- Medium operations (5-30s): SSE streaming with progress updates
|
|
231
|
-
- Long operations (>30s):
|
|
231
|
+
- Long operations (>30s): Background queue with operation tracking
|
|
232
232
|
|
|
233
233
|
**Response Mode Override:**
|
|
234
234
|
Use query parameter `?mode=sync|async` to override automatic strategy selection.
|
|
@@ -317,7 +317,7 @@ async def asyncio_detailed(
|
|
|
317
317
|
**Execution Strategies (automatic):**
|
|
318
318
|
- Fast operations (<5s): Immediate synchronous response
|
|
319
319
|
- Medium operations (5-30s): SSE streaming with progress updates
|
|
320
|
-
- Long operations (>30s):
|
|
320
|
+
- Long operations (>30s): Background queue with operation tracking
|
|
321
321
|
|
|
322
322
|
**Response Mode Override:**
|
|
323
323
|
Use query parameter `?mode=sync|async` to override automatic strategy selection.
|
|
@@ -409,7 +409,7 @@ async def asyncio(
|
|
|
409
409
|
**Execution Strategies (automatic):**
|
|
410
410
|
- Fast operations (<5s): Immediate synchronous response
|
|
411
411
|
- Medium operations (5-30s): SSE streaming with progress updates
|
|
412
|
-
- Long operations (>30s):
|
|
412
|
+
- Long operations (>30s): Background queue with operation tracking
|
|
413
413
|
|
|
414
414
|
**Response Mode Override:**
|
|
415
415
|
Use query parameter `?mode=sync|async` to override automatic strategy selection.
|
|
@@ -124,7 +124,7 @@ def sync_detailed(
|
|
|
124
124
|
**Execution Strategies (automatic):**
|
|
125
125
|
- Fast operations (<5s): Immediate synchronous response
|
|
126
126
|
- Medium operations (5-30s): SSE streaming with progress updates
|
|
127
|
-
- Long operations (>30s):
|
|
127
|
+
- Long operations (>30s): Background queue with operation tracking
|
|
128
128
|
|
|
129
129
|
**Response Mode Override:**
|
|
130
130
|
Use query parameter `?mode=sync|async` to override automatic strategy selection.
|
|
@@ -179,7 +179,7 @@ def sync(
|
|
|
179
179
|
**Execution Strategies (automatic):**
|
|
180
180
|
- Fast operations (<5s): Immediate synchronous response
|
|
181
181
|
- Medium operations (5-30s): SSE streaming with progress updates
|
|
182
|
-
- Long operations (>30s):
|
|
182
|
+
- Long operations (>30s): Background queue with operation tracking
|
|
183
183
|
|
|
184
184
|
**Response Mode Override:**
|
|
185
185
|
Use query parameter `?mode=sync|async` to override automatic strategy selection.
|
|
@@ -229,7 +229,7 @@ async def asyncio_detailed(
|
|
|
229
229
|
**Execution Strategies (automatic):**
|
|
230
230
|
- Fast operations (<5s): Immediate synchronous response
|
|
231
231
|
- Medium operations (5-30s): SSE streaming with progress updates
|
|
232
|
-
- Long operations (>30s):
|
|
232
|
+
- Long operations (>30s): Background queue with operation tracking
|
|
233
233
|
|
|
234
234
|
**Response Mode Override:**
|
|
235
235
|
Use query parameter `?mode=sync|async` to override automatic strategy selection.
|
|
@@ -282,7 +282,7 @@ async def asyncio(
|
|
|
282
282
|
**Execution Strategies (automatic):**
|
|
283
283
|
- Fast operations (<5s): Immediate synchronous response
|
|
284
284
|
- Medium operations (5-30s): SSE streaming with progress updates
|
|
285
|
-
- Long operations (>30s):
|
|
285
|
+
- Long operations (>30s): Background queue with operation tracking
|
|
286
286
|
|
|
287
287
|
**Response Mode Override:**
|
|
288
288
|
Use query parameter `?mode=sync|async` to override automatic strategy selection.
|
|
@@ -103,7 +103,7 @@ def sync_detailed(
|
|
|
103
103
|
**What Happens (status='uploaded'):**
|
|
104
104
|
1. File validated in S3
|
|
105
105
|
2. Row count calculated
|
|
106
|
-
3. DuckDB staging triggered immediately (
|
|
106
|
+
3. DuckDB staging triggered immediately (background task)
|
|
107
107
|
4. If ingest_to_graph=true, graph ingestion queued
|
|
108
108
|
5. File queryable in DuckDB within seconds
|
|
109
109
|
|
|
@@ -165,7 +165,7 @@ def sync(
|
|
|
165
165
|
**What Happens (status='uploaded'):**
|
|
166
166
|
1. File validated in S3
|
|
167
167
|
2. Row count calculated
|
|
168
|
-
3. DuckDB staging triggered immediately (
|
|
168
|
+
3. DuckDB staging triggered immediately (background task)
|
|
169
169
|
4. If ingest_to_graph=true, graph ingestion queued
|
|
170
170
|
5. File queryable in DuckDB within seconds
|
|
171
171
|
|
|
@@ -222,7 +222,7 @@ async def asyncio_detailed(
|
|
|
222
222
|
**What Happens (status='uploaded'):**
|
|
223
223
|
1. File validated in S3
|
|
224
224
|
2. Row count calculated
|
|
225
|
-
3. DuckDB staging triggered immediately (
|
|
225
|
+
3. DuckDB staging triggered immediately (background task)
|
|
226
226
|
4. If ingest_to_graph=true, graph ingestion queued
|
|
227
227
|
5. File queryable in DuckDB within seconds
|
|
228
228
|
|
|
@@ -282,7 +282,7 @@ async def asyncio(
|
|
|
282
282
|
**What Happens (status='uploaded'):**
|
|
283
283
|
1. File validated in S3
|
|
284
284
|
2. Row count calculated
|
|
285
|
-
3. DuckDB staging triggered immediately (
|
|
285
|
+
3. DuckDB staging triggered immediately (background task)
|
|
286
286
|
4. If ingest_to_graph=true, graph ingestion queued
|
|
287
287
|
5. File queryable in DuckDB within seconds
|
|
288
288
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# RoboSystems Python Client Extensions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Production-Ready Extensions** for the RoboSystems Financial Knowledge Graph API
|
|
4
4
|
|
|
5
5
|
[](https://www.python.org/downloads/)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -17,7 +17,7 @@ The RoboSystems Python Client Extensions provide enhanced functionality for the
|
|
|
17
17
|
- **Caching** with TTL and LRU eviction
|
|
18
18
|
- **Full Async/Await Support** throughout
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## Quick Start
|
|
21
21
|
|
|
22
22
|
### Installation
|
|
23
23
|
|
|
@@ -84,7 +84,7 @@ async def main():
|
|
|
84
84
|
asyncio.run(main())
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
##
|
|
87
|
+
## Authentication
|
|
88
88
|
|
|
89
89
|
### API Key Authentication (Recommended)
|
|
90
90
|
|
|
@@ -139,7 +139,7 @@ dev_ext = create_extensions(
|
|
|
139
139
|
)
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
##
|
|
142
|
+
## Advanced Features
|
|
143
143
|
|
|
144
144
|
### Query Builder
|
|
145
145
|
|
|
@@ -182,7 +182,7 @@ print(f"Complexity: {cost['complexity_category']} (score: {cost['complexity_scor
|
|
|
182
182
|
|
|
183
183
|
# Get optimization recommendations
|
|
184
184
|
for rec in cost['recommendations']:
|
|
185
|
-
print(f"
|
|
185
|
+
print(f"Tip: {rec}")
|
|
186
186
|
```
|
|
187
187
|
|
|
188
188
|
### Result Processing
|
|
@@ -271,7 +271,7 @@ client.connect("operation_id")
|
|
|
271
271
|
client.close()
|
|
272
272
|
```
|
|
273
273
|
|
|
274
|
-
##
|
|
274
|
+
## Examples
|
|
275
275
|
|
|
276
276
|
### Financial Data Analysis
|
|
277
277
|
|
|
@@ -332,7 +332,7 @@ if final_batch:
|
|
|
332
332
|
process_transaction_batch(final_batch)
|
|
333
333
|
total_processed += len(final_batch)
|
|
334
334
|
|
|
335
|
-
print(f"
|
|
335
|
+
print(f"Processed {total_processed:,} transactions total")
|
|
336
336
|
```
|
|
337
337
|
|
|
338
338
|
### Error Handling
|
|
@@ -361,7 +361,7 @@ except Exception as e:
|
|
|
361
361
|
print(f"Query failed: {e}")
|
|
362
362
|
```
|
|
363
363
|
|
|
364
|
-
##
|
|
364
|
+
## Performance Optimization
|
|
365
365
|
|
|
366
366
|
### Connection Pooling
|
|
367
367
|
|
|
@@ -391,21 +391,21 @@ query = "MATCH (c:Company) WHERE c.revenue > 1000000 RETURN c"
|
|
|
391
391
|
# Check syntax
|
|
392
392
|
validation = validate_cypher_query(query)
|
|
393
393
|
if not validation['valid']:
|
|
394
|
-
print("
|
|
394
|
+
print("Query has syntax errors:", validation['issues'])
|
|
395
395
|
|
|
396
396
|
# Estimate cost
|
|
397
397
|
cost = estimate_query_cost(query)
|
|
398
|
-
print(f"
|
|
398
|
+
print(f"Query complexity: {cost['complexity_category']}")
|
|
399
399
|
|
|
400
400
|
# Follow recommendations
|
|
401
401
|
for rec in cost['recommendations']:
|
|
402
|
-
print(f"
|
|
402
|
+
print(f"Optimization tip: {rec}")
|
|
403
403
|
|
|
404
404
|
# Execute only if reasonable complexity
|
|
405
405
|
if cost['complexity_category'] in ['low', 'medium']:
|
|
406
406
|
result = extensions.execute_query("graph_id", query)
|
|
407
407
|
else:
|
|
408
|
-
print("
|
|
408
|
+
print("Query may be too expensive - consider optimization")
|
|
409
409
|
```
|
|
410
410
|
|
|
411
411
|
### Caching Strategy
|
|
@@ -425,7 +425,7 @@ else:
|
|
|
425
425
|
cache = results_cache
|
|
426
426
|
```
|
|
427
427
|
|
|
428
|
-
##
|
|
428
|
+
## Testing
|
|
429
429
|
|
|
430
430
|
Run the test suite:
|
|
431
431
|
|
|
@@ -457,7 +457,7 @@ def test_query_execution():
|
|
|
457
457
|
assert result["data"] == [{"count": 100}]
|
|
458
458
|
```
|
|
459
459
|
|
|
460
|
-
##
|
|
460
|
+
## Configuration
|
|
461
461
|
|
|
462
462
|
### Environment Variables
|
|
463
463
|
|
|
@@ -492,7 +492,7 @@ config = RoboSystemsExtensionConfig(
|
|
|
492
492
|
extensions = RoboSystemsExtensions(config)
|
|
493
493
|
```
|
|
494
494
|
|
|
495
|
-
##
|
|
495
|
+
## API Reference
|
|
496
496
|
|
|
497
497
|
### Core Classes
|
|
498
498
|
|
|
@@ -528,7 +528,7 @@ extensions = RoboSystemsExtensions(config)
|
|
|
528
528
|
- **`format_duration(milliseconds)`** - Human-readable time formatting
|
|
529
529
|
- **`create_extensions(method, **kwargs)`** - Extensions factory
|
|
530
530
|
|
|
531
|
-
##
|
|
531
|
+
## Troubleshooting
|
|
532
532
|
|
|
533
533
|
### Common Issues
|
|
534
534
|
|
|
@@ -545,9 +545,9 @@ pip install pandas # For DataFrame conversion (optional)
|
|
|
545
545
|
extensions = AuthenticatedExtensions("your-api-key")
|
|
546
546
|
try:
|
|
547
547
|
result = extensions.execute_query("graph_id", "MATCH (n) RETURN count(n) LIMIT 1")
|
|
548
|
-
print("
|
|
548
|
+
print("Authentication successful")
|
|
549
549
|
except Exception as e:
|
|
550
|
-
print(f"
|
|
550
|
+
print(f"Auth failed: {e}")
|
|
551
551
|
```
|
|
552
552
|
|
|
553
553
|
**Connection Issues**
|
|
@@ -585,11 +585,7 @@ logging.basicConfig(level=logging.DEBUG)
|
|
|
585
585
|
extensions = AuthenticatedExtensions("your-key")
|
|
586
586
|
```
|
|
587
587
|
|
|
588
|
-
##
|
|
589
|
-
|
|
590
|
-
MIT License - see [LICENSE](LICENSE) file for details.
|
|
591
|
-
|
|
592
|
-
## 🤝 Contributing
|
|
588
|
+
## Contributing
|
|
593
589
|
|
|
594
590
|
1. Fork the repository
|
|
595
591
|
2. Create a feature branch
|
|
@@ -597,7 +593,7 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
597
593
|
4. Run the test suite: `python run_tests.py`
|
|
598
594
|
5. Submit a pull request
|
|
599
595
|
|
|
600
|
-
##
|
|
596
|
+
## Support
|
|
601
597
|
|
|
602
598
|
- **API Reference**: [api.robosystems.ai](https://api.robosystems.ai)
|
|
603
599
|
- **Issues**: [GitHub Issues](https://github.com/RoboFinSystems/robosystems-python-client/issues)
|
|
@@ -177,7 +177,7 @@ class AgentClient:
|
|
|
177
177
|
else datetime.now().isoformat(),
|
|
178
178
|
)
|
|
179
179
|
|
|
180
|
-
# Check if this is a queued response (async
|
|
180
|
+
# Check if this is a queued response (async background task execution)
|
|
181
181
|
is_queued = False
|
|
182
182
|
queued_response = None
|
|
183
183
|
|
|
@@ -15,6 +15,7 @@ from ..api.materialize.get_materialization_status import (
|
|
|
15
15
|
sync_detailed as get_materialization_status,
|
|
16
16
|
)
|
|
17
17
|
from ..models.materialize_request import MaterializeRequest
|
|
18
|
+
from .operation_client import OperationClient, OperationProgress, MonitorOptions
|
|
18
19
|
|
|
19
20
|
logger = logging.getLogger(__name__)
|
|
20
21
|
|
|
@@ -27,6 +28,7 @@ class MaterializationOptions:
|
|
|
27
28
|
rebuild: bool = False
|
|
28
29
|
force: bool = False
|
|
29
30
|
on_progress: Optional[Callable[[str], None]] = None
|
|
31
|
+
timeout: Optional[int] = 600 # 10 minute default timeout
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
@dataclass
|
|
@@ -66,6 +68,14 @@ class MaterializationClient:
|
|
|
66
68
|
self.base_url = config["base_url"]
|
|
67
69
|
self.headers = config.get("headers", {})
|
|
68
70
|
self.token = config.get("token")
|
|
71
|
+
self._operation_client = None
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def operation_client(self) -> OperationClient:
|
|
75
|
+
"""Get or create the operation client for SSE monitoring."""
|
|
76
|
+
if self._operation_client is None:
|
|
77
|
+
self._operation_client = OperationClient(self.config)
|
|
78
|
+
return self._operation_client
|
|
69
79
|
|
|
70
80
|
def materialize(
|
|
71
81
|
self,
|
|
@@ -75,13 +85,13 @@ class MaterializationClient:
|
|
|
75
85
|
"""
|
|
76
86
|
Materialize graph from DuckDB staging tables.
|
|
77
87
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
Submits a materialization job to Dagster and monitors progress via SSE.
|
|
89
|
+
The operation runs asynchronously on the server but this method waits
|
|
90
|
+
for completion and returns the final result.
|
|
81
91
|
|
|
82
92
|
Args:
|
|
83
93
|
graph_id: Graph database identifier
|
|
84
|
-
options: Materialization options (ignore_errors, rebuild, force)
|
|
94
|
+
options: Materialization options (ignore_errors, rebuild, force, timeout)
|
|
85
95
|
|
|
86
96
|
Returns:
|
|
87
97
|
MaterializationResult with detailed execution information
|
|
@@ -96,7 +106,7 @@ class MaterializationClient:
|
|
|
96
106
|
|
|
97
107
|
try:
|
|
98
108
|
if options.on_progress:
|
|
99
|
-
options.on_progress("
|
|
109
|
+
options.on_progress("Submitting materialization job...")
|
|
100
110
|
|
|
101
111
|
request = MaterializeRequest(
|
|
102
112
|
ignore_errors=options.ignore_errors,
|
|
@@ -125,6 +135,7 @@ class MaterializationClient:
|
|
|
125
135
|
|
|
126
136
|
response = materialize_graph(**kwargs)
|
|
127
137
|
|
|
138
|
+
# Handle non-200 status codes
|
|
128
139
|
if response.status_code != 200 or not response.parsed:
|
|
129
140
|
error_msg = f"Materialization failed: {response.status_code}"
|
|
130
141
|
if hasattr(response, "content"):
|
|
@@ -148,25 +159,68 @@ class MaterializationClient:
|
|
|
148
159
|
error=error_msg,
|
|
149
160
|
)
|
|
150
161
|
|
|
162
|
+
# Get the operation_id from the queued response
|
|
151
163
|
result_data = response.parsed
|
|
164
|
+
operation_id = result_data.operation_id
|
|
152
165
|
|
|
153
166
|
if options.on_progress:
|
|
154
|
-
options.on_progress(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
success=True,
|
|
167
|
+
options.on_progress(f"Materialization queued (operation: {operation_id})")
|
|
168
|
+
|
|
169
|
+
# Monitor the operation via SSE until completion
|
|
170
|
+
def on_sse_progress(progress: OperationProgress):
|
|
171
|
+
if options.on_progress:
|
|
172
|
+
msg = progress.message
|
|
173
|
+
if progress.percentage is not None:
|
|
174
|
+
msg += f" ({progress.percentage:.0f}%)"
|
|
175
|
+
options.on_progress(msg)
|
|
176
|
+
|
|
177
|
+
monitor_options = MonitorOptions(
|
|
178
|
+
on_progress=on_sse_progress,
|
|
179
|
+
timeout=options.timeout,
|
|
168
180
|
)
|
|
169
181
|
|
|
182
|
+
op_result = self.operation_client.monitor_operation(operation_id, monitor_options)
|
|
183
|
+
|
|
184
|
+
# Convert operation result to materialization result
|
|
185
|
+
if op_result.status.value == "completed":
|
|
186
|
+
# Extract details from SSE completion event result
|
|
187
|
+
sse_result = op_result.result or {}
|
|
188
|
+
|
|
189
|
+
if options.on_progress:
|
|
190
|
+
tables = sse_result.get("tables_materialized", [])
|
|
191
|
+
rows = sse_result.get("total_rows", 0)
|
|
192
|
+
time_ms = sse_result.get("execution_time_ms", 0)
|
|
193
|
+
options.on_progress(
|
|
194
|
+
f"✅ Materialization complete: {len(tables)} tables, "
|
|
195
|
+
f"{rows:,} rows in {time_ms:.2f}ms"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
return MaterializationResult(
|
|
199
|
+
status="success",
|
|
200
|
+
was_stale=sse_result.get("was_stale", False),
|
|
201
|
+
stale_reason=sse_result.get("stale_reason"),
|
|
202
|
+
tables_materialized=sse_result.get("tables_materialized", []),
|
|
203
|
+
total_rows=sse_result.get("total_rows", 0),
|
|
204
|
+
execution_time_ms=sse_result.get(
|
|
205
|
+
"execution_time_ms", op_result.execution_time_ms or 0
|
|
206
|
+
),
|
|
207
|
+
message=sse_result.get("message", "Graph materialized successfully"),
|
|
208
|
+
success=True,
|
|
209
|
+
)
|
|
210
|
+
else:
|
|
211
|
+
# Operation failed or was cancelled
|
|
212
|
+
return MaterializationResult(
|
|
213
|
+
status=op_result.status.value,
|
|
214
|
+
was_stale=False,
|
|
215
|
+
stale_reason=None,
|
|
216
|
+
tables_materialized=[],
|
|
217
|
+
total_rows=0,
|
|
218
|
+
execution_time_ms=op_result.execution_time_ms or 0,
|
|
219
|
+
message=op_result.error or f"Operation {op_result.status.value}",
|
|
220
|
+
success=False,
|
|
221
|
+
error=op_result.error,
|
|
222
|
+
)
|
|
223
|
+
|
|
170
224
|
except Exception as e:
|
|
171
225
|
logger.error(f"Materialization failed: {e}")
|
|
172
226
|
return MaterializationResult(
|
|
@@ -104,8 +104,10 @@ class SSEClient:
|
|
|
104
104
|
|
|
105
105
|
try:
|
|
106
106
|
self.client = httpx.Client(timeout=self.config.timeout)
|
|
107
|
-
self.
|
|
108
|
-
|
|
107
|
+
self._context_manager = self.client.stream(
|
|
108
|
+
"GET", url, params=params, headers=headers
|
|
109
|
+
)
|
|
110
|
+
self._response = self._context_manager.__enter__()
|
|
109
111
|
|
|
110
112
|
self.reconnect_attempts = 0
|
|
111
113
|
self.emit("connected", None)
|
|
@@ -124,11 +126,9 @@ class SSEClient:
|
|
|
124
126
|
|
|
125
127
|
try:
|
|
126
128
|
event_buffer = {"event": None, "data": [], "id": None, "retry": None}
|
|
127
|
-
print("[SSE DEBUG] Starting to process events...")
|
|
128
129
|
|
|
129
130
|
for line in self._response.iter_lines():
|
|
130
131
|
if self.closed:
|
|
131
|
-
print("[SSE DEBUG] Stream closed, breaking out of loop")
|
|
132
132
|
break
|
|
133
133
|
|
|
134
134
|
line = line.strip()
|
|
@@ -136,7 +136,6 @@ class SSEClient:
|
|
|
136
136
|
# Empty line indicates end of event
|
|
137
137
|
if not line:
|
|
138
138
|
if event_buffer["data"] or event_buffer["event"]:
|
|
139
|
-
print(f"[SSE DEBUG] Dispatching event: {event_buffer.get('event')}")
|
|
140
139
|
self._dispatch_event(event_buffer)
|
|
141
140
|
event_buffer = {"event": None, "data": [], "id": None, "retry": None}
|
|
142
141
|
continue
|
|
@@ -172,13 +171,9 @@ class SSEClient:
|
|
|
172
171
|
|
|
173
172
|
# Handle final event if stream ends without empty line
|
|
174
173
|
if event_buffer["data"] or event_buffer["event"]:
|
|
175
|
-
print("[SSE DEBUG] Dispatching final event after stream end")
|
|
176
174
|
self._dispatch_event(event_buffer)
|
|
177
175
|
|
|
178
|
-
print("[SSE DEBUG] Event processing loop ended")
|
|
179
|
-
|
|
180
176
|
except Exception as error:
|
|
181
|
-
print(f"[SSE DEBUG] Exception in event processing: {error}")
|
|
182
177
|
if not self.closed:
|
|
183
178
|
self.emit("error", error)
|
|
184
179
|
|
|
@@ -214,13 +209,14 @@ class SSEClient:
|
|
|
214
209
|
# Emit typed event
|
|
215
210
|
self.emit(event_type, parsed_data)
|
|
216
211
|
|
|
217
|
-
# Check for completion events
|
|
212
|
+
# Check for completion events - just set flag, don't close from within loop
|
|
213
|
+
# The loop will break on next iteration and close() will be called in finally
|
|
218
214
|
if event_type in [
|
|
219
215
|
EventType.OPERATION_COMPLETED.value,
|
|
220
216
|
EventType.OPERATION_ERROR.value,
|
|
221
217
|
EventType.OPERATION_CANCELLED.value,
|
|
222
218
|
]:
|
|
223
|
-
self.
|
|
219
|
+
self.closed = True
|
|
224
220
|
|
|
225
221
|
def _handle_error(
|
|
226
222
|
self, error: Exception, operation_id: str, from_sequence: int
|
|
@@ -285,12 +281,13 @@ class SSEClient:
|
|
|
285
281
|
"""Close the SSE connection"""
|
|
286
282
|
self.closed = True
|
|
287
283
|
|
|
288
|
-
if self.
|
|
284
|
+
if hasattr(self, "_context_manager") and self._context_manager:
|
|
289
285
|
try:
|
|
290
|
-
self.
|
|
286
|
+
self._context_manager.__exit__(None, None, None)
|
|
291
287
|
except Exception:
|
|
292
288
|
pass
|
|
293
|
-
self.
|
|
289
|
+
self._context_manager = None
|
|
290
|
+
self._response = None
|
|
294
291
|
|
|
295
292
|
if self.client:
|
|
296
293
|
self.client.close()
|
|
@@ -334,10 +331,10 @@ class AsyncSSEClient:
|
|
|
334
331
|
|
|
335
332
|
try:
|
|
336
333
|
self.client = httpx.AsyncClient(timeout=self.config.timeout)
|
|
337
|
-
self.
|
|
334
|
+
self._context_manager = self.client.stream(
|
|
338
335
|
"GET", url, params=params, headers=headers
|
|
339
336
|
)
|
|
340
|
-
await self.
|
|
337
|
+
self._response = await self._context_manager.__aenter__()
|
|
341
338
|
|
|
342
339
|
self.reconnect_attempts = 0
|
|
343
340
|
self.emit("connected", None)
|
|
@@ -401,13 +398,9 @@ class AsyncSSEClient:
|
|
|
401
398
|
|
|
402
399
|
# Handle final event if stream ends without empty line
|
|
403
400
|
if event_buffer["data"] or event_buffer["event"]:
|
|
404
|
-
print("[SSE DEBUG] Dispatching final event after stream end")
|
|
405
401
|
self._dispatch_event(event_buffer)
|
|
406
402
|
|
|
407
|
-
print("[SSE DEBUG] Event processing loop ended")
|
|
408
|
-
|
|
409
403
|
except Exception as error:
|
|
410
|
-
print(f"[SSE DEBUG] Exception in event processing: {error}")
|
|
411
404
|
if not self.closed:
|
|
412
405
|
self.emit("error", error)
|
|
413
406
|
|
|
@@ -443,13 +436,14 @@ class AsyncSSEClient:
|
|
|
443
436
|
# Emit typed event
|
|
444
437
|
self.emit(event_type, parsed_data)
|
|
445
438
|
|
|
446
|
-
# Check for completion events
|
|
439
|
+
# Check for completion events - just set flag, don't close from within loop
|
|
440
|
+
# The loop will break on next iteration and close() will be called in finally
|
|
447
441
|
if event_type in [
|
|
448
442
|
EventType.OPERATION_COMPLETED.value,
|
|
449
443
|
EventType.OPERATION_ERROR.value,
|
|
450
444
|
EventType.OPERATION_CANCELLED.value,
|
|
451
445
|
]:
|
|
452
|
-
|
|
446
|
+
self.closed = True
|
|
453
447
|
|
|
454
448
|
async def _handle_error(
|
|
455
449
|
self, error: Exception, operation_id: str, from_sequence: int
|
|
@@ -512,12 +506,13 @@ class AsyncSSEClient:
|
|
|
512
506
|
"""Close the SSE connection (async)"""
|
|
513
507
|
self.closed = True
|
|
514
508
|
|
|
515
|
-
if self.
|
|
509
|
+
if hasattr(self, "_context_manager") and self._context_manager:
|
|
516
510
|
try:
|
|
517
|
-
await self.
|
|
511
|
+
await self._context_manager.__aexit__(None, None, None)
|
|
518
512
|
except Exception:
|
|
519
513
|
pass
|
|
520
|
-
self.
|
|
514
|
+
self._context_manager = None
|
|
515
|
+
self._response = None
|
|
521
516
|
|
|
522
517
|
if self.client:
|
|
523
518
|
await self.client.aclose()
|
|
@@ -197,7 +197,9 @@ class SubgraphWorkspaceClient:
|
|
|
197
197
|
sse_url = f"{self.api._base_url}/v1/operations/{operation_id}/stream"
|
|
198
198
|
headers = {"X-API-Key": self.api.token}
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
# Use longer timeout for SSE streaming (Dagster jobs can take time)
|
|
201
|
+
timeout = httpx.Timeout(connect=30.0, read=120.0, write=30.0, pool=30.0)
|
|
202
|
+
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
201
203
|
async with client.stream("GET", sse_url, headers=headers) as sse_response:
|
|
202
204
|
async for line in sse_response.aiter_lines():
|
|
203
205
|
if line.startswith("data: "):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Mapping
|
|
4
|
-
from typing import Any, TypeVar
|
|
4
|
+
from typing import Any, TypeVar
|
|
5
5
|
|
|
6
6
|
from attrs import define as _attrs_define
|
|
7
7
|
from attrs import field as _attrs_field
|
|
@@ -13,102 +13,64 @@ T = TypeVar("T", bound="MaterializeResponse")
|
|
|
13
13
|
|
|
14
14
|
@_attrs_define
|
|
15
15
|
class MaterializeResponse:
|
|
16
|
-
"""
|
|
16
|
+
"""Response for queued materialization operation.
|
|
17
|
+
|
|
18
|
+
Example:
|
|
19
|
+
{'graph_id': 'kg_abc123', 'message': 'Materialization queued. Monitor via SSE stream.', 'operation_id':
|
|
20
|
+
'550e8400-e29b-41d4-a716-446655440000', 'status': 'queued'}
|
|
21
|
+
|
|
17
22
|
Attributes:
|
|
18
|
-
status (str): Materialization status
|
|
19
23
|
graph_id (str): Graph database identifier
|
|
20
|
-
|
|
21
|
-
tables_materialized (list[str]): List of tables successfully materialized
|
|
22
|
-
total_rows (int): Total rows materialized across all tables
|
|
23
|
-
execution_time_ms (float): Total materialization time
|
|
24
|
+
operation_id (str): SSE operation ID for progress tracking
|
|
24
25
|
message (str): Human-readable status message
|
|
25
|
-
|
|
26
|
+
status (str | Unset): Operation status Default: 'queued'.
|
|
26
27
|
"""
|
|
27
28
|
|
|
28
|
-
status: str
|
|
29
29
|
graph_id: str
|
|
30
|
-
|
|
31
|
-
tables_materialized: list[str]
|
|
32
|
-
total_rows: int
|
|
33
|
-
execution_time_ms: float
|
|
30
|
+
operation_id: str
|
|
34
31
|
message: str
|
|
35
|
-
|
|
32
|
+
status: str | Unset = "queued"
|
|
36
33
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
37
34
|
|
|
38
35
|
def to_dict(self) -> dict[str, Any]:
|
|
39
|
-
status = self.status
|
|
40
|
-
|
|
41
36
|
graph_id = self.graph_id
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
tables_materialized = self.tables_materialized
|
|
46
|
-
|
|
47
|
-
total_rows = self.total_rows
|
|
48
|
-
|
|
49
|
-
execution_time_ms = self.execution_time_ms
|
|
38
|
+
operation_id = self.operation_id
|
|
50
39
|
|
|
51
40
|
message = self.message
|
|
52
41
|
|
|
53
|
-
|
|
54
|
-
if isinstance(self.stale_reason, Unset):
|
|
55
|
-
stale_reason = UNSET
|
|
56
|
-
else:
|
|
57
|
-
stale_reason = self.stale_reason
|
|
42
|
+
status = self.status
|
|
58
43
|
|
|
59
44
|
field_dict: dict[str, Any] = {}
|
|
60
45
|
field_dict.update(self.additional_properties)
|
|
61
46
|
field_dict.update(
|
|
62
47
|
{
|
|
63
|
-
"status": status,
|
|
64
48
|
"graph_id": graph_id,
|
|
65
|
-
"
|
|
66
|
-
"tables_materialized": tables_materialized,
|
|
67
|
-
"total_rows": total_rows,
|
|
68
|
-
"execution_time_ms": execution_time_ms,
|
|
49
|
+
"operation_id": operation_id,
|
|
69
50
|
"message": message,
|
|
70
51
|
}
|
|
71
52
|
)
|
|
72
|
-
if
|
|
73
|
-
field_dict["
|
|
53
|
+
if status is not UNSET:
|
|
54
|
+
field_dict["status"] = status
|
|
74
55
|
|
|
75
56
|
return field_dict
|
|
76
57
|
|
|
77
58
|
@classmethod
|
|
78
59
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
|
79
60
|
d = dict(src_dict)
|
|
80
|
-
status = d.pop("status")
|
|
81
|
-
|
|
82
61
|
graph_id = d.pop("graph_id")
|
|
83
62
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
tables_materialized = cast(list[str], d.pop("tables_materialized"))
|
|
87
|
-
|
|
88
|
-
total_rows = d.pop("total_rows")
|
|
89
|
-
|
|
90
|
-
execution_time_ms = d.pop("execution_time_ms")
|
|
63
|
+
operation_id = d.pop("operation_id")
|
|
91
64
|
|
|
92
65
|
message = d.pop("message")
|
|
93
66
|
|
|
94
|
-
|
|
95
|
-
if data is None:
|
|
96
|
-
return data
|
|
97
|
-
if isinstance(data, Unset):
|
|
98
|
-
return data
|
|
99
|
-
return cast(None | str | Unset, data)
|
|
100
|
-
|
|
101
|
-
stale_reason = _parse_stale_reason(d.pop("stale_reason", UNSET))
|
|
67
|
+
status = d.pop("status", UNSET)
|
|
102
68
|
|
|
103
69
|
materialize_response = cls(
|
|
104
|
-
status=status,
|
|
105
70
|
graph_id=graph_id,
|
|
106
|
-
|
|
107
|
-
tables_materialized=tables_materialized,
|
|
108
|
-
total_rows=total_rows,
|
|
109
|
-
execution_time_ms=execution_time_ms,
|
|
71
|
+
operation_id=operation_id,
|
|
110
72
|
message=message,
|
|
111
|
-
|
|
73
|
+
status=status,
|
|
112
74
|
)
|
|
113
75
|
|
|
114
76
|
materialize_response.additional_properties = d
|
|
@@ -6,9 +6,9 @@ robosystems_client/sdk-config.yaml,sha256=Y_A8qSC2zHLYy6d443Rlgdkw2GleOSFjYvq_Qm
|
|
|
6
6
|
robosystems_client/types.py,sha256=p37iLHBX-cpNh7jZmbG4Xl1UI6hexvRS-HkY_QBlZWE,1301
|
|
7
7
|
robosystems_client/api/__init__.py,sha256=zTSiG_ujSjAqWPyc435YXaX9XTlpMjiJWBbV-f-YtdA,45
|
|
8
8
|
robosystems_client/api/agent/__init__.py,sha256=5vd9uJWAjRqa9xzxzYkLD1yoZ12Ld_bAaNB5WX4fbE8,56
|
|
9
|
-
robosystems_client/api/agent/auto_select_agent.py,sha256=
|
|
9
|
+
robosystems_client/api/agent/auto_select_agent.py,sha256=uHoIjrKoA6e7GyQu1DMAIPGZ1JAx-xA8sYodyLTFUfU,16396
|
|
10
10
|
robosystems_client/api/agent/batch_process_queries.py,sha256=d0htQmRZoTig8XnSQHINhPDf10w_kgPfTSBNGDUrg1c,6697
|
|
11
|
-
robosystems_client/api/agent/execute_specific_agent.py,sha256=
|
|
11
|
+
robosystems_client/api/agent/execute_specific_agent.py,sha256=pZWf6dsWtMjJ2qkV0tOLEhWB8s9HlsEXUtu2DZKv_RE,9428
|
|
12
12
|
robosystems_client/api/agent/get_agent_metadata.py,sha256=MS4MCA5fQE2F654iVTTnWGxckDx3YlPvYScMtsyKkAk,5730
|
|
13
13
|
robosystems_client/api/agent/list_agents.py,sha256=BZ0H9ZfpwyXY57TPvkMVSsm72d6JcOY9qDUU4LxTvyE,6183
|
|
14
14
|
robosystems_client/api/agent/recommend_agent.py,sha256=fy4cm-DA_FHiDsX9gtrHfITOmHJFGNekOkxAhPWz6I8,6601
|
|
@@ -67,7 +67,7 @@ robosystems_client/api/files/create_file_upload.py,sha256=OVMbGuJ004lqYrHmUFzr2g
|
|
|
67
67
|
robosystems_client/api/files/delete_file.py,sha256=wAt0wHIYgX1D57HwQvUUb1GMr2GQzXNq3wz0MuCSI3I,9815
|
|
68
68
|
robosystems_client/api/files/get_file.py,sha256=cZv2YurkjF8bhBzC_pprblPUguunV0ciOjuB4ygQS1Q,10558
|
|
69
69
|
robosystems_client/api/files/list_files.py,sha256=3JnJ4gwZS7k4KdjnHp7fv0jq8JuTGzV0xRhZcMEfBcA,10541
|
|
70
|
-
robosystems_client/api/files/update_file.py,sha256=
|
|
70
|
+
robosystems_client/api/files/update_file.py,sha256=X80_TbYmQvL7mLdQejCy-UzUiyVx96SpCE2EHNJJSr8,9043
|
|
71
71
|
robosystems_client/api/graph_health/__init__.py,sha256=5vd9uJWAjRqa9xzxzYkLD1yoZ12Ld_bAaNB5WX4fbE8,56
|
|
72
72
|
robosystems_client/api/graph_health/get_database_health.py,sha256=rGMjLUjD5DfIGv1Qu8BARWRafZhTqyrsb65YCIFe1k8,8738
|
|
73
73
|
robosystems_client/api/graph_info/__init__.py,sha256=5vd9uJWAjRqa9xzxzYkLD1yoZ12Ld_bAaNB5WX4fbE8,56
|
|
@@ -141,20 +141,20 @@ robosystems_client/api/user/update_user_password.py,sha256=0couyI63dlspqvtO62cI4
|
|
|
141
141
|
robosystems_client/api/views/__init__.py,sha256=5vd9uJWAjRqa9xzxzYkLD1yoZ12Ld_bAaNB5WX4fbE8,56
|
|
142
142
|
robosystems_client/api/views/create_view.py,sha256=aBjk0FsAj6A3K2iqjE7DcTLKjDRsxegFbs5RC0hmbqA,6316
|
|
143
143
|
robosystems_client/api/views/save_view.py,sha256=T7PUUNvp0JIeOYWG2n8Nm4Y9hhEWV7i-Gl2Vl8Oz4Ls,9224
|
|
144
|
-
robosystems_client/extensions/README.md,sha256=
|
|
144
|
+
robosystems_client/extensions/README.md,sha256=NUkPte5GM8rJkT-tVrVXbujbKVxP-sw-QocYaLZfAL0,15971
|
|
145
145
|
robosystems_client/extensions/__init__.py,sha256=eTuJQGygQTOWC51YVhJOWUWFUMLcPo7MpZ0H3GaxoR0,7076
|
|
146
|
-
robosystems_client/extensions/agent_client.py,sha256=
|
|
146
|
+
robosystems_client/extensions/agent_client.py,sha256=kvC5UO5JjTpsVB_1ARFN2H7MSDZYjw016zJrnNTMZUM,17759
|
|
147
147
|
robosystems_client/extensions/auth_integration.py,sha256=ABOJ8aVjfHehNGNzim1iR9-Cdh7Mr22ce-WgWWeqJt0,6535
|
|
148
148
|
robosystems_client/extensions/dataframe_utils.py,sha256=gK1bgkVqBF0TvWVdGQvqWrt-ur_Rw11j8uNtMoulLWE,12312
|
|
149
149
|
robosystems_client/extensions/element_mapping_client.py,sha256=yuh0QPQBPM33E7r6QWWDiKm3T4TfCdbn2kvO3Jlw4Cs,18516
|
|
150
150
|
robosystems_client/extensions/extensions.py,sha256=IqTuVJNt6vvO5LhnH-9SexCF5k9Abqig1sBdY4SdD1I,6858
|
|
151
151
|
robosystems_client/extensions/file_client.py,sha256=bknqmbYrIqxc2PnWskeDvcWYP2bemBQaxY_4A6zr5LY,12326
|
|
152
152
|
robosystems_client/extensions/graph_client.py,sha256=OBi0xj0SLIRKLeSu_DiGt2ZakCmhggvNrMP3jdRfEgQ,10326
|
|
153
|
-
robosystems_client/extensions/materialization_client.py,sha256=
|
|
153
|
+
robosystems_client/extensions/materialization_client.py,sha256=3PzzEYn5NqENbb4PggGb1Z31SWmcW9D2mJ83uem22_c,8923
|
|
154
154
|
robosystems_client/extensions/operation_client.py,sha256=B1qju-wWQrnrnVJixKGgsA_KEInviwJwdlJxzm_i7P0,13359
|
|
155
155
|
robosystems_client/extensions/query_client.py,sha256=cX3e8EBoTeg4Lwm6edJYRULM2UmGpfqNX3f48S8TQbE,19430
|
|
156
|
-
robosystems_client/extensions/sse_client.py,sha256=
|
|
157
|
-
robosystems_client/extensions/subgraph_workspace_client.py,sha256=
|
|
156
|
+
robosystems_client/extensions/sse_client.py,sha256=rqYs3mfnLhE3kwdhEWcbeiAFfT_6AFZRY5rpXpV5KHg,14956
|
|
157
|
+
robosystems_client/extensions/subgraph_workspace_client.py,sha256=ISMo7xkOB1Py35njIV_qjw94RJJKEZk8Ag4RGkII3-o,24208
|
|
158
158
|
robosystems_client/extensions/table_client.py,sha256=hegYiPlR-HMacyinDDACZaMeNn0bamkYtbkzDkVawVI,4521
|
|
159
159
|
robosystems_client/extensions/token_utils.py,sha256=qCK_s1vBzRnSYwtgncPZRLJVIw3WXmzqNTWjdEEpdgs,10899
|
|
160
160
|
robosystems_client/extensions/utils.py,sha256=vhmUnEsq-UEAMgNhmkqlbJg4oJj096QPiHALEHJ-y4A,16207
|
|
@@ -300,7 +300,7 @@ robosystems_client/models/list_table_files_response.py,sha256=D4SuwlQy11oOhEq9k2
|
|
|
300
300
|
robosystems_client/models/login_request.py,sha256=8LbzTIfoUWAKzkEYs7E6AXr4TEPAeejoX8kIMBRm7XQ,1579
|
|
301
301
|
robosystems_client/models/logout_user_response_logoutuser.py,sha256=dpvqyIqCDQdXVyx-gDyl3qex2KuyjJUE0OuldQcL4k0,1257
|
|
302
302
|
robosystems_client/models/materialize_request.py,sha256=t3TXLcPJabN_lSx6BwEeku2v2zGceHFRVSS6TpK59Iw,1483
|
|
303
|
-
robosystems_client/models/materialize_response.py,sha256=
|
|
303
|
+
robosystems_client/models/materialize_response.py,sha256=Luuf3FjUj1cZQ0QmcONR5ulMbKKvjhL5SkkMx7lBp-Q,2386
|
|
304
304
|
robosystems_client/models/materialize_status_response.py,sha256=eXNmPgudtCneoVb2rbSW9DGGTG10DFoGN-oaiHuYjLo,5402
|
|
305
305
|
robosystems_client/models/mcp_tool_call.py,sha256=dz--5OucQNcbFT7GaUR1uw-ZgSxHI0-zVYmz0J9fixE,2215
|
|
306
306
|
robosystems_client/models/mcp_tool_call_arguments.py,sha256=FX9fVbI_8tzM1XF4oKaH7Tf2WFChsSuIHn03yZY0Q5w,1245
|
|
@@ -414,7 +414,7 @@ robosystems_client/models/view_axis_config_member_labels_type_0.py,sha256=kkzpHx
|
|
|
414
414
|
robosystems_client/models/view_config.py,sha256=HQnqYjLMXRhjZLOc5ypwILriMFKuvPzu0hPQi2vyNoM,3795
|
|
415
415
|
robosystems_client/models/view_source.py,sha256=h66cASj-P_-qOptKv26uAIe9PtIewU2nTs42Ls-lFFk,4098
|
|
416
416
|
robosystems_client/models/view_source_type.py,sha256=KpgczHUeOinV01jdLvytZ2URKwcsRcp1doPx2D3USyw,169
|
|
417
|
-
robosystems_client-0.2.
|
|
418
|
-
robosystems_client-0.2.
|
|
419
|
-
robosystems_client-0.2.
|
|
420
|
-
robosystems_client-0.2.
|
|
417
|
+
robosystems_client-0.2.23.dist-info/METADATA,sha256=z_qtJfV6oiE6r4anQJcG7tf6OfZ7mXoevVJv7rc2UwE,3950
|
|
418
|
+
robosystems_client-0.2.23.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
419
|
+
robosystems_client-0.2.23.dist-info/licenses/LICENSE,sha256=LjFqQPU4eQh7jAQ04SmE9eC0j74HCdXvzbo0hjW4mWo,1063
|
|
420
|
+
robosystems_client-0.2.23.dist-info/RECORD,,
|
|
File without changes
|
{robosystems_client-0.2.22.dist-info → robosystems_client-0.2.23.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|