From 71d261af2660244d8a66b14348113f58cd12ae4b Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Tue, 12 May 2026 09:26:26 +0800 Subject: [PATCH] docs: add YAML configuration implementation plan Add detailed step-by-step implementation plan for migrating from .env to YAML configuration using pydantic-yaml. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/plans/2026-05-12-yaml-configuration.md | 511 ++++++++++++++++++++ 1 file changed, 511 insertions(+) create mode 100644 docs/plans/2026-05-12-yaml-configuration.md diff --git a/docs/plans/2026-05-12-yaml-configuration.md b/docs/plans/2026-05-12-yaml-configuration.md new file mode 100644 index 0000000..3d71159 --- /dev/null +++ b/docs/plans/2026-05-12-yaml-configuration.md @@ -0,0 +1,511 @@ +# YAML Configuration Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Migrate FastAPI configuration from .env to YAML using pydantic-yaml + +**Architecture:** Create new `config/` module with YAML-based settings using `pydantic-yaml`, preserving type validation and improving readability through nested configuration structure. + +**Tech Stack:** pydantic-yaml, pyyaml, pytest + +--- + +## Task 1: Add pydantic-yaml Dependency + +**Files:** +- Modify: `requirements.txt` + +**Step 1: Add pydantic-yaml to requirements.txt** + +Add this line to `requirements.txt`: +```txt +pydantic-yaml>=0.12.0 +``` + +**Step 2: Install the dependency** + +Run: `.venv/Scripts/pip install pydantic-yaml` + +Expected: Successfully installs pydantic-yaml and its dependencies + +**Step 3: Verify installation** + +Run: `.venv/Scripts/python -c "import pydantic_yaml; print(pydantic_yaml.__version__)"` + +Expected: Prints version number without errors + +**Step 4: Commit** + +```bash +git add requirements.txt +git commit -m "deps: add pydantic-yaml for YAML configuration support" +``` + +--- + +## Task 2: Create config Directory Structure + +**Files:** +- Create: `config/__init__.py` +- Create: `config/settings.py` +- Create: `config/settings.yaml` + +**Step 1: Create config directory** + +Run: `mkdir config` + +**Step 2: Create config/__init__.py** + +```python +from config.settings import settings, Settings, load_settings + +__all__ = ["settings", "Settings", "load_settings"] +``` + +**Step 3: Create config/settings.py with configuration classes** + +```python +from pathlib import Path +from sqlalchemy.engine import URL +from pydantic import BaseModel +from pydantic_yaml import YamlModel + + +class SqlServerConfig(BaseModel): + """SQL Server 连接配置""" + host: str + port: int = 1433 + database: str + username: str + password: str + driver: str = "{ODBC Driver 18 for SQL Server}" + trust_server_certificate: str = "yes" + + +class Settings(YamlModel): + """应用配置""" + database: SqlServerConfig + + @property + def database_url(self) -> URL: + """构建数据库连接 URL""" + conf = self.database.sql_server + return URL.create( + "mssql+pyodbc", + username=conf.username, + password=conf.password, + host=conf.host, + port=conf.port, + database=conf.database, + query={ + "driver": conf.driver.strip("{}"), + "TrustServerCertificate": conf.trust_server_certificate, + }, + ) + + +def load_settings(config_path: str = "config/settings.yaml") -> Settings: + """加载 YAML 配置文件""" + path = Path(config_path) + if not path.exists(): + raise FileNotFoundError( + f"配置文件不存在: {config_path}\n" + f"请确保文件存在于项目根目录或指定正确路径" + ) + return Settings.parse_yaml_file(path) + + +# 全局配置单例 +settings = load_settings() +``` + +**Step 4: Create config/settings.yaml with current configuration** + +```yaml +# 数据库配置 +database: + sql_server: + host: 192.168.110.114 + port: 1433 + database: CompanyDB + username: peng + password: Cqbld123456. + driver: "{ODBC Driver 18 for SQL Server}" + trust_server_certificate: yes +``` + +**Step 5: Commit** + +```bash +git add config/ +git commit -m "feat: add YAML configuration module with pydantic-yaml" +``` + +--- + +## Task 3: Write Configuration Loading Tests + +**Files:** +- Create: `tests/test_config.py` +- Create: `tests/fixtures/test_config.yaml` + +**Step 1: Create tests/fixtures directory** + +Run: `mkdir tests/fixtures` + +**Step 2: Create tests/fixtures/test_config.yaml** + +```yaml +# 测试配置 +database: + sql_server: + host: localhost + port: 1433 + database: TestDB + username: test_user + password: test_pass + driver: "{ODBC Driver 18 for SQL Server}" + trust_server_certificate: yes +``` + +**Step 3: Write the failing test for settings loading** + +Create `tests/test_config.py`: + +```python +import pytest +from config.settings import load_settings + + +def test_load_settings_success(): + """测试成功加载配置文件""" + settings = load_settings("tests/fixtures/test_config.yaml") + assert settings.database.sql_server.port == 1433 + assert settings.database.sql_server.host == "localhost" + assert settings.database.sql_server.database == "TestDB" + + +def test_load_settings_file_not_found(): + """测试配置文件不存在时抛出异常""" + with pytest.raises(FileNotFoundError): + load_settings("nonexistent.yaml") + + +def test_database_url_property(): + """测试 database_url 属性生成""" + settings = load_settings("tests/fixtures/test_config.yaml") + url = settings.database_url + assert "mssql+pyodbc" in str(url) + assert "test_user" in str(url) + assert "TestDB" in str(url) +``` + +**Step 4: Run tests to verify they pass** + +Run: `.venv/Scripts/pytest tests/test_config.py -v` + +Expected: All tests PASS + +**Step 5: Commit** + +```bash +git add tests/test_config.py tests/fixtures/test_config.yaml +git commit -m "test: add configuration loading tests" +``` + +--- + +## Task 4: Update database.py to Use New Config + +**Files:** +- Modify: `app/core/database.py` + +**Step 1: Read current database.py to understand usage** + +Check the current import and usage of settings + +**Step 2: Update import statement** + +Change: +```python +from app.core.config import settings +``` + +To: +```python +from config.settings import settings +``` + +**Step 3: Verify the rest of the code works unchanged** + +The database_url property should work exactly as before + +**Step 4: Run existing tests to ensure no breakage** + +Run: `.venv/Scripts/pytest tests/ -v` + +Expected: All existing tests still PASS + +**Step 5: Commit** + +```bash +git add app/core/database.py +git commit -m "refactor: update database.py to use new config module" +``` + +--- + +## Task 5: Update API Module Imports + +**Files:** +- Modify: `app/api/v1/box.py` +- Modify: `app/api/v1/location.py` +- Modify: `app/services/box_service.py` +- Modify: `app/services/location_service.py` + +**Step 1: Check all files that import from app.core.config** + +Run: `grep -r "from app.core.config import" app/` + +**Step 2: Update each file's import** + +Change: +```python +from app.core.config import settings +``` + +To: +```python +from config.settings import settings +``` + +**Step 3: Run tests after each file change** + +Run: `.venv/Scripts/pytest tests/ -v` + +Expected: All tests PASS + +**Step 4: Commit all import changes** + +```bash +git add app/api/v1/box.py app/api/v1/location.py app/services/ +git commit -m "refactor: update all imports to use new config module" +``` + +--- + +## Task 6: Update Test Fixtures + +**Files:** +- Modify: `tests/conftest.py` + +**Step 1: Read current conftest.py** + +Check for any references to app.core.config + +**Step 2: Update imports in conftest.py** + +Change any: +```python +from app.core.config import settings +``` + +To: +```python +from config.settings import settings +``` + +**Step 3: Run tests** + +Run: `.venv/Scripts/pytest tests/ -v` + +Expected: All tests PASS + +**Step 4: Commit** + +```bash +git add tests/conftest.py +git commit -m "test: update conftest imports" +``` + +--- + +## Task 7: Update Main Application Entry Point + +**Files:** +- Modify: `app/main.py` + +**Step 1: Read current main.py** + +Check for any config-related initialization + +**Step 2: Add error handling for configuration loading** + +Add at the top of main.py or update existing initialization: + +```python +import sys +from config.settings import load_settings + +try: + settings = load_settings() +except FileNotFoundError as e: + print(f"配置文件错误: {e}") + sys.exit(1) +except Exception as e: + print(f"加载配置失败: {e}") + sys.exit(1) +``` + +**Step 3: Test application startup** + +Run: `.venv/Scripts/python -m app.main` + +Expected: Application starts without errors + +**Step 4: Commit** + +```bash +git add app/main.py +git commit -m "feat: add config error handling on startup" +``` + +--- + +## Task 8: Remove Old Configuration Files + +**Files:** +- Delete: `app/core/config.py` +- Delete: `.env` + +**Step 1: Verify no remaining imports of old config** + +Run: `grep -r "from app.core.config import" .` + +Expected: No results (or only in .git directory) + +**Step 2: Remove old config.py file** + +Run: `rm app/core/config.py` + +**Step 3: Remove .env file** + +Run: `rm .env` + +**Step 4: Add .env to .gitignore if not already present** + +Ensure `.env` is in `.gitignore` + +**Step 5: Run final test suite** + +Run: `.venv/Scripts/pytest tests/ -v` + +Expected: All tests PASS + +**Step 6: Run application to verify** + +Run: `.venv/Scripts/python -m app.main` + +Expected: Application starts successfully + +**Step 7: Commit cleanup** + +```bash +git add app/core/config.py .env .gitignore +git commit -m "refactor: remove old .env and config.py files" +``` + +--- + +## Task 9: Update .gitignore for YAML Config + +**Files:** +- Modify: `.gitignore` (if it exists, otherwise create) + +**Step 1: Check if .gitignore exists** + +Run: `ls -la .gitignore` + +**Step 2: Add or verify config/settings.local.yaml is ignored** + +Add to `.gitignore`: +```gitignore +# Local configuration overrides +config/settings.local.yaml +``` + +This allows developers to have local overrides without committing them + +**Step 3: Commit** + +```bash +git add .gitignore +git commit -m "chore: ignore local YAML config overrides" +``` + +--- + +## Task 10: Final Verification and Documentation + +**Files:** +- Update: `README.md` (if exists) + +**Step 1: Run complete test suite** + +Run: `.venv/Scripts/pytest tests/ -v --cov=app --cov-report=term-missing` + +Expected: All tests pass, coverage maintained + +**Step 2: Start application and verify database connection** + +Run: `.venv/Scripts/python -m app.main` + +Expected: Application connects to database successfully + +**Step 3: Update README.md with configuration instructions** + +Add to README.md: +```markdown +## Configuration + +Configuration is managed via YAML files in the `config/` directory. + +### Configuration File + +Edit `config/settings.yaml` to configure your environment: + +```yaml +database: + sql_server: + host: your-host + port: 1433 + database: your-database + username: your-username + password: your-password +``` + +### Local Overrides + +For local development, create `config/settings.local.yaml` to override specific values without committing them. +``` + +**Step 4: Final commit** + +```bash +git add README.md +git commit -m "docs: update README with YAML configuration instructions" +``` + +--- + +## Verification Checklist + +After completing all tasks: + +- [ ] All tests pass: `pytest tests/ -v` +- [ ] Application starts successfully +- [ ] Database connection works +- [ ] No references to `app.core.config` remain +- [ ] `.env` file removed +- [ ] YAML config file exists and is valid +- [ ] README updated with configuration instructions