Fix NFO 503 error on server reload with config fallback
- Add dynamic config loading in get_nfo_service() dependency - Handle settings reset during uvicorn reload in development - Add comprehensive tests for settings priority and fallback behavior - All 4 unit tests passing (settings priority, config fallback, error cases) - Update documentation with reload scenario fix
This commit is contained in:
@@ -123,6 +123,27 @@ All tasks completed! Recent issues have been resolved.
|
||||
|
||||
## Recently Fixed Issues:
|
||||
|
||||
### ✅ Fixed: NFO 503 Error After Server Reload (2026-01-18)
|
||||
|
||||
**Issue:** POST http://127.0.0.1:8000/api/nfo/blue-exorcist/create returns 503 (Service Unavailable) with "NFO service not configured. TMDB API key required." even though TMDB API key is configured in config.json. This occurred after server reloads in development mode (--reload).
|
||||
|
||||
**Root Cause:** The `settings` singleton is recreated on each module reload by uvicorn in development mode. The TMDB API key loaded from config.json during startup was lost when modules were reloaded, causing `settings.tmdb_api_key` to be None again.
|
||||
|
||||
**Solution:** Modified `get_nfo_service()` dependency function to check `settings.tmdb_api_key` first, and if not found, fall back to loading from `config.json` directly. This ensures the TMDB API key is always available even after hot reloads in development.
|
||||
|
||||
**Files Modified:**
|
||||
- [src/server/api/nfo.py](../src/server/api/nfo.py)
|
||||
|
||||
**Tests Added:**
|
||||
- [tests/unit/test_nfo_dependency.py](../tests/unit/test_nfo_dependency.py) - Tests for config fallback behavior
|
||||
|
||||
**Verification:**
|
||||
- All 4 unit tests pass
|
||||
- NFO endpoints work correctly even after server reloads
|
||||
- Falls back gracefully to config.json when settings are reset
|
||||
|
||||
---
|
||||
|
||||
### ✅ Fixed: NFO Creation 500 Error - Missing Folder (2026-01-18)
|
||||
|
||||
**Issue:** POST http://127.0.0.1:8000/api/nfo/blue-period/create returns 500 (Internal Server Error) with message "Series folder not found: /home/lukas/Volume/serien/Blue Period"
|
||||
@@ -132,13 +153,16 @@ All tasks completed! Recent issues have been resolved.
|
||||
**Solution:** Modified `create_tvshow_nfo()` in `nfo_service.py` to automatically create the series folder (with `mkdir(parents=True, exist_ok=True)`) if it doesn't exist, instead of raising an error.
|
||||
|
||||
**Files Modified:**
|
||||
|
||||
- [src/core/services/nfo_service.py](../src/core/services/nfo_service.py)
|
||||
|
||||
**Tests Added:**
|
||||
|
||||
- [tests/unit/test_nfo_service_folder_creation.py](../tests/unit/test_nfo_service_folder_creation.py) - Unit test verifying folder creation
|
||||
- [tests/integration/test_nfo_folder_creation.py](../tests/integration/test_nfo_folder_creation.py) - Integration test for end-to-end verification
|
||||
|
||||
**Verification:**
|
||||
**Verification:**
|
||||
|
||||
- Unit and integration tests pass
|
||||
- NFO files can now be created for series even when the folder doesn't exist
|
||||
- Folder is automatically created when needed
|
||||
|
||||
@@ -45,14 +45,28 @@ async def get_nfo_service() -> NFOService:
|
||||
Raises:
|
||||
HTTPException: If NFO service not configured
|
||||
"""
|
||||
if not settings.tmdb_api_key:
|
||||
# Check if TMDB API key is in settings
|
||||
tmdb_api_key = settings.tmdb_api_key
|
||||
|
||||
# If not in settings, try to load from config.json
|
||||
if not tmdb_api_key:
|
||||
try:
|
||||
from src.server.services.config_service import get_config_service
|
||||
config_service = get_config_service()
|
||||
config = config_service.load_config()
|
||||
if config.nfo and config.nfo.tmdb_api_key:
|
||||
tmdb_api_key = config.nfo.tmdb_api_key
|
||||
except Exception:
|
||||
pass # Config loading failed, tmdb_api_key remains None
|
||||
|
||||
if not tmdb_api_key:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="NFO service not configured. TMDB API key required."
|
||||
)
|
||||
|
||||
return NFOService(
|
||||
tmdb_api_key=settings.tmdb_api_key,
|
||||
tmdb_api_key=tmdb_api_key,
|
||||
anime_directory=settings.anime_directory,
|
||||
image_size=settings.nfo_image_size,
|
||||
auto_create=settings.nfo_auto_create
|
||||
|
||||
112
tests/unit/test_nfo_dependency.py
Normal file
112
tests/unit/test_nfo_dependency.py
Normal file
@@ -0,0 +1,112 @@
|
||||
"""Tests for NFO service dependency with config fallback.
|
||||
|
||||
Tests that get_nfo_service() correctly loads TMDB API key from config.json
|
||||
when it's not in settings (e.g., after server reload in development).
|
||||
"""
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from fastapi import HTTPException
|
||||
|
||||
from src.server.api.nfo import get_nfo_service
|
||||
from src.server.models.config import NFOConfig, AppConfig
|
||||
from src.config.settings import settings
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_nfo_service_with_settings_tmdb_key():
|
||||
"""Test get_nfo_service when TMDB key is in settings."""
|
||||
# Set TMDB API key in settings
|
||||
original_key = settings.tmdb_api_key
|
||||
settings.tmdb_api_key = "test_api_key_from_settings"
|
||||
|
||||
try:
|
||||
nfo_service = await get_nfo_service()
|
||||
assert nfo_service is not None
|
||||
assert nfo_service.tmdb_client.api_key == "test_api_key_from_settings"
|
||||
finally:
|
||||
settings.tmdb_api_key = original_key
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_nfo_service_fallback_to_config():
|
||||
"""Test get_nfo_service falls back to config.json when key not in settings."""
|
||||
# Clear TMDB API key from settings
|
||||
original_key = settings.tmdb_api_key
|
||||
settings.tmdb_api_key = None
|
||||
|
||||
try:
|
||||
# Mock config service to return NFO config with API key
|
||||
mock_config = AppConfig(
|
||||
name="Test",
|
||||
data_dir="data",
|
||||
nfo=NFOConfig(
|
||||
tmdb_api_key="test_api_key_from_config",
|
||||
auto_create=False,
|
||||
update_on_scan=False
|
||||
)
|
||||
)
|
||||
|
||||
with patch('src.server.services.config_service.get_config_service') as mock_get_config:
|
||||
mock_config_service = MagicMock()
|
||||
mock_config_service.load_config.return_value = mock_config
|
||||
mock_get_config.return_value = mock_config_service
|
||||
|
||||
nfo_service = await get_nfo_service()
|
||||
assert nfo_service is not None
|
||||
assert nfo_service.tmdb_client.api_key == "test_api_key_from_config"
|
||||
finally:
|
||||
settings.tmdb_api_key = original_key
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_nfo_service_no_key_raises_503():
|
||||
"""Test get_nfo_service raises 503 when no TMDB key available."""
|
||||
# Clear TMDB API key from settings
|
||||
original_key = settings.tmdb_api_key
|
||||
settings.tmdb_api_key = None
|
||||
|
||||
try:
|
||||
# Mock config service to return config without API key
|
||||
mock_config = AppConfig(
|
||||
name="Test",
|
||||
data_dir="data",
|
||||
nfo=NFOConfig(
|
||||
tmdb_api_key=None,
|
||||
auto_create=False,
|
||||
update_on_scan=False
|
||||
)
|
||||
)
|
||||
|
||||
with patch('src.server.services.config_service.get_config_service') as mock_get_config:
|
||||
mock_config_service = MagicMock()
|
||||
mock_config_service.load_config.return_value = mock_config
|
||||
mock_get_config.return_value = mock_config_service
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_nfo_service()
|
||||
|
||||
assert exc_info.value.status_code == 503
|
||||
assert "TMDB API key required" in exc_info.value.detail
|
||||
finally:
|
||||
settings.tmdb_api_key = original_key
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_nfo_service_config_load_fails_raises_503():
|
||||
"""Test get_nfo_service raises 503 when config loading fails."""
|
||||
# Clear TMDB API key from settings
|
||||
original_key = settings.tmdb_api_key
|
||||
settings.tmdb_api_key = None
|
||||
|
||||
try:
|
||||
# Mock config service to raise exception
|
||||
with patch('src.server.services.config_service.get_config_service') as mock_get_config:
|
||||
mock_get_config.side_effect = Exception("Config file not found")
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_nfo_service()
|
||||
|
||||
assert exc_info.value.status_code == 503
|
||||
assert "TMDB API key required" in exc_info.value.detail
|
||||
finally:
|
||||
settings.tmdb_api_key = original_key
|
||||
Reference in New Issue
Block a user