From 7afe4a5f55f5704b3fe1ac782ddc04ab659277f0 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Tue, 10 Feb 2026 16:18:57 -0600 Subject: [PATCH] Fix TUI corruption from logging and improve sync error handling - Redirect all logging to data/logs/sba_scout.log instead of stderr - Prevents log output from corrupting the Textual TUI display - Add loading spinner for sync operations to show progress - Improve error messages for Cloudflare/API errors - Add TROUBLESHOOTING.md guide for common sync issues - Exclude data/ directory from git Co-Authored-By: Claude Sonnet 4.5 --- .gitignore | 3 ++ TROUBLESHOOTING.md | 63 +++++++++++++++++++++++++++++++++++++ src/sba_scout/api/client.py | 15 ++++++--- src/sba_scout/app.py | 44 +++++++++++++++++++++++--- 4 files changed, 116 insertions(+), 9 deletions(-) create mode 100644 TROUBLESHOOTING.md diff --git a/.gitignore b/.gitignore index 28b7b6d..348deee 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ wheels/ __pycache__/ *.pyc .venv/ + +# Data directory (contains user settings, logs, and database) +data/ diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 0000000..e120616 --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,63 @@ +# Troubleshooting Guide + +## Sync Errors + +### API Key Configuration + +The app requires a valid API key to sync data from the SBA Major Domo API. + +**Option 1: Settings Screen (Recommended)** +1. Launch the app: `sba-scout` +2. Press `x` to open Settings +3. Enter your API key +4. Click "Save Settings" + +**Option 2: Edit settings.yaml directly** +1. Edit `data/settings.yaml` +2. Set `api.api_key` to your actual API key +3. Save the file +4. Restart the app + +### Viewing Logs + +All errors are now logged to `data/logs/sba_scout.log` instead of the terminal. + +To view recent errors: +```bash +tail -f data/logs/sba_scout.log +``` + +To search for specific errors: +```bash +grep "ERROR" data/logs/sba_scout.log +``` + +### Common Errors + +**"API key not configured"** +- Your API key is missing or set to the placeholder value +- Fix: Configure your API key using one of the methods above + +**"Sync failed: Invalid API key"** (401/403 error) +- Your API key is invalid or expired +- Fix: Get a new API key from the SBA Major Domo system + +**"Sync failed: API error 500"** +- The API server is experiencing issues +- Fix: Wait a few minutes and try again + +**"Request failed: Connection timeout"** +- Network connectivity issues +- Fix: Check your internet connection, or increase timeout in settings + +## Getting an API Key + +To get an API key for the SBA Major Domo API: +1. Visit the SBA Major Domo system +2. Go to your account settings +3. Generate a new API key +4. Copy the key and paste it into the app settings + +## Need More Help? + +Check the full logs at `data/logs/sba_scout.log` for detailed error messages and stack traces. diff --git a/src/sba_scout/api/client.py b/src/sba_scout/api/client.py index 858a639..a5e08d3 100644 --- a/src/sba_scout/api/client.py +++ b/src/sba_scout/api/client.py @@ -112,11 +112,16 @@ class LeagueAPIClient: response.raise_for_status() return response.json() except httpx.HTTPStatusError as e: - logger.error(f"API error: {e.response.status_code} - {e.response.text}") - raise LeagueAPIError( - f"API request failed: {e.response.text}", - status_code=e.response.status_code, - ) + # Check if this is a Cloudflare HTML error page + response_text = e.response.text + if "cloudflare" in response_text.lower() and e.response.status_code == 403: + error_msg = "API blocked by Cloudflare (missing or invalid API key)" + logger.error(f"API error: {e.response.status_code} - Cloudflare block") + else: + error_msg = f"API request failed (HTTP {e.response.status_code})" + logger.error(f"API error: {e.response.status_code} - {response_text[:200]}") + + raise LeagueAPIError(error_msg, status_code=e.response.status_code) except httpx.RequestError as e: logger.error(f"Request error: {e}") raise LeagueAPIError(f"Request failed: {str(e)}") diff --git a/src/sba_scout/app.py b/src/sba_scout/app.py index 6e30285..d6765ab 100644 --- a/src/sba_scout/app.py +++ b/src/sba_scout/app.py @@ -23,10 +23,20 @@ from .screens.matchup import MatchupScreen from .screens.roster import RosterScreen from .screens.settings import SettingsScreen -# Configure logging +# Configure logging - write to file to avoid interfering with TUI +from pathlib import Path + +log_dir = Path("data/logs") +log_dir.mkdir(parents=True, exist_ok=True) +log_file = log_dir / "sba_scout.log" + logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + handlers=[ + logging.FileHandler(log_file), + # Don't add StreamHandler - it corrupts the TUI + ], ) logger = logging.getLogger(__name__) @@ -100,6 +110,7 @@ class DashboardScreen(Screen): # Status bar with Horizontal(id="status-bar"): yield Label("Last sync: Never", id="sync-status") + yield LoadingIndicator(id="sync-loader") yield Button("Sync Now [s]", id="btn-sync", variant="success") yield Button("Settings [x]", id="btn-settings", variant="default") @@ -186,28 +197,47 @@ class DashboardScreen(Screen): async def action_sync_data(self) -> None: """Sync data from the league API.""" + from .api.client import LeagueAPIError from .api.sync import sync_all sync_btn = self.query_one("#btn-sync", Button) sync_status = self.query_one("#sync-status", Label) + sync_loader = self.query_one("#sync-loader", LoadingIndicator) sync_btn.disabled = True + sync_loader.display = True sync_status.update("Syncing...") try: + settings = get_settings() + async with get_session() as session: - counts = await sync_all(session, season=13) + counts = await sync_all(session, season=settings.team.current_season) sync_status.update(f"Synced: {counts['teams']} teams, {counts['players']} players") # Refresh roster summary await self.load_roster_summary() + except LeagueAPIError as e: + logger.error(f"API error during sync: {e.message}") + if "cloudflare" in e.message.lower() or e.status_code == 403: + sync_status.update("Sync disabled: API key required") + elif e.status_code == 401: + sync_status.update("Sync failed: Invalid API key") + elif e.status_code: + sync_status.update(f"Sync failed: HTTP {e.status_code}") + else: + sync_status.update(f"Sync failed: {str(e.message)[:40]}") + except Exception as e: - logger.error(f"Sync failed: {e}") - sync_status.update(f"Sync failed: {str(e)[:50]}") + logger.error(f"Unexpected error during sync: {e}", exc_info=True) + # Show a user-friendly truncated message + error_msg = str(e).split("\n")[0][:50] + sync_status.update(f"Sync failed: {error_msg}") finally: + sync_loader.display = False sync_btn.disabled = False @@ -313,6 +343,12 @@ class SBAScoutApp(App): width: 1fr; } + #sync-loader { + width: auto; + margin-right: 1; + display: none; + } + #btn-sync { width: auto; }