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.
Files changed (192) hide show
  1. nl2sql/__init__.py +38 -0
  2. nl2sql/adapters/__init__.py +0 -0
  3. nl2sql/adapters/duckdb/__init__.py +0 -0
  4. nl2sql/adapters/duckdb/adapter.py +71 -0
  5. nl2sql/adapters/mssql/__init__.py +0 -0
  6. nl2sql/adapters/mssql/adapter.py +122 -0
  7. nl2sql/adapters/mysql/__init__.py +0 -0
  8. nl2sql/adapters/mysql/adapter.py +123 -0
  9. nl2sql/adapters/postgres/__init__.py +0 -0
  10. nl2sql/adapters/postgres/adapter.py +115 -0
  11. nl2sql/adapters/sqlalchemy_base/__init__.py +17 -0
  12. nl2sql/adapters/sqlalchemy_base/adapter.py +476 -0
  13. nl2sql/adapters/sqlalchemy_base/models.py +36 -0
  14. nl2sql/adapters/sqlite/__init__.py +0 -0
  15. nl2sql/adapters/sqlite/adapter.py +88 -0
  16. nl2sql/aggregation/__init__.py +3 -0
  17. nl2sql/aggregation/aggregator.py +98 -0
  18. nl2sql/aggregation/engines/__init__.py +3 -0
  19. nl2sql/aggregation/engines/polars_duckdb.py +125 -0
  20. nl2sql/api/__init__.py +0 -0
  21. nl2sql/api/auth_api.py +60 -0
  22. nl2sql/api/benchmark_api.py +114 -0
  23. nl2sql/api/datasource_api.py +132 -0
  24. nl2sql/api/indexing_api.py +59 -0
  25. nl2sql/api/llm_api.py +82 -0
  26. nl2sql/api/policy_api.py +135 -0
  27. nl2sql/api/query_api.py +138 -0
  28. nl2sql/api/result_api.py +24 -0
  29. nl2sql/api/settings_api.py +65 -0
  30. nl2sql/auth/__init__.py +8 -0
  31. nl2sql/auth/models.py +36 -0
  32. nl2sql/auth/rbac.py +25 -0
  33. nl2sql/cli/__init__.py +0 -0
  34. nl2sql/cli/checks.py +53 -0
  35. nl2sql/cli/commands/__init__.py +0 -0
  36. nl2sql/cli/commands/benchmark.py +34 -0
  37. nl2sql/cli/commands/doctor.py +49 -0
  38. nl2sql/cli/commands/indexing.py +126 -0
  39. nl2sql/cli/commands/info.py +25 -0
  40. nl2sql/cli/commands/install.py +27 -0
  41. nl2sql/cli/commands/policy.py +57 -0
  42. nl2sql/cli/commands/run.py +166 -0
  43. nl2sql/cli/commands/setup.py +415 -0
  44. nl2sql/cli/commands/visualize.py +34 -0
  45. nl2sql/cli/common/decorators.py +34 -0
  46. nl2sql/cli/config.py +24 -0
  47. nl2sql/cli/console.py +52 -0
  48. nl2sql/cli/demo/__init__.py +1 -0
  49. nl2sql/cli/demo/data.py +87 -0
  50. nl2sql/cli/demo/defaults.py +122 -0
  51. nl2sql/cli/demo/factory.py +289 -0
  52. nl2sql/cli/demo/manager.py +230 -0
  53. nl2sql/cli/demo/schemas.py +336 -0
  54. nl2sql/cli/demo/writers/__init__.py +0 -0
  55. nl2sql/cli/demo/writers/docker.py +182 -0
  56. nl2sql/cli/demo/writers/sqlite.py +88 -0
  57. nl2sql/cli/generators/datasources/__init__.py +3 -0
  58. nl2sql/cli/generators/datasources/generator.py +24 -0
  59. nl2sql/cli/generators/datasources/templates.py +7 -0
  60. nl2sql/cli/generators/env/__init__.py +3 -0
  61. nl2sql/cli/generators/env/generator.py +46 -0
  62. nl2sql/cli/generators/env/templates.py +25 -0
  63. nl2sql/cli/generators/llm/__init__.py +3 -0
  64. nl2sql/cli/generators/llm/generator.py +24 -0
  65. nl2sql/cli/generators/llm/templates.py +4 -0
  66. nl2sql/cli/generators/policies/__init__.py +3 -0
  67. nl2sql/cli/generators/policies/generator.py +20 -0
  68. nl2sql/cli/generators/policies/templates.py +2 -0
  69. nl2sql/cli/main.py +195 -0
  70. nl2sql/cli/reporting.py +878 -0
  71. nl2sql/cli/types.py +13 -0
  72. nl2sql/common/__init__.py +1 -0
  73. nl2sql/common/cancellation.py +25 -0
  74. nl2sql/common/context.py +5 -0
  75. nl2sql/common/errors.py +109 -0
  76. nl2sql/common/event_logger.py +88 -0
  77. nl2sql/common/exceptions.py +3 -0
  78. nl2sql/common/logger.py +119 -0
  79. nl2sql/common/metrics.py +50 -0
  80. nl2sql/common/resilience.py +59 -0
  81. nl2sql/common/settings.py +195 -0
  82. nl2sql/configs/__init__.py +6 -0
  83. nl2sql/configs/datasources.py +10 -0
  84. nl2sql/configs/llm.py +36 -0
  85. nl2sql/configs/manager.py +176 -0
  86. nl2sql/configs/policies.py +14 -0
  87. nl2sql/configs/sample_questions.py +11 -0
  88. nl2sql/configs/secrets.py +11 -0
  89. nl2sql/context.py +106 -0
  90. nl2sql/datasources/__init__.py +21 -0
  91. nl2sql/datasources/discovery.py +28 -0
  92. nl2sql/datasources/models.py +21 -0
  93. nl2sql/datasources/protocols.py +3 -0
  94. nl2sql/datasources/registry.py +172 -0
  95. nl2sql/evaluation/__init__.py +6 -0
  96. nl2sql/evaluation/benchmark_runner.py +320 -0
  97. nl2sql/evaluation/evaluator.py +134 -0
  98. nl2sql/evaluation/types.py +22 -0
  99. nl2sql/execution/__init__.py +4 -0
  100. nl2sql/execution/artifacts/__init__.py +3 -0
  101. nl2sql/execution/artifacts/parquet.py +41 -0
  102. nl2sql/execution/artifacts/store.py +165 -0
  103. nl2sql/execution/contracts.py +57 -0
  104. nl2sql/execution/execution_store.py +25 -0
  105. nl2sql/execution/executor/__init__.py +3 -0
  106. nl2sql/execution/executor/sql_executor.py +116 -0
  107. nl2sql/indexing/__init__.py +7 -0
  108. nl2sql/indexing/chunk_builder.py +227 -0
  109. nl2sql/indexing/embeddings.py +180 -0
  110. nl2sql/indexing/enrichment_service.py +316 -0
  111. nl2sql/indexing/models.py +209 -0
  112. nl2sql/indexing/orchestrator.py +90 -0
  113. nl2sql/indexing/vector_store.py +422 -0
  114. nl2sql/llm/__init__.py +8 -0
  115. nl2sql/llm/models.py +10 -0
  116. nl2sql/llm/registry.py +214 -0
  117. nl2sql/pipeline/__init__.py +1 -0
  118. nl2sql/pipeline/graph.py +73 -0
  119. nl2sql/pipeline/graph_utils.py +141 -0
  120. nl2sql/pipeline/nodes/__init__.py +25 -0
  121. nl2sql/pipeline/nodes/aggregator/__init__.py +4 -0
  122. nl2sql/pipeline/nodes/aggregator/node.py +55 -0
  123. nl2sql/pipeline/nodes/aggregator/prompts.py +20 -0
  124. nl2sql/pipeline/nodes/aggregator/schemas.py +28 -0
  125. nl2sql/pipeline/nodes/answer_synthesizer/__init__.py +4 -0
  126. nl2sql/pipeline/nodes/answer_synthesizer/node.py +98 -0
  127. nl2sql/pipeline/nodes/answer_synthesizer/prompts.py +19 -0
  128. nl2sql/pipeline/nodes/answer_synthesizer/schemas.py +24 -0
  129. nl2sql/pipeline/nodes/ast_planner/__init__.py +4 -0
  130. nl2sql/pipeline/nodes/ast_planner/node.py +104 -0
  131. nl2sql/pipeline/nodes/ast_planner/prompts.py +138 -0
  132. nl2sql/pipeline/nodes/ast_planner/schemas.py +236 -0
  133. nl2sql/pipeline/nodes/datasource_resolver/__init__.py +4 -0
  134. nl2sql/pipeline/nodes/datasource_resolver/node.py +253 -0
  135. nl2sql/pipeline/nodes/datasource_resolver/schemas.py +21 -0
  136. nl2sql/pipeline/nodes/decomposer/__init__.py +3 -0
  137. nl2sql/pipeline/nodes/decomposer/node.py +219 -0
  138. nl2sql/pipeline/nodes/decomposer/prompts.py +96 -0
  139. nl2sql/pipeline/nodes/decomposer/schemas.py +143 -0
  140. nl2sql/pipeline/nodes/executor/__init__.py +3 -0
  141. nl2sql/pipeline/nodes/executor/node.py +107 -0
  142. nl2sql/pipeline/nodes/generator/__init__.py +4 -0
  143. nl2sql/pipeline/nodes/generator/node.py +267 -0
  144. nl2sql/pipeline/nodes/generator/schemas.py +13 -0
  145. nl2sql/pipeline/nodes/global_planner/__init__.py +4 -0
  146. nl2sql/pipeline/nodes/global_planner/node.py +186 -0
  147. nl2sql/pipeline/nodes/global_planner/schemas.py +101 -0
  148. nl2sql/pipeline/nodes/refiner/__init__.py +4 -0
  149. nl2sql/pipeline/nodes/refiner/node.py +132 -0
  150. nl2sql/pipeline/nodes/refiner/prompts.py +28 -0
  151. nl2sql/pipeline/nodes/refiner/schemas.py +13 -0
  152. nl2sql/pipeline/nodes/schema_retriever/__init__.py +3 -0
  153. nl2sql/pipeline/nodes/schema_retriever/node.py +252 -0
  154. nl2sql/pipeline/nodes/schema_retriever/schema.py +27 -0
  155. nl2sql/pipeline/nodes/validator/__init__.py +7 -0
  156. nl2sql/pipeline/nodes/validator/node.py +839 -0
  157. nl2sql/pipeline/nodes/validator/schemas.py +12 -0
  158. nl2sql/pipeline/pipeline_runner.py +72 -0
  159. nl2sql/pipeline/routes.py +72 -0
  160. nl2sql/pipeline/runtime.py +153 -0
  161. nl2sql/pipeline/state.py +92 -0
  162. nl2sql/pipeline/subgraphs/__init__.py +5 -0
  163. nl2sql/pipeline/subgraphs/schemas.py +23 -0
  164. nl2sql/pipeline/subgraphs/sql_agent.py +167 -0
  165. nl2sql/public_api.py +199 -0
  166. nl2sql/schema/__init__.py +37 -0
  167. nl2sql/schema/in_memory_store.py +173 -0
  168. nl2sql/schema/protocol.py +88 -0
  169. nl2sql/schema/sqlite_store.py +233 -0
  170. nl2sql/schema/store.py +29 -0
  171. nl2sql/secrets/__init__.py +14 -0
  172. nl2sql/secrets/factory.py +85 -0
  173. nl2sql/secrets/interfaces.py +16 -0
  174. nl2sql/secrets/manager.py +139 -0
  175. nl2sql/secrets/models.py +56 -0
  176. nl2sql/secrets/providers/aws.py +30 -0
  177. nl2sql/secrets/providers/azure.py +49 -0
  178. nl2sql/secrets/providers/env.py +8 -0
  179. nl2sql/secrets/providers/hashi.py +46 -0
  180. nl2sql/services/__init__.py +0 -0
  181. nl2sql/services/callbacks/__init__.py +0 -0
  182. nl2sql/services/callbacks/monitor.py +84 -0
  183. nl2sql/services/callbacks/node_context.py +7 -0
  184. nl2sql/services/callbacks/node_handlers.py +187 -0
  185. nl2sql/services/callbacks/node_metrics.py +14 -0
  186. nl2sql/services/callbacks/presenter.py +12 -0
  187. nl2sql/services/callbacks/token_handler.py +56 -0
  188. nl2sql_engine-0.1.0.dist-info/METADATA +295 -0
  189. nl2sql_engine-0.1.0.dist-info/RECORD +192 -0
  190. nl2sql_engine-0.1.0.dist-info/WHEEL +5 -0
  191. nl2sql_engine-0.1.0.dist-info/entry_points.txt +9 -0
  192. nl2sql_engine-0.1.0.dist-info/top_level.txt +1 -0
@@ -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
+ )
@@ -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)
@@ -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
@@ -0,0 +1,8 @@
1
+ from .models import UserContext, RolePolicy
2
+ from .rbac import RBAC
3
+
4
+ __all__ = [
5
+ "UserContext",
6
+ "RBAC",
7
+ "RolePolicy"
8
+ ]
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
+