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
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Database syncing functionality for generating markdown documentation from database schemas."""
|
|
2
|
+
|
|
3
|
+
from .bigquery import sync_bigquery
|
|
4
|
+
from .databricks import sync_databricks
|
|
5
|
+
from .duckdb import sync_duckdb
|
|
6
|
+
from .postgres import sync_postgres
|
|
7
|
+
from .provider import DatabaseSyncProvider
|
|
8
|
+
from .snowflake import sync_snowflake
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"DatabaseSyncProvider",
|
|
12
|
+
"sync_bigquery",
|
|
13
|
+
"sync_databricks",
|
|
14
|
+
"sync_duckdb",
|
|
15
|
+
"sync_postgres",
|
|
16
|
+
"sync_snowflake",
|
|
17
|
+
]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from rich.progress import Progress
|
|
4
|
+
|
|
5
|
+
from nao_core.commands.sync.accessors import DataAccessor
|
|
6
|
+
from nao_core.commands.sync.cleanup import DatabaseSyncState
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def sync_bigquery(
|
|
10
|
+
db_config,
|
|
11
|
+
base_path: Path,
|
|
12
|
+
progress: Progress,
|
|
13
|
+
accessors: list[DataAccessor],
|
|
14
|
+
) -> DatabaseSyncState:
|
|
15
|
+
"""Sync BigQuery database schema to markdown files.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
db_config: The database configuration
|
|
19
|
+
base_path: Base output path
|
|
20
|
+
progress: Rich progress instance
|
|
21
|
+
accessors: List of data accessors to run
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
DatabaseSyncState with sync results and tracked paths
|
|
25
|
+
"""
|
|
26
|
+
conn = db_config.connect()
|
|
27
|
+
db_path = base_path / "type=bigquery" / f"database={db_config.project_id}"
|
|
28
|
+
state = DatabaseSyncState(db_path=db_path)
|
|
29
|
+
|
|
30
|
+
if db_config.dataset_id:
|
|
31
|
+
datasets = [db_config.dataset_id]
|
|
32
|
+
else:
|
|
33
|
+
datasets = conn.list_databases()
|
|
34
|
+
|
|
35
|
+
dataset_task = progress.add_task(
|
|
36
|
+
f"[dim]{db_config.name}[/dim]",
|
|
37
|
+
total=len(datasets),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
for dataset in datasets:
|
|
41
|
+
try:
|
|
42
|
+
all_tables = conn.list_tables(database=dataset)
|
|
43
|
+
except Exception:
|
|
44
|
+
progress.update(dataset_task, advance=1)
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
# Filter tables based on include/exclude patterns
|
|
48
|
+
tables = [t for t in all_tables if db_config.matches_pattern(dataset, t)]
|
|
49
|
+
|
|
50
|
+
# Skip dataset if no tables match
|
|
51
|
+
if not tables:
|
|
52
|
+
progress.update(dataset_task, advance=1)
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
dataset_path = db_path / f"schema={dataset}"
|
|
56
|
+
dataset_path.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
state.add_schema(dataset)
|
|
58
|
+
|
|
59
|
+
table_task = progress.add_task(
|
|
60
|
+
f" [cyan]{dataset}[/cyan]",
|
|
61
|
+
total=len(tables),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
for table in tables:
|
|
65
|
+
table_path = dataset_path / f"table={table}"
|
|
66
|
+
table_path.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
|
|
68
|
+
for accessor in accessors:
|
|
69
|
+
content = accessor.generate(conn, dataset, table)
|
|
70
|
+
output_file = table_path / accessor.filename
|
|
71
|
+
output_file.write_text(content)
|
|
72
|
+
|
|
73
|
+
state.add_table(dataset, table)
|
|
74
|
+
progress.update(table_task, advance=1)
|
|
75
|
+
|
|
76
|
+
progress.update(dataset_task, advance=1)
|
|
77
|
+
|
|
78
|
+
return state
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from rich.progress import Progress
|
|
4
|
+
|
|
5
|
+
from nao_core.commands.sync.accessors import DataAccessor
|
|
6
|
+
from nao_core.commands.sync.cleanup import DatabaseSyncState
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def sync_databricks(
|
|
10
|
+
db_config,
|
|
11
|
+
base_path: Path,
|
|
12
|
+
progress: Progress,
|
|
13
|
+
accessors: list[DataAccessor],
|
|
14
|
+
) -> DatabaseSyncState:
|
|
15
|
+
"""Sync Databricks database schema to markdown files.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
db_config: The database configuration
|
|
19
|
+
base_path: Base output path
|
|
20
|
+
progress: Rich progress instance
|
|
21
|
+
accessors: List of data accessors to run
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
DatabaseSyncState with sync results and tracked paths
|
|
25
|
+
"""
|
|
26
|
+
conn = db_config.connect()
|
|
27
|
+
catalog = db_config.catalog or "main"
|
|
28
|
+
db_path = base_path / "type=databricks" / f"database={catalog}"
|
|
29
|
+
state = DatabaseSyncState(db_path=db_path)
|
|
30
|
+
|
|
31
|
+
if db_config.schema:
|
|
32
|
+
schemas = [db_config.schema]
|
|
33
|
+
else:
|
|
34
|
+
schemas = conn.list_databases()
|
|
35
|
+
|
|
36
|
+
schema_task = progress.add_task(
|
|
37
|
+
f"[dim]{db_config.name}[/dim]",
|
|
38
|
+
total=len(schemas),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
for schema in schemas:
|
|
42
|
+
try:
|
|
43
|
+
all_tables = conn.list_tables(database=schema)
|
|
44
|
+
except Exception:
|
|
45
|
+
progress.update(schema_task, advance=1)
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
# Filter tables based on include/exclude patterns
|
|
49
|
+
tables = [t for t in all_tables if db_config.matches_pattern(schema, t)]
|
|
50
|
+
|
|
51
|
+
# Skip schema if no tables match
|
|
52
|
+
if not tables:
|
|
53
|
+
progress.update(schema_task, advance=1)
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
schema_path = db_path / f"schema={schema}"
|
|
57
|
+
schema_path.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
state.add_schema(schema)
|
|
59
|
+
|
|
60
|
+
table_task = progress.add_task(
|
|
61
|
+
f" [cyan]{schema}[/cyan]",
|
|
62
|
+
total=len(tables),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
for table in tables:
|
|
66
|
+
table_path = schema_path / f"table={table}"
|
|
67
|
+
table_path.mkdir(parents=True, exist_ok=True)
|
|
68
|
+
|
|
69
|
+
for accessor in accessors:
|
|
70
|
+
content = accessor.generate(conn, schema, table)
|
|
71
|
+
output_file = table_path / accessor.filename
|
|
72
|
+
output_file.write_text(content)
|
|
73
|
+
|
|
74
|
+
state.add_table(schema, table)
|
|
75
|
+
progress.update(table_task, advance=1)
|
|
76
|
+
|
|
77
|
+
progress.update(schema_task, advance=1)
|
|
78
|
+
|
|
79
|
+
return state
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from rich.progress import Progress
|
|
4
|
+
|
|
5
|
+
from nao_core.commands.sync.accessors import DataAccessor
|
|
6
|
+
from nao_core.commands.sync.cleanup import DatabaseSyncState
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def sync_duckdb(
|
|
10
|
+
db_config,
|
|
11
|
+
base_path: Path,
|
|
12
|
+
progress: Progress,
|
|
13
|
+
accessors: list[DataAccessor],
|
|
14
|
+
) -> DatabaseSyncState:
|
|
15
|
+
"""Sync DuckDB database schema to markdown files.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
db_config: The database configuration
|
|
19
|
+
base_path: Base output path
|
|
20
|
+
progress: Rich progress instance
|
|
21
|
+
accessors: List of data accessors to run
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
DatabaseSyncState with sync results and tracked paths
|
|
25
|
+
"""
|
|
26
|
+
conn = db_config.connect()
|
|
27
|
+
|
|
28
|
+
# Derive database name from path
|
|
29
|
+
if db_config.path == ":memory:":
|
|
30
|
+
db_name = "memory"
|
|
31
|
+
else:
|
|
32
|
+
db_name = Path(db_config.path).stem
|
|
33
|
+
|
|
34
|
+
db_path = base_path / "type=duckdb" / f"database={db_name}"
|
|
35
|
+
state = DatabaseSyncState(db_path=db_path)
|
|
36
|
+
|
|
37
|
+
# List all schemas in DuckDB
|
|
38
|
+
schemas = conn.list_databases()
|
|
39
|
+
|
|
40
|
+
schema_task = progress.add_task(
|
|
41
|
+
f"[dim]{db_config.name}[/dim]",
|
|
42
|
+
total=len(schemas),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
for schema in schemas:
|
|
46
|
+
try:
|
|
47
|
+
all_tables = conn.list_tables(database=schema)
|
|
48
|
+
except Exception:
|
|
49
|
+
progress.update(schema_task, advance=1)
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
# Filter tables based on include/exclude patterns
|
|
53
|
+
tables = [t for t in all_tables if db_config.matches_pattern(schema, t)]
|
|
54
|
+
|
|
55
|
+
# Skip schema if no tables match
|
|
56
|
+
if not tables:
|
|
57
|
+
progress.update(schema_task, advance=1)
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
schema_path = db_path / f"schema={schema}"
|
|
61
|
+
schema_path.mkdir(parents=True, exist_ok=True)
|
|
62
|
+
state.add_schema(schema)
|
|
63
|
+
|
|
64
|
+
table_task = progress.add_task(
|
|
65
|
+
f" [cyan]{schema}[/cyan]",
|
|
66
|
+
total=len(tables),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
for table in tables:
|
|
70
|
+
table_path = schema_path / f"table={table}"
|
|
71
|
+
table_path.mkdir(parents=True, exist_ok=True)
|
|
72
|
+
|
|
73
|
+
for accessor in accessors:
|
|
74
|
+
content = accessor.generate(conn, schema, table)
|
|
75
|
+
output_file = table_path / accessor.filename
|
|
76
|
+
output_file.write_text(content)
|
|
77
|
+
|
|
78
|
+
state.add_table(schema, table)
|
|
79
|
+
progress.update(table_task, advance=1)
|
|
80
|
+
|
|
81
|
+
progress.update(schema_task, advance=1)
|
|
82
|
+
|
|
83
|
+
return state
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from rich.progress import Progress
|
|
4
|
+
|
|
5
|
+
from nao_core.commands.sync.accessors import DataAccessor
|
|
6
|
+
from nao_core.commands.sync.cleanup import DatabaseSyncState
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def sync_postgres(
|
|
10
|
+
db_config,
|
|
11
|
+
base_path: Path,
|
|
12
|
+
progress: Progress,
|
|
13
|
+
accessors: list[DataAccessor],
|
|
14
|
+
) -> DatabaseSyncState:
|
|
15
|
+
"""Sync PostgreSQL database schema to markdown files.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
db_config: The database configuration
|
|
19
|
+
base_path: Base output path
|
|
20
|
+
progress: Rich progress instance
|
|
21
|
+
accessors: List of data accessors to run
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
DatabaseSyncState with sync results and tracked paths
|
|
25
|
+
"""
|
|
26
|
+
conn = db_config.connect()
|
|
27
|
+
db_path = base_path / "type=postgres" / f"database={db_config.database}"
|
|
28
|
+
state = DatabaseSyncState(db_path=db_path)
|
|
29
|
+
|
|
30
|
+
if db_config.schema_name:
|
|
31
|
+
schemas = [db_config.schema_name]
|
|
32
|
+
else:
|
|
33
|
+
schemas = conn.list_databases()
|
|
34
|
+
|
|
35
|
+
schema_task = progress.add_task(
|
|
36
|
+
f"[dim]{db_config.name}[/dim]",
|
|
37
|
+
total=len(schemas),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
for schema in schemas:
|
|
41
|
+
try:
|
|
42
|
+
all_tables = conn.list_tables(database=schema)
|
|
43
|
+
except Exception:
|
|
44
|
+
progress.update(schema_task, advance=1)
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
# Filter tables based on include/exclude patterns
|
|
48
|
+
tables = [t for t in all_tables if db_config.matches_pattern(schema, t)]
|
|
49
|
+
|
|
50
|
+
# Skip schema if no tables match
|
|
51
|
+
if not tables:
|
|
52
|
+
progress.update(schema_task, advance=1)
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
schema_path = db_path / f"schema={schema}"
|
|
56
|
+
schema_path.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
state.add_schema(schema)
|
|
58
|
+
|
|
59
|
+
table_task = progress.add_task(
|
|
60
|
+
f" [cyan]{schema}[/cyan]",
|
|
61
|
+
total=len(tables),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
for table in tables:
|
|
65
|
+
table_path = schema_path / f"table={table}"
|
|
66
|
+
table_path.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
|
|
68
|
+
for accessor in accessors:
|
|
69
|
+
content = accessor.generate(conn, schema, table)
|
|
70
|
+
output_file = table_path / accessor.filename
|
|
71
|
+
output_file.write_text(content)
|
|
72
|
+
|
|
73
|
+
state.add_table(schema, table)
|
|
74
|
+
progress.update(table_task, advance=1)
|
|
75
|
+
|
|
76
|
+
progress.update(schema_task, advance=1)
|
|
77
|
+
|
|
78
|
+
return state
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Database sync provider implementation."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
|
|
8
|
+
|
|
9
|
+
from nao_core.commands.sync.accessors import DataAccessor
|
|
10
|
+
from nao_core.commands.sync.cleanup import DatabaseSyncState, cleanup_stale_paths
|
|
11
|
+
from nao_core.commands.sync.registry import get_accessors
|
|
12
|
+
from nao_core.config import AnyDatabaseConfig, NaoConfig
|
|
13
|
+
|
|
14
|
+
from ..base import SyncProvider, SyncResult
|
|
15
|
+
from .bigquery import sync_bigquery
|
|
16
|
+
from .databricks import sync_databricks
|
|
17
|
+
from .duckdb import sync_duckdb
|
|
18
|
+
from .postgres import sync_postgres
|
|
19
|
+
from .snowflake import sync_snowflake
|
|
20
|
+
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
# Registry mapping database types to their sync functions
|
|
24
|
+
DATABASE_SYNC_FUNCTIONS = {
|
|
25
|
+
"bigquery": sync_bigquery,
|
|
26
|
+
"duckdb": sync_duckdb,
|
|
27
|
+
"databricks": sync_databricks,
|
|
28
|
+
"snowflake": sync_snowflake,
|
|
29
|
+
"postgres": sync_postgres,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DatabaseSyncProvider(SyncProvider):
|
|
34
|
+
"""Provider for syncing database schemas to markdown documentation."""
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def name(self) -> str:
|
|
38
|
+
return "Databases"
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def emoji(self) -> str:
|
|
42
|
+
return "🗄️"
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def default_output_dir(self) -> str:
|
|
46
|
+
return "databases"
|
|
47
|
+
|
|
48
|
+
def get_items(self, config: NaoConfig) -> list[AnyDatabaseConfig]:
|
|
49
|
+
return config.databases
|
|
50
|
+
|
|
51
|
+
def sync(self, items: list[Any], output_path: Path, project_path: Path | None = None) -> SyncResult:
|
|
52
|
+
"""Sync all configured databases.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
items: List of database configurations
|
|
56
|
+
output_path: Base path where database schemas are stored
|
|
57
|
+
project_path: Path to the nao project root (for template resolution)
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
SyncResult with datasets and tables synced
|
|
61
|
+
"""
|
|
62
|
+
if not items:
|
|
63
|
+
console.print("\n[dim]No databases configured[/dim]")
|
|
64
|
+
return SyncResult(provider_name=self.name, items_synced=0)
|
|
65
|
+
|
|
66
|
+
# Set project path for template resolution
|
|
67
|
+
DataAccessor.set_project_path(project_path)
|
|
68
|
+
|
|
69
|
+
total_datasets = 0
|
|
70
|
+
total_tables = 0
|
|
71
|
+
total_removed = 0
|
|
72
|
+
sync_states: list[DatabaseSyncState] = []
|
|
73
|
+
|
|
74
|
+
console.print(f"\n[bold cyan]{self.emoji} Syncing {self.name}[/bold cyan]")
|
|
75
|
+
console.print(f"[dim]Location:[/dim] {output_path.absolute()}\n")
|
|
76
|
+
|
|
77
|
+
with Progress(
|
|
78
|
+
SpinnerColumn(style="dim"),
|
|
79
|
+
TextColumn("[progress.description]{task.description}"),
|
|
80
|
+
BarColumn(bar_width=30, style="dim", complete_style="cyan", finished_style="green"),
|
|
81
|
+
TaskProgressColumn(),
|
|
82
|
+
console=console,
|
|
83
|
+
transient=False,
|
|
84
|
+
) as progress:
|
|
85
|
+
for db in items:
|
|
86
|
+
# Get accessors from database config
|
|
87
|
+
db_accessors = get_accessors(db.accessors)
|
|
88
|
+
accessor_names = [a.filename.replace(".md", "") for a in db_accessors]
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
console.print(f"[dim]{db.name} accessors:[/dim] {', '.join(accessor_names)}")
|
|
92
|
+
|
|
93
|
+
sync_fn = DATABASE_SYNC_FUNCTIONS.get(db.type)
|
|
94
|
+
if sync_fn:
|
|
95
|
+
state = sync_fn(db, output_path, progress, db_accessors)
|
|
96
|
+
sync_states.append(state)
|
|
97
|
+
total_datasets += state.schemas_synced
|
|
98
|
+
total_tables += state.tables_synced
|
|
99
|
+
else:
|
|
100
|
+
console.print(f"[yellow]⚠ Unsupported database type: {db.type}[/yellow]")
|
|
101
|
+
except Exception as e:
|
|
102
|
+
console.print(f"[bold red]✗[/bold red] Failed to sync {db.name}: {e}")
|
|
103
|
+
|
|
104
|
+
# Clean up stale files after all syncs complete
|
|
105
|
+
for state in sync_states:
|
|
106
|
+
removed = cleanup_stale_paths(state, verbose=True)
|
|
107
|
+
total_removed += removed
|
|
108
|
+
|
|
109
|
+
# Build summary
|
|
110
|
+
summary = f"{total_tables} tables across {total_datasets} datasets"
|
|
111
|
+
if total_removed > 0:
|
|
112
|
+
summary += f", {total_removed} stale removed"
|
|
113
|
+
|
|
114
|
+
return SyncResult(
|
|
115
|
+
provider_name=self.name,
|
|
116
|
+
items_synced=total_tables,
|
|
117
|
+
details={
|
|
118
|
+
"datasets": total_datasets,
|
|
119
|
+
"tables": total_tables,
|
|
120
|
+
"removed": total_removed,
|
|
121
|
+
},
|
|
122
|
+
summary=summary,
|
|
123
|
+
)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from rich.progress import Progress
|
|
4
|
+
|
|
5
|
+
from nao_core.commands.sync.accessors import DataAccessor
|
|
6
|
+
from nao_core.commands.sync.cleanup import DatabaseSyncState
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def sync_snowflake(
|
|
10
|
+
db_config,
|
|
11
|
+
base_path: Path,
|
|
12
|
+
progress: Progress,
|
|
13
|
+
accessors: list[DataAccessor],
|
|
14
|
+
) -> DatabaseSyncState:
|
|
15
|
+
"""Sync Snowflake database schema to markdown files.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
db_config: The database configuration
|
|
19
|
+
base_path: Base output path
|
|
20
|
+
progress: Rich progress instance
|
|
21
|
+
accessors: List of data accessors to run
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
DatabaseSyncState with sync results and tracked paths
|
|
25
|
+
"""
|
|
26
|
+
conn = db_config.connect()
|
|
27
|
+
db_path = base_path / "type=snowflake" / f"database={db_config.database}"
|
|
28
|
+
state = DatabaseSyncState(db_path=db_path)
|
|
29
|
+
|
|
30
|
+
if db_config.schema:
|
|
31
|
+
schemas = [db_config.schema]
|
|
32
|
+
else:
|
|
33
|
+
schemas = conn.list_databases()
|
|
34
|
+
|
|
35
|
+
schema_task = progress.add_task(
|
|
36
|
+
f"[dim]{db_config.name}[/dim]",
|
|
37
|
+
total=len(schemas),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
for schema in schemas:
|
|
41
|
+
try:
|
|
42
|
+
all_tables = conn.list_tables(database=schema)
|
|
43
|
+
except Exception:
|
|
44
|
+
progress.update(schema_task, advance=1)
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
# Filter tables based on include/exclude patterns
|
|
48
|
+
tables = [t for t in all_tables if db_config.matches_pattern(schema, t)]
|
|
49
|
+
|
|
50
|
+
# Skip schema if no tables match
|
|
51
|
+
if not tables:
|
|
52
|
+
progress.update(schema_task, advance=1)
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
schema_path = db_path / f"schema={schema}"
|
|
56
|
+
schema_path.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
state.add_schema(schema)
|
|
58
|
+
|
|
59
|
+
table_task = progress.add_task(
|
|
60
|
+
f" [cyan]{schema}[/cyan]",
|
|
61
|
+
total=len(tables),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
for table in tables:
|
|
65
|
+
table_path = schema_path / f"table={table}"
|
|
66
|
+
table_path.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
|
|
68
|
+
for accessor in accessors:
|
|
69
|
+
content = accessor.generate(conn, schema, table)
|
|
70
|
+
output_file = table_path / accessor.filename
|
|
71
|
+
output_file.write_text(content)
|
|
72
|
+
|
|
73
|
+
state.add_table(schema, table)
|
|
74
|
+
progress.update(table_task, advance=1)
|
|
75
|
+
|
|
76
|
+
progress.update(schema_task, advance=1)
|
|
77
|
+
|
|
78
|
+
return state
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
"""Repository
|
|
1
|
+
"""Repository sync provider implementation."""
|
|
2
2
|
|
|
3
3
|
import subprocess
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
5
6
|
|
|
6
7
|
from rich.console import Console
|
|
7
8
|
|
|
9
|
+
from nao_core.config import NaoConfig
|
|
8
10
|
from nao_core.config.repos import RepoConfig
|
|
9
11
|
|
|
12
|
+
from ..base import SyncProvider, SyncResult
|
|
13
|
+
|
|
10
14
|
console = Console()
|
|
11
15
|
|
|
12
16
|
|
|
@@ -76,28 +80,47 @@ def clone_or_pull_repo(repo: RepoConfig, base_path: Path) -> bool:
|
|
|
76
80
|
return False
|
|
77
81
|
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
"""
|
|
83
|
+
class RepositorySyncProvider(SyncProvider):
|
|
84
|
+
"""Provider for syncing git repositories."""
|
|
81
85
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
@property
|
|
87
|
+
def name(self) -> str:
|
|
88
|
+
return "Repositories"
|
|
85
89
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
@property
|
|
91
|
+
def emoji(self) -> str:
|
|
92
|
+
return "📦"
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def default_output_dir(self) -> str:
|
|
96
|
+
return "repos"
|
|
97
|
+
|
|
98
|
+
def get_items(self, config: NaoConfig) -> list[RepoConfig]:
|
|
99
|
+
return config.repos
|
|
100
|
+
|
|
101
|
+
def sync(self, items: list[Any], output_path: Path, project_path: Path | None = None) -> SyncResult:
|
|
102
|
+
"""Sync all configured repositories.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
items: List of repository configurations
|
|
106
|
+
output_path: Base path where repositories are stored
|
|
107
|
+
project_path: Path to the nao project root (unused for repos)
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
SyncResult with number of successfully synced repositories
|
|
111
|
+
"""
|
|
112
|
+
if not items:
|
|
113
|
+
return SyncResult(provider_name=self.name, items_synced=0)
|
|
91
114
|
|
|
92
|
-
|
|
93
|
-
|
|
115
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
116
|
+
success_count = 0
|
|
94
117
|
|
|
95
|
-
|
|
96
|
-
|
|
118
|
+
console.print(f"\n[bold cyan]{self.emoji} Syncing {self.name}[/bold cyan]")
|
|
119
|
+
console.print(f"[dim]Location:[/dim] {output_path.absolute()}\n")
|
|
97
120
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
121
|
+
for repo in items:
|
|
122
|
+
if clone_or_pull_repo(repo, output_path):
|
|
123
|
+
success_count += 1
|
|
124
|
+
console.print(f" [green]✓[/green] {repo.name}")
|
|
102
125
|
|
|
103
|
-
|
|
126
|
+
return SyncResult(provider_name=self.name, items_synced=success_count)
|
nao_core/config/__init__.py
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
from .base import NaoConfig
|
|
2
|
-
from .databases import
|
|
2
|
+
from .databases import (
|
|
3
|
+
AccessorType,
|
|
4
|
+
AnyDatabaseConfig,
|
|
5
|
+
BigQueryConfig,
|
|
6
|
+
DatabaseType,
|
|
7
|
+
DatabricksConfig,
|
|
8
|
+
DuckDBConfig,
|
|
9
|
+
PostgresConfig,
|
|
10
|
+
SnowflakeConfig,
|
|
11
|
+
)
|
|
12
|
+
from .exceptions import InitError
|
|
3
13
|
from .llm import LLMConfig, LLMProvider
|
|
4
14
|
from .slack import SlackConfig
|
|
5
15
|
|
|
@@ -8,8 +18,13 @@ __all__ = [
|
|
|
8
18
|
"AccessorType",
|
|
9
19
|
"AnyDatabaseConfig",
|
|
10
20
|
"BigQueryConfig",
|
|
21
|
+
"DuckDBConfig",
|
|
22
|
+
"DatabricksConfig",
|
|
23
|
+
"SnowflakeConfig",
|
|
24
|
+
"PostgresConfig",
|
|
11
25
|
"DatabaseType",
|
|
12
26
|
"LLMConfig",
|
|
13
27
|
"LLMProvider",
|
|
14
28
|
"SlackConfig",
|
|
29
|
+
"InitError",
|
|
15
30
|
]
|