From 04cc62ea0669dbd9b93a2930f81bec14fe4cac77 Mon Sep 17 00:00:00 2001 From: Sven Geboers Date: Thu, 30 Apr 2026 23:59:02 +0200 Subject: [PATCH] refactor: tighten exception handling in database.py and add BLE lint rule - Wrap duckdb.connect() in try/except with specific duckdb.Error - Replace bare with in _init_database - Replace broad with for ALTER TABLE - Add ruff BLE (blind except) lint rule to prevent regressions - Add tests verifying graceful error handling for connect, insert, query P2-002: Fix broad exception handling --- database.py | 20 +++++++++++----- pyproject.toml | 2 +- tests/test_database_exceptions.py | 38 +++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 tests/test_database_exceptions.py diff --git a/database.py b/database.py index c0e346e..a9ee544 100644 --- a/database.py +++ b/database.py @@ -39,12 +39,17 @@ class MotionDatabase: fh.write("[]") return - conn = duckdb.connect(self.db_path) + try: + conn = duckdb.connect(self.db_path) + except (duckdb.Error, OSError) as e: + _logger.warning("Could not connect to DuckDB at %s: %s. Operating in file mode.", self.db_path, e) + self._file_mode = True + return # Create sequence for auto-incrementing IDs try: conn.execute("CREATE SEQUENCE IF NOT EXISTS motions_id_seq START 1") - except: + except duckdb.Error: pass # Create tables with proper ID handling @@ -72,7 +77,7 @@ class MotionDatabase: "ALTER TABLE motions ADD COLUMN IF NOT EXISTS externe_identifier TEXT" ) conn.execute("ALTER TABLE motions ADD COLUMN IF NOT EXISTS body_text TEXT") - except Exception: + except duckdb.Error: # Best-effort: if ALTER fails for any reason, continue without stopping app startup _logger.debug( "Could not ALTER motions table to add new columns (may already exist or unsupported)." @@ -188,7 +193,10 @@ class MotionDatabase: ) """) - conn.close() + try: + conn.close() + except duckdb.Error: + pass def reset_database(self): """Development helper: drop known tables and re-run initialization. @@ -201,7 +209,7 @@ class MotionDatabase: for t in ("party_results", "user_sessions", "motions"): try: conn.execute(f"DROP TABLE IF EXISTS {t}") - except Exception: + except duckdb.Error: pass # Recreate schema conn.close() @@ -209,7 +217,7 @@ class MotionDatabase: finally: try: conn.close() - except Exception: + except duckdb.Error: pass def append_audit_event( diff --git a/pyproject.toml b/pyproject.toml index bebd2c2..d1a2cc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dev = [ ] [tool.ruff.lint] -select = ["T20"] +select = ["T20", "BLE"] ignore = [] [tool.ruff.lint.per-file-ignores] diff --git a/tests/test_database_exceptions.py b/tests/test_database_exceptions.py new file mode 100644 index 0000000..a34e2ca --- /dev/null +++ b/tests/test_database_exceptions.py @@ -0,0 +1,38 @@ +import logging +from unittest.mock import MagicMock, patch + +import pytest + +from database import MotionDatabase + + +class TestMotionDatabaseExceptionHandling: + @patch("database.duckdb") + def test_init_database_catches_duckdb_connect_errors(self, mock_duckdb, tmp_path): + mock_duckdb.Error = Exception + mock_duckdb.connect.side_effect = mock_duckdb.Error("db locked") + db = MotionDatabase(db_path=str(tmp_path / "test.db")) + assert db._file_mode is True + + @patch("database.duckdb") + def test_insert_motion_catches_errors_and_returns_false(self, mock_duckdb, tmp_path): + mock_duckdb.Error = Exception + mock_conn = MagicMock() + mock_duckdb.connect.return_value = mock_conn + + db = MotionDatabase(db_path=str(tmp_path / "test.db")) + # Now make insert_motion fail + mock_conn.execute.side_effect = mock_duckdb.Error("constraint violation") + result = db.insert_motion({"title": "Test", "date": "2024-01-01", "url": "http://test"}) + assert result is False + + @patch("database.duckdb") + def test_query_motions_catches_errors_and_returns_empty_list(self, mock_duckdb, tmp_path): + mock_duckdb.Error = Exception + mock_conn = MagicMock() + mock_duckdb.connect.return_value = mock_conn + + db = MotionDatabase(db_path=str(tmp_path / "test.db")) + mock_conn.execute.side_effect = mock_duckdb.Error("syntax error") + result = db.get_filtered_motions() + assert result == []