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
« 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
11from appdirs import user_data_dir
13# NOTE do not `import from mindsdb` here
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)
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
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
39def create_data_dir(path: Path) -> None:
40 """Create a directory and checks that it is writable.
42 Args:
43 path (Path): path to create and check
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}")
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
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}")
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"
69HTTP_AUTH_TYPE = HTTP_AUTH_TYPE()
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)
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 """
97 __instance: "Config" = None
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
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
116 self = super().__new__(cls, *args, **kwargs)
117 cls.__instance = self
119 self.fetch_user_config()
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
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
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
230 self.prepare_env_config()
232 self.fetch_auto_config()
233 self.merge_configs()
235 return cls.__instance
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 }
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
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
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
284 # region vars: username and password
285 http_username = os.environ.get("MINDSDB_USERNAME")
286 http_password = os.environ.get("MINDSDB_PASSWORD")
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 )
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
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}")
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
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 )
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()
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 }
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']}")
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 )
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 )
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 )
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 )
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}
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
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}")
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}")
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.
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
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.
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
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
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()
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)
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
480 _merge_configs(new_config, self._auto_config or {})
481 _merge_configs(new_config, self._env_config or {})
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
492 self._config = new_config
494 def __getitem__(self, key):
495 self.ensure_auto_config_is_relevant()
496 return self._config[key]
498 def get(self, key, default=None):
499 self.ensure_auto_config_is_relevant()
500 return self._config.get(key, default)
502 def get_all(self):
503 self.ensure_auto_config_is_relevant()
504 return self._config
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()
519 if overwrite:
520 _overwrite_configs(self._auto_config, data)
521 else:
522 _merge_configs(self._auto_config, data)
524 self.auto_config_path.write_text(json.dumps(self._auto_config, indent=4))
526 self.auto_config_mtime = self.auto_config_path.stat().st_mtime
528 self.merge_configs()
530 def raise_warnings(self, logger) -> None:
531 """Show warnings about config options"""
533 if "storage_dir" in self._config:
534 logger.warning("The 'storage_dir' config option is no longer supported. Use 'paths.root' instead.")
536 if "log" in self._config:
537 logger.warning("The 'log' config option is no longer supported. Use 'logging' instead.")
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 )
549 @property
550 def cmd_args(self):
551 if self._cmd_args is None:
552 self.parse_cmd_args()
553 return self._cmd_args
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
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
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 )
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")
603 self._cmd_args = parser.parse_args()
605 @property
606 def paths(self):
607 return self._config["paths"]
609 @property
610 def user_config(self):
611 return self._user_config
613 @property
614 def auto_config(self):
615 return self._auto_config
617 @property
618 def env_config(self):
619 return self._env_config
621 @property
622 def is_cloud(self):
623 return self._config.get("cloud", False)
626config = Config()