robosystems-client 0.2.16__py3-none-any.whl → 0.2.18__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.

@@ -0,0 +1,237 @@
1
+ """Materialization Client for RoboSystems API
2
+
3
+ Manages graph materialization from DuckDB staging tables.
4
+ Treats the graph database as a materialized view of the mutable DuckDB data lake.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Dict, Any, Optional, Callable
9
+ import logging
10
+
11
+ from ..api.materialization.materialize_graph import (
12
+ sync_detailed as materialize_graph,
13
+ )
14
+ from ..api.materialization.get_materialization_status import (
15
+ sync_detailed as get_materialization_status,
16
+ )
17
+ from ..models.materialize_request import MaterializeRequest
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ @dataclass
23
+ class MaterializationOptions:
24
+ """Options for graph materialization operations"""
25
+
26
+ ignore_errors: bool = True
27
+ rebuild: bool = False
28
+ force: bool = False
29
+ on_progress: Optional[Callable[[str], None]] = None
30
+
31
+
32
+ @dataclass
33
+ class MaterializationResult:
34
+ """Result from materialization operation"""
35
+
36
+ status: str
37
+ was_stale: bool
38
+ stale_reason: Optional[str]
39
+ tables_materialized: list[str]
40
+ total_rows: int
41
+ execution_time_ms: float
42
+ message: str
43
+ success: bool = True
44
+ error: Optional[str] = None
45
+
46
+
47
+ @dataclass
48
+ class MaterializationStatus:
49
+ """Status information about graph materialization"""
50
+
51
+ graph_id: str
52
+ is_stale: bool
53
+ stale_reason: Optional[str]
54
+ stale_since: Optional[str]
55
+ last_materialized_at: Optional[str]
56
+ materialization_count: int
57
+ hours_since_materialization: Optional[float]
58
+ message: str
59
+
60
+
61
+ class MaterializationClient:
62
+ """Client for managing graph materialization operations"""
63
+
64
+ def __init__(self, config: Dict[str, Any]):
65
+ self.config = config
66
+ self.base_url = config["base_url"]
67
+ self.headers = config.get("headers", {})
68
+ self.token = config.get("token")
69
+
70
+ def materialize(
71
+ self,
72
+ graph_id: str,
73
+ options: Optional[MaterializationOptions] = None,
74
+ ) -> MaterializationResult:
75
+ """
76
+ Materialize graph from DuckDB staging tables.
77
+
78
+ Rebuilds the complete graph database from the current state of DuckDB
79
+ staging tables. Automatically discovers all tables, materializes them in
80
+ the correct order (nodes before relationships), and clears the staleness flag.
81
+
82
+ Args:
83
+ graph_id: Graph database identifier
84
+ options: Materialization options (ignore_errors, rebuild, force)
85
+
86
+ Returns:
87
+ MaterializationResult with detailed execution information
88
+
89
+ When to use:
90
+ - After batch uploads (files uploaded with ingest_to_graph=false)
91
+ - After cascade file deletions (graph marked stale)
92
+ - Periodic full refresh to ensure consistency
93
+ - Recovery from partial materialization failures
94
+ """
95
+ options = options or MaterializationOptions()
96
+
97
+ try:
98
+ if options.on_progress:
99
+ options.on_progress("Starting graph materialization...")
100
+
101
+ request = MaterializeRequest(
102
+ ignore_errors=options.ignore_errors,
103
+ rebuild=options.rebuild,
104
+ force=options.force,
105
+ )
106
+
107
+ from ..client import AuthenticatedClient
108
+
109
+ if not self.token:
110
+ raise Exception("No API key provided. Set X-API-Key in headers.")
111
+
112
+ client = AuthenticatedClient(
113
+ base_url=self.base_url,
114
+ token=self.token,
115
+ prefix="",
116
+ auth_header_name="X-API-Key",
117
+ headers=self.headers,
118
+ )
119
+
120
+ kwargs = {
121
+ "graph_id": graph_id,
122
+ "client": client,
123
+ "body": request,
124
+ }
125
+
126
+ response = materialize_graph(**kwargs)
127
+
128
+ if response.status_code != 200 or not response.parsed:
129
+ error_msg = f"Materialization failed: {response.status_code}"
130
+ if hasattr(response, "content"):
131
+ try:
132
+ import json
133
+
134
+ error_data = json.loads(response.content)
135
+ error_msg = error_data.get("detail", error_msg)
136
+ except Exception:
137
+ pass
138
+
139
+ return MaterializationResult(
140
+ status="failed",
141
+ was_stale=False,
142
+ stale_reason=None,
143
+ tables_materialized=[],
144
+ total_rows=0,
145
+ execution_time_ms=0,
146
+ message=error_msg,
147
+ success=False,
148
+ error=error_msg,
149
+ )
150
+
151
+ result_data = response.parsed
152
+
153
+ if options.on_progress:
154
+ options.on_progress(
155
+ f"✅ Materialization complete: {len(result_data.tables_materialized)} tables, "
156
+ f"{result_data.total_rows:,} rows in {result_data.execution_time_ms:.2f}ms"
157
+ )
158
+
159
+ return MaterializationResult(
160
+ status=result_data.status,
161
+ was_stale=result_data.was_stale,
162
+ stale_reason=result_data.stale_reason,
163
+ tables_materialized=result_data.tables_materialized,
164
+ total_rows=result_data.total_rows,
165
+ execution_time_ms=result_data.execution_time_ms,
166
+ message=result_data.message,
167
+ success=True,
168
+ )
169
+
170
+ except Exception as e:
171
+ logger.error(f"Materialization failed: {e}")
172
+ return MaterializationResult(
173
+ status="failed",
174
+ was_stale=False,
175
+ stale_reason=None,
176
+ tables_materialized=[],
177
+ total_rows=0,
178
+ execution_time_ms=0,
179
+ message=str(e),
180
+ success=False,
181
+ error=str(e),
182
+ )
183
+
184
+ def status(self, graph_id: str) -> Optional[MaterializationStatus]:
185
+ """
186
+ Get current materialization status for the graph.
187
+
188
+ Shows whether the graph is stale (DuckDB has changes not yet in graph database),
189
+ when it was last materialized, and how long since last materialization.
190
+
191
+ Args:
192
+ graph_id: Graph database identifier
193
+
194
+ Returns:
195
+ MaterializationStatus with staleness and timing information
196
+ """
197
+ try:
198
+ from ..client import AuthenticatedClient
199
+
200
+ if not self.token:
201
+ raise Exception("No API key provided. Set X-API-Key in headers.")
202
+
203
+ client = AuthenticatedClient(
204
+ base_url=self.base_url,
205
+ token=self.token,
206
+ prefix="",
207
+ auth_header_name="X-API-Key",
208
+ headers=self.headers,
209
+ )
210
+
211
+ kwargs = {
212
+ "graph_id": graph_id,
213
+ "client": client,
214
+ }
215
+
216
+ response = get_materialization_status(**kwargs)
217
+
218
+ if response.status_code != 200 or not response.parsed:
219
+ logger.error(f"Failed to get materialization status: {response.status_code}")
220
+ return None
221
+
222
+ status_data = response.parsed
223
+
224
+ return MaterializationStatus(
225
+ graph_id=status_data.graph_id,
226
+ is_stale=status_data.is_stale,
227
+ stale_reason=status_data.stale_reason,
228
+ stale_since=status_data.stale_since,
229
+ last_materialized_at=status_data.last_materialized_at,
230
+ materialization_count=status_data.materialization_count,
231
+ hours_since_materialization=status_data.hours_since_materialization,
232
+ message=status_data.message,
233
+ )
234
+
235
+ except Exception as e:
236
+ logger.error(f"Failed to get materialization status: {e}")
237
+ return None
@@ -0,0 +1,187 @@
1
+ """Table Client for RoboSystems API
2
+
3
+ Manages DuckDB staging table operations.
4
+ Tables provide SQL-queryable staging layer before graph materialization.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Dict, Any, Optional
9
+ import logging
10
+
11
+ from ..api.tables.list_tables import (
12
+ sync_detailed as list_tables,
13
+ )
14
+ from ..api.tables.query_tables import (
15
+ sync_detailed as query_tables,
16
+ )
17
+ from ..models.table_query_request import TableQueryRequest
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ @dataclass
23
+ class TableInfo:
24
+ """Information about a DuckDB staging table"""
25
+
26
+ table_name: str
27
+ table_type: str
28
+ row_count: int
29
+ file_count: int
30
+ total_size_bytes: int
31
+
32
+
33
+ @dataclass
34
+ class QueryResult:
35
+ """Result from SQL query execution"""
36
+
37
+ columns: list[str]
38
+ rows: list[list[Any]]
39
+ row_count: int
40
+ execution_time_ms: float
41
+ success: bool = True
42
+ error: Optional[str] = None
43
+
44
+
45
+ class TableClient:
46
+ """Client for managing DuckDB staging tables"""
47
+
48
+ def __init__(self, config: Dict[str, Any]):
49
+ self.config = config
50
+ self.base_url = config["base_url"]
51
+ self.headers = config.get("headers", {})
52
+ self.token = config.get("token")
53
+
54
+ def list(self, graph_id: str) -> list[TableInfo]:
55
+ """
56
+ List all DuckDB staging tables in a graph.
57
+
58
+ Args:
59
+ graph_id: Graph database identifier
60
+
61
+ Returns:
62
+ List of TableInfo objects with metadata
63
+ """
64
+ try:
65
+ from ..client import AuthenticatedClient
66
+
67
+ if not self.token:
68
+ raise Exception("No API key provided. Set X-API-Key in headers.")
69
+
70
+ client = AuthenticatedClient(
71
+ base_url=self.base_url,
72
+ token=self.token,
73
+ prefix="",
74
+ auth_header_name="X-API-Key",
75
+ headers=self.headers,
76
+ )
77
+
78
+ kwargs = {
79
+ "graph_id": graph_id,
80
+ "client": client,
81
+ }
82
+
83
+ response = list_tables(**kwargs)
84
+
85
+ if response.status_code != 200 or not response.parsed:
86
+ logger.error(f"Failed to list tables: {response.status_code}")
87
+ return []
88
+
89
+ table_data = response.parsed
90
+ tables = getattr(table_data, "tables", [])
91
+
92
+ return [
93
+ TableInfo(
94
+ table_name=t.table_name,
95
+ table_type=t.table_type,
96
+ row_count=t.row_count,
97
+ file_count=t.file_count or 0,
98
+ total_size_bytes=t.total_size_bytes or 0,
99
+ )
100
+ for t in tables
101
+ ]
102
+
103
+ except Exception as e:
104
+ logger.error(f"Failed to list tables: {e}")
105
+ return []
106
+
107
+ def query(
108
+ self, graph_id: str, sql_query: str, limit: Optional[int] = None
109
+ ) -> QueryResult:
110
+ """
111
+ Execute SQL query against DuckDB staging tables.
112
+
113
+ Args:
114
+ graph_id: Graph database identifier
115
+ sql_query: SQL query to execute
116
+ limit: Optional row limit
117
+
118
+ Returns:
119
+ QueryResult with columns and rows
120
+
121
+ Example:
122
+ >>> result = client.tables.query(
123
+ ... graph_id,
124
+ ... "SELECT * FROM Entity WHERE entity_type = 'CORPORATION'"
125
+ ... )
126
+ >>> for row in result.rows:
127
+ ... print(row)
128
+ """
129
+ try:
130
+ final_query = sql_query
131
+ if limit is not None:
132
+ final_query = f"{sql_query.rstrip(';')} LIMIT {limit}"
133
+
134
+ request = TableQueryRequest(sql=final_query)
135
+
136
+ from ..client import AuthenticatedClient
137
+
138
+ if not self.token:
139
+ raise Exception("No API key provided. Set X-API-Key in headers.")
140
+
141
+ client = AuthenticatedClient(
142
+ base_url=self.base_url,
143
+ token=self.token,
144
+ prefix="",
145
+ auth_header_name="X-API-Key",
146
+ headers=self.headers,
147
+ )
148
+
149
+ kwargs = {
150
+ "graph_id": graph_id,
151
+ "client": client,
152
+ "body": request,
153
+ }
154
+
155
+ response = query_tables(**kwargs)
156
+
157
+ if response.status_code != 200 or not response.parsed:
158
+ error_msg = f"Query failed: {response.status_code}"
159
+ return QueryResult(
160
+ columns=[],
161
+ rows=[],
162
+ row_count=0,
163
+ execution_time_ms=0,
164
+ success=False,
165
+ error=error_msg,
166
+ )
167
+
168
+ result_data = response.parsed
169
+
170
+ return QueryResult(
171
+ columns=result_data.columns,
172
+ rows=result_data.rows,
173
+ row_count=len(result_data.rows),
174
+ execution_time_ms=getattr(result_data, "execution_time_ms", 0),
175
+ success=True,
176
+ )
177
+
178
+ except Exception as e:
179
+ logger.error(f"Query failed: {e}")
180
+ return QueryResult(
181
+ columns=[],
182
+ rows=[],
183
+ row_count=0,
184
+ execution_time_ms=0,
185
+ success=False,
186
+ error=str(e),
187
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robosystems-client
3
- Version: 0.2.16
3
+ Version: 0.2.18
4
4
  Summary: Python Client for RoboSystems financial graph database API
5
5
  Author: RFS LLC
6
6
  License: MIT
@@ -142,18 +142,20 @@ robosystems_client/api/views/__init__.py,sha256=5vd9uJWAjRqa9xzxzYkLD1yoZ12Ld_bA
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
144
  robosystems_client/extensions/README.md,sha256=qfHFjdgA_J-zNXziNZE6M1MKJiwVkocBi01w_HhvzEk,16136
145
- robosystems_client/extensions/__init__.py,sha256=FNJ0IP9ZwiPOXzDS7Sc8PAqLXxyFgje41_S5MVivDAs,6687
145
+ robosystems_client/extensions/__init__.py,sha256=eTuJQGygQTOWC51YVhJOWUWFUMLcPo7MpZ0H3GaxoR0,7076
146
146
  robosystems_client/extensions/agent_client.py,sha256=Db2C4hrakVsf6ScnBcNk6rte3Kwn4cQBEHsR_joWMTs,17750
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
- robosystems_client/extensions/extensions.py,sha256=QkKIc6cU7uJ5unvH5bdrvq8RuAraqGHh7eY7wpwMVy8,6360
150
+ robosystems_client/extensions/extensions.py,sha256=7vsD3QeIKbwhC1UqNskFjsfKkg_ZO3PPDnc6TxV3PoA,6722
151
+ robosystems_client/extensions/file_client.py,sha256=WNttw8BtdpLsb3N2fkvim-a8eDSshi8LM3nMtfEmLt0,11843
151
152
  robosystems_client/extensions/graph_client.py,sha256=OBi0xj0SLIRKLeSu_DiGt2ZakCmhggvNrMP3jdRfEgQ,10326
153
+ robosystems_client/extensions/materialization_client.py,sha256=5QszocQXzlCE9exg_As0HfBhDBXsL-J2EYOUwHuGyh8,6819
152
154
  robosystems_client/extensions/operation_client.py,sha256=B1qju-wWQrnrnVJixKGgsA_KEInviwJwdlJxzm_i7P0,13359
153
155
  robosystems_client/extensions/query_client.py,sha256=cX3e8EBoTeg4Lwm6edJYRULM2UmGpfqNX3f48S8TQbE,19430
154
156
  robosystems_client/extensions/sse_client.py,sha256=XvQIq3JQ0Yiax11E7cwclhupShYOpEMURM2cYQodiz8,15058
155
157
  robosystems_client/extensions/subgraph_workspace_client.py,sha256=Ioc7FNJEKaD_kAJBeymwtFlVI-U9t47RouD5ibUHv4g,24036
156
- robosystems_client/extensions/table_ingest_client.py,sha256=1i1trTGjO35S7G9zefCmS1Aqqzt-IK7lJ7pIPwGL3y8,13022
158
+ robosystems_client/extensions/table_client.py,sha256=hegYiPlR-HMacyinDDACZaMeNn0bamkYtbkzDkVawVI,4521
157
159
  robosystems_client/extensions/token_utils.py,sha256=qCK_s1vBzRnSYwtgncPZRLJVIw3WXmzqNTWjdEEpdgs,10899
158
160
  robosystems_client/extensions/utils.py,sha256=vhmUnEsq-UEAMgNhmkqlbJg4oJj096QPiHALEHJ-y4A,16207
159
161
  robosystems_client/extensions/view_builder_client.py,sha256=E1LSiDHAvPf2IhifGOliOAwk5vJyu5PWAnr8ZnyulZM,18590
@@ -412,7 +414,7 @@ robosystems_client/models/view_axis_config_member_labels_type_0.py,sha256=kkzpHx
412
414
  robosystems_client/models/view_config.py,sha256=HQnqYjLMXRhjZLOc5ypwILriMFKuvPzu0hPQi2vyNoM,3795
413
415
  robosystems_client/models/view_source.py,sha256=h66cASj-P_-qOptKv26uAIe9PtIewU2nTs42Ls-lFFk,4098
414
416
  robosystems_client/models/view_source_type.py,sha256=KpgczHUeOinV01jdLvytZ2URKwcsRcp1doPx2D3USyw,169
415
- robosystems_client-0.2.16.dist-info/METADATA,sha256=ZMmCsQ8SU9K2uJ8YDSVmEFe5HMduIl-zar5xq8jYgmU,3904
416
- robosystems_client-0.2.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
417
- robosystems_client-0.2.16.dist-info/licenses/LICENSE,sha256=LjFqQPU4eQh7jAQ04SmE9eC0j74HCdXvzbo0hjW4mWo,1063
418
- robosystems_client-0.2.16.dist-info/RECORD,,
417
+ robosystems_client-0.2.18.dist-info/METADATA,sha256=ozIcNAvC7Hf5CCRDjadlzmKZXK1ihSVc6VSQuU2OU7w,3904
418
+ robosystems_client-0.2.18.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
419
+ robosystems_client-0.2.18.dist-info/licenses/LICENSE,sha256=LjFqQPU4eQh7jAQ04SmE9eC0j74HCdXvzbo0hjW4mWo,1063
420
+ robosystems_client-0.2.18.dist-info/RECORD,,