diff --git a/src/badger/actions/config.py b/src/badger/actions/config.py index 4373e3d8..46e12d67 100644 --- a/src/badger/actions/config.py +++ b/src/badger/actions/config.py @@ -31,8 +31,14 @@ def config_settings(args): def _config_path_var(var_name): config_singleton = init_settings() - display_name = config_singleton[var_name]['display name'] - desc = config_singleton[var_name]['description'] + + is_path = config_singleton.read_is_path(var_name) + + if not is_path: + raise KeyError + + display_name = config_singleton.read_display_name(var_name) + desc = config_singleton.read_description(var_name) print(f'=== Configure {display_name} ===') print(f'*** {desc} ***\n') diff --git a/src/badger/actions/doctor.py b/src/badger/actions/doctor.py index 3785c68f..ff7e4ebe 100644 --- a/src/badger/actions/doctor.py +++ b/src/badger/actions/doctor.py @@ -3,7 +3,6 @@ def self_check(args): - config = init_settings() # Reset Badger if args.reset: @@ -37,17 +36,14 @@ def check_n_config_paths(): issue_list = [] for pname in config._config.dict(by_alias=True): - if not config.read_value(pname): + if config.read_value(pname) == None: good = False - # dname = BADGER_PATH_DICT[pname]['display name'] - # print(f'\n{dname} needs to be configured!') - issue_list.append(pname) if not good: # Initial setup init = True - while False: + while True: _res = input( 'If this is your first time launching Badger, you should initialize it now.\n' 'Proceed ([y]/n)? ') diff --git a/src/badger/settings.py b/src/badger/settings.py index fd28e293..a076097c 100644 --- a/src/badger/settings.py +++ b/src/badger/settings.py @@ -24,6 +24,7 @@ class Setting(BaseModel): display_name: str description: str value: Optional[Union[str, int, bool, None]] = Field(None, description="The value of the setting which can be of different types.") + is_path: bool class BadgerConfig(BaseModel): """ @@ -50,41 +51,49 @@ class BadgerConfig(BaseModel): display_name="plugin root", description="This setting (BADGER_PLUGIN_ROOT) tells Badger where to look for the plugins", value=None, + is_path=True ) BADGER_DB_ROOT: Setting = Setting( display_name="database root", description="This setting (BADGER_DB_ROOT) tells Badger where to store the routine database", value=None, + is_path=True ) BADGER_LOGBOOK_ROOT: Setting = Setting( display_name="logbook root", description="This setting (BADGER_LOGBOOK_ROOT) tells Badger where to send the logs (GUI mode)", value=None, + is_path=True ) BADGER_ARCHIVE_ROOT: Setting = Setting( display_name="archive root", description="This setting (BADGER_ARCHIVE_ROOT) tells Badger where to archive the historical optimization runs", value=None, + is_path=True ) BADGER_DATA_DUMP_PERIOD: Setting = Setting( display_name="data dump period", description="Minimum time interval between data dumps, unit is second", value=1, + is_path=False ) BADGER_THEME: Setting = Setting( display_name="theme", description="Theme for the Badger GUI", value="dark", + is_path=False ) BADGER_ENABLE_ADVANCED: Setting = Setting( display_name="enable advanced features", description="Enable advanced features on the GUI", value=False, + is_path=False ) AUTO_REFRESH: Setting = Setting( display_name="Auto-refresh", description="Permits each run to start from the initial points calculated based on the current values and the rules", - value=False + value=False, + is_path=False ) class ConfigSingleton: @@ -120,19 +129,21 @@ def load_or_create_config(config_path: str) -> BadgerConfig: # Convert each entry in config_data to an instance of Setting for key, value in config_data.items(): - if isinstance(value, dict) and 'value' in value: + if isinstance(value, dict) and 'value' in value: # Convert to Setting instance config_data[key] = Setting( display_name=value.get('display_name', key), description=value.get('description', f"Setting for {key.replace('_', ' ').lower()}"), - value=value['value'] + value=value['value'], + is_path=value.get('is_path', key) ) else: # If it's a direct value, wrap it in a Setting config_data[key] = Setting( display_name=key, description=f"Setting for {key.replace('_', ' ').lower()}", - value=value + value=value, + is_path=False ) try: @@ -216,6 +227,84 @@ def read_value(self, key: str, return_value_field: bool = True) -> Any: return data[key]['value'] if return_value_field else data[key] raise KeyError(f"Key '{key}' not found in the configuration.") + def read_description(self, key: str, return_description_field: bool = True) -> Any: + """ + Searches for the key in all sections of the configuration. + + Parameters + ---------- + key: str + The key to search for. + return_description_field: bool + If True, returns the 'description' field of the setting; otherwise, returns the entire setting. + + Returns + ------- + str + The description associated with the provided key. + + Raises + ------ + KeyError + If the key is not found in the configuration. + """ + data = self._config.dict(by_alias=True) + if key in data: + return data[key]['description'] if return_description_field else data[key] + raise KeyError(f"Key '{key}' not found in the configuration.") + + def read_display_name(self, key: str, return_display_name_field: bool = True) -> Any: + """ + Searches for the key in all sections of the configuration. + + Parameters + ---------- + key: str + The key to search for. + return_display_name_field: bool + If True, returns the 'display_name' field of the setting; otherwise, returns the entire setting. + + Returns + ------- + str + The display_name associated with the provided key. + + Raises + ------ + KeyError + If the key is not found in the configuration. + """ + data = self._config.dict(by_alias=True) + if key in data: + return data[key]['display_name'] if return_display_name_field else data[key] + raise KeyError(f"Key '{key}' not found in the configuration.") + + def read_is_path(self, key: str, return_is_path_field: bool = True) -> Any: + """ + Searches for the key in all sections of the configuration. + + Parameters + ---------- + key: str + The key to search for. + return_is_path_field: bool + If True, returns the 'is_path' field of the setting; otherwise, returns the entire setting. + + Returns + ------- + bool + The is_path associated with the provided key. + + Raises + ------ + KeyError + If the key is not found in the configuration. + """ + data = self._config.dict(by_alias=True) + if key in data: + return data[key]['is_path'] if return_is_path_field else data[key] + raise KeyError(f"Key '{key}' not found in the configuration.") + def write_value(self, key: str, value: Any) -> None: """A method for setting a new value to the config. diff --git a/src/badger/tests/test_settings.py b/src/badger/tests/test_settings.py index 152798cc..dcc74e67 100644 --- a/src/badger/tests/test_settings.py +++ b/src/badger/tests/test_settings.py @@ -10,13 +10,13 @@ class TestBadgerConfig: def setup_method(self, method): self.mock_badger_config = BadgerConfig( - BADGER_PLUGIN_ROOT=Setting(display_name="plugin root", description="Mock plugin root", value="/mock/plugin/root"), - BADGER_DB_ROOT=Setting(display_name="database root", description="Mock database root", value="/mock/db/root"), - BADGER_LOGBOOK_ROOT=Setting(display_name="logbook root", description="Mock logbook root", value="/mock/logbook/root"), - BADGER_ARCHIVE_ROOT=Setting(display_name="archive root", description="Mock archive root", value="/mock/archive/root"), - BADGER_DATA_DUMP_PERIOD=Setting(display_name="data dump period", description="Mock data dump period", value=5), - BADGER_THEME=Setting(display_name="theme", description="Mock theme", value="light"), - BADGER_ENABLE_ADVANCED=Setting(display_name="enable advanced", description="Mock enable advanced", value=True), + BADGER_PLUGIN_ROOT=Setting(display_name="plugin root", description="Mock plugin root", value="/mock/plugin/root", is_path=True), + BADGER_DB_ROOT=Setting(display_name="database root", description="Mock database root", value="/mock/db/root", is_path=True), + BADGER_LOGBOOK_ROOT=Setting(display_name="logbook root", description="Mock logbook root", value="/mock/logbook/root", is_path=True), + BADGER_ARCHIVE_ROOT=Setting(display_name="archive root", description="Mock archive root", value="/mock/archive/root", is_path=True), + BADGER_DATA_DUMP_PERIOD=Setting(display_name="data dump period", description="Mock data dump period", value=5, is_path=False), + BADGER_THEME=Setting(display_name="theme", description="Mock theme", value="light", is_path=False), + BADGER_ENABLE_ADVANCED=Setting(display_name="enable advanced", description="Mock enable advanced", value=True, is_path=False), ) def teardown_method(self, method): @@ -98,6 +98,27 @@ def test_read_value(self): value = config_singleton.read_value('BADGER_PLUGIN_ROOT') assert value == '/mock/plugin/root' + def test_read_display_name(self): + with patch('os.path.exists', return_value=True): + with patch("builtins.open", mock_open(read_data=yaml.dump(self.mock_badger_config.dict(by_alias=True)))): + config_singleton = ConfigSingleton(self.config_file) + display_name = config_singleton.read_display_name('BADGER_PLUGIN_ROOT') + assert display_name == 'plugin root' + + def test_read_description(self): + with patch('os.path.exists', return_value=True): + with patch("builtins.open", mock_open(read_data=yaml.dump(self.mock_badger_config.dict(by_alias=True)))): + config_singleton = ConfigSingleton(self.config_file) + description = config_singleton.read_description('BADGER_PLUGIN_ROOT') + assert description == 'Mock plugin root' + + def test_read_is_path(self): + with patch('os.path.exists', return_value=True): + with patch("builtins.open", mock_open(read_data=yaml.dump(self.mock_badger_config.dict(by_alias=True)))): + config_singleton = ConfigSingleton(self.config_file) + is_path = config_singleton.read_is_path('BADGER_PLUGIN_ROOT') + assert is_path == True + def test_write_value(self): with patch('os.path.exists', return_value=True): with patch("builtins.open", mock_open(read_data=yaml.dump(self.mock_badger_config.dict(by_alias=True)))):