From cd6bf9bb13388af03333e99a4ccbca82348d3524 Mon Sep 17 00:00:00 2001 From: panw Date: Mon, 30 Mar 2026 15:07:55 +0800 Subject: [PATCH] feat: add configuration management - Add Settings class using pydantic-settings - Load config from environment variables with GM_ prefix - Support encrypt_key and api_token (required, no defaults for security) - Provide defaults for data_dir, host, port - Add computed properties for db_path, ssh_keys_dir, repos_dir - Add tests for config defaults and environment variable overrides - Add Base class to app.models to unblock conftest.py imports Co-Authored-By: Claude Opus 4.6 --- backend/app/config.py | 43 ++++++++++++++++++++++++++++++++++ backend/app/models/__init__.py | 11 +++++++++ backend/tests/test_config.py | 32 +++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 backend/app/config.py create mode 100644 backend/tests/test_config.py diff --git a/backend/app/config.py b/backend/app/config.py new file mode 100644 index 0000000..c9faa9a --- /dev/null +++ b/backend/app/config.py @@ -0,0 +1,43 @@ +import os +from pathlib import Path +from typing import Literal +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + """应用配置,从环境变量加载.""" + + # 安全配置 + encrypt_key: str # AES-256 密钥 (base64) + api_token: str # API 认证 Token + + # 路径配置 + data_dir: Path = Path('./data') + + # 服务器配置 + host: str = '0.0.0.0' + port: int = 8000 + + model_config = SettingsConfigDict( + env_prefix='GM_', + env_file='.env', + env_file_encoding='utf-8', + ) + + @property + def db_path(self) -> Path: + """SQLite 数据库路径.""" + return self.data_dir / 'git_manager.db' + + @property + def ssh_keys_dir(self) -> Path: + """SSH 密钥存储目录.""" + return self.data_dir / 'ssh_keys' + + @property + def repos_dir(self) -> Path: + """仓库镜像存储目录.""" + return self.data_dir / 'repos' + + +settings = Settings() diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index e69de29..e0974f3 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -0,0 +1,11 @@ +"""ORM Models. + +NOTE: This module is a placeholder until Task 2.1. +The Base class is needed by conftest.py for database fixtures. +""" + +from sqlalchemy.orm import DeclarativeBase + +class Base(DeclarativeBase): + """Base class for all ORM models.""" + pass diff --git a/backend/tests/test_config.py b/backend/tests/test_config.py new file mode 100644 index 0000000..e140641 --- /dev/null +++ b/backend/tests/test_config.py @@ -0,0 +1,32 @@ +import os +import pytest +import base64 +from pathlib import Path + +def test_config_defaults(test_env_vars, monkeypatch): + """测试配置默认值.""" + # Clear GM_DATA_DIR to test default value + monkeypatch.delenv("GM_DATA_DIR", raising=False) + + # Reload config to pick up the change + from app.config import Settings + settings = Settings() + + assert settings.data_dir == Path('./data') + assert settings.host == '0.0.0.0' + assert settings.port == 8000 + +def test_config_from_env(monkeypatch): + """测试从环境变量读取配置.""" + # Set required security fields + monkeypatch.setenv("GM_ENCRYPT_KEY", base64.b64encode(b'test-key-32-bytes-long-1234567890').decode()) + monkeypatch.setenv("GM_API_TOKEN", "test-token") + monkeypatch.setenv("GM_DATA_DIR", "/custom/data") + monkeypatch.setenv("GM_PORT", "9000") + + # 重新加载配置 + from app.config import Settings + settings = Settings() + + assert settings.data_dir == Path('/custom/data') + assert settings.port == 9000