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.
Files changed (73) hide show
  1. nao_core/__init__.py +1 -1
  2. nao_core/bin/fastapi/main.py +21 -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/0005_add_project_tables.sql +39 -0
  6. nao_core/bin/migrations-postgres/meta/0004_snapshot.json +847 -0
  7. nao_core/bin/migrations-postgres/meta/0005_snapshot.json +1129 -0
  8. nao_core/bin/migrations-postgres/meta/_journal.json +14 -0
  9. nao_core/bin/migrations-sqlite/0004_input_and_output_tokens.sql +8 -0
  10. nao_core/bin/migrations-sqlite/0005_add_project_tables.sql +38 -0
  11. nao_core/bin/migrations-sqlite/meta/0004_snapshot.json +819 -0
  12. nao_core/bin/migrations-sqlite/meta/0005_snapshot.json +1086 -0
  13. nao_core/bin/migrations-sqlite/meta/_journal.json +14 -0
  14. nao_core/bin/nao-chat-server +0 -0
  15. nao_core/bin/public/assets/{code-block-F6WJLWQG-EQr6mTlQ.js → code-block-F6WJLWQG-TAi8koem.js} +3 -3
  16. nao_core/bin/public/assets/index-BfHcd9Xz.css +1 -0
  17. nao_core/bin/public/assets/index-Mzo9bkag.js +557 -0
  18. nao_core/bin/public/index.html +2 -2
  19. nao_core/commands/chat.py +11 -10
  20. nao_core/commands/debug.py +10 -6
  21. nao_core/commands/init.py +66 -27
  22. nao_core/commands/sync/__init__.py +40 -21
  23. nao_core/commands/sync/accessors.py +219 -141
  24. nao_core/commands/sync/cleanup.py +133 -0
  25. nao_core/commands/sync/providers/__init__.py +30 -0
  26. nao_core/commands/sync/providers/base.py +87 -0
  27. nao_core/commands/sync/providers/databases/__init__.py +17 -0
  28. nao_core/commands/sync/providers/databases/bigquery.py +78 -0
  29. nao_core/commands/sync/providers/databases/databricks.py +79 -0
  30. nao_core/commands/sync/providers/databases/duckdb.py +83 -0
  31. nao_core/commands/sync/providers/databases/postgres.py +78 -0
  32. nao_core/commands/sync/providers/databases/provider.py +123 -0
  33. nao_core/commands/sync/providers/databases/snowflake.py +78 -0
  34. nao_core/commands/sync/providers/repositories/__init__.py +5 -0
  35. nao_core/commands/sync/{repositories.py → providers/repositories/provider.py} +43 -20
  36. nao_core/config/__init__.py +16 -1
  37. nao_core/config/base.py +23 -4
  38. nao_core/config/databases/__init__.py +37 -11
  39. nao_core/config/databases/base.py +7 -0
  40. nao_core/config/databases/bigquery.py +29 -1
  41. nao_core/config/databases/databricks.py +69 -0
  42. nao_core/config/databases/duckdb.py +33 -0
  43. nao_core/config/databases/postgres.py +78 -0
  44. nao_core/config/databases/snowflake.py +115 -0
  45. nao_core/config/exceptions.py +7 -0
  46. nao_core/templates/__init__.py +12 -0
  47. nao_core/templates/defaults/databases/columns.md.j2 +23 -0
  48. nao_core/templates/defaults/databases/description.md.j2 +32 -0
  49. nao_core/templates/defaults/databases/preview.md.j2 +22 -0
  50. nao_core/templates/defaults/databases/profiling.md.j2 +34 -0
  51. nao_core/templates/engine.py +133 -0
  52. {nao_core-0.0.29.dist-info → nao_core-0.0.31.dist-info}/METADATA +9 -4
  53. nao_core-0.0.31.dist-info/RECORD +86 -0
  54. nao_core/bin/public/assets/_chat-layout-BTlqRUE5.js +0 -1
  55. nao_core/bin/public/assets/_chat-layout.index-DOARokp1.js +0 -1
  56. nao_core/bin/public/assets/agentProvider-C6dGIy-H.js +0 -1
  57. nao_core/bin/public/assets/button-By_1dzVx.js +0 -1
  58. nao_core/bin/public/assets/folder-DnRS5rg3.js +0 -1
  59. nao_core/bin/public/assets/index-CElAN2SH.css +0 -1
  60. nao_core/bin/public/assets/index-ZTHASguQ.js +0 -59
  61. nao_core/bin/public/assets/input-CUQA5tsi.js +0 -1
  62. nao_core/bin/public/assets/login-BUQDum3t.js +0 -1
  63. nao_core/bin/public/assets/mermaid-FSSLJTFX-Dc6ZvCPw.js +0 -427
  64. nao_core/bin/public/assets/sidebar-bgEk7Xg8.js +0 -1
  65. nao_core/bin/public/assets/signinForm-CGAhnAkv.js +0 -1
  66. nao_core/bin/public/assets/signup-D2n11La3.js +0 -1
  67. nao_core/bin/public/assets/user-CYl8Tly2.js +0 -1
  68. nao_core/bin/public/assets/utils-DzJYey0s.js +0 -1
  69. nao_core/commands/sync/databases.py +0 -132
  70. nao_core-0.0.29.dist-info/RECORD +0 -69
  71. {nao_core-0.0.29.dist-info → nao_core-0.0.31.dist-info}/WHEEL +0 -0
  72. {nao_core-0.0.29.dist-info → nao_core-0.0.31.dist-info}/entry_points.txt +0 -0
  73. {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.29"
2
+ __version__ = "0.0.31"
@@ -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
- cursor = connection.raw_sql(request.sql) # type: ignore[attr-defined]
89
- df = cursor.to_dataframe()
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");