nl2sql-engine 0.1.0__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.
- nl2sql/__init__.py +38 -0
- nl2sql/adapters/__init__.py +0 -0
- nl2sql/adapters/duckdb/__init__.py +0 -0
- nl2sql/adapters/duckdb/adapter.py +71 -0
- nl2sql/adapters/mssql/__init__.py +0 -0
- nl2sql/adapters/mssql/adapter.py +122 -0
- nl2sql/adapters/mysql/__init__.py +0 -0
- nl2sql/adapters/mysql/adapter.py +123 -0
- nl2sql/adapters/postgres/__init__.py +0 -0
- nl2sql/adapters/postgres/adapter.py +115 -0
- nl2sql/adapters/sqlalchemy_base/__init__.py +17 -0
- nl2sql/adapters/sqlalchemy_base/adapter.py +476 -0
- nl2sql/adapters/sqlalchemy_base/models.py +36 -0
- nl2sql/adapters/sqlite/__init__.py +0 -0
- nl2sql/adapters/sqlite/adapter.py +88 -0
- nl2sql/aggregation/__init__.py +3 -0
- nl2sql/aggregation/aggregator.py +98 -0
- nl2sql/aggregation/engines/__init__.py +3 -0
- nl2sql/aggregation/engines/polars_duckdb.py +125 -0
- nl2sql/api/__init__.py +0 -0
- nl2sql/api/auth_api.py +60 -0
- nl2sql/api/benchmark_api.py +114 -0
- nl2sql/api/datasource_api.py +132 -0
- nl2sql/api/indexing_api.py +59 -0
- nl2sql/api/llm_api.py +82 -0
- nl2sql/api/policy_api.py +135 -0
- nl2sql/api/query_api.py +138 -0
- nl2sql/api/result_api.py +24 -0
- nl2sql/api/settings_api.py +65 -0
- nl2sql/auth/__init__.py +8 -0
- nl2sql/auth/models.py +36 -0
- nl2sql/auth/rbac.py +25 -0
- nl2sql/cli/__init__.py +0 -0
- nl2sql/cli/checks.py +53 -0
- nl2sql/cli/commands/__init__.py +0 -0
- nl2sql/cli/commands/benchmark.py +34 -0
- nl2sql/cli/commands/doctor.py +49 -0
- nl2sql/cli/commands/indexing.py +126 -0
- nl2sql/cli/commands/info.py +25 -0
- nl2sql/cli/commands/install.py +27 -0
- nl2sql/cli/commands/policy.py +57 -0
- nl2sql/cli/commands/run.py +166 -0
- nl2sql/cli/commands/setup.py +415 -0
- nl2sql/cli/commands/visualize.py +34 -0
- nl2sql/cli/common/decorators.py +34 -0
- nl2sql/cli/config.py +24 -0
- nl2sql/cli/console.py +52 -0
- nl2sql/cli/demo/__init__.py +1 -0
- nl2sql/cli/demo/data.py +87 -0
- nl2sql/cli/demo/defaults.py +122 -0
- nl2sql/cli/demo/factory.py +289 -0
- nl2sql/cli/demo/manager.py +230 -0
- nl2sql/cli/demo/schemas.py +336 -0
- nl2sql/cli/demo/writers/__init__.py +0 -0
- nl2sql/cli/demo/writers/docker.py +182 -0
- nl2sql/cli/demo/writers/sqlite.py +88 -0
- nl2sql/cli/generators/datasources/__init__.py +3 -0
- nl2sql/cli/generators/datasources/generator.py +24 -0
- nl2sql/cli/generators/datasources/templates.py +7 -0
- nl2sql/cli/generators/env/__init__.py +3 -0
- nl2sql/cli/generators/env/generator.py +46 -0
- nl2sql/cli/generators/env/templates.py +25 -0
- nl2sql/cli/generators/llm/__init__.py +3 -0
- nl2sql/cli/generators/llm/generator.py +24 -0
- nl2sql/cli/generators/llm/templates.py +4 -0
- nl2sql/cli/generators/policies/__init__.py +3 -0
- nl2sql/cli/generators/policies/generator.py +20 -0
- nl2sql/cli/generators/policies/templates.py +2 -0
- nl2sql/cli/main.py +195 -0
- nl2sql/cli/reporting.py +878 -0
- nl2sql/cli/types.py +13 -0
- nl2sql/common/__init__.py +1 -0
- nl2sql/common/cancellation.py +25 -0
- nl2sql/common/context.py +5 -0
- nl2sql/common/errors.py +109 -0
- nl2sql/common/event_logger.py +88 -0
- nl2sql/common/exceptions.py +3 -0
- nl2sql/common/logger.py +119 -0
- nl2sql/common/metrics.py +50 -0
- nl2sql/common/resilience.py +59 -0
- nl2sql/common/settings.py +195 -0
- nl2sql/configs/__init__.py +6 -0
- nl2sql/configs/datasources.py +10 -0
- nl2sql/configs/llm.py +36 -0
- nl2sql/configs/manager.py +176 -0
- nl2sql/configs/policies.py +14 -0
- nl2sql/configs/sample_questions.py +11 -0
- nl2sql/configs/secrets.py +11 -0
- nl2sql/context.py +106 -0
- nl2sql/datasources/__init__.py +21 -0
- nl2sql/datasources/discovery.py +28 -0
- nl2sql/datasources/models.py +21 -0
- nl2sql/datasources/protocols.py +3 -0
- nl2sql/datasources/registry.py +172 -0
- nl2sql/evaluation/__init__.py +6 -0
- nl2sql/evaluation/benchmark_runner.py +320 -0
- nl2sql/evaluation/evaluator.py +134 -0
- nl2sql/evaluation/types.py +22 -0
- nl2sql/execution/__init__.py +4 -0
- nl2sql/execution/artifacts/__init__.py +3 -0
- nl2sql/execution/artifacts/parquet.py +41 -0
- nl2sql/execution/artifacts/store.py +165 -0
- nl2sql/execution/contracts.py +57 -0
- nl2sql/execution/execution_store.py +25 -0
- nl2sql/execution/executor/__init__.py +3 -0
- nl2sql/execution/executor/sql_executor.py +116 -0
- nl2sql/indexing/__init__.py +7 -0
- nl2sql/indexing/chunk_builder.py +227 -0
- nl2sql/indexing/embeddings.py +180 -0
- nl2sql/indexing/enrichment_service.py +316 -0
- nl2sql/indexing/models.py +209 -0
- nl2sql/indexing/orchestrator.py +90 -0
- nl2sql/indexing/vector_store.py +422 -0
- nl2sql/llm/__init__.py +8 -0
- nl2sql/llm/models.py +10 -0
- nl2sql/llm/registry.py +214 -0
- nl2sql/pipeline/__init__.py +1 -0
- nl2sql/pipeline/graph.py +73 -0
- nl2sql/pipeline/graph_utils.py +141 -0
- nl2sql/pipeline/nodes/__init__.py +25 -0
- nl2sql/pipeline/nodes/aggregator/__init__.py +4 -0
- nl2sql/pipeline/nodes/aggregator/node.py +55 -0
- nl2sql/pipeline/nodes/aggregator/prompts.py +20 -0
- nl2sql/pipeline/nodes/aggregator/schemas.py +28 -0
- nl2sql/pipeline/nodes/answer_synthesizer/__init__.py +4 -0
- nl2sql/pipeline/nodes/answer_synthesizer/node.py +98 -0
- nl2sql/pipeline/nodes/answer_synthesizer/prompts.py +19 -0
- nl2sql/pipeline/nodes/answer_synthesizer/schemas.py +24 -0
- nl2sql/pipeline/nodes/ast_planner/__init__.py +4 -0
- nl2sql/pipeline/nodes/ast_planner/node.py +104 -0
- nl2sql/pipeline/nodes/ast_planner/prompts.py +138 -0
- nl2sql/pipeline/nodes/ast_planner/schemas.py +236 -0
- nl2sql/pipeline/nodes/datasource_resolver/__init__.py +4 -0
- nl2sql/pipeline/nodes/datasource_resolver/node.py +253 -0
- nl2sql/pipeline/nodes/datasource_resolver/schemas.py +21 -0
- nl2sql/pipeline/nodes/decomposer/__init__.py +3 -0
- nl2sql/pipeline/nodes/decomposer/node.py +219 -0
- nl2sql/pipeline/nodes/decomposer/prompts.py +96 -0
- nl2sql/pipeline/nodes/decomposer/schemas.py +143 -0
- nl2sql/pipeline/nodes/executor/__init__.py +3 -0
- nl2sql/pipeline/nodes/executor/node.py +107 -0
- nl2sql/pipeline/nodes/generator/__init__.py +4 -0
- nl2sql/pipeline/nodes/generator/node.py +267 -0
- nl2sql/pipeline/nodes/generator/schemas.py +13 -0
- nl2sql/pipeline/nodes/global_planner/__init__.py +4 -0
- nl2sql/pipeline/nodes/global_planner/node.py +186 -0
- nl2sql/pipeline/nodes/global_planner/schemas.py +101 -0
- nl2sql/pipeline/nodes/refiner/__init__.py +4 -0
- nl2sql/pipeline/nodes/refiner/node.py +132 -0
- nl2sql/pipeline/nodes/refiner/prompts.py +28 -0
- nl2sql/pipeline/nodes/refiner/schemas.py +13 -0
- nl2sql/pipeline/nodes/schema_retriever/__init__.py +3 -0
- nl2sql/pipeline/nodes/schema_retriever/node.py +252 -0
- nl2sql/pipeline/nodes/schema_retriever/schema.py +27 -0
- nl2sql/pipeline/nodes/validator/__init__.py +7 -0
- nl2sql/pipeline/nodes/validator/node.py +839 -0
- nl2sql/pipeline/nodes/validator/schemas.py +12 -0
- nl2sql/pipeline/pipeline_runner.py +72 -0
- nl2sql/pipeline/routes.py +72 -0
- nl2sql/pipeline/runtime.py +153 -0
- nl2sql/pipeline/state.py +92 -0
- nl2sql/pipeline/subgraphs/__init__.py +5 -0
- nl2sql/pipeline/subgraphs/schemas.py +23 -0
- nl2sql/pipeline/subgraphs/sql_agent.py +167 -0
- nl2sql/public_api.py +199 -0
- nl2sql/schema/__init__.py +37 -0
- nl2sql/schema/in_memory_store.py +173 -0
- nl2sql/schema/protocol.py +88 -0
- nl2sql/schema/sqlite_store.py +233 -0
- nl2sql/schema/store.py +29 -0
- nl2sql/secrets/__init__.py +14 -0
- nl2sql/secrets/factory.py +85 -0
- nl2sql/secrets/interfaces.py +16 -0
- nl2sql/secrets/manager.py +139 -0
- nl2sql/secrets/models.py +56 -0
- nl2sql/secrets/providers/aws.py +30 -0
- nl2sql/secrets/providers/azure.py +49 -0
- nl2sql/secrets/providers/env.py +8 -0
- nl2sql/secrets/providers/hashi.py +46 -0
- nl2sql/services/__init__.py +0 -0
- nl2sql/services/callbacks/__init__.py +0 -0
- nl2sql/services/callbacks/monitor.py +84 -0
- nl2sql/services/callbacks/node_context.py +7 -0
- nl2sql/services/callbacks/node_handlers.py +187 -0
- nl2sql/services/callbacks/node_metrics.py +14 -0
- nl2sql/services/callbacks/presenter.py +12 -0
- nl2sql/services/callbacks/token_handler.py +56 -0
- nl2sql_engine-0.1.0.dist-info/METADATA +295 -0
- nl2sql_engine-0.1.0.dist-info/RECORD +192 -0
- nl2sql_engine-0.1.0.dist-info/WHEEL +5 -0
- nl2sql_engine-0.1.0.dist-info/entry_points.txt +9 -0
- nl2sql_engine-0.1.0.dist-info/top_level.txt +1 -0
nl2sql/api/policy_api.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Policy API for NL2SQL.
|
|
3
|
+
|
|
4
|
+
Provides public entry points for policy validation and integrity checks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import pathlib
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import List, Optional, Set
|
|
12
|
+
|
|
13
|
+
from nl2sql.common.settings import settings
|
|
14
|
+
from nl2sql.configs import ConfigManager
|
|
15
|
+
from nl2sql.context import NL2SQLContext
|
|
16
|
+
from nl2sql.datasources import DatasourceRegistry
|
|
17
|
+
from nl2sql.secrets import secret_manager
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class PolicyValidationEntry:
|
|
22
|
+
role: str
|
|
23
|
+
target: str
|
|
24
|
+
status: str
|
|
25
|
+
details: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class PolicyValidationReport:
|
|
30
|
+
ok: bool
|
|
31
|
+
entries: List[PolicyValidationEntry] = field(default_factory=list)
|
|
32
|
+
errors: List[str] = field(default_factory=list)
|
|
33
|
+
available_datasources: Set[str] = field(default_factory=set)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PolicyAPI:
|
|
37
|
+
"""
|
|
38
|
+
API for policy validation and integrity checks.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, ctx: Optional[NL2SQLContext] = None):
|
|
42
|
+
self._ctx = ctx
|
|
43
|
+
|
|
44
|
+
def validate_policies(
|
|
45
|
+
self,
|
|
46
|
+
policies_path: Optional[pathlib.Path] = None,
|
|
47
|
+
datasources_path: Optional[pathlib.Path] = None,
|
|
48
|
+
secrets_path: Optional[pathlib.Path] = None,
|
|
49
|
+
) -> PolicyValidationReport:
|
|
50
|
+
"""
|
|
51
|
+
Validate policy syntax and integrity against defined datasources.
|
|
52
|
+
"""
|
|
53
|
+
cm = self._ctx.config_manager if self._ctx else ConfigManager()
|
|
54
|
+
|
|
55
|
+
policies_path = pathlib.Path(policies_path) if policies_path else pathlib.Path(settings.policies_config_path)
|
|
56
|
+
datasources_path = pathlib.Path(datasources_path) if datasources_path else pathlib.Path(settings.datasource_config_path)
|
|
57
|
+
secrets_path = pathlib.Path(secrets_path) if secrets_path else pathlib.Path(settings.secrets_config_path)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
policy_cfg = cm.load_policies(policies_path)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
return PolicyValidationReport(ok=False, errors=[str(e)])
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
secret_configs = cm.load_secrets(secrets_path)
|
|
66
|
+
if secret_configs:
|
|
67
|
+
secret_manager.configure(secret_configs)
|
|
68
|
+
|
|
69
|
+
ds_configs = cm.load_datasources(datasources_path)
|
|
70
|
+
registry = DatasourceRegistry(secret_manager)
|
|
71
|
+
registry.register_datasources(ds_configs)
|
|
72
|
+
available_ds = set(registry.list_ids())
|
|
73
|
+
except Exception as e:
|
|
74
|
+
return PolicyValidationReport(ok=False, errors=[str(e)])
|
|
75
|
+
|
|
76
|
+
entries: List[PolicyValidationEntry] = []
|
|
77
|
+
has_errors = False
|
|
78
|
+
|
|
79
|
+
for role_id, role_def in policy_cfg.roles.items():
|
|
80
|
+
for ds in role_def.allowed_datasources:
|
|
81
|
+
if ds == "*":
|
|
82
|
+
entries.append(
|
|
83
|
+
PolicyValidationEntry(role=role_id, target="Datasource: *", status="OK", details="Global Access")
|
|
84
|
+
)
|
|
85
|
+
continue
|
|
86
|
+
if ds not in available_ds:
|
|
87
|
+
entries.append(
|
|
88
|
+
PolicyValidationEntry(
|
|
89
|
+
role=role_id,
|
|
90
|
+
target=f"Datasource: {ds}",
|
|
91
|
+
status="MISSING",
|
|
92
|
+
details="Datasource not defined in config",
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
has_errors = True
|
|
96
|
+
else:
|
|
97
|
+
entries.append(
|
|
98
|
+
PolicyValidationEntry(role=role_id, target=f"Datasource: {ds}", status="OK", details="Verified")
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
for rule in role_def.allowed_tables:
|
|
102
|
+
if rule == "*":
|
|
103
|
+
entries.append(
|
|
104
|
+
PolicyValidationEntry(role=role_id, target="Table: *", status="OK", details="Global Access")
|
|
105
|
+
)
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
parts = rule.split(".")
|
|
109
|
+
if len(parts) >= 2:
|
|
110
|
+
ds_part = parts[0]
|
|
111
|
+
if ds_part not in available_ds and ds_part != "*":
|
|
112
|
+
entries.append(
|
|
113
|
+
PolicyValidationEntry(
|
|
114
|
+
role=role_id,
|
|
115
|
+
target=f"Table Rule: {rule}",
|
|
116
|
+
status="INVALID_DS",
|
|
117
|
+
details=f"Datasource '{ds_part}' unknown",
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
has_errors = True
|
|
121
|
+
else:
|
|
122
|
+
entries.append(
|
|
123
|
+
PolicyValidationEntry(
|
|
124
|
+
role=role_id,
|
|
125
|
+
target=f"Table Rule: {rule}",
|
|
126
|
+
status="OK",
|
|
127
|
+
details="DS Verified",
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return PolicyValidationReport(
|
|
132
|
+
ok=not has_errors,
|
|
133
|
+
entries=entries,
|
|
134
|
+
available_datasources=available_ds,
|
|
135
|
+
)
|
nl2sql/api/query_api.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Query API for NL2SQL
|
|
3
|
+
|
|
4
|
+
Provides functionality for executing natural language queries against databases.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Optional, List, Dict, Any
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
from nl2sql.context import NL2SQLContext
|
|
13
|
+
from nl2sql.pipeline.runtime import run_with_graph
|
|
14
|
+
from nl2sql.auth import UserContext
|
|
15
|
+
from nl2sql.execution.contracts import ArtifactRef
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SubQueryResult(BaseModel):
|
|
19
|
+
"""Represents the result of a sub-query execution."""
|
|
20
|
+
id: str = Field(default="")
|
|
21
|
+
intent: str = Field(default="")
|
|
22
|
+
sql: str = Field(default="")
|
|
23
|
+
datasource_id: str = Field(default="")
|
|
24
|
+
schema_version: str = Field(default="")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class QueryResult(BaseModel):
|
|
28
|
+
"""Represents the result of a query execution.
|
|
29
|
+
|
|
30
|
+
Row data is deliberately not inlined: results live in artifact storage and are
|
|
31
|
+
addressable through ``artifact_refs``.
|
|
32
|
+
"""
|
|
33
|
+
sub_queries: List[SubQueryResult] = Field(default_factory=list)
|
|
34
|
+
final_answer: Optional[Dict[str, Any]] = None
|
|
35
|
+
errors: List[Dict[str, Any]] = Field(default_factory=list)
|
|
36
|
+
trace_id: str = Field(default="")
|
|
37
|
+
reasoning: List[Dict[str, Any]] = Field(default_factory=list)
|
|
38
|
+
warnings: List[Dict[str, Any]] = Field(default_factory=list)
|
|
39
|
+
artifact_refs: Dict[str, ArtifactRef] = Field(default_factory=dict)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _field(source: Any, name: str, default: Any = None) -> Any:
|
|
43
|
+
"""Read ``name`` off a mapping or an object.
|
|
44
|
+
|
|
45
|
+
LangGraph may hand back either dicts or model instances for nested state.
|
|
46
|
+
"""
|
|
47
|
+
if source is None:
|
|
48
|
+
return default
|
|
49
|
+
if isinstance(source, dict):
|
|
50
|
+
return source.get(name, default)
|
|
51
|
+
return getattr(source, name, default)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _enum_value(value: Any) -> str:
|
|
55
|
+
"""Render an enum (or plain value) as its string value."""
|
|
56
|
+
if value is None:
|
|
57
|
+
return ""
|
|
58
|
+
return str(getattr(value, "value", value))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _error_summary(error: Any) -> Dict[str, Any]:
|
|
62
|
+
"""Project a PipelineError onto a client-safe summary (no stack traces)."""
|
|
63
|
+
return {
|
|
64
|
+
"node": _field(error, "node", "") or "",
|
|
65
|
+
"message": _field(error, "message", "") or "",
|
|
66
|
+
"error_code": _enum_value(_field(error, "error_code")),
|
|
67
|
+
"severity": _enum_value(_field(error, "severity")),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _sub_query_results(state: Dict[str, Any]) -> List[SubQueryResult]:
|
|
72
|
+
"""Build the per-sub-query view from ``subgraph_outputs``."""
|
|
73
|
+
results: List[SubQueryResult] = []
|
|
74
|
+
for output in (state.get("subgraph_outputs") or {}).values():
|
|
75
|
+
sub_query = _field(output, "sub_query")
|
|
76
|
+
if not sub_query:
|
|
77
|
+
continue
|
|
78
|
+
results.append(
|
|
79
|
+
SubQueryResult(
|
|
80
|
+
id=_field(sub_query, "id", "") or "",
|
|
81
|
+
intent=_field(sub_query, "intent", "") or "",
|
|
82
|
+
datasource_id=_field(sub_query, "datasource_id", "") or "",
|
|
83
|
+
schema_version=_field(sub_query, "schema_version", "") or "",
|
|
84
|
+
sql=_field(output, "sql_draft", "") or "",
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
return results
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def result_from_state(state: Dict[str, Any]) -> QueryResult:
|
|
91
|
+
"""Build a typed :class:`QueryResult` from a raw pipeline graph state."""
|
|
92
|
+
state = state or {}
|
|
93
|
+
return QueryResult(
|
|
94
|
+
sub_queries=_sub_query_results(state),
|
|
95
|
+
final_answer=_field(state.get("answer_synthesizer_response"), "final_answer"),
|
|
96
|
+
errors=[_error_summary(error) for error in (state.get("errors") or [])],
|
|
97
|
+
trace_id=state.get("trace_id") or "",
|
|
98
|
+
reasoning=list(state.get("reasoning") or []),
|
|
99
|
+
warnings=list(state.get("warnings") or []),
|
|
100
|
+
artifact_refs=state.get("artifact_refs") or {},
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class QueryAPI:
|
|
105
|
+
"""
|
|
106
|
+
API for executing natural language queries against databases.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def __init__(self, ctx: NL2SQLContext):
|
|
110
|
+
self._ctx = ctx
|
|
111
|
+
|
|
112
|
+
def run_query(
|
|
113
|
+
self,
|
|
114
|
+
natural_language: str,
|
|
115
|
+
datasource_id: Optional[str] = None,
|
|
116
|
+
execute: bool = True,
|
|
117
|
+
user_context: Optional[UserContext] = None,
|
|
118
|
+
) -> QueryResult:
|
|
119
|
+
"""
|
|
120
|
+
Execute a natural language query against the database.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
natural_language: The natural language query to execute
|
|
124
|
+
datasource_id: Optional specific datasource to query (otherwise auto-resolved)
|
|
125
|
+
execute: Whether to actually execute the SQL against the database
|
|
126
|
+
user_context: Optional user context for permissions
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
A :class:`QueryResult` built from the pipeline graph state.
|
|
130
|
+
"""
|
|
131
|
+
state = run_with_graph(
|
|
132
|
+
self._ctx,
|
|
133
|
+
natural_language,
|
|
134
|
+
datasource_id=datasource_id,
|
|
135
|
+
execute=execute,
|
|
136
|
+
user_context=user_context
|
|
137
|
+
)
|
|
138
|
+
return result_from_state(state)
|
nl2sql/api/result_api.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Result API for NL2SQL
|
|
3
|
+
|
|
4
|
+
Provides functionality for result management and storage.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from nl2sql.context import NL2SQLContext
|
|
12
|
+
from nl2sql.execution.contracts import ArtifactRef
|
|
13
|
+
from nl2sql_adapter_sdk.contracts import ResultFrame
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ResultAPI:
|
|
17
|
+
"""
|
|
18
|
+
API for result management and storage.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, ctx: NL2SQLContext):
|
|
22
|
+
self._ctx = ctx
|
|
23
|
+
|
|
24
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Settings API for NL2SQL
|
|
3
|
+
|
|
4
|
+
Provides functionality for configuration and settings management.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Dict, Any
|
|
10
|
+
|
|
11
|
+
from nl2sql.context import NL2SQLContext
|
|
12
|
+
from nl2sql.common.settings import Settings, settings
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SettingsAPI:
|
|
16
|
+
"""
|
|
17
|
+
API for configuration and settings management.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, ctx: NL2SQLContext):
|
|
21
|
+
self._ctx = ctx
|
|
22
|
+
# ``settings`` is a module singleton that ``reload_settings()`` updates in
|
|
23
|
+
# place, so binding it once here keeps reading the live values.
|
|
24
|
+
self._settings: Settings = settings
|
|
25
|
+
|
|
26
|
+
def get_current_settings(self) -> Dict[str, Any]:
|
|
27
|
+
"""
|
|
28
|
+
Get the current application settings.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Dictionary of current settings
|
|
32
|
+
"""
|
|
33
|
+
if self._settings:
|
|
34
|
+
return self._settings.model_dump()
|
|
35
|
+
else:
|
|
36
|
+
# Fallback to the global settings
|
|
37
|
+
from nl2sql.common.settings import settings
|
|
38
|
+
return settings.model_dump()
|
|
39
|
+
|
|
40
|
+
def get_setting(self, key: str) -> Any:
|
|
41
|
+
"""
|
|
42
|
+
Get a specific setting value.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
key: Setting key to retrieve
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Value of the setting
|
|
49
|
+
"""
|
|
50
|
+
settings_dict = self.get_current_settings()
|
|
51
|
+
return settings_dict.get(key)
|
|
52
|
+
|
|
53
|
+
def validate_configuration(self) -> bool:
|
|
54
|
+
"""
|
|
55
|
+
Validate the current configuration.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
True if configuration is valid, False otherwise
|
|
59
|
+
"""
|
|
60
|
+
try:
|
|
61
|
+
# Try to access all settings to validate them
|
|
62
|
+
self.get_current_settings()
|
|
63
|
+
return True
|
|
64
|
+
except Exception:
|
|
65
|
+
return False
|
nl2sql/auth/__init__.py
ADDED
nl2sql/auth/models.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field, field_validator
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
from pydantic import ConfigDict
|
|
4
|
+
|
|
5
|
+
class UserContext(BaseModel):
|
|
6
|
+
"""User identity and permission context."""
|
|
7
|
+
user_id: Optional[str] = Field(default=None, description="Unique identifier for the user.")
|
|
8
|
+
tenant_id: Optional[str] = Field(default=None, description="Organization/Tenant identifier.")
|
|
9
|
+
roles: List[str] = Field(default_factory=list, description="List of assigned roles.")
|
|
10
|
+
model_config = ConfigDict(extra="ignore")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RolePolicy(BaseModel):
|
|
14
|
+
"""Defines access control rules for a specific role."""
|
|
15
|
+
|
|
16
|
+
description: str = Field(..., description="Human-readable description of the role")
|
|
17
|
+
role: str = Field(..., description="Role ID used for logging and auditing")
|
|
18
|
+
allowed_datasources: List[str] = Field(default_factory=list, description="List of allowed datasource IDs or '*'")
|
|
19
|
+
allowed_tables: List[str] = Field(default_factory=list, description="List of allowed tables in 'datasource.table' format")
|
|
20
|
+
|
|
21
|
+
@field_validator("allowed_tables")
|
|
22
|
+
def validate_namespace(cls, v: List[str]) -> List[str]:
|
|
23
|
+
"""Enforces strict namespacing for allowed tables."""
|
|
24
|
+
for table in v:
|
|
25
|
+
if table == "*":
|
|
26
|
+
continue
|
|
27
|
+
|
|
28
|
+
if table.endswith(".*"):
|
|
29
|
+
if table.count(".") < 1:
|
|
30
|
+
raise ValueError(f"Invalid wildcard '{table}'. Must be 'datasource.*'.")
|
|
31
|
+
continue
|
|
32
|
+
|
|
33
|
+
if "." not in table:
|
|
34
|
+
raise ValueError(f"Invalid table '{table}'. Policy requires explicit 'datasource.table' format to prevent ambiguity.")
|
|
35
|
+
|
|
36
|
+
return v
|
nl2sql/auth/rbac.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from .models import RolePolicy
|
|
2
|
+
from .models import UserContext
|
|
3
|
+
from typing import List, Dict
|
|
4
|
+
|
|
5
|
+
class RBAC:
|
|
6
|
+
def __init__(self, policies: Dict[str, RolePolicy]):
|
|
7
|
+
self.policies = policies
|
|
8
|
+
|
|
9
|
+
def is_allowed(self, user_ctx: UserContext, datasource_id: str, table: str) -> bool:
|
|
10
|
+
policy = [self.policies.get(role) for role in user_ctx.roles]
|
|
11
|
+
if not policy:
|
|
12
|
+
return False
|
|
13
|
+
return any(p.is_allowed(datasource_id, table) for p in policy)
|
|
14
|
+
|
|
15
|
+
def get_allowed_tables(self, user_ctx: UserContext) -> List[str]:
|
|
16
|
+
policy = [self.policies.get(role) for role in user_ctx.roles]
|
|
17
|
+
if not policy:
|
|
18
|
+
return []
|
|
19
|
+
return list(set().union(*[p.allowed_tables for p in policy]))
|
|
20
|
+
|
|
21
|
+
def get_allowed_datasources(self, user_ctx: UserContext) -> List[str]:
|
|
22
|
+
policy = [self.policies.get(role) for role in user_ctx.roles]
|
|
23
|
+
if not policy:
|
|
24
|
+
return []
|
|
25
|
+
return list(set().union(*[p.allowed_datasources for p in policy]))
|
nl2sql/cli/__init__.py
ADDED
|
File without changes
|
nl2sql/cli/checks.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
from typing import Dict, Tuple
|
|
3
|
+
import pathlib
|
|
4
|
+
from rich.markup import escape
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
from rich.text import Text
|
|
7
|
+
from nl2sql.cli.console import console, print_success, print_error
|
|
8
|
+
|
|
9
|
+
def check_package(name: str) -> bool:
|
|
10
|
+
"""Checks if a python package is installed."""
|
|
11
|
+
import_name = name.replace("-", "_")
|
|
12
|
+
return importlib.util.find_spec(import_name) is not None
|
|
13
|
+
|
|
14
|
+
def verify_connectivity(print_table: bool = True) -> bool:
|
|
15
|
+
"""
|
|
16
|
+
Checks connectivity for all profiles in the default config.
|
|
17
|
+
Returns True if all checks passed, False otherwise.
|
|
18
|
+
"""
|
|
19
|
+
from nl2sql.common.settings import settings
|
|
20
|
+
from nl2sql.datasources import load_profiles
|
|
21
|
+
from nl2sql.diagnostics import check_connectivity as core_check
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
profiles = load_profiles(pathlib.Path(settings.datasource_config_path))
|
|
25
|
+
|
|
26
|
+
with console.status("[bold green]Verifying connectivity...[/bold green]"):
|
|
27
|
+
results = core_check(list(profiles.values()))
|
|
28
|
+
|
|
29
|
+
all_ok = True
|
|
30
|
+
|
|
31
|
+
if print_table:
|
|
32
|
+
conn_table = Table(show_header=True, header_style="bold cyan")
|
|
33
|
+
conn_table.add_column("Datasource ID")
|
|
34
|
+
conn_table.add_column("Status")
|
|
35
|
+
conn_table.add_column("Details")
|
|
36
|
+
|
|
37
|
+
for ds_id, (success, msg) in results.items():
|
|
38
|
+
if not success:
|
|
39
|
+
all_ok = False
|
|
40
|
+
|
|
41
|
+
if print_table:
|
|
42
|
+
status = "[green]OK[/green]" if success else "[red]Failed[/red]"
|
|
43
|
+
details = msg if not success else ""
|
|
44
|
+
conn_table.add_row(Text(str(ds_id)), status, Text(str(details)))
|
|
45
|
+
|
|
46
|
+
if print_table:
|
|
47
|
+
console.print(conn_table)
|
|
48
|
+
|
|
49
|
+
return all_ok
|
|
50
|
+
|
|
51
|
+
except Exception as e:
|
|
52
|
+
console.print(f"[red]Connectivity check failed: {escape(str(e))}[/red]")
|
|
53
|
+
return False
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from nl2sql import BenchmarkAPI, BenchmarkConfig
|
|
3
|
+
from nl2sql.cli.reporting import ConsolePresenter
|
|
4
|
+
from nl2sql.cli.common.decorators import handle_cli_errors
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@handle_cli_errors
|
|
8
|
+
def run_benchmark(
|
|
9
|
+
config: BenchmarkConfig,
|
|
10
|
+
) -> None:
|
|
11
|
+
"""Runs the benchmark suite based on provided arguments.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
config (BenchmarkConfig): Benchmark run configuration.
|
|
15
|
+
"""
|
|
16
|
+
presenter = ConsolePresenter()
|
|
17
|
+
api = BenchmarkAPI()
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
matrix_result = api.run_matrix(config, progress_callback=presenter.track)
|
|
21
|
+
except Exception as e:
|
|
22
|
+
presenter.print_error(f"Benchmark Failed: {e}")
|
|
23
|
+
sys.exit(1)
|
|
24
|
+
|
|
25
|
+
for name, result in matrix_result.results_by_config.items():
|
|
26
|
+
presenter.print_header(f"Evaluating Config: {name}")
|
|
27
|
+
presenter.print_dataset_benchmark_results(
|
|
28
|
+
result.results,
|
|
29
|
+
iterations=result.iterations,
|
|
30
|
+
routing_only=config.routing_only,
|
|
31
|
+
)
|
|
32
|
+
presenter.print_metrics_summary(result.metrics, result.results, routing_only=config.routing_only)
|
|
33
|
+
if config.export_path:
|
|
34
|
+
presenter.export_results(result.results, config.export_path)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import importlib.util
|
|
3
|
+
from rich.markup import escape
|
|
4
|
+
from rich.table import Table
|
|
5
|
+
from rich.panel import Panel
|
|
6
|
+
from nl2sql.cli.console import console, print_success, print_error
|
|
7
|
+
from nl2sql.cli.config import ADAPTER_DRIVERS, KNOWN_ADAPTERS
|
|
8
|
+
from nl2sql.cli.checks import check_package, verify_connectivity
|
|
9
|
+
|
|
10
|
+
from nl2sql.cli.common.decorators import handle_cli_errors
|
|
11
|
+
|
|
12
|
+
@handle_cli_errors
|
|
13
|
+
def doctor_command():
|
|
14
|
+
console.print(Panel("[bold cyan]NL2SQL Doctor[/bold cyan]"))
|
|
15
|
+
|
|
16
|
+
# 1. Python Version
|
|
17
|
+
py_ver = sys.version.split()[0]
|
|
18
|
+
console.print(f"Python Version: {py_ver}")
|
|
19
|
+
if sys.version_info < (3, 9):
|
|
20
|
+
print_error("Python 3.9+ required.")
|
|
21
|
+
else:
|
|
22
|
+
print_success("Python version OK.")
|
|
23
|
+
|
|
24
|
+
# 2. Core Check
|
|
25
|
+
if importlib.util.find_spec("nl2sql"):
|
|
26
|
+
print_success("Core package (nl2sql) installed.")
|
|
27
|
+
else:
|
|
28
|
+
print_error("Core package (nl2sql) NOT found.")
|
|
29
|
+
|
|
30
|
+
# 3. Adapters
|
|
31
|
+
console.print("\n[bold]Adapters:[/bold]")
|
|
32
|
+
table = Table(show_header=True, header_style="bold magenta")
|
|
33
|
+
table.add_column("Database")
|
|
34
|
+
table.add_column("Package")
|
|
35
|
+
table.add_column("Status")
|
|
36
|
+
|
|
37
|
+
for name, pkg in KNOWN_ADAPTERS.items():
|
|
38
|
+
# The adapter module always ships with nl2sql; the driver is what an
|
|
39
|
+
# extra adds, so that is what decides whether the dialect is usable.
|
|
40
|
+
ok = check_package(ADAPTER_DRIVERS[name])
|
|
41
|
+
status = "[green]Installed[/green]" if ok else "[red]Missing[/red]"
|
|
42
|
+
table.add_row(name, escape(pkg), status)
|
|
43
|
+
|
|
44
|
+
console.print(table)
|
|
45
|
+
|
|
46
|
+
# 4. Connectivity Check
|
|
47
|
+
console.print("\n[bold]Connectivity:[/bold]")
|
|
48
|
+
verify_connectivity(print_table=True)
|
|
49
|
+
|