nao-core 0.0.29__py3-none-any.whl → 0.0.30__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.
- nao_core/__init__.py +1 -1
- nao_core/bin/fastapi/main.py +15 -2
- nao_core/bin/fastapi/test_main.py +156 -0
- nao_core/bin/migrations-postgres/0004_input_and_output_tokens.sql +8 -0
- nao_core/bin/migrations-postgres/meta/0004_snapshot.json +847 -0
- nao_core/bin/migrations-postgres/meta/_journal.json +7 -0
- nao_core/bin/migrations-sqlite/0004_input_and_output_tokens.sql +8 -0
- nao_core/bin/migrations-sqlite/meta/0004_snapshot.json +819 -0
- nao_core/bin/migrations-sqlite/meta/_journal.json +7 -0
- nao_core/bin/nao-chat-server +0 -0
- nao_core/bin/public/assets/{code-block-F6WJLWQG-EQr6mTlQ.js → code-block-F6WJLWQG-z4zcca7w.js} +3 -3
- nao_core/bin/public/assets/index-ClduEZSo.css +1 -0
- nao_core/bin/public/assets/index-DhhS7iVA.js +473 -0
- nao_core/bin/public/index.html +2 -2
- nao_core/commands/debug.py +10 -6
- nao_core/commands/init.py +39 -23
- nao_core/commands/sync/accessors.py +2 -3
- nao_core/commands/sync/databases.py +243 -1
- nao_core/config/__init__.py +14 -1
- nao_core/config/databases/__init__.py +32 -11
- nao_core/config/databases/base.py +6 -0
- nao_core/config/databases/bigquery.py +29 -1
- nao_core/config/databases/databricks.py +69 -0
- nao_core/config/databases/duckdb.py +33 -0
- nao_core/config/databases/snowflake.py +115 -0
- nao_core/config/exceptions.py +7 -0
- {nao_core-0.0.29.dist-info → nao_core-0.0.30.dist-info}/METADATA +5 -4
- {nao_core-0.0.29.dist-info → nao_core-0.0.30.dist-info}/RECORD +31 -35
- nao_core/bin/public/assets/_chat-layout-BTlqRUE5.js +0 -1
- nao_core/bin/public/assets/_chat-layout.index-DOARokp1.js +0 -1
- nao_core/bin/public/assets/agentProvider-C6dGIy-H.js +0 -1
- nao_core/bin/public/assets/button-By_1dzVx.js +0 -1
- nao_core/bin/public/assets/folder-DnRS5rg3.js +0 -1
- nao_core/bin/public/assets/index-CElAN2SH.css +0 -1
- nao_core/bin/public/assets/index-ZTHASguQ.js +0 -59
- nao_core/bin/public/assets/input-CUQA5tsi.js +0 -1
- nao_core/bin/public/assets/login-BUQDum3t.js +0 -1
- nao_core/bin/public/assets/mermaid-FSSLJTFX-Dc6ZvCPw.js +0 -427
- nao_core/bin/public/assets/sidebar-bgEk7Xg8.js +0 -1
- nao_core/bin/public/assets/signinForm-CGAhnAkv.js +0 -1
- nao_core/bin/public/assets/signup-D2n11La3.js +0 -1
- nao_core/bin/public/assets/user-CYl8Tly2.js +0 -1
- nao_core/bin/public/assets/utils-DzJYey0s.js +0 -1
- {nao_core-0.0.29.dist-info → nao_core-0.0.30.dist-info}/WHEEL +0 -0
- {nao_core-0.0.29.dist-info → nao_core-0.0.30.dist-info}/entry_points.txt +0 -0
- {nao_core-0.0.29.dist-info → nao_core-0.0.30.dist-info}/licenses/LICENSE +0 -0
nao_core/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# nao Core CLI package
|
|
2
|
-
__version__ = "0.0.
|
|
2
|
+
__version__ = "0.0.30"
|
nao_core/bin/fastapi/main.py
CHANGED
|
@@ -85,8 +85,21 @@ async def execute_sql(request: ExecuteSQLRequest):
|
|
|
85
85
|
|
|
86
86
|
connection = db_config.connect()
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
# Use raw_sql to execute arbitrary SQL (including CTEs)
|
|
89
|
+
cursor = connection.raw_sql(request.sql)
|
|
90
|
+
|
|
91
|
+
# Handle different cursor types from different backends
|
|
92
|
+
if hasattr(cursor, 'fetchdf'):
|
|
93
|
+
# DuckDB returns a cursor with fetchdf()
|
|
94
|
+
df = cursor.fetchdf()
|
|
95
|
+
elif hasattr(cursor, 'to_dataframe'):
|
|
96
|
+
# Some backends return cursors with to_dataframe()
|
|
97
|
+
df = cursor.to_dataframe()
|
|
98
|
+
else:
|
|
99
|
+
# Fallback: try to use pandas read_sql or fetchall
|
|
100
|
+
import pandas as pd
|
|
101
|
+
columns = [desc[0] for desc in cursor.description]
|
|
102
|
+
df = pd.DataFrame(cursor.fetchall(), columns=columns)
|
|
90
103
|
|
|
91
104
|
def convert_value(v):
|
|
92
105
|
if isinstance(v, (np.integer,)):
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
import yaml
|
|
6
|
+
from fastapi.testclient import TestClient
|
|
7
|
+
|
|
8
|
+
from main import app
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def assert_sql_result(data: dict, *, row_count: int, columns: list[str], expected_data: list[dict]):
|
|
12
|
+
"""Assert that SQL response data matches expected values."""
|
|
13
|
+
assert data["row_count"] == row_count
|
|
14
|
+
assert data["columns"] == columns
|
|
15
|
+
assert len(data["data"]) == row_count
|
|
16
|
+
assert data["data"] == expected_data
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def duckdb_project_folder():
|
|
21
|
+
"""Create a temporary project folder with a DuckDB config."""
|
|
22
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
23
|
+
config = {
|
|
24
|
+
"project_name": "test-project",
|
|
25
|
+
"databases": [
|
|
26
|
+
{
|
|
27
|
+
"name": "test-duckdb",
|
|
28
|
+
"type": "duckdb",
|
|
29
|
+
"path": ":memory:",
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
}
|
|
33
|
+
config_path = Path(tmpdir) / "nao_config.yaml"
|
|
34
|
+
with config_path.open("w") as f:
|
|
35
|
+
yaml.dump(config, f)
|
|
36
|
+
yield tmpdir
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_execute_sql_simple_duckdb(duckdb_project_folder):
|
|
40
|
+
"""Test execute_sql endpoint with a DuckDB in-memory database."""
|
|
41
|
+
client = TestClient(app)
|
|
42
|
+
|
|
43
|
+
response = client.post(
|
|
44
|
+
"/execute_sql",
|
|
45
|
+
json={
|
|
46
|
+
"sql": "SELECT 1 AS id, 'hello' AS message",
|
|
47
|
+
"nao_project_folder": duckdb_project_folder,
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
assert response.status_code == 200
|
|
52
|
+
assert_sql_result(
|
|
53
|
+
response.json(),
|
|
54
|
+
row_count=1,
|
|
55
|
+
columns=["id", "message"],
|
|
56
|
+
expected_data=[{"id": 1, "message": "hello"}],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_execute_sql_with_cte_duckdb(duckdb_project_folder):
|
|
61
|
+
"""Test execute_sql endpoint with a DuckDB in-memory database."""
|
|
62
|
+
client = TestClient(app)
|
|
63
|
+
|
|
64
|
+
response = client.post(
|
|
65
|
+
"/execute_sql",
|
|
66
|
+
json={
|
|
67
|
+
"sql": "WITH test AS (SELECT 1 AS id, 'hello' AS message) SELECT * FROM test",
|
|
68
|
+
"nao_project_folder": duckdb_project_folder,
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
assert response.status_code == 200
|
|
73
|
+
assert_sql_result(
|
|
74
|
+
response.json(),
|
|
75
|
+
row_count=1,
|
|
76
|
+
columns=["id", "message"],
|
|
77
|
+
expected_data=[{"id": 1, "message": "hello"}],
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# BigQuery tests (requires SSO authentication)
|
|
82
|
+
|
|
83
|
+
@pytest.fixture
|
|
84
|
+
def bigquery_project_folder():
|
|
85
|
+
"""Create a temporary project folder with a BigQuery config using SSO."""
|
|
86
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
87
|
+
config = {
|
|
88
|
+
"project_name": "test-project",
|
|
89
|
+
"databases": [
|
|
90
|
+
{
|
|
91
|
+
"name": "nao-bigquery",
|
|
92
|
+
"type": "bigquery",
|
|
93
|
+
"project_id": "nao-corp",
|
|
94
|
+
"sso": True,
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
}
|
|
98
|
+
config_path = Path(tmpdir) / "nao_config.yaml"
|
|
99
|
+
with config_path.open("w") as f:
|
|
100
|
+
yaml.dump(config, f)
|
|
101
|
+
yield tmpdir
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_execute_sql_simple_bigquery(bigquery_project_folder):
|
|
105
|
+
"""Test execute_sql endpoint with BigQuery using SSO."""
|
|
106
|
+
client = TestClient(app)
|
|
107
|
+
|
|
108
|
+
response = client.post(
|
|
109
|
+
"/execute_sql",
|
|
110
|
+
json={
|
|
111
|
+
"sql": "SELECT 1 AS id, 'hello' AS message",
|
|
112
|
+
"nao_project_folder": bigquery_project_folder,
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
assert response.status_code == 200
|
|
117
|
+
assert_sql_result(
|
|
118
|
+
response.json(),
|
|
119
|
+
row_count=1,
|
|
120
|
+
columns=["id", "message"],
|
|
121
|
+
expected_data=[{"id": 1, "message": "hello"}],
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_execute_sql_with_cte_bigquery(bigquery_project_folder):
|
|
126
|
+
"""Test execute_sql endpoint with a CTE query on BigQuery."""
|
|
127
|
+
client = TestClient(app)
|
|
128
|
+
|
|
129
|
+
cte_sql = """
|
|
130
|
+
WITH users AS (
|
|
131
|
+
SELECT 1 AS id, 'Alice' AS name
|
|
132
|
+
UNION ALL SELECT 2, 'Bob'
|
|
133
|
+
UNION ALL SELECT 3, 'Charlie'
|
|
134
|
+
)
|
|
135
|
+
SELECT * FROM users
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
response = client.post(
|
|
139
|
+
"/execute_sql",
|
|
140
|
+
json={
|
|
141
|
+
"sql": cte_sql,
|
|
142
|
+
"nao_project_folder": bigquery_project_folder,
|
|
143
|
+
},
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
assert response.status_code == 200
|
|
147
|
+
assert_sql_result(
|
|
148
|
+
response.json(),
|
|
149
|
+
row_count=3,
|
|
150
|
+
columns=["id", "name"],
|
|
151
|
+
expected_data=[
|
|
152
|
+
{"id": 1, "name": "Alice"},
|
|
153
|
+
{"id": 2, "name": "Bob"},
|
|
154
|
+
{"id": 3, "name": "Charlie"},
|
|
155
|
+
],
|
|
156
|
+
)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
ALTER TABLE "message_part" ADD COLUMN "input_total_tokens" integer;--> statement-breakpoint
|
|
2
|
+
ALTER TABLE "message_part" ADD COLUMN "input_no_cache_tokens" integer;--> statement-breakpoint
|
|
3
|
+
ALTER TABLE "message_part" ADD COLUMN "input_cache_read_tokens" integer;--> statement-breakpoint
|
|
4
|
+
ALTER TABLE "message_part" ADD COLUMN "input_cache_write_tokens" integer;--> statement-breakpoint
|
|
5
|
+
ALTER TABLE "message_part" ADD COLUMN "output_total_tokens" integer;--> statement-breakpoint
|
|
6
|
+
ALTER TABLE "message_part" ADD COLUMN "output_text_tokens" integer;--> statement-breakpoint
|
|
7
|
+
ALTER TABLE "message_part" ADD COLUMN "output_reasoning_tokens" integer;--> statement-breakpoint
|
|
8
|
+
ALTER TABLE "message_part" ADD COLUMN "total_tokens" integer;
|