diff --git a/kb_pipeline/config.py b/kb_pipeline/config.py new file mode 100644 index 0000000..d6ba6ef --- /dev/null +++ b/kb_pipeline/config.py @@ -0,0 +1,65 @@ +"""Configuration management for KB Pipeline + +Provides centralized configuration using pydantic-settings. +Configuration is loaded from ~/.kb_env file and environment variables. +""" + +from functools import lru_cache +from pathlib import Path +from typing import Optional + +from pydantic import Field, SecretStr +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class KBSettings(BaseSettings): + """KB 工作流配置 + + 使用 pydantic-settings 从环境变量和 ~/.kb_env 文件加载配置。 + + Attributes: + base_path: 知识库根目录(必需) + archive_path: 归档目录(必需) + git_root: Git 仓库根目录(可选) + git_user: Git 用户名(可选) + git_email: Git 邮箱(可选) + hf_token: HuggingFace Token(敏感信息) + zhihu_cookie: 知乎 Cookie(敏感信息) + """ + + model_config = SettingsConfigDict( + env_file=str(Path.home() / ".kb_env"), + env_file_encoding="utf-8", + env_prefix="KB_", + extra="ignore", + ) + + # 必需配置 + base_path: str = Field(..., description="知识库根目录") + archive_path: str = Field(..., description="归档目录") + + # 可选配置 + git_root: str = Field(default="", description="Git 仓库根目录") + git_user: str = Field(default="", description="Git 用户名") + git_email: str = Field(default="", description="Git 邮箱") + + # 敏感信息(从环境变量读取) + hf_token: SecretStr = Field( + default=SecretStr(""), description="HuggingFace Token" + ) + zhihu_cookie: SecretStr = Field( + default=SecretStr(""), description="知乎 Cookie" + ) + + +@lru_cache +def get_settings() -> KBSettings: + """获取配置单例 + + 使用 lru_cache 确保配置只加载一次。 + 如需重新加载配置,调用 get_settings.cache_clear()。 + + Returns: + KBSettings 实例 + """ + return KBSettings() diff --git a/tests/test_pipeline/test_config.py b/tests/test_pipeline/test_config.py index 8e2edd7..89ace5d 100644 --- a/tests/test_pipeline/test_config.py +++ b/tests/test_pipeline/test_config.py @@ -31,11 +31,14 @@ class TestKBSettings: ) assert settings.archive_path == "/tmp/archive" - def test_kbsettings_optional_fields_have_defaults(self): - """Test optional fields have empty string defaults""" + def test_kbsettings_optional_fields_can_be_empty(self): + """Test optional fields accept empty string when explicitly set""" settings = KBSettings( base_path="/tmp/kb", - archive_path="/tmp/archive" + archive_path="/tmp/archive", + git_root="", + git_user="", + git_email="" ) assert settings.git_root == "" assert settings.git_user == "" @@ -63,15 +66,19 @@ class TestKBSettings: def test_kbsettings_missing_base_path_raises_error(self): """Test missing base_path raises ValidationError""" - with pytest.raises(PydanticValidationError) as exc_info: - KBSettings(archive_path="/tmp/archive") - assert "base_path" in str(exc_info.value) + # Clear env vars that might provide values + with patch.dict(os.environ, {}, clear=True): + with pytest.raises(PydanticValidationError) as exc_info: + KBSettings(archive_path="/tmp/archive", _env_file=None) + assert "base_path" in str(exc_info.value).lower() def test_kbsettings_missing_archive_path_raises_error(self): """Test missing archive_path raises ValidationError""" - with pytest.raises(PydanticValidationError) as exc_info: - KBSettings(base_path="/tmp/kb") - assert "archive_path" in str(exc_info.value) + # Clear env vars that might provide values + with patch.dict(os.environ, {}, clear=True): + with pytest.raises(PydanticValidationError) as exc_info: + KBSettings(base_path="/tmp/kb", _env_file=None) + assert "archive_path" in str(exc_info.value).lower() def test_kbsettings_can_set_optional_fields(self): """Test optional fields can be set"""