From c2c7c066d04cdca6d7c1f936d8fc878ce8041101 Mon Sep 17 00:00:00 2001 From: Sourish-07 Date: Tue, 30 Dec 2025 15:48:59 -0500 Subject: [PATCH 1/5] feat(config): add explicit validation for required configuration fields --- qlib/config.py | 31 ++++++++++++++++++++++++---- qlib/tests/test_config_validation.py | 17 +++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 qlib/tests/test_config_validation.py diff --git a/qlib/config.py b/qlib/config.py index 4e5d62564f7..3ffdc0aae59 100644 --- a/qlib/config.py +++ b/qlib/config.py @@ -62,16 +62,33 @@ class QSettings(BaseSettings): class Config: def __init__(self, default_conf): - self.__dict__["_default_config"] = copy.deepcopy(default_conf) # avoiding conflicts with __getattr__ + self.__dict__["_default_config"] = copy.deepcopy(default_conf) self.reset() + def validate(self): + errors = [] + + if not self.get("provider_uri"): + errors.append( + "provider_uri must be set (e.g. ~/.qlib/qlib_data or a valid path)" + ) + + if not self.get("region"): + errors.append( + "region must be specified (e.g. 'cn', 'us')" + ) + + if errors: + raise ValueError( + "Invalid Qlib configuration:\n- " + "\n- ".join(errors) + ) + def __getitem__(self, key): return self.__dict__["_config"][key] def __getattr__(self, attr): if attr in self.__dict__["_config"]: return self.__dict__["_config"][attr] - raise AttributeError(f"No such `{attr}` in self._config") def get(self, key, default=None): @@ -109,14 +126,20 @@ def set_conf_from_C(self, config_c): @staticmethod def register_from_C(config, skip_register=True): - from .utils import set_log_with_config # pylint: disable=C0415 + from .utils import set_log_with_config if C.registered and skip_register: return + C.set_conf_from_C(config) + + + C.validate() + if C.logging_config: set_log_with_config(C.logging_config) + C.register() @@ -523,4 +546,4 @@ def registered(self): # global config -C = QlibConfig(_default_config) +C = QlibConfig(_default_config) \ No newline at end of file diff --git a/qlib/tests/test_config_validation.py b/qlib/tests/test_config_validation.py new file mode 100644 index 00000000000..ff64e327819 --- /dev/null +++ b/qlib/tests/test_config_validation.py @@ -0,0 +1,17 @@ +import pytest +from qlib.config import C, Config + + +def test_missing_provider_uri_raises(): + + default_conf = { + "provider_uri": None, + "region": "us" + } + + cfg = Config(default_conf) + + with pytest.raises(ValueError) as exc: + cfg.validate() + + assert "provider_uri must be set" in str(exc.value) \ No newline at end of file From 94330a29647cc6205259c38ddd85c190ee01b64c Mon Sep 17 00:00:00 2001 From: Sourish-07 Date: Thu, 8 Jan 2026 16:22:11 -0500 Subject: [PATCH 2/5] feat(config): add explicit validation for required configuration fields --- qlib/config.py | 6 +++--- qlib/tests/test_config_validation.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/qlib/config.py b/qlib/config.py index 3ffdc0aae59..994892c87eb 100644 --- a/qlib/config.py +++ b/qlib/config.py @@ -131,11 +131,11 @@ def register_from_C(config, skip_register=True): if C.registered and skip_register: return - - C.set_conf_from_C(config) + C.set_conf_from_C(config) - C.validate() + if not skip_register: + C.validate() if C.logging_config: set_log_with_config(C.logging_config) diff --git a/qlib/tests/test_config_validation.py b/qlib/tests/test_config_validation.py index ff64e327819..60a9c89963e 100644 --- a/qlib/tests/test_config_validation.py +++ b/qlib/tests/test_config_validation.py @@ -1,12 +1,12 @@ import pytest -from qlib.config import C, Config + +from qlib.config import Config def test_missing_provider_uri_raises(): - default_conf = { "provider_uri": None, - "region": "us" + "region": "us", } cfg = Config(default_conf) @@ -14,4 +14,4 @@ def test_missing_provider_uri_raises(): with pytest.raises(ValueError) as exc: cfg.validate() - assert "provider_uri must be set" in str(exc.value) \ No newline at end of file + assert "provider_uri must be set" in str(exc.value) From c43be31dae638889f5c8eca8edc10d5d714977c8 Mon Sep 17 00:00:00 2001 From: Sourish-07 Date: Sun, 25 Jan 2026 18:41:49 -0500 Subject: [PATCH 3/5] fix(config): always validate configuration regardless of registration --- qlib/config.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qlib/config.py b/qlib/config.py index 994892c87eb..da90c1f628f 100644 --- a/qlib/config.py +++ b/qlib/config.py @@ -133,9 +133,7 @@ def register_from_C(config, skip_register=True): C.set_conf_from_C(config) - - if not skip_register: - C.validate() + C.validate() if C.logging_config: set_log_with_config(C.logging_config) From 5bc30eb53a936a893a7a699d35cc55e2526e71a4 Mon Sep 17 00:00:00 2001 From: Linlang Date: Mon, 2 Feb 2026 16:02:04 +0800 Subject: [PATCH 4/5] config: clarify validation semantics and document future migration plan --- qlib/config.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/qlib/config.py b/qlib/config.py index da90c1f628f..bd212443de2 100644 --- a/qlib/config.py +++ b/qlib/config.py @@ -65,21 +65,21 @@ def __init__(self, default_conf): self.__dict__["_default_config"] = copy.deepcopy(default_conf) self.reset() + # TODO: This validation logic is a temporary solution. + # The long-term goal is to migrate Qlib Config to a typed configuration + # system based on pydantic.BaseModel, with explicit schema and field validation. def validate(self): errors = [] if not self.get("provider_uri"): - errors.append( - "provider_uri must be set (e.g. ~/.qlib/qlib_data or a valid path)" - ) + errors.append("provider_uri must be set (e.g. ~/.qlib/qlib_data or a valid path)") if not self.get("region"): - errors.append( - "region must be specified (e.g. 'cn', 'us')" - ) + errors.append("region must be specified (e.g. 'cn', 'us')") if errors: raise ValueError( + "Invalid Qlib configuration (note: the global config has already been updated):\n" "Invalid Qlib configuration:\n- " + "\n- ".join(errors) ) @@ -131,7 +131,6 @@ def register_from_C(config, skip_register=True): if C.registered and skip_register: return - C.set_conf_from_C(config) C.validate() @@ -544,4 +543,4 @@ def registered(self): # global config -C = QlibConfig(_default_config) \ No newline at end of file +C = QlibConfig(_default_config) From b76610f93f96f131b7ddc86f07190dca1d5b5777 Mon Sep 17 00:00:00 2001 From: Linlang Date: Mon, 2 Feb 2026 16:16:18 +0800 Subject: [PATCH 5/5] fix: pylint error --- qlib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qlib/config.py b/qlib/config.py index bd212443de2..58954da846d 100644 --- a/qlib/config.py +++ b/qlib/config.py @@ -126,7 +126,7 @@ def set_conf_from_C(self, config_c): @staticmethod def register_from_C(config, skip_register=True): - from .utils import set_log_with_config + from .utils import set_log_with_config # pylint: disable=C0415 if C.registered and skip_register: return