nao-core 0.0.28__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.
Files changed (46) hide show
  1. nao_core/__init__.py +1 -1
  2. nao_core/bin/fastapi/main.py +15 -2
  3. nao_core/bin/fastapi/test_main.py +156 -0
  4. nao_core/bin/migrations-postgres/0004_input_and_output_tokens.sql +8 -0
  5. nao_core/bin/migrations-postgres/meta/0004_snapshot.json +847 -0
  6. nao_core/bin/migrations-postgres/meta/_journal.json +7 -0
  7. nao_core/bin/migrations-sqlite/0004_input_and_output_tokens.sql +8 -0
  8. nao_core/bin/migrations-sqlite/meta/0004_snapshot.json +819 -0
  9. nao_core/bin/migrations-sqlite/meta/_journal.json +7 -0
  10. nao_core/bin/nao-chat-server +0 -0
  11. nao_core/bin/public/assets/{code-block-F6WJLWQG-EQr6mTlQ.js → code-block-F6WJLWQG-z4zcca7w.js} +3 -3
  12. nao_core/bin/public/assets/index-ClduEZSo.css +1 -0
  13. nao_core/bin/public/assets/index-DhhS7iVA.js +473 -0
  14. nao_core/bin/public/index.html +2 -2
  15. nao_core/commands/debug.py +10 -6
  16. nao_core/commands/init.py +39 -23
  17. nao_core/commands/sync/accessors.py +2 -3
  18. nao_core/commands/sync/databases.py +243 -1
  19. nao_core/config/__init__.py +14 -1
  20. nao_core/config/databases/__init__.py +32 -11
  21. nao_core/config/databases/base.py +6 -0
  22. nao_core/config/databases/bigquery.py +29 -1
  23. nao_core/config/databases/databricks.py +69 -0
  24. nao_core/config/databases/duckdb.py +33 -0
  25. nao_core/config/databases/snowflake.py +115 -0
  26. nao_core/config/exceptions.py +7 -0
  27. {nao_core-0.0.28.dist-info → nao_core-0.0.30.dist-info}/METADATA +5 -4
  28. {nao_core-0.0.28.dist-info → nao_core-0.0.30.dist-info}/RECORD +31 -35
  29. nao_core/bin/public/assets/_chat-layout-BTlqRUE5.js +0 -1
  30. nao_core/bin/public/assets/_chat-layout.index-DOARokp1.js +0 -1
  31. nao_core/bin/public/assets/agentProvider-C6dGIy-H.js +0 -1
  32. nao_core/bin/public/assets/button-By_1dzVx.js +0 -1
  33. nao_core/bin/public/assets/folder-DnRS5rg3.js +0 -1
  34. nao_core/bin/public/assets/index-CElAN2SH.css +0 -1
  35. nao_core/bin/public/assets/index-ZTHASguQ.js +0 -59
  36. nao_core/bin/public/assets/input-CUQA5tsi.js +0 -1
  37. nao_core/bin/public/assets/login-BUQDum3t.js +0 -1
  38. nao_core/bin/public/assets/mermaid-FSSLJTFX-Dc6ZvCPw.js +0 -427
  39. nao_core/bin/public/assets/sidebar-bgEk7Xg8.js +0 -1
  40. nao_core/bin/public/assets/signinForm-CGAhnAkv.js +0 -1
  41. nao_core/bin/public/assets/signup-D2n11La3.js +0 -1
  42. nao_core/bin/public/assets/user-CYl8Tly2.js +0 -1
  43. nao_core/bin/public/assets/utils-DzJYey0s.js +0 -1
  44. {nao_core-0.0.28.dist-info → nao_core-0.0.30.dist-info}/WHEEL +0 -0
  45. {nao_core-0.0.28.dist-info → nao_core-0.0.30.dist-info}/entry_points.txt +0 -0
  46. {nao_core-0.0.28.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.28"
2
+ __version__ = "0.0.30"
@@ -85,8 +85,21 @@ async def execute_sql(request: ExecuteSQLRequest):
85
85
 
86
86
  connection = db_config.connect()
87
87
 
88
- cursor = connection.raw_sql(request.sql) # type: ignore[attr-defined]
89
- df = cursor.to_dataframe()
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;