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,10 @@
1
+
2
+ from typing import Optional, Dict, Any, Union, List
3
+ from pydantic import BaseModel, Field
4
+ from nl2sql.datasources import DatasourceConfig, ConnectionConfig
5
+
6
+
7
+ class DatasourceFileConfig(BaseModel):
8
+ """File-level schema for datasources.yaml."""
9
+ version: int = Field(1, description="Schema version")
10
+ datasources: List[DatasourceConfig]
nl2sql/configs/llm.py ADDED
@@ -0,0 +1,36 @@
1
+
2
+ from typing import Optional, Dict
3
+ from pydantic import BaseModel, Field, SecretStr, field_serializer
4
+
5
+ class AgentConfig(BaseModel):
6
+ """Configuration for a specific agent's LLM.
7
+
8
+ The single definition. ``nl2sql.llm.models`` re-exports this class, so
9
+ ``ConfigManager``/``LLMGenerator`` and ``LLMRegistry`` share one model.
10
+ Do not add a second copy: the previous duplicate silently swallowed a fix
11
+ applied to only one of the two.
12
+ """
13
+ provider: str
14
+ model: str
15
+ temperature: float = 0.0
16
+ api_key: Optional[SecretStr] = None
17
+ base_url: Optional[str] = Field(
18
+ None,
19
+ description=(
20
+ "Override the provider endpoint. Defaults to the provider preset: "
21
+ "the OpenRouter gateway for 'openrouter', the local Ollama daemon "
22
+ "for 'ollama', the client default for 'openai'. Set it to reach any "
23
+ "other OpenAI-compatible endpoint."
24
+ ),
25
+ )
26
+ name: str = Field("default", description="Name of the agent")
27
+
28
+ @field_serializer("api_key", when_used="json")
29
+ def _serialize_api_key(self, value):
30
+ return value.get_secret_value() if value else None
31
+
32
+ class LLMFileConfig(BaseModel):
33
+ """Global LLM configuration (File Envelope)."""
34
+ version: int = Field(1, description="Schema version")
35
+ default: AgentConfig
36
+ agents: Optional[Dict[str, AgentConfig]] = Field(default_factory=dict)
@@ -0,0 +1,176 @@
1
+
2
+ import yaml
3
+ import json
4
+ import shutil
5
+ import pathlib
6
+ from typing import List, Dict, Optional, Union
7
+ from pydantic import ValidationError
8
+
9
+ from nl2sql.common.settings import settings
10
+ from .datasources import DatasourceConfig, DatasourceFileConfig
11
+ from .llm import LLMFileConfig
12
+ from .policies import PolicyFileConfig
13
+ from .secrets import SecretProviderConfig, SecretsFileConfig
14
+ from .sample_questions import SampleQuestionsFileConfig
15
+
16
+ class ConfigManager:
17
+ """
18
+ Centralized manager for reading and writing application configuration.
19
+ Enforces consistency and handles file I/O for Datasources, LLMs, Policies, and Secrets.
20
+
21
+ Secret references such as ``${env:VAR}`` are returned verbatim. Resolution
22
+ happens at point of use, in ``DatasourceRegistry`` and ``LLMRegistry``.
23
+ Do not resolve them here: a resolved config written back to disk would
24
+ persist real credentials in plaintext.
25
+ """
26
+
27
+ def __init__(self, project_root: Optional[pathlib.Path] = None):
28
+ """
29
+ Args:
30
+ project_root: Optional override for project root.
31
+ If None, uses settings paths or CWD resolution strategy.
32
+ """
33
+ self.project_root = project_root
34
+
35
+ # Resolve root: Use override, or CWD
36
+ root = self.project_root or pathlib.Path.cwd()
37
+
38
+ # Default paths from settings if not overridden
39
+ self._ds_path = root / settings.datasource_config_path
40
+ self._llm_path = root / settings.llm_config_path
41
+ self._policy_path = root / settings.policies_config_path
42
+ self._secrets_path = root / settings.secrets_config_path
43
+ self._sample_questions_path = root / settings.sample_questions_path
44
+
45
+ def ensure_config_dirs(self) -> None:
46
+ """Ensures that the configuration directories exist."""
47
+ for path in [self._ds_path, self._llm_path, self._policy_path, self._secrets_path]:
48
+ if not path.parent.exists():
49
+ path.parent.mkdir(parents=True, exist_ok=True)
50
+
51
+ def load_datasources(self, path: Optional[pathlib.Path] = None) -> List[DatasourceConfig]:
52
+ """
53
+ Loads datasource configurations from YAML.
54
+ Handles Legacy (Dict) vs V2 (List under 'datasources' key) formats.
55
+ """
56
+ target_path = path or self._ds_path
57
+
58
+ if not target_path.exists():
59
+ raise FileNotFoundError(f"Datasource config not found: {target_path}")
60
+
61
+ try:
62
+ content = target_path.read_text(encoding="utf-8")
63
+ raw = yaml.safe_load(content) or {}
64
+ except ImportError as exc:
65
+ raise RuntimeError("PyYAML is required to load datasource configs") from exc
66
+ except Exception as e:
67
+ raise ValueError(f"Failed to parse YAML from {target_path}: {e}")
68
+
69
+ try:
70
+ # Structurally validate the file envelope
71
+ file_config = DatasourceFileConfig.model_validate(raw)
72
+ return file_config.datasources
73
+ except ValidationError as e:
74
+ raise ValueError(f"Datasource Configuration Invalid: {e}")
75
+
76
+ def load_llm(self, path: Optional[pathlib.Path] = None) -> LLMFileConfig:
77
+ """
78
+ Loads LLM configuration.
79
+ Returns nl2sql.configs.LLMFileConfig object.
80
+ """
81
+ target_path = path or self._llm_path
82
+
83
+ if not target_path.exists():
84
+ raise FileNotFoundError(f"LLM config not found: {target_path}")
85
+
86
+ try:
87
+ data = yaml.safe_load(target_path.read_text()) or {}
88
+
89
+ # Use File Config for validation
90
+ return LLMFileConfig.model_validate(data)
91
+ except ValidationError as e:
92
+ raise ValueError(f"LLM Configuration Invalid: {e}")
93
+
94
+
95
+
96
+ def load_policies(self, path: Optional[pathlib.Path] = None) -> PolicyFileConfig:
97
+ """
98
+ Loads Policy configuration.
99
+ Returns nl2sql.configs.PolicyFileConfig object.
100
+ """
101
+ target_path = path or self._policy_path
102
+
103
+ if not target_path.exists():
104
+ raise FileNotFoundError(f"Policy config not found: {target_path}")
105
+
106
+ try:
107
+ with open(target_path, "r") as f:
108
+ raw_json = f.read()
109
+
110
+ data = json.loads(raw_json)
111
+ # Use File Config for validation
112
+ return PolicyFileConfig.model_validate(data)
113
+ except ValidationError as ve:
114
+ raise ValueError(f"Policy Schema Validation Failed: {ve}")
115
+ except Exception as e:
116
+ raise ValueError(f"Failed to load policies: {e}")
117
+
118
+
119
+
120
+ def load_secrets(self, path: Optional[pathlib.Path] = None) -> List[SecretProviderConfig]:
121
+ """Loads Secret configurations."""
122
+ target_path = path or self._secrets_path
123
+ if not target_path.exists():
124
+ return []
125
+
126
+ try:
127
+ content = target_path.read_text(encoding="utf-8")
128
+ raw = yaml.safe_load(content) or []
129
+
130
+ file_config = SecretsFileConfig.model_validate(raw)
131
+ return file_config.providers
132
+ except ValidationError as e:
133
+ raise ValueError(f"Secret Configuration Invalid: {e}")
134
+ except Exception as e:
135
+ raise ValueError(f"Failed to load secrets: {e}")
136
+
137
+
138
+ def load_sample_questions(self, path: Optional[pathlib.Path] = None, ds_id: Optional[str] = None) -> Union[Dict[str, List[str]], List[str]]:
139
+ """Loads sample questions from a YAML file."""
140
+ target_path = path or self._sample_questions_path
141
+
142
+ if not target_path.exists():
143
+ raise FileNotFoundError(f"Sample questions file not found: {target_path}")
144
+
145
+ try:
146
+ raw = yaml.safe_load(target_path.read_text(encoding="utf-8")) or {}
147
+
148
+ if ds_id:
149
+ return raw.get(ds_id, [])
150
+
151
+ return raw
152
+ except Exception as e:
153
+ raise ValueError(f"Failed to load sample questions: {e}")
154
+
155
+ def get_example_questions(self, datasource_id: str) -> List[str]:
156
+ """Returns example questions for a datasource, if configured."""
157
+ try:
158
+ questions = self.load_sample_questions(ds_id=datasource_id)
159
+ return questions or []
160
+ except FileNotFoundError:
161
+ return []
162
+
163
+ def get_datasource_description(self, datasource_id: str) -> Optional[str]:
164
+ """Returns the configured datasource description if available."""
165
+ try:
166
+ datasources = self.load_datasources()
167
+ except FileNotFoundError:
168
+ return None
169
+ for ds in datasources:
170
+ if ds.id == datasource_id:
171
+ return ds.description
172
+ return None
173
+
174
+
175
+
176
+
@@ -0,0 +1,14 @@
1
+
2
+ from typing import List, Dict, Optional
3
+ from pydantic import BaseModel, Field, field_validator
4
+ from nl2sql.auth import RolePolicy
5
+
6
+
7
+
8
+
9
+ class PolicyFileConfig(BaseModel):
10
+ """File-level schema for policies.json."""
11
+ version: int = Field(1, description="Schema version")
12
+ roles: Dict[str, RolePolicy]
13
+
14
+
@@ -0,0 +1,11 @@
1
+ from typing import Dict, List
2
+
3
+ from pydantic import BaseModel, Field, ConfigDict
4
+
5
+
6
+ class SampleQuestionsFileConfig(BaseModel):
7
+ """File-level schema for sample_questions.yaml."""
8
+
9
+ datasources: Dict[str, List[str]] = Field(default_factory=dict)
10
+
11
+ model_config = ConfigDict(extra="ignore")
@@ -0,0 +1,11 @@
1
+
2
+ from typing import List
3
+
4
+ from pydantic import BaseModel, Field
5
+ from nl2sql.secrets.models import SecretProviderConfig
6
+
7
+ class SecretsFileConfig(BaseModel):
8
+ """File-level schema for secrets.yaml."""
9
+ version: int = Field(1, description="Schema version")
10
+ providers: List[SecretProviderConfig]
11
+
nl2sql/context.py ADDED
@@ -0,0 +1,106 @@
1
+ from __future__ import annotations
2
+ import pathlib
3
+ from typing import Optional
4
+
5
+ from nl2sql.configs import ConfigManager
6
+ from nl2sql.datasources import DatasourceRegistry
7
+ from nl2sql.llm import LLMRegistry
8
+ from nl2sql.indexing.vector_store import VectorStore
9
+ from nl2sql.secrets import SecretManager
10
+ from nl2sql.common.settings import settings
11
+ from nl2sql.auth import RBAC
12
+
13
+ from nl2sql.schema import build_schema_store
14
+ from nl2sql.execution import ExecutionStore
15
+ from nl2sql.execution.artifacts import build_artifact_store
16
+
17
+ from nl2sql.common.logger import get_logger
18
+ logger = get_logger(__name__)
19
+
20
+
21
+ class NL2SQLContext:
22
+ """
23
+ Centralized application context that manages the initialization lifecycle.
24
+
25
+ Ensures that secrets are loaded BEFORE datasources, and handles
26
+ registry instantiation in a consistent order.
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ ds_config_path: Optional[pathlib.Path] = None,
32
+ secrets_config_path: Optional[pathlib.Path] = None,
33
+ llm_config_path: Optional[pathlib.Path] = None,
34
+ vector_store_path: Optional[pathlib.Path] = None,
35
+ policies_config_path: Optional[pathlib.Path] = None,
36
+ ) :
37
+ """
38
+ Factory method to create a context from configuration paths.
39
+ Resolves defaults from global settings if paths are not provided.
40
+ """
41
+ ds_config_path = ds_config_path or pathlib.Path(settings.datasource_config_path)
42
+ secrets_config_path = secrets_config_path or pathlib.Path(settings.secrets_config_path)
43
+ llm_config_path = llm_config_path or pathlib.Path(settings.llm_config_path)
44
+ policies_config_path = policies_config_path or pathlib.Path(settings.policies_config_path)
45
+
46
+
47
+ logger.info(f"Loading datasource configuration from {ds_config_path}")
48
+ logger.info(f"Loading secrets configuration from {secrets_config_path}")
49
+ logger.info(f"Loading LLM configuration from {llm_config_path}")
50
+ logger.info(f"Loading policies configuration from {policies_config_path}")
51
+ logger.info(f"Loading vector store configuration from {vector_store_path}")
52
+
53
+ # Validate vector store configuration first so misconfiguration fails fast,
54
+ # before secrets, datasource and LLM registries are constructed.
55
+ vector_store_collection_name = settings.vector_store_collection_name
56
+ if not vector_store_collection_name:
57
+ raise ValueError(
58
+ "VECTOR_STORE_COLLECTION name is not configured. "
59
+ "Set vector_store_collection_name in settings."
60
+ )
61
+
62
+ vector_store_path = vector_store_path or (
63
+ pathlib.Path(settings.vector_store_path) if settings.vector_store_path else None
64
+ )
65
+ if not vector_store_path:
66
+ raise ValueError(
67
+ "VECTOR_STORE path is not configured. "
68
+ "Pass vector_store_path or set vector_store_path in settings."
69
+ )
70
+
71
+ cm = ConfigManager()
72
+ self.tenant_id = settings.tenant_id
73
+ self.config_manager = cm
74
+
75
+ secret_configs = cm.load_secrets(secrets_config_path)
76
+ secret_manager = SecretManager()
77
+ if secret_configs:
78
+ secret_manager.configure(secret_configs)
79
+
80
+ ds_configs = cm.load_datasources(ds_config_path)
81
+ self.ds_registry = DatasourceRegistry(secret_manager)
82
+ self.ds_registry.register_datasources(ds_configs)
83
+
84
+ llm_cfg = cm.load_llm(llm_config_path)
85
+ self.llm_registry = LLMRegistry(secret_manager)
86
+
87
+ agents = llm_cfg.agents or {}
88
+ agents["default"] = llm_cfg.default
89
+ self.llm_registry.register_llms(agents)
90
+
91
+ self.policies_cfg = cm.load_policies(policies_config_path)
92
+ self.rbac = RBAC(self.policies_cfg.roles)
93
+
94
+ self.vector_store = VectorStore(
95
+ collection_name=vector_store_collection_name,
96
+ persist_directory=vector_store_path,
97
+ )
98
+ self.schema_store = build_schema_store(
99
+ settings.schema_store_backend,
100
+ settings.schema_store_max_versions,
101
+ path=pathlib.Path(settings.schema_store_path),
102
+ )
103
+ self.execution_store = ExecutionStore()
104
+ self.artifact_store = build_artifact_store()
105
+
106
+
@@ -0,0 +1,21 @@
1
+ """Datasource management, configuration, and adapter discovery."""
2
+ from nl2sql.datasources.registry import DatasourceRegistry
3
+ from nl2sql.datasources.discovery import discover_adapters
4
+ from nl2sql.datasources.models import (
5
+ DatasourceConfig,
6
+ ConnectionConfig,
7
+ )
8
+ from nl2sql.datasources.protocols import DatasourceAdapterProtocol
9
+ from nl2sql_adapter_sdk.contracts import AdapterRequest, ResultFrame
10
+ from nl2sql_adapter_sdk.capabilities import DatasourceCapability
11
+
12
+ __all__ = [
13
+ "DatasourceRegistry",
14
+ "discover_adapters",
15
+ "DatasourceConfig",
16
+ "ConnectionConfig",
17
+ "DatasourceAdapterProtocol",
18
+ "AdapterRequest",
19
+ "DatasourceCapability",
20
+ "ResultFrame",
21
+ ]
@@ -0,0 +1,28 @@
1
+ from importlib.metadata import entry_points
2
+ from typing import Dict, Type
3
+
4
+ from nl2sql.datasources.protocols import DatasourceAdapterProtocol
5
+ from nl2sql.common.logger import get_logger
6
+
7
+ logger = get_logger(__name__)
8
+
9
+ def discover_adapters() -> Dict[str, Type[DatasourceAdapterProtocol]]:
10
+ """Discovers installed adapters via 'nl2sql.adapters' entry points.
11
+
12
+ Returns:
13
+ Dict[str, Type[DatasourceAdapterProtocol]]: Dict mapping adapter name (e.g., 'postgres')
14
+ to the Adapter Class.
15
+ """
16
+ adapters = {}
17
+ try:
18
+ eps = entry_points(group="nl2sql.adapters")
19
+ except TypeError:
20
+ eps = entry_points().get("nl2sql.adapters", [])
21
+ for ep in eps:
22
+ try:
23
+ AdapterCls = ep.load()
24
+ adapters[ep.name] = AdapterCls
25
+ except Exception as e:
26
+ logger.error(f"Failed to load adapter {ep.name}: {e}")
27
+
28
+ return adapters
@@ -0,0 +1,21 @@
1
+
2
+
3
+ from pydantic import BaseModel, Field
4
+ from typing import Optional, Dict, Any, List
5
+
6
+
7
+ class ConnectionConfig(BaseModel):
8
+ """Database connection details."""
9
+ type: str
10
+
11
+ model_config = {"extra": "allow"}
12
+
13
+ class DatasourceConfig(BaseModel):
14
+ """Configuration for a single datasource."""
15
+ id: str
16
+ description: Optional[str] = None
17
+ connection: ConnectionConfig
18
+ options: Dict[str, Any] = Field(default_factory=dict)
19
+
20
+
21
+
@@ -0,0 +1,3 @@
1
+ from nl2sql_adapter_sdk.protocols import DatasourceAdapterProtocol
2
+
3
+ __all__ = ["DatasourceAdapterProtocol"]
@@ -0,0 +1,172 @@
1
+ from typing import Dict, List, Any, Set
2
+ from threading import RLock
3
+
4
+ from nl2sql_adapter_sdk.capabilities import DatasourceCapability
5
+ from nl2sql.datasources.discovery import discover_adapters
6
+ from nl2sql.datasources.protocols import DatasourceAdapterProtocol
7
+ from nl2sql.secrets import SecretManager
8
+ from .models import DatasourceConfig, ConnectionConfig
9
+
10
+ class DatasourceRegistry:
11
+ """Manages a collection of active DatasourceAdapters.
12
+
13
+ Acts as the factory and cache for DataSourceAdapter instances.
14
+ """
15
+
16
+ def __init__(self, secret_manager: SecretManager):
17
+ """Initializes the registry by eagerly creating adapters for all configs."""
18
+ self._adapters: Dict[str, DatasourceAdapterProtocol] = {}
19
+ self._capabilities: Dict[str, Set[str]] = {}
20
+ self._available_adapters = discover_adapters()
21
+ self._secret_manager = secret_manager
22
+ self._lock = RLock()
23
+
24
+ def find_and_resolve_secret(self, key: str) -> str:
25
+ """Attempts to resolve a secret using the secret manager.
26
+
27
+ Args:
28
+ key (str): The name of the secret to resolve.
29
+
30
+ Returns:
31
+ str: The resolved secret value.
32
+
33
+ Raises:
34
+ ValueError: If the secret cannot be resolved.
35
+ """
36
+ return self._secret_manager.resolve(key)
37
+
38
+ def resolved_connection(self, unresolved_connection: ConnectionConfig) -> ConnectionConfig:
39
+ """Resolves all secrets in a connection dictionary.
40
+
41
+ Args:
42
+ unresolved_connection (ConnectionConfig): The connection dictionary with potential secrets.
43
+
44
+ Returns:
45
+ ConnectionConfig: The connection dictionary with resolved secrets.
46
+ """
47
+ from pydantic import SecretStr
48
+
49
+ resolved_connection = unresolved_connection.model_dump()
50
+ for key, value in unresolved_connection.model_dump().items():
51
+ if isinstance(value, str) and value.startswith("${") and value.endswith("}"):
52
+ secret_val = self.find_and_resolve_secret(value)
53
+ resolved_connection[key] = SecretStr(secret_val)
54
+ return ConnectionConfig(**resolved_connection)
55
+
56
+ def register_datasources(self, configs: List[DatasourceConfig]):
57
+ for config in configs:
58
+ try:
59
+ self.register_datasource(config)
60
+ except Exception as e:
61
+ raise ValueError(f"Failed to initialize adapter for '{config.id}': {e}") from e
62
+
63
+ def _normalize_capabilities(self, caps: Any) -> Set[str]:
64
+ if not caps:
65
+ return set()
66
+ normalized = set()
67
+ for cap in caps:
68
+ if isinstance(cap, DatasourceCapability):
69
+ normalized.add(cap.value)
70
+ else:
71
+ normalized.add(str(cap))
72
+ return normalized
73
+
74
+ def register_datasource(self, config: DatasourceConfig) -> DatasourceAdapterProtocol:
75
+ """Registers a new datasource dynamically.
76
+
77
+ Args:
78
+ config: The datasource configuration dictionary or object.
79
+
80
+ Returns:
81
+ DatasourceAdapterProtocol: The created and registered adapter.
82
+
83
+ Raises:
84
+ ValueError: If configuration is invalid or adapter type is unknown.
85
+ """
86
+ ds_id = config.id
87
+ if not ds_id:
88
+ raise ValueError("Datasource ID is required. Please check your configuration.")
89
+
90
+ connection = config.connection
91
+ conn_type = connection.type.lower()
92
+ resolved_connection = self.resolved_connection(connection)
93
+ connection_args = resolved_connection.model_dump()
94
+
95
+ if conn_type in self._available_adapters:
96
+ adapter_cls = self._available_adapters[conn_type]
97
+
98
+ adapter = adapter_cls(
99
+ datasource_id=ds_id,
100
+ datasource_engine_type=conn_type,
101
+ connection_args=connection_args,
102
+ statement_timeout_ms=config.options.get("statement_timeout_ms"),
103
+ row_limit=config.options.get("row_limit"),
104
+ max_bytes=config.options.get("max_bytes"),
105
+ )
106
+ with self._lock:
107
+ self._adapters[ds_id] = adapter
108
+ if hasattr(adapter, "capabilities"):
109
+ self._capabilities[ds_id] = self._normalize_capabilities(
110
+ adapter.capabilities()
111
+ )
112
+ else:
113
+ self._capabilities[ds_id] = {DatasourceCapability.SUPPORTS_SQL.value}
114
+ return adapter
115
+ else:
116
+ raise ValueError(
117
+ f"No adapter found for engine type: '{conn_type}' in datasource '{ds_id}'"
118
+ )
119
+
120
+
121
+ def get_adapter(self, datasource_id: str) -> DatasourceAdapterProtocol:
122
+ """Retrieves the adapter for a datasource.
123
+
124
+ Args:
125
+ datasource_id: The ID of the datasource.
126
+
127
+ Returns:
128
+ DatasourceAdapterProtocol: The active adapter instance.
129
+
130
+ Raises:
131
+ ValueError: If the datasource ID is unknown.
132
+ """
133
+ with self._lock:
134
+ if datasource_id not in self._adapters:
135
+ raise ValueError(f"Unknown datasource ID: {datasource_id}")
136
+ return self._adapters[datasource_id]
137
+
138
+ def get_dialect(self, datasource_id: str) -> str:
139
+ """Returns a normalized dialect string from the adapter.
140
+
141
+ Args:
142
+ datasource_id: The ID of the datasource.
143
+
144
+ Returns:
145
+ str: The dialect string (e.g., 'postgres').
146
+ """
147
+ return self.get_adapter(datasource_id).get_dialect()
148
+
149
+ def get_capabilities(self, datasource_id: str) -> Set[str]:
150
+ """Returns the capability flags for a datasource."""
151
+ with self._lock:
152
+ if datasource_id not in self._capabilities:
153
+ raise ValueError(f"Unknown datasource ID: {datasource_id}")
154
+ return set(self._capabilities[datasource_id])
155
+
156
+ def list_adapters(self) -> List[DatasourceAdapterProtocol]:
157
+ """Returns a list of all registered adapters.
158
+
159
+ Returns:
160
+ List[DatasourceAdapterProtocol]: All active adapters.
161
+ """
162
+ with self._lock:
163
+ return list(self._adapters.values())
164
+
165
+ def list_ids(self) -> List[str]:
166
+ """Returns a list of all registered datasource IDs.
167
+
168
+ Returns:
169
+ List[str]: All registered IDs.
170
+ """
171
+ with self._lock:
172
+ return list(self._adapters.keys())
@@ -0,0 +1,6 @@
1
+ """Evaluation tools for the NL2SQL pipeline."""
2
+ from nl2sql.evaluation.evaluator import ModelEvaluator
3
+ from nl2sql.evaluation.types import BenchmarkConfig
4
+ from nl2sql.evaluation.benchmark_runner import BenchmarkRunner
5
+
6
+ __all__ = ["ModelEvaluator", "BenchmarkConfig", "BenchmarkRunner"]