nao-core 0.0.29__py3-none-any.whl → 0.0.31__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 +21 -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/0005_add_project_tables.sql +39 -0
- nao_core/bin/migrations-postgres/meta/0004_snapshot.json +847 -0
- nao_core/bin/migrations-postgres/meta/0005_snapshot.json +1129 -0
- nao_core/bin/migrations-postgres/meta/_journal.json +14 -0
- nao_core/bin/migrations-sqlite/0004_input_and_output_tokens.sql +8 -0
- nao_core/bin/migrations-sqlite/0005_add_project_tables.sql +38 -0
- nao_core/bin/migrations-sqlite/meta/0004_snapshot.json +819 -0
- nao_core/bin/migrations-sqlite/meta/0005_snapshot.json +1086 -0
- nao_core/bin/migrations-sqlite/meta/_journal.json +14 -0
- nao_core/bin/nao-chat-server +0 -0
- nao_core/bin/public/assets/{code-block-F6WJLWQG-EQr6mTlQ.js → code-block-F6WJLWQG-TAi8koem.js} +3 -3
- nao_core/bin/public/assets/index-BfHcd9Xz.css +1 -0
- nao_core/bin/public/assets/index-Mzo9bkag.js +557 -0
- nao_core/bin/public/index.html +2 -2
- nao_core/commands/chat.py +11 -10
- nao_core/commands/debug.py +10 -6
- nao_core/commands/init.py +66 -27
- nao_core/commands/sync/__init__.py +40 -21
- nao_core/commands/sync/accessors.py +219 -141
- nao_core/commands/sync/cleanup.py +133 -0
- nao_core/commands/sync/providers/__init__.py +30 -0
- nao_core/commands/sync/providers/base.py +87 -0
- nao_core/commands/sync/providers/databases/__init__.py +17 -0
- nao_core/commands/sync/providers/databases/bigquery.py +78 -0
- nao_core/commands/sync/providers/databases/databricks.py +79 -0
- nao_core/commands/sync/providers/databases/duckdb.py +83 -0
- nao_core/commands/sync/providers/databases/postgres.py +78 -0
- nao_core/commands/sync/providers/databases/provider.py +123 -0
- nao_core/commands/sync/providers/databases/snowflake.py +78 -0
- nao_core/commands/sync/providers/repositories/__init__.py +5 -0
- nao_core/commands/sync/{repositories.py → providers/repositories/provider.py} +43 -20
- nao_core/config/__init__.py +16 -1
- nao_core/config/base.py +23 -4
- nao_core/config/databases/__init__.py +37 -11
- nao_core/config/databases/base.py +7 -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/postgres.py +78 -0
- nao_core/config/databases/snowflake.py +115 -0
- nao_core/config/exceptions.py +7 -0
- nao_core/templates/__init__.py +12 -0
- nao_core/templates/defaults/databases/columns.md.j2 +23 -0
- nao_core/templates/defaults/databases/description.md.j2 +32 -0
- nao_core/templates/defaults/databases/preview.md.j2 +22 -0
- nao_core/templates/defaults/databases/profiling.md.j2 +34 -0
- nao_core/templates/engine.py +133 -0
- {nao_core-0.0.29.dist-info → nao_core-0.0.31.dist-info}/METADATA +9 -4
- nao_core-0.0.31.dist-info/RECORD +86 -0
- 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/commands/sync/databases.py +0 -132
- nao_core-0.0.29.dist-info/RECORD +0 -69
- {nao_core-0.0.29.dist-info → nao_core-0.0.31.dist-info}/WHEEL +0 -0
- {nao_core-0.0.29.dist-info → nao_core-0.0.31.dist-info}/entry_points.txt +0 -0
- {nao_core-0.0.29.dist-info → nao_core-0.0.31.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.31"
|
nao_core/bin/fastapi/main.py
CHANGED
|
@@ -7,6 +7,9 @@ import os
|
|
|
7
7
|
import sys
|
|
8
8
|
import numpy as np
|
|
9
9
|
from pathlib import Path
|
|
10
|
+
from dotenv import load_dotenv
|
|
11
|
+
|
|
12
|
+
load_dotenv()
|
|
10
13
|
|
|
11
14
|
cli_path = Path(__file__).parent.parent.parent / "cli"
|
|
12
15
|
sys.path.insert(0, str(cli_path))
|
|
@@ -85,8 +88,21 @@ async def execute_sql(request: ExecuteSQLRequest):
|
|
|
85
88
|
|
|
86
89
|
connection = db_config.connect()
|
|
87
90
|
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
# Use raw_sql to execute arbitrary SQL (including CTEs)
|
|
92
|
+
cursor = connection.raw_sql(request.sql)
|
|
93
|
+
|
|
94
|
+
# Handle different cursor types from different backends
|
|
95
|
+
if hasattr(cursor, 'fetchdf'):
|
|
96
|
+
# DuckDB returns a cursor with fetchdf()
|
|
97
|
+
df = cursor.fetchdf()
|
|
98
|
+
elif hasattr(cursor, 'to_dataframe'):
|
|
99
|
+
# Some backends return cursors with to_dataframe()
|
|
100
|
+
df = cursor.to_dataframe()
|
|
101
|
+
else:
|
|
102
|
+
# Fallback: try to use pandas read_sql or fetchall
|
|
103
|
+
import pandas as pd
|
|
104
|
+
columns = [desc[0] for desc in cursor.description]
|
|
105
|
+
df = pd.DataFrame(cursor.fetchall(), columns=columns)
|
|
90
106
|
|
|
91
107
|
def convert_value(v):
|
|
92
108
|
if isinstance(v, (np.integer,)):
|
|
@@ -113,4 +129,7 @@ async def execute_sql(request: ExecuteSQLRequest):
|
|
|
113
129
|
raise HTTPException(status_code=500, detail=str(e))
|
|
114
130
|
|
|
115
131
|
if __name__ == "__main__":
|
|
132
|
+
nao_project_folder = os.getenv('NAO_PROJECT_FOLDER')
|
|
133
|
+
if nao_project_folder:
|
|
134
|
+
os.chdir(nao_project_folder)
|
|
116
135
|
uvicorn.run("main:app", host="0.0.0.0", port=port, reload=True)
|
|
@@ -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;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
CREATE TABLE "project" (
|
|
2
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
3
|
+
"name" text NOT NULL,
|
|
4
|
+
"type" text NOT NULL,
|
|
5
|
+
"path" text,
|
|
6
|
+
"slack_bot_token" text,
|
|
7
|
+
"slack_signing_secret" text,
|
|
8
|
+
"created_at" timestamp DEFAULT now() NOT NULL,
|
|
9
|
+
"updated_at" timestamp DEFAULT now() NOT NULL,
|
|
10
|
+
CONSTRAINT "local_project_path_required" CHECK (CASE WHEN "project"."type" = 'local' THEN "project"."path" IS NOT NULL ELSE TRUE END)
|
|
11
|
+
);
|
|
12
|
+
--> statement-breakpoint
|
|
13
|
+
CREATE TABLE "project_llm_config" (
|
|
14
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
15
|
+
"project_id" text NOT NULL,
|
|
16
|
+
"provider" text NOT NULL,
|
|
17
|
+
"api_key" text NOT NULL,
|
|
18
|
+
"created_at" timestamp DEFAULT now() NOT NULL,
|
|
19
|
+
"updated_at" timestamp DEFAULT now() NOT NULL,
|
|
20
|
+
CONSTRAINT "project_llm_config_unique" UNIQUE("id","project_id","provider")
|
|
21
|
+
);
|
|
22
|
+
--> statement-breakpoint
|
|
23
|
+
CREATE TABLE "project_member" (
|
|
24
|
+
"project_id" text NOT NULL,
|
|
25
|
+
"user_id" text NOT NULL,
|
|
26
|
+
"role" text NOT NULL,
|
|
27
|
+
"created_at" timestamp DEFAULT now() NOT NULL,
|
|
28
|
+
CONSTRAINT "project_member_project_id_user_id_pk" PRIMARY KEY("project_id","user_id")
|
|
29
|
+
);
|
|
30
|
+
--> statement-breakpoint
|
|
31
|
+
ALTER TABLE "chat" ADD COLUMN "project_id" text NOT NULL;--> statement-breakpoint
|
|
32
|
+
ALTER TABLE "project_llm_config" ADD CONSTRAINT "project_llm_config_project_id_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."project"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
33
|
+
ALTER TABLE "project_member" ADD CONSTRAINT "project_member_project_id_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."project"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
34
|
+
ALTER TABLE "project_member" ADD CONSTRAINT "project_member_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
35
|
+
CREATE INDEX "project_llm_config_projectId_idx" ON "project_llm_config" USING btree ("project_id");--> statement-breakpoint
|
|
36
|
+
CREATE INDEX "project_member_userId_idx" ON "project_member" USING btree ("user_id");--> statement-breakpoint
|
|
37
|
+
DELETE FROM "chat";--> statement-breakpoint
|
|
38
|
+
ALTER TABLE "chat" ADD CONSTRAINT "chat_project_id_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."project"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
39
|
+
CREATE INDEX "chat_projectId_idx" ON "chat" USING btree ("project_id");
|