diff --git a/.gitignore b/.gitignore index 57cef4a4..5a7db33f 100644 --- a/.gitignore +++ b/.gitignore @@ -159,5 +159,8 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. .idea/ +# Visual Studio Code project settings +.vscode/ + notes.md data/ diff --git a/devika.py b/devika.py index c03350d1..1f14811d 100644 --- a/devika.py +++ b/devika.py @@ -183,7 +183,7 @@ def set_settings(): data = request.json print("Data: ", data) config.config.update(data) - config.save_config() + config.dump_config() return jsonify({"message": "Settings updated"}) diff --git a/src/config.py b/src/config.py index 3b1ab354..4f3e2bb1 100644 --- a/src/config.py +++ b/src/config.py @@ -4,6 +4,8 @@ class Config: _instance = None + _CONFIG_FILE = "config.toml" + _SAMPLE_CONFIG_FILE = "sample.config.toml" def __new__(cls): if cls._instance is None: @@ -13,11 +15,11 @@ def __new__(cls): def _load_config(self): # If the config file doesn't exist, copy from the sample - if not os.path.exists("config.toml"): - with open("sample.config.toml", "r") as f_in, open("config.toml", "w") as f_out: + if not os.path.exists(self._CONFIG_FILE): + with open(self._SAMPLE_CONFIG_FILE, "r") as f_in, open(self._CONFIG_FILE, "w") as f_out: f_out.write(f_in.read()) - self.config = toml.load("config.toml") + self.config = toml.load(self._CONFIG_FILE) def get_config(self): return self.config @@ -84,84 +86,84 @@ def get_logging_prompts(self): def set_bing_api_key(self, key): self.config["API_KEYS"]["BING"] = key - self.save_config() + self.dump_config() def set_bing_api_endpoint(self, endpoint): self.config["API_ENDPOINTS"]["BING"] = endpoint - self.save_config() + self.dump_config() def set_google_search_api_key(self, key): self.config["API_KEYS"]["GOOGLE_SEARCH"] = key - self.save_config() + self.dump_config() def set_google_search_engine_id(self, key): self.config["API_KEYS"]["GOOGLE_SEARCH_ENGINE_ID"] = key - self.save_config() + self.dump_config() def set_google_search_api_endpoint(self, endpoint): self.config["API_ENDPOINTS"]["GOOGLE_SEARCH"] = endpoint - self.save_config() + self.dump_config() def set_ollama_api_endpoint(self, endpoint): self.config["API_ENDPOINTS"]["OLLAMA"] = endpoint - self.save_config() + self.dump_config() def set_claude_api_key(self, key): self.config["API_KEYS"]["CLAUDE"] = key - self.save_config() + self.dump_config() def set_openai_api_key(self, key): self.config["API_KEYS"]["OPENAI"] = key - self.save_config() + self.dump_config() def set_gemini_api_key(self, key): self.config["API_KEYS"]["GEMINI"] = key - self.save_config() + self.dump_config() def set_mistral_api_key(self, key): self.config["API_KEYS"]["MISTRAL"] = key - self.save_config() + self.dump_config() def set_groq_api_key(self, key): self.config["API_KEYS"]["GROQ"] = key - self.save_config() + self.dump_config() def set_netlify_api_key(self, key): self.config["API_KEYS"]["NETLIFY"] = key - self.save_config() + self.dump_config() def set_sqlite_db(self, db): self.config["STORAGE"]["SQLITE_DB"] = db - self.save_config() + self.dump_config() def set_screenshots_dir(self, dir): self.config["STORAGE"]["SCREENSHOTS_DIR"] = dir - self.save_config() + self.dump_config() def set_pdfs_dir(self, dir): self.config["STORAGE"]["PDFS_DIR"] = dir - self.save_config() + self.dump_config() def set_projects_dir(self, dir): self.config["STORAGE"]["PROJECTS_DIR"] = dir - self.save_config() + self.dump_config() def set_logs_dir(self, dir): self.config["STORAGE"]["LOGS_DIR"] = dir - self.save_config() + self.dump_config() def set_repos_dir(self, dir): self.config["STORAGE"]["REPOS_DIR"] = dir - self.save_config() + self.dump_config() def set_logging_rest_api(self, value): self.config["LOGGING"]["LOG_REST_API"] = "true" if value else "false" - self.save_config() + self.dump_config() def set_logging_prompts(self, value): self.config["LOGGING"]["LOG_PROMPTS"] = "true" if value else "false" - self.save_config() + self.dump_config() - def save_config(self): - with open("config.toml", "w") as f: + def dump_config(self): + with open(self._CONFIG_FILE, "w") as f: toml.dump(self.config, f) diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 00000000..1a03edf6 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,58 @@ +from src.config import Config +import os +import shutil +from pytest import fixture + + +class TestConfig: + + _TEST_CONFIG_FILE = "sample.config.toml" + + @fixture + def setup(self): + # Delete Config singleton + Config._instance = None + + @fixture + def patch_config(self, mocker, tmpdir): + """ + Patch the class Config + Config._CONFIG_FILE is changed to a path towards a temporary file + The temporary file is a copy of _TEST_CONFIG_FILE + """ + assert os.path.isfile(self._TEST_CONFIG_FILE) + p = tmpdir + p = shutil.copy(self._TEST_CONFIG_FILE, p) + return mocker.patch("src.config.Config._CONFIG_FILE", p) + + @fixture + def patch_load_config(self, mocker): + """ + Patch load_config in class Config + The return value from load_config is changed to `{}` + """ + return mocker.patch("src.config.Config._load_config", return_value={}) + + def test_creation(self, setup, patch_config): + assert Config() is not None + + def test_singleton(self, setup, patch_config): + assert Config() == Config() + + def test_load_config_called_once_during_multiple_access(self, setup, patch_config, patch_load_config): + Config(), Config(), Config(), Config(), Config() + patch_load_config.assert_called_once() + + def test_dump_config_correctly_saves_config(self, setup, patch_config): + # Read config from file and modify values + Config().set_bing_api_key("10random_bing_key01") + Config().set_logs_dir("10random_logs_dir01") + # (as of March 28th, setters save to file) + # Get the config that was written to file + config_saved = Config().get_config() + # Force loading of the config from the file by deleting singleton + Config._instance = None + # Get the config loaded from file + config_loaded = Config().get_config() + + assert config_saved == config_loaded \ No newline at end of file