ainative-python 2.0.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.
- ainative/__init__.py +46 -0
- ainative/agent_coordination.py +249 -0
- ainative/agent_identity_system.py +1566 -0
- ainative/agent_learning.py +239 -0
- ainative/agent_orchestration.py +202 -0
- ainative/agent_state.py +231 -0
- ainative/agent_swarm/__init__.py +510 -0
- ainative/auth.py +113 -0
- ainative/cli.py +698 -0
- ainative/cli_utils/__init__.py +13 -0
- ainative/cli_utils/diff.py +292 -0
- ainative/cli_utils/formatters.py +227 -0
- ainative/client.py +272 -0
- ainative/commands/__init__.py +28 -0
- ainative/commands/agents.py +238 -0
- ainative/commands/coordination.py +108 -0
- ainative/commands/inspect.py +483 -0
- ainative/commands/learning.py +119 -0
- ainative/commands/local.py +544 -0
- ainative/commands/state.py +144 -0
- ainative/commands/swarm.py +184 -0
- ainative/commands/sync.py +157 -0
- ainative/commands/tasks.py +191 -0
- ainative/exceptions.py +87 -0
- ainative/zerodb/__init__.py +89 -0
- ainative/zerodb/analytics.py +232 -0
- ainative/zerodb/memory.py +260 -0
- ainative/zerodb/projects.py +224 -0
- ainative/zerodb/tables.py +362 -0
- ainative/zerodb/vectors.py +231 -0
- ainative_python-2.0.0.dist-info/METADATA +550 -0
- ainative_python-2.0.0.dist-info/RECORD +35 -0
- ainative_python-2.0.0.dist-info/WHEEL +5 -0
- ainative_python-2.0.0.dist-info/entry_points.txt +2 -0
- ainative_python-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database Diff Logic
|
|
3
|
+
|
|
4
|
+
Computes differences between local and cloud database states including
|
|
5
|
+
schema, data, and vectors.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, Any, List, Optional, Set
|
|
9
|
+
from difflib import unified_diff
|
|
10
|
+
import json
|
|
11
|
+
|
|
12
|
+
from ..client import AINativeClient
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DatabaseDiff:
|
|
16
|
+
"""Compute differences between local and cloud databases."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, local_client: AINativeClient, cloud_client: AINativeClient):
|
|
19
|
+
"""
|
|
20
|
+
Initialize database differ.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
local_client: Client connected to local API
|
|
24
|
+
cloud_client: Client connected to cloud API
|
|
25
|
+
"""
|
|
26
|
+
self.local = local_client
|
|
27
|
+
self.cloud = cloud_client
|
|
28
|
+
|
|
29
|
+
def compute_schema_diff(self) -> Dict[str, Any]:
|
|
30
|
+
"""
|
|
31
|
+
Compute schema differences between local and cloud.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Dictionary containing:
|
|
35
|
+
- tables_to_create: List of tables in local but not cloud
|
|
36
|
+
- tables_to_drop: List of tables in cloud but not local
|
|
37
|
+
- tables_to_alter: List of tables with schema changes
|
|
38
|
+
"""
|
|
39
|
+
# Fetch table lists
|
|
40
|
+
try:
|
|
41
|
+
local_tables = self._fetch_tables(self.local)
|
|
42
|
+
cloud_tables = self._fetch_tables(self.cloud)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
return {
|
|
45
|
+
"error": f"Failed to fetch tables: {str(e)}",
|
|
46
|
+
"tables_to_create": [],
|
|
47
|
+
"tables_to_drop": [],
|
|
48
|
+
"tables_to_alter": []
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
local_names = {t["table_name"] for t in local_tables}
|
|
52
|
+
cloud_names = {t["table_name"] for t in cloud_tables}
|
|
53
|
+
|
|
54
|
+
# Tables to create (in local but not cloud)
|
|
55
|
+
tables_to_create = []
|
|
56
|
+
for table in local_tables:
|
|
57
|
+
if table["table_name"] not in cloud_names:
|
|
58
|
+
tables_to_create.append({
|
|
59
|
+
"table_name": table["table_name"],
|
|
60
|
+
"schema": table.get("schema", {}),
|
|
61
|
+
"description": table.get("description")
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
# Tables to drop (in cloud but not local)
|
|
65
|
+
tables_to_drop = []
|
|
66
|
+
for table in cloud_tables:
|
|
67
|
+
if table["table_name"] not in local_names:
|
|
68
|
+
tables_to_drop.append({
|
|
69
|
+
"table_name": table["table_name"]
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
# Tables to alter (schema changes)
|
|
73
|
+
tables_to_alter = []
|
|
74
|
+
local_map = {t["table_name"]: t for t in local_tables}
|
|
75
|
+
cloud_map = {t["table_name"]: t for t in cloud_tables}
|
|
76
|
+
|
|
77
|
+
for name in local_names & cloud_names:
|
|
78
|
+
local_schema = local_map[name].get("schema", {})
|
|
79
|
+
cloud_schema = cloud_map[name].get("schema", {})
|
|
80
|
+
|
|
81
|
+
if local_schema != cloud_schema:
|
|
82
|
+
# Compute field changes
|
|
83
|
+
local_fields = set(local_schema.get("fields", {}).keys())
|
|
84
|
+
cloud_fields = set(cloud_schema.get("fields", {}).keys())
|
|
85
|
+
|
|
86
|
+
added_fields = local_fields - cloud_fields
|
|
87
|
+
removed_fields = cloud_fields - local_fields
|
|
88
|
+
|
|
89
|
+
# Check for type changes in existing fields
|
|
90
|
+
type_changes = []
|
|
91
|
+
for field in local_fields & cloud_fields:
|
|
92
|
+
local_type = local_schema.get("fields", {}).get(field)
|
|
93
|
+
cloud_type = cloud_schema.get("fields", {}).get(field)
|
|
94
|
+
if local_type != cloud_type:
|
|
95
|
+
type_changes.append({
|
|
96
|
+
"field": field,
|
|
97
|
+
"old_type": cloud_type,
|
|
98
|
+
"new_type": local_type
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
tables_to_alter.append({
|
|
102
|
+
"table_name": name,
|
|
103
|
+
"added_fields": list(added_fields),
|
|
104
|
+
"removed_fields": list(removed_fields),
|
|
105
|
+
"type_changes": type_changes,
|
|
106
|
+
"local_schema": local_schema,
|
|
107
|
+
"cloud_schema": cloud_schema
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
"tables_to_create": tables_to_create,
|
|
112
|
+
"tables_to_drop": tables_to_drop,
|
|
113
|
+
"tables_to_alter": tables_to_alter
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
def compute_data_diff(self) -> Dict[str, Any]:
|
|
117
|
+
"""
|
|
118
|
+
Compute data differences between local and cloud.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Dictionary containing row counts and changes for each table
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
local_tables = self._fetch_tables(self.local)
|
|
125
|
+
cloud_tables = self._fetch_tables(self.cloud)
|
|
126
|
+
except Exception as e:
|
|
127
|
+
return {
|
|
128
|
+
"error": f"Failed to fetch tables: {str(e)}",
|
|
129
|
+
"total_new_rows": 0,
|
|
130
|
+
"total_updated_rows": 0,
|
|
131
|
+
"total_deleted_rows": 0,
|
|
132
|
+
"table_details": []
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
local_map = {t["table_name"]: t for t in local_tables}
|
|
136
|
+
cloud_map = {t["table_name"]: t for t in cloud_tables}
|
|
137
|
+
|
|
138
|
+
table_details = []
|
|
139
|
+
total_new = 0
|
|
140
|
+
total_updated = 0
|
|
141
|
+
total_deleted = 0
|
|
142
|
+
|
|
143
|
+
# Compare tables that exist in both
|
|
144
|
+
for name in set(local_map.keys()) & set(cloud_map.keys()):
|
|
145
|
+
local_count = local_map[name].get("row_count", 0)
|
|
146
|
+
cloud_count = cloud_map[name].get("row_count", 0)
|
|
147
|
+
|
|
148
|
+
# Simple heuristic: if local has more rows, they're "new"
|
|
149
|
+
# In reality, we'd need to compare actual row data
|
|
150
|
+
new_rows = max(0, local_count - cloud_count)
|
|
151
|
+
deleted_rows = max(0, cloud_count - local_count)
|
|
152
|
+
|
|
153
|
+
if new_rows > 0 or deleted_rows > 0:
|
|
154
|
+
table_details.append({
|
|
155
|
+
"table_name": name,
|
|
156
|
+
"local_count": local_count,
|
|
157
|
+
"cloud_count": cloud_count,
|
|
158
|
+
"new_rows": new_rows,
|
|
159
|
+
"deleted_rows": deleted_rows,
|
|
160
|
+
"updated_rows": 0 # Would require row-level comparison
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
total_new += new_rows
|
|
164
|
+
total_deleted += deleted_rows
|
|
165
|
+
|
|
166
|
+
# Tables that only exist locally (all rows are "new")
|
|
167
|
+
for name in set(local_map.keys()) - set(cloud_map.keys()):
|
|
168
|
+
local_count = local_map[name].get("row_count", 0)
|
|
169
|
+
if local_count > 0:
|
|
170
|
+
table_details.append({
|
|
171
|
+
"table_name": name,
|
|
172
|
+
"local_count": local_count,
|
|
173
|
+
"cloud_count": 0,
|
|
174
|
+
"new_rows": local_count,
|
|
175
|
+
"deleted_rows": 0,
|
|
176
|
+
"updated_rows": 0
|
|
177
|
+
})
|
|
178
|
+
total_new += local_count
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
"total_new_rows": total_new,
|
|
182
|
+
"total_updated_rows": total_updated,
|
|
183
|
+
"total_deleted_rows": total_deleted,
|
|
184
|
+
"table_details": table_details
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
def compute_vectors_diff(self) -> Dict[str, Any]:
|
|
188
|
+
"""
|
|
189
|
+
Compute vector differences between local and cloud.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Dictionary containing vector statistics and changes
|
|
193
|
+
"""
|
|
194
|
+
try:
|
|
195
|
+
local_stats = self._fetch_vector_stats(self.local)
|
|
196
|
+
cloud_stats = self._fetch_vector_stats(self.cloud)
|
|
197
|
+
except Exception as e:
|
|
198
|
+
return {
|
|
199
|
+
"error": f"Failed to fetch vector stats: {str(e)}",
|
|
200
|
+
"total_upserts": 0,
|
|
201
|
+
"total_deletes": 0,
|
|
202
|
+
"namespace_details": []
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# Extract namespace stats
|
|
206
|
+
local_namespaces = local_stats.get("namespaces", {})
|
|
207
|
+
cloud_namespaces = cloud_stats.get("namespaces", {})
|
|
208
|
+
|
|
209
|
+
namespace_details = []
|
|
210
|
+
total_upserts = 0
|
|
211
|
+
total_deletes = 0
|
|
212
|
+
|
|
213
|
+
# All namespaces
|
|
214
|
+
all_namespaces = set(local_namespaces.keys()) | set(cloud_namespaces.keys())
|
|
215
|
+
|
|
216
|
+
for ns in all_namespaces:
|
|
217
|
+
local_count = local_namespaces.get(ns, {}).get("vector_count", 0)
|
|
218
|
+
cloud_count = cloud_namespaces.get(ns, {}).get("vector_count", 0)
|
|
219
|
+
|
|
220
|
+
upserts = max(0, local_count - cloud_count)
|
|
221
|
+
deletes = max(0, cloud_count - local_count)
|
|
222
|
+
|
|
223
|
+
if upserts > 0 or deletes > 0:
|
|
224
|
+
namespace_details.append({
|
|
225
|
+
"namespace": ns,
|
|
226
|
+
"local_count": local_count,
|
|
227
|
+
"cloud_count": cloud_count,
|
|
228
|
+
"upserts": upserts,
|
|
229
|
+
"deletes": deletes
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
total_upserts += upserts
|
|
233
|
+
total_deletes += deletes
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
"total_upserts": total_upserts,
|
|
237
|
+
"total_deletes": total_deletes,
|
|
238
|
+
"namespace_details": namespace_details,
|
|
239
|
+
"local_total": local_stats.get("total_vectors", 0),
|
|
240
|
+
"cloud_total": cloud_stats.get("total_vectors", 0)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
def _fetch_tables(self, client: AINativeClient) -> List[Dict[str, Any]]:
|
|
244
|
+
"""
|
|
245
|
+
Fetch all tables from a client.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
client: AINative client instance
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
List of table dictionaries
|
|
252
|
+
"""
|
|
253
|
+
try:
|
|
254
|
+
result = client.zerodb.tables.list_tables(limit=1000)
|
|
255
|
+
return result.get("tables", [])
|
|
256
|
+
except Exception as e:
|
|
257
|
+
# If tables endpoint fails, return empty list
|
|
258
|
+
return []
|
|
259
|
+
|
|
260
|
+
def _fetch_vector_stats(self, client: AINativeClient) -> Dict[str, Any]:
|
|
261
|
+
"""
|
|
262
|
+
Fetch vector statistics from a client.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
client: AINative client instance
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Dictionary with vector statistics
|
|
269
|
+
"""
|
|
270
|
+
try:
|
|
271
|
+
# Get project ID from client's organization context
|
|
272
|
+
# First, try to get list of projects
|
|
273
|
+
projects_result = client.zerodb.projects.list(limit=1)
|
|
274
|
+
projects = projects_result.get("projects", [])
|
|
275
|
+
|
|
276
|
+
if not projects:
|
|
277
|
+
# No projects available
|
|
278
|
+
return {
|
|
279
|
+
"total_vectors": 0,
|
|
280
|
+
"namespaces": {}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
# Use the first project for stats
|
|
284
|
+
project_id = projects[0].get("id")
|
|
285
|
+
result = client.zerodb.vectors.describe_index_stats(project_id=project_id)
|
|
286
|
+
return result
|
|
287
|
+
except Exception as e:
|
|
288
|
+
# Return empty stats if not available
|
|
289
|
+
return {
|
|
290
|
+
"total_vectors": 0,
|
|
291
|
+
"namespaces": {}
|
|
292
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Diff Formatters
|
|
3
|
+
|
|
4
|
+
Rich console formatters for displaying database diffs with colors.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any, List
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.text import Text
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DiffFormatter:
|
|
18
|
+
"""Format database diffs for rich console output."""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
"""Initialize formatter."""
|
|
22
|
+
self.console = console
|
|
23
|
+
|
|
24
|
+
def format_schema_diff(self, schema_diff: Dict[str, Any]):
|
|
25
|
+
"""
|
|
26
|
+
Format and display schema differences.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
schema_diff: Schema diff dictionary from DatabaseDiff
|
|
30
|
+
"""
|
|
31
|
+
if schema_diff.get("error"):
|
|
32
|
+
self.console.print(f"[red]Schema Error:[/red] {schema_diff['error']}")
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
tables_to_create = schema_diff.get("tables_to_create", [])
|
|
36
|
+
tables_to_drop = schema_diff.get("tables_to_drop", [])
|
|
37
|
+
tables_to_alter = schema_diff.get("tables_to_alter", [])
|
|
38
|
+
|
|
39
|
+
if not (tables_to_create or tables_to_drop or tables_to_alter):
|
|
40
|
+
self.console.print("[dim]Schema: No changes[/dim]")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
# Header
|
|
44
|
+
self.console.print("\n[bold]Schema:[/bold]")
|
|
45
|
+
|
|
46
|
+
# Tables to create
|
|
47
|
+
if tables_to_create:
|
|
48
|
+
for table in tables_to_create:
|
|
49
|
+
fields = table.get("schema", {}).get("fields", {})
|
|
50
|
+
field_str = ", ".join(
|
|
51
|
+
f"{name}: {ftype}" for name, ftype in fields.items()
|
|
52
|
+
)
|
|
53
|
+
self.console.print(
|
|
54
|
+
f" [green]+[/green] Create table: [bold]{table['table_name']}[/bold] "
|
|
55
|
+
f"({field_str})"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Tables to alter
|
|
59
|
+
if tables_to_alter:
|
|
60
|
+
for table in tables_to_alter:
|
|
61
|
+
table_name = table["table_name"]
|
|
62
|
+
added = table.get("added_fields", [])
|
|
63
|
+
removed = table.get("removed_fields", [])
|
|
64
|
+
type_changes = table.get("type_changes", [])
|
|
65
|
+
|
|
66
|
+
changes = []
|
|
67
|
+
if added:
|
|
68
|
+
changes.append(f"add columns: {', '.join(added)}")
|
|
69
|
+
if removed:
|
|
70
|
+
changes.append(f"remove columns: {', '.join(removed)}")
|
|
71
|
+
if type_changes:
|
|
72
|
+
type_str = ", ".join(
|
|
73
|
+
f"{tc['field']} ({tc['old_type']} → {tc['new_type']})"
|
|
74
|
+
for tc in type_changes
|
|
75
|
+
)
|
|
76
|
+
changes.append(f"change types: {type_str}")
|
|
77
|
+
|
|
78
|
+
change_desc = "; ".join(changes)
|
|
79
|
+
self.console.print(
|
|
80
|
+
f" [yellow]~[/yellow] Alter table: [bold]{table_name}[/bold] "
|
|
81
|
+
f"({change_desc})"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Tables to drop
|
|
85
|
+
if tables_to_drop:
|
|
86
|
+
for table in tables_to_drop:
|
|
87
|
+
self.console.print(
|
|
88
|
+
f" [red]-[/red] Drop table: [bold]{table['table_name']}[/bold]"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def format_data_diff(self, data_diff: Dict[str, Any]):
|
|
92
|
+
"""
|
|
93
|
+
Format and display data differences.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
data_diff: Data diff dictionary from DatabaseDiff
|
|
97
|
+
"""
|
|
98
|
+
if data_diff.get("error"):
|
|
99
|
+
self.console.print(f"[red]Data Error:[/red] {data_diff['error']}")
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
total_new = data_diff.get("total_new_rows", 0)
|
|
103
|
+
total_updated = data_diff.get("total_updated_rows", 0)
|
|
104
|
+
total_deleted = data_diff.get("total_deleted_rows", 0)
|
|
105
|
+
table_details = data_diff.get("table_details", [])
|
|
106
|
+
|
|
107
|
+
if total_new == 0 and total_updated == 0 and total_deleted == 0:
|
|
108
|
+
self.console.print("[dim]Data: No changes[/dim]")
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
# Header
|
|
112
|
+
self.console.print("\n[bold]Data:[/bold]")
|
|
113
|
+
|
|
114
|
+
# Summary
|
|
115
|
+
if total_new > 0:
|
|
116
|
+
self.console.print(f" [green]+[/green] {total_new} new rows")
|
|
117
|
+
if total_updated > 0:
|
|
118
|
+
self.console.print(f" [yellow]~[/yellow] {total_updated} updated rows")
|
|
119
|
+
if total_deleted > 0:
|
|
120
|
+
self.console.print(f" [red]-[/red] {total_deleted} deleted rows")
|
|
121
|
+
|
|
122
|
+
# Table-level details (if there are multiple tables)
|
|
123
|
+
if len(table_details) > 1:
|
|
124
|
+
self.console.print("\n[dim] By table:[/dim]")
|
|
125
|
+
for detail in table_details:
|
|
126
|
+
parts = []
|
|
127
|
+
if detail["new_rows"] > 0:
|
|
128
|
+
parts.append(f"[green]+{detail['new_rows']}[/green]")
|
|
129
|
+
if detail["updated_rows"] > 0:
|
|
130
|
+
parts.append(f"[yellow]~{detail['updated_rows']}[/yellow]")
|
|
131
|
+
if detail["deleted_rows"] > 0:
|
|
132
|
+
parts.append(f"[red]-{detail['deleted_rows']}[/red]")
|
|
133
|
+
|
|
134
|
+
changes = " ".join(parts)
|
|
135
|
+
self.console.print(
|
|
136
|
+
f" {detail['table_name']}: {changes} "
|
|
137
|
+
f"[dim](local: {detail['local_count']}, "
|
|
138
|
+
f"cloud: {detail['cloud_count']})[/dim]"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def format_vectors_diff(self, vectors_diff: Dict[str, Any]):
|
|
142
|
+
"""
|
|
143
|
+
Format and display vector differences.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
vectors_diff: Vectors diff dictionary from DatabaseDiff
|
|
147
|
+
"""
|
|
148
|
+
if vectors_diff.get("error"):
|
|
149
|
+
self.console.print(f"[red]Vectors Error:[/red] {vectors_diff['error']}")
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
total_upserts = vectors_diff.get("total_upserts", 0)
|
|
153
|
+
total_deletes = vectors_diff.get("total_deletes", 0)
|
|
154
|
+
namespace_details = vectors_diff.get("namespace_details", [])
|
|
155
|
+
|
|
156
|
+
if total_upserts == 0 and total_deletes == 0:
|
|
157
|
+
self.console.print("[dim]Vectors: No changes[/dim]")
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
# Header
|
|
161
|
+
self.console.print("\n[bold]Vectors:[/bold]")
|
|
162
|
+
|
|
163
|
+
# Summary
|
|
164
|
+
if total_upserts > 0:
|
|
165
|
+
self.console.print(f" [green]+[/green] Upsert {total_upserts} embeddings")
|
|
166
|
+
if total_deletes > 0:
|
|
167
|
+
self.console.print(f" [red]-[/red] Delete {total_deletes} stale vectors")
|
|
168
|
+
|
|
169
|
+
# Namespace details (if multiple namespaces)
|
|
170
|
+
if len(namespace_details) > 1:
|
|
171
|
+
self.console.print("\n[dim] By namespace:[/dim]")
|
|
172
|
+
for detail in namespace_details:
|
|
173
|
+
parts = []
|
|
174
|
+
if detail["upserts"] > 0:
|
|
175
|
+
parts.append(f"[green]+{detail['upserts']}[/green]")
|
|
176
|
+
if detail["deletes"] > 0:
|
|
177
|
+
parts.append(f"[red]-{detail['deletes']}[/red]")
|
|
178
|
+
|
|
179
|
+
changes = " ".join(parts)
|
|
180
|
+
self.console.print(
|
|
181
|
+
f" {detail['namespace']}: {changes} "
|
|
182
|
+
f"[dim](local: {detail['local_count']}, "
|
|
183
|
+
f"cloud: {detail['cloud_count']})[/dim]"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def format_summary_table(
|
|
187
|
+
self,
|
|
188
|
+
schema_diff: Dict[str, Any],
|
|
189
|
+
data_diff: Dict[str, Any],
|
|
190
|
+
vectors_diff: Dict[str, Any]
|
|
191
|
+
):
|
|
192
|
+
"""
|
|
193
|
+
Format an overall summary table.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
schema_diff: Schema diff dictionary
|
|
197
|
+
data_diff: Data diff dictionary
|
|
198
|
+
vectors_diff: Vectors diff dictionary
|
|
199
|
+
"""
|
|
200
|
+
table = Table(title="Sync Plan Summary", show_header=True)
|
|
201
|
+
table.add_column("Category", style="cyan")
|
|
202
|
+
table.add_column("Changes", justify="right")
|
|
203
|
+
|
|
204
|
+
# Schema
|
|
205
|
+
schema_changes = (
|
|
206
|
+
len(schema_diff.get("tables_to_create", [])) +
|
|
207
|
+
len(schema_diff.get("tables_to_alter", [])) +
|
|
208
|
+
len(schema_diff.get("tables_to_drop", []))
|
|
209
|
+
)
|
|
210
|
+
table.add_row("Schema", str(schema_changes))
|
|
211
|
+
|
|
212
|
+
# Data
|
|
213
|
+
data_changes = (
|
|
214
|
+
data_diff.get("total_new_rows", 0) +
|
|
215
|
+
data_diff.get("total_updated_rows", 0) +
|
|
216
|
+
data_diff.get("total_deleted_rows", 0)
|
|
217
|
+
)
|
|
218
|
+
table.add_row("Data Rows", str(data_changes))
|
|
219
|
+
|
|
220
|
+
# Vectors
|
|
221
|
+
vector_changes = (
|
|
222
|
+
vectors_diff.get("total_upserts", 0) +
|
|
223
|
+
vectors_diff.get("total_deletes", 0)
|
|
224
|
+
)
|
|
225
|
+
table.add_row("Vectors", str(vector_changes))
|
|
226
|
+
|
|
227
|
+
self.console.print(table)
|