Coverage for mindsdb / utilities / config.py: 59%

289 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-21 00:36 +0000

1import os 

2import sys 

3import json 

4import argparse 

5import datetime 

6import dataclasses 

7from pathlib import Path 

8from copy import deepcopy 

9from urllib.parse import urlparse 

10 

11from appdirs import user_data_dir 

12 

13# NOTE do not `import from mindsdb` here 

14 

15 

16def _merge_key_recursive(target_dict, source_dict, key): 

17 if key not in target_dict: 17 ↛ 18line 17 didn't jump to line 18 because the condition on line 17 was never true

18 target_dict[key] = source_dict[key] 

19 elif not isinstance(target_dict[key], dict) or not isinstance(source_dict[key], dict): 

20 target_dict[key] = source_dict[key] 

21 else: 

22 for k in list(source_dict[key].keys()): 

23 _merge_key_recursive(target_dict[key], source_dict[key], k) 

24 

25 

26def _merge_configs(original_config: dict, override_config: dict) -> dict: 

27 for key in list(override_config.keys()): 

28 _merge_key_recursive(original_config, override_config, key) 

29 return original_config 

30 

31 

32def _overwrite_configs(original_config: dict, override_config: dict) -> dict: 

33 """Overwrite original config with override config.""" 

34 for key in list(override_config.keys()): 

35 original_config[key] = override_config[key] 

36 return original_config 

37 

38 

39def create_data_dir(path: Path) -> None: 

40 """Create a directory and checks that it is writable. 

41 

42 Args: 

43 path (Path): path to create and check 

44 

45 Raises: 

46 NotADirectoryError: if path exists, but it is not a directory 

47 PermissionError: if path exists/created, but it is not writable 

48 Exception: if directory could not be created 

49 """ 

50 if path.exists() and not path.is_dir(): 50 ↛ 51line 50 didn't jump to line 51 because the condition on line 50 was never true

51 raise NotADirectoryError(f"The path is not a directory: {path}") 

52 

53 try: 

54 path.mkdir(mode=0o777, exist_ok=True, parents=True) 

55 except Exception as e: 

56 raise Exception("MindsDB storage directory could not be created") from e 

57 

58 if not os.access(path, os.W_OK): 58 ↛ 59line 58 didn't jump to line 59 because the condition on line 58 was never true

59 raise PermissionError(f"The directory is not allowed for writing: {path}") 

60 

61 

62@dataclasses.dataclass(frozen=True) 

63class HTTP_AUTH_TYPE: 

64 SESSION: str = "session" 

65 TOKEN: str = "token" 

66 SESSION_OR_TOKEN: str = "session_or_token" 

67 

68 

69HTTP_AUTH_TYPE = HTTP_AUTH_TYPE() 

70 

71 

72class Config: 

73 """Application config. Singletone, initialized just once. Re-initialyze if `config.auto.json` is changed. 

74 The class loads multiple configs and merge then in one. If a config option defined in multiple places (config file, 

75 env var, cmd arg, etc), then it will be resolved in following order of priority: 

76 - default config values (lowest priority) 

77 - `config.json` provided by the user 

78 - `config.auto.json` 

79 - config values collected from env vars 

80 - values from cmd args (most priority) 

81 

82 Attributes: 

83 __instance (Config): instance of 'Config' to make it singleton 

84 _config (dict): application config, the result of merging other configs 

85 _user_config (dict): config provided by the user (usually with cmd arg `--config=config.json`) 

86 _env_config (dict): config collected from different env vars 

87 _auto_config (dict): config that is editd by the app itself (e.g. when you change values in GUI) 

88 _default_config (dict): config with default values 

89 config_path (Path): path to the `config.json` provided by the user 

90 storage_root_path (Path): path to storage root folder 

91 auto_config_path (Path): path to `config.auto.json` 

92 auto_config_mtime (float): mtime of `config.auto.json` when it was loaded to `self._auto_config` 

93 _cmd_args (argparse.Namespace): cmd args 

94 use_docker_env (bool): is the app run in docker env 

95 """ 

96 

97 __instance: "Config" = None 

98 

99 _config: dict = None 

100 _user_config: dict = None 

101 _env_config: dict = None 

102 _auto_config: dict = None 

103 _default_config: dict = None 

104 config_path: Path = None 

105 storage_root_path: Path = None 

106 auto_config_path: Path = None 

107 auto_config_mtime: float = 0 

108 _cmd_args: argparse.Namespace = None 

109 use_docker_env: bool = os.environ.get("MINDSDB_DOCKER_ENV", False) is not False 

110 

111 def __new__(cls, *args, **kwargs) -> "Config": 

112 """Make class singletone and initialize config.""" 

113 if cls.__instance is not None: 

114 return cls.__instance 

115 

116 self = super().__new__(cls, *args, **kwargs) 

117 cls.__instance = self 

118 

119 self.fetch_user_config() 

120 

121 # region determine root path 

122 if self.storage_root_path is None: 122 ↛ 134line 122 didn't jump to line 134 because the condition on line 122 was always true

123 if isinstance(os.environ.get("MINDSDB_STORAGE_DIR"), str): 

124 self.storage_root_path = os.environ["MINDSDB_STORAGE_DIR"] 

125 elif "root" in self._user_config.get("paths", {}): 125 ↛ 126line 125 didn't jump to line 126 because the condition on line 125 was never true

126 self.storage_root_path = self.user_config["paths"]["root"] 

127 else: 

128 self.storage_root_path = os.path.join(user_data_dir("mindsdb", "mindsdb"), "var/") 

129 self.storage_root_path = Path(self.storage_root_path) 

130 create_data_dir(self.storage_root_path) 

131 # endregion 

132 

133 # region prepare default config 

134 api_host = "127.0.0.1" if not self.use_docker_env else "0.0.0.0" 

135 self._default_config = { 

136 "permanent_storage": {"location": "absent"}, 

137 "storage_db": ( 

138 "sqlite:///" 

139 + str(self.storage_root_path / "mindsdb.sqlite3.db") 

140 + "?check_same_thread=False&timeout=30" 

141 ), 

142 "paths": { 

143 "root": self.storage_root_path, 

144 "content": self.storage_root_path / "content", 

145 "storage": self.storage_root_path / "storage", 

146 "static": self.storage_root_path / "static", 

147 "tmp": self.storage_root_path / "tmp", 

148 "log": self.storage_root_path / "log", 

149 "cache": self.storage_root_path / "cache", 

150 "locks": self.storage_root_path / "locks", 

151 }, 

152 "auth": { 

153 "http_auth_type": HTTP_AUTH_TYPE.SESSION_OR_TOKEN, # token | session | session_or_token 

154 "http_auth_enabled": False, 

155 "http_permanent_session_lifetime": datetime.timedelta(days=31), 

156 "username": "mindsdb", 

157 "password": "", 

158 }, 

159 "logging": { 

160 "handlers": { 

161 "console": { 

162 "enabled": True, 

163 "formatter": "default", 

164 "level": "INFO", # MINDSDB_CONSOLE_LOG_LEVEL or MINDSDB_LOG_LEVEL (obsolete) 

165 }, 

166 "file": { 

167 "enabled": False, 

168 "level": "INFO", # MINDSDB_FILE_LOG_LEVEL 

169 "filename": "app.log", 

170 "maxBytes": 1 << 19, # 0.5 Mb 

171 "backupCount": 3, 

172 }, 

173 }, 

174 "resources_log": {"enabled": False, "level": "INFO", "interval": 60}, 

175 }, 

176 "gui": {"open_on_start": True, "autoupdate": True}, 

177 "debug": False, 

178 "environment": "local", 

179 "integrations": {}, 

180 "api": { 

181 "http": { 

182 "host": api_host, 

183 "port": "47334", 

184 "restart_on_failure": True, 

185 "max_restart_count": 1, 

186 "max_restart_interval_seconds": 60, 

187 "a2wsgi": {"workers": 10, "send_queue_size": 10}, 

188 }, 

189 "mysql": { 

190 "host": api_host, 

191 "port": "47335", 

192 "database": "mindsdb", 

193 "ssl": True, 

194 "restart_on_failure": True, 

195 "max_restart_count": 1, 

196 "max_restart_interval_seconds": 60, 

197 }, 

198 "litellm": { 

199 "host": "0.0.0.0", # API server binds to all interfaces by default 

200 "port": "8000", 

201 }, 

202 }, 

203 "cache": {"type": "local"}, 

204 "ml_task_queue": {"type": "local"}, 

205 "url_file_upload": {"enabled": True, "allowed_origins": [], "disallowed_origins": []}, 

206 "file_upload_domains": [], # deprecated, use config[url_file_upload][allowed_origins] instead 

207 "web_crawling_allowed_sites": [], 

208 "cloud": False, 

209 "jobs": {"disable": False}, 

210 "tasks": {"disable": False}, 

211 "default_project": "mindsdb", 

212 "default_llm": {}, 

213 "default_embedding_model": {}, 

214 "default_reranking_model": {}, 

215 "data_catalog": { 

216 "enabled": False, 

217 }, 

218 "pid_file_content": None, 

219 } 

220 # endregion 

221 

222 # region find 'auto' config file, create if not exists 

223 auto_config_name = "config.auto.json" 

224 auto_config_path = self.storage_root_path.joinpath(auto_config_name) 

225 if not auto_config_path.is_file(): 

226 auto_config_path.write_text("{}") 

227 self.auto_config_path = auto_config_path 

228 # endregion 

229 

230 self.prepare_env_config() 

231 

232 self.fetch_auto_config() 

233 self.merge_configs() 

234 

235 return cls.__instance 

236 

237 def prepare_env_config(self) -> None: 

238 """Collect config values from env vars to self._env_config""" 

239 self._env_config = { 

240 "logging": {"handlers": {"console": {}, "file": {}}}, 

241 "api": {"http": {}}, 

242 "auth": {}, 

243 "paths": {}, 

244 "permanent_storage": {}, 

245 "ml_task_queue": {}, 

246 "gui": {}, 

247 } 

248 

249 # region storage root path 

250 if os.environ.get("MINDSDB_STORAGE_DIR", "") != "": 

251 storage_root_path = Path(os.environ["MINDSDB_STORAGE_DIR"]) 

252 self._env_config["paths"] = { 

253 "root": storage_root_path, 

254 "content": storage_root_path / "content", 

255 "storage": storage_root_path / "storage", 

256 "static": storage_root_path / "static", 

257 "tmp": storage_root_path / "tmp", 

258 "log": storage_root_path / "log", 

259 "cache": storage_root_path / "cache", 

260 "locks": storage_root_path / "locks", 

261 } 

262 # endregion 

263 

264 # region vars: permanent storage disabled? 

265 if os.environ.get("MINDSDB_STORAGE_BACKUP_DISABLED", "").lower() in ( 265 ↛ 269line 265 didn't jump to line 269 because the condition on line 265 was never true

266 "1", 

267 "true", 

268 ): 

269 self._env_config["permanent_storage"] = {"location": "absent"} 

270 # endregion 

271 

272 # region vars: ml queue 

273 if os.environ.get("MINDSDB_ML_QUEUE_TYPE", "").lower() == "redis": 273 ↛ 274line 273 didn't jump to line 274 because the condition on line 273 was never true

274 self._env_config["ml_task_queue"] = { 

275 "type": "redis", 

276 "host": os.environ.get("MINDSDB_ML_QUEUE_HOST", "localhost"), 

277 "port": int(os.environ.get("MINDSDB_ML_QUEUE_PORT", 6379)), 

278 "db": int(os.environ.get("MINDSDB_ML_QUEUE_DB", 0)), 

279 "username": os.environ.get("MINDSDB_ML_QUEUE_USERNAME"), 

280 "password": os.environ.get("MINDSDB_ML_QUEUE_PASSWORD"), 

281 } 

282 # endregion 

283 

284 # region vars: username and password 

285 http_username = os.environ.get("MINDSDB_USERNAME") 

286 http_password = os.environ.get("MINDSDB_PASSWORD") 

287 

288 if bool(http_username) != bool(http_password): 288 ↛ 289line 288 didn't jump to line 289 because the condition on line 288 was never true

289 raise ValueError( 

290 "Both MINDSDB_USERNAME and MINDSDB_PASSWORD must be set together and must be non-empty strings." 

291 ) 

292 

293 # If both username and password are set, enable HTTP auth. 

294 if http_username and http_password: 294 ↛ 295line 294 didn't jump to line 295 because the condition on line 294 was never true

295 self._env_config["auth"]["http_auth_enabled"] = True 

296 self._env_config["auth"]["username"] = http_username 

297 self._env_config["auth"]["password"] = http_password 

298 # endregion 

299 

300 http_auth_type = os.environ.get("MINDSDB_HTTP_AUTH_TYPE", "").lower() 

301 if http_auth_type in dataclasses.astuple(HTTP_AUTH_TYPE): 301 ↛ 302line 301 didn't jump to line 302 because the condition on line 301 was never true

302 self._env_config["auth"]["http_auth_type"] = http_auth_type 

303 elif http_auth_type != "": 303 ↛ 304line 303 didn't jump to line 304 because the condition on line 303 was never true

304 raise ValueError(f"Wrong value of env var MINDSDB_HTTP_AUTH_TYPE={http_auth_type}") 

305 

306 # region logging 

307 if os.environ.get("MINDSDB_LOG_LEVEL", "") != "": 307 ↛ 308line 307 didn't jump to line 308 because the condition on line 307 was never true

308 self._env_config["logging"]["handlers"]["console"]["level"] = os.environ["MINDSDB_LOG_LEVEL"] 

309 self._env_config["logging"]["handlers"]["console"]["enabled"] = True 

310 if os.environ.get("MINDSDB_CONSOLE_LOG_LEVEL", "") != "": 310 ↛ 311line 310 didn't jump to line 311 because the condition on line 310 was never true

311 self._env_config["logging"]["handlers"]["console"]["level"] = os.environ["MINDSDB_CONSOLE_LOG_LEVEL"] 

312 self._env_config["logging"]["handlers"]["console"]["enabled"] = True 

313 if os.environ.get("MINDSDB_FILE_LOG_LEVEL", "") != "": 313 ↛ 314line 313 didn't jump to line 314 because the condition on line 313 was never true

314 self._env_config["logging"]["handlers"]["file"]["level"] = os.environ["MINDSDB_FILE_LOG_LEVEL"] 

315 self._env_config["logging"]["handlers"]["file"]["enabled"] = True 

316 # endregion 

317 

318 if os.environ.get("MINDSDB_DB_CON", "") != "": 

319 self._env_config["storage_db"] = os.environ["MINDSDB_DB_CON"] 

320 url = urlparse(self._env_config["storage_db"]) 

321 is_valid = url.scheme and (url.netloc or url.scheme == "sqlite") 

322 if not is_valid: 

323 raise ValueError( 

324 f"Invalid MINDSDB_DB_CON value: {os.environ['MINDSDB_DB_CON']!r}\n" 

325 f"Expected format: scheme://user:password@host:port/database\n" 

326 "Examples:\n" 

327 " - postgresql://user:pass@localhost:5432/database\n" 

328 " - sqlite:///path/to/database.db" 

329 ) 

330 

331 if os.environ.get("MINDSDB_DEFAULT_PROJECT", "") != "": 331 ↛ 332line 331 didn't jump to line 332 because the condition on line 331 was never true

332 self._env_config["default_project"] = os.environ["MINDSDB_DEFAULT_PROJECT"].lower() 

333 

334 if os.environ.get("MINDSDB_DEFAULT_LLM_API_KEY", "") != "": 334 ↛ 335line 334 didn't jump to line 335 because the condition on line 334 was never true

335 self._env_config["default_llm"] = {"api_key": os.environ["MINDSDB_DEFAULT_LLM_API_KEY"]} 

336 if os.environ.get("MINDSDB_DEFAULT_EMBEDDING_MODEL_API_KEY", "") != "": 336 ↛ 337line 336 didn't jump to line 337 because the condition on line 336 was never true

337 self._env_config["default_embedding_model"] = { 

338 "api_key": os.environ["MINDSDB_DEFAULT_EMBEDDING_MODEL_API_KEY"] 

339 } 

340 if os.environ.get("MINDSDB_DEFAULT_RERANKING_MODEL_API_KEY", "") != "": 340 ↛ 341line 340 didn't jump to line 341 because the condition on line 340 was never true

341 self._env_config["default_reranking_model"] = { 

342 "api_key": os.environ["MINDSDB_DEFAULT_RERANKING_MODEL_API_KEY"] 

343 } 

344 

345 # Reranker configuration from environment variables 

346 reranker_config = {} 

347 if os.environ.get("MINDSDB_RERANKER_N", "") != "": 347 ↛ 348line 347 didn't jump to line 348 because the condition on line 347 was never true

348 try: 

349 reranker_config["n"] = int(os.environ["MINDSDB_RERANKER_N"]) 

350 except ValueError: 

351 raise ValueError(f"MINDSDB_RERANKER_N must be an integer, got: {os.environ['MINDSDB_RERANKER_N']}") 

352 

353 if os.environ.get("MINDSDB_RERANKER_LOGPROBS", "") != "": 353 ↛ 354line 353 didn't jump to line 354 because the condition on line 353 was never true

354 logprobs_value = os.environ["MINDSDB_RERANKER_LOGPROBS"].lower() 

355 if logprobs_value in ("true", "1", "yes", "y"): 

356 reranker_config["logprobs"] = True 

357 elif logprobs_value in ("false", "0", "no", "n"): 

358 reranker_config["logprobs"] = False 

359 else: 

360 raise ValueError( 

361 f"MINDSDB_RERANKER_LOGPROBS must be a boolean value, got: {os.environ['MINDSDB_RERANKER_LOGPROBS']}" 

362 ) 

363 

364 if os.environ.get("MINDSDB_RERANKER_TOP_LOGPROBS", "") != "": 364 ↛ 365line 364 didn't jump to line 365 because the condition on line 364 was never true

365 try: 

366 reranker_config["top_logprobs"] = int(os.environ["MINDSDB_RERANKER_TOP_LOGPROBS"]) 

367 except ValueError: 

368 raise ValueError( 

369 f"MINDSDB_RERANKER_TOP_LOGPROBS must be an integer, got: {os.environ['MINDSDB_RERANKER_TOP_LOGPROBS']}" 

370 ) 

371 

372 if os.environ.get("MINDSDB_RERANKER_MAX_TOKENS", "") != "": 372 ↛ 373line 372 didn't jump to line 373 because the condition on line 372 was never true

373 try: 

374 reranker_config["max_tokens"] = int(os.environ["MINDSDB_RERANKER_MAX_TOKENS"]) 

375 except ValueError: 

376 raise ValueError( 

377 f"MINDSDB_RERANKER_MAX_TOKENS must be an integer, got: {os.environ['MINDSDB_RERANKER_MAX_TOKENS']}" 

378 ) 

379 

380 if os.environ.get("MINDSDB_RERANKER_VALID_CLASS_TOKENS", "") != "": 380 ↛ 381line 380 didn't jump to line 381 because the condition on line 380 was never true

381 try: 

382 reranker_config["valid_class_tokens"] = os.environ["MINDSDB_RERANKER_VALID_CLASS_TOKENS"].split(",") 

383 except ValueError: 

384 raise ValueError( 

385 f"MINDSDB_RERANKER_VALID_CLASS_TOKENS must be a comma-separated list of strings, got: {os.environ['MINDSDB_RERANKER_VALID_CLASS_TOKENS']}" 

386 ) 

387 

388 if reranker_config: 388 ↛ 389line 388 didn't jump to line 389 because the condition on line 388 was never true

389 if "default_reranking_model" not in self._env_config: 

390 self._env_config["default_reranking_model"] = {} 

391 self._env_config["default_reranking_model"].update(reranker_config) 

392 if os.environ.get("MINDSDB_DATA_CATALOG_ENABLED", "").lower() in ("1", "true"): 392 ↛ 393line 392 didn't jump to line 393 because the condition on line 392 was never true

393 self._env_config["data_catalog"] = {"enabled": True} 

394 

395 if os.environ.get("MINDSDB_NO_STUDIO", "").lower() in ("1", "true"): 395 ↛ 396line 395 didn't jump to line 396 because the condition on line 395 was never true

396 self._env_config["gui"]["open_on_start"] = False 

397 self._env_config["gui"]["autoupdate"] = False 

398 

399 mindsdb_gui_autoupdate = os.environ.get("MINDSDB_GUI_AUTOUPDATE", "").lower() 

400 if mindsdb_gui_autoupdate in ("0", "false"): 400 ↛ 401line 400 didn't jump to line 401 because the condition on line 400 was never true

401 self._env_config["gui"]["autoupdate"] = False 

402 elif mindsdb_gui_autoupdate in ("1", "true"): 402 ↛ 403line 402 didn't jump to line 403 because the condition on line 402 was never true

403 self._env_config["gui"]["autoupdate"] = True 

404 elif mindsdb_gui_autoupdate != "": 404 ↛ 405line 404 didn't jump to line 405 because the condition on line 404 was never true

405 raise ValueError(f"Wrong value of env var MINDSDB_GUI_AUTOUPDATE={mindsdb_gui_autoupdate}") 

406 

407 if os.environ.get("MINDSDB_PID_FILE_CONTENT", "") != "": 407 ↛ 408line 407 didn't jump to line 408 because the condition on line 407 was never true

408 try: 

409 self._env_config["pid_file_content"] = json.loads(os.environ["MINDSDB_PID_FILE_CONTENT"]) 

410 except json.JSONDecodeError as e: 

411 raise ValueError(f"MINDSDB_PID_FILE_CONTENT contains invalid JSON: {e}") 

412 

413 def fetch_auto_config(self) -> bool: 

414 """Load dict readed from config.auto.json to `auto_config`. 

415 Do it only if `auto_config` was not loaded before or config.auto.json been changed. 

416 

417 Returns: 

418 bool: True if config was loaded or updated 

419 """ 

420 try: 

421 if ( 

422 self.auto_config_path.is_file() 

423 and self.auto_config_path.read_text() != "" 

424 and self.auto_config_mtime != self.auto_config_path.stat().st_mtime 

425 ): 

426 self._auto_config = json.loads(self.auto_config_path.read_text()) 

427 self.auto_config_mtime = self.auto_config_path.stat().st_mtime 

428 return True 

429 except json.JSONDecodeError as e: 

430 raise ValueError( 

431 f"The 'auto' configuration file ({self.auto_config_path}) contains invalid JSON: {e}\nFile content: {self.auto_config_path.read_text()}" 

432 ) 

433 except FileNotFoundError: 

434 # this shouldn't happen during normal work, but it looks like it happens 

435 # when using `prefect` as a result of race conditions or something else. 

436 return False 

437 return False 

438 

439 def fetch_user_config(self) -> bool: 

440 """Read config provided by the user to `user_config`. Do it only if `user_config` was not loaded before. 

441 

442 Returns: 

443 bool: True if config was loaded 

444 """ 

445 if self._user_config is None: 445 ↛ 463line 445 didn't jump to line 463 because the condition on line 445 was always true

446 cmd_args_config = self.cmd_args.config 

447 if isinstance(os.environ.get("MINDSDB_CONFIG_PATH"), str): 

448 self.config_path = os.environ["MINDSDB_CONFIG_PATH"] 

449 elif isinstance(cmd_args_config, str): 449 ↛ 450line 449 didn't jump to line 450 because the condition on line 449 was never true

450 self.config_path = cmd_args_config 

451 

452 if isinstance(self.config_path, str): 

453 self.config_path = Path(self.config_path) 

454 if not self.config_path.is_file(): 454 ↛ 455line 454 didn't jump to line 455 because the condition on line 454 was never true

455 raise FileNotFoundError(f"The configuration file was not found at the path: {self.config_path}") 

456 try: 

457 self._user_config = json.loads(self.config_path.read_text()) 

458 except json.JSONDecodeError as e: 

459 raise ValueError(f"The configuration file ({self.config_path}) contains invalid JSON: {e}") 

460 else: 

461 self._user_config = {} 

462 return True 

463 return False 

464 

465 def ensure_auto_config_is_relevant(self) -> None: 

466 """Check if auto config has not been changed. If changed - reload main config.""" 

467 updated = self.fetch_auto_config() 

468 if updated: 468 ↛ 469line 468 didn't jump to line 469 because the condition on line 468 was never true

469 self.merge_configs() 

470 

471 def merge_configs(self) -> None: 

472 """Merge multiple configs to one.""" 

473 new_config = deepcopy(self._default_config) 

474 _merge_configs(new_config, self._user_config) 

475 

476 if getattr(self.cmd_args, "no_studio", None) is True: 476 ↛ 477line 476 didn't jump to line 477 because the condition on line 476 was never true

477 new_config["gui"]["open_on_start"] = False 

478 new_config["gui"]["autoupdate"] = False 

479 

480 _merge_configs(new_config, self._auto_config or {}) 

481 _merge_configs(new_config, self._env_config or {}) 

482 

483 # region create dirs 

484 for key, value in new_config["paths"].items(): 

485 if isinstance(value, str): 485 ↛ 486line 485 didn't jump to line 486 because the condition on line 485 was never true

486 new_config["paths"][key] = Path(value) 

487 elif isinstance(value, Path) is False: 487 ↛ 488line 487 didn't jump to line 488 because the condition on line 487 was never true

488 raise ValueError(f"Unexpected path value: {value}") 

489 create_data_dir(new_config["paths"][key]) 

490 # endregion 

491 

492 self._config = new_config 

493 

494 def __getitem__(self, key): 

495 self.ensure_auto_config_is_relevant() 

496 return self._config[key] 

497 

498 def get(self, key, default=None): 

499 self.ensure_auto_config_is_relevant() 

500 return self._config.get(key, default) 

501 

502 def get_all(self): 

503 self.ensure_auto_config_is_relevant() 

504 return self._config 

505 

506 def update(self, data: dict, overwrite: bool = False) -> None: 

507 """ 

508 Update values in `auto` config. 

509 Args: 

510 data (dict): data to update in `auto` config. 

511 overwrite (bool): if True, overwrite existing keys, otherwise merge them. 

512 - False (default): Merge recursively. Existing nested dictionaries are preserved 

513 and only the specified keys in `data` are updated. 

514 - True: Overwrite completely. Existing keys are replaced entirely with values 

515 from `data`, discarding any nested structure not present in `data`. 

516 """ 

517 self.ensure_auto_config_is_relevant() 

518 

519 if overwrite: 

520 _overwrite_configs(self._auto_config, data) 

521 else: 

522 _merge_configs(self._auto_config, data) 

523 

524 self.auto_config_path.write_text(json.dumps(self._auto_config, indent=4)) 

525 

526 self.auto_config_mtime = self.auto_config_path.stat().st_mtime 

527 

528 self.merge_configs() 

529 

530 def raise_warnings(self, logger) -> None: 

531 """Show warnings about config options""" 

532 

533 if "storage_dir" in self._config: 

534 logger.warning("The 'storage_dir' config option is no longer supported. Use 'paths.root' instead.") 

535 

536 if "log" in self._config: 

537 logger.warning("The 'log' config option is no longer supported. Use 'logging' instead.") 

538 

539 file_upload_domains = self._config.get("file_upload_domains") 

540 if isinstance(file_upload_domains, list) and len(file_upload_domains) > 0: 

541 allowed_origins = self._config["url_file_upload"]["allowed_origins"] 

542 if isinstance(allowed_origins, list) and len(allowed_origins) == 0: 

543 self._config["url_file_upload"]["allowed_origins"] = file_upload_domains 

544 logger.warning( 

545 'Config option "file_upload_domains" is deprecated, ' 

546 'use config["url_file_upload"]["allowed_origins"] instead.' 

547 ) 

548 

549 @property 

550 def cmd_args(self): 

551 if self._cmd_args is None: 

552 self.parse_cmd_args() 

553 return self._cmd_args 

554 

555 def parse_cmd_args(self) -> None: 

556 """Collect cmd args to self._cmd_args (accessable as self.cmd_args)""" 

557 if self._cmd_args is not None: 557 ↛ 558line 557 didn't jump to line 558 because the condition on line 557 was never true

558 return 

559 

560 # if it is not mindsdb run, then set args to empty 

561 if (sys.modules["__main__"].__package__ or "").lower() != "mindsdb" and os.environ.get( 561 ↛ 578line 561 didn't jump to line 578 because the condition on line 561 was always true

562 "MINDSDB_RUNTIME" 

563 ) != "1": 

564 self._cmd_args = argparse.Namespace( 

565 api=None, 

566 config=None, 

567 install_handlers=None, 

568 verbose=False, 

569 no_studio=False, 

570 version=False, 

571 ml_task_queue_consumer=None, 

572 agent=None, 

573 project=None, 

574 update_gui=False, 

575 ) 

576 return 

577 

578 parser = argparse.ArgumentParser(description="CL argument for mindsdb server") 

579 parser.add_argument("--api", type=str, default=None) 

580 parser.add_argument("--config", type=str, default=None) 

581 parser.add_argument("--install-handlers", type=str, default=None) 

582 parser.add_argument("--verbose", action="store_true") 

583 parser.add_argument("--no_studio", action="store_true") 

584 parser.add_argument("-v", "--version", action="store_true") 

585 parser.add_argument("--ml_task_queue_consumer", action="store_true", default=None) 

586 parser.add_argument( 

587 "--agent", 

588 type=str, 

589 default=None, 

590 help="Name of the agent to use with litellm APIs", 

591 ) 

592 parser.add_argument( 

593 "--project", 

594 type=str, 

595 default=None, 

596 help="Project containing the agent (default: mindsdb)", 

597 ) 

598 

599 parser.add_argument("--project-name", type=str, default=None, help="MindsDB project name") 

600 parser.add_argument("--update-gui", action="store_true", default=False, help="Update GUI and exit") 

601 parser.add_argument("--load-tokenizer", action="store_true", default=False, help="Preload tokenizer and exit") 

602 

603 self._cmd_args = parser.parse_args() 

604 

605 @property 

606 def paths(self): 

607 return self._config["paths"] 

608 

609 @property 

610 def user_config(self): 

611 return self._user_config 

612 

613 @property 

614 def auto_config(self): 

615 return self._auto_config 

616 

617 @property 

618 def env_config(self): 

619 return self._env_config 

620 

621 @property 

622 def is_cloud(self): 

623 return self._config.get("cloud", False) 

624 

625 

626config = Config()