From 8da51fff2be754925d440d99159eab0bd449caae Mon Sep 17 00:00:00 2001 From: aserper Date: Sat, 13 Dec 2025 23:20:03 -0500 Subject: [PATCH] Fix linting errors: Remove unused imports and reformat with black --- bot.py | 1 - main.py | 35 +++++++++++++++++++++++------------ test_bot.py | 21 +++++++++++++-------- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/bot.py b/bot.py index 2a1263e..c4ff2eb 100644 --- a/bot.py +++ b/bot.py @@ -1,7 +1,6 @@ """Mastodon RSS Bot - Core functionality""" import logging -import os import time from pathlib import Path from typing import List, Optional, Set diff --git a/main.py b/main.py index e083634..41f7186 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,7 @@ import os import sys from dataclasses import dataclass, field from pathlib import Path -from typing import List, Optional +from typing import List from bot import MastodonRSSBot @@ -11,7 +11,7 @@ from bot import MastodonRSSBot logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", - handlers=[logging.StreamHandler(sys.stdout)] + handlers=[logging.StreamHandler(sys.stdout)], ) logger = logging.getLogger(__name__) @@ -19,6 +19,7 @@ logger = logging.getLogger(__name__) @dataclass class Config: """Configuration loaded from environment variables.""" + instance_url: str client_id: str client_secret: str @@ -26,7 +27,9 @@ class Config: feed_urls: List[str] = field(default_factory=list) toot_visibility: str = "public" check_interval: int = 300 - state_file: Path = field(default_factory=lambda: Path("/state/processed_entries.txt")) + state_file: Path = field( + default_factory=lambda: Path("/state/processed_entries.txt") + ) @classmethod def from_env(cls) -> "Config": @@ -38,18 +41,22 @@ class Config: if not all([instance_url, client_id, client_secret, access_token]): missing = [ - k for k, v in { + k + for k, v in { "MASTODON_INSTANCE_URL": instance_url, "MASTODON_CLIENT_ID": client_id, "MASTODON_CLIENT_SECRET": client_secret, - "MASTODON_ACCESS_TOKEN": access_token - }.items() if not v + "MASTODON_ACCESS_TOKEN": access_token, + }.items() + if not v ] - raise ValueError(f"Missing required environment variables: {', '.join(missing)}") + raise ValueError( + f"Missing required environment variables: {', '.join(missing)}" + ) # Parse feeds feed_urls = [] - + # 1. Legacy single feed URL if os.environ.get("RSS_FEED_URL"): feed_urls.append(os.environ["RSS_FEED_URL"]) @@ -83,17 +90,21 @@ class Config: unique_feed_urls = list(dict.fromkeys(feed_urls)) if not unique_feed_urls: - raise ValueError("No RSS feeds configured. Please set RSS_FEED_URL, RSS_FEEDS, or FEEDS_FILE.") + raise ValueError( + "No RSS feeds configured. Please set RSS_FEED_URL, RSS_FEEDS, or FEEDS_FILE." + ) return cls( instance_url=instance_url, # type: ignore # checked above - client_id=client_id, # type: ignore - client_secret=client_secret,# type: ignore + client_id=client_id, # type: ignore + client_secret=client_secret, # type: ignore access_token=access_token, # type: ignore feed_urls=unique_feed_urls, toot_visibility=os.environ.get("TOOT_VISIBILITY", "public"), check_interval=int(os.environ.get("CHECK_INTERVAL", "300")), - state_file=Path(os.environ.get("PROCESSED_ENTRIES_FILE", "/state/processed_entries.txt")) + state_file=Path( + os.environ.get("PROCESSED_ENTRIES_FILE", "/state/processed_entries.txt") + ), ) diff --git a/test_bot.py b/test_bot.py index ce86bfa..b0139bc 100644 --- a/test_bot.py +++ b/test_bot.py @@ -52,7 +52,7 @@ class TestMastodonRSSBot(unittest.TestCase): def test_save_processed_entries_error(self, mock_mastodon): """Test error handling when saving processed entries fails""" bot = MastodonRSSBot(**self.test_config) - + # Mock Path.write_text to raise exception with patch.object(Path, "write_text", side_effect=Exception("Disk full")): # Should not raise exception @@ -70,17 +70,17 @@ class TestMastodonRSSBot(unittest.TestCase): feed = bot.parse_feed("https://example.com/feed.xml") self.assertIsNotNone(feed) - # We can't easily assert the log/print was called without mocking logging, + # We can't easily assert the log/print was called without mocking logging, # but execution flow is covered. @patch("bot.Mastodon") def test_run_keyboard_interrupt(self, mock_mastodon): """Test clean exit on KeyboardInterrupt""" bot = MastodonRSSBot(**self.test_config) - + # Mock process_new_entries to raise KeyboardInterrupt bot.process_new_entries = Mock(side_effect=KeyboardInterrupt) - + # Should exit cleanly bot.run() bot.process_new_entries.assert_called_once() @@ -90,12 +90,14 @@ class TestMastodonRSSBot(unittest.TestCase): def test_run_exception_retry(self, mock_mastodon, mock_sleep): """Test retry logic on exception in main loop""" bot = MastodonRSSBot(**self.test_config) - + # Raise exception once, then KeyboardInterrupt to exit loop - bot.process_new_entries = Mock(side_effect=[Exception("Network Error"), KeyboardInterrupt]) - + bot.process_new_entries = Mock( + side_effect=[Exception("Network Error"), KeyboardInterrupt] + ) + bot.run() - + self.assertEqual(bot.process_new_entries.call_count, 2) mock_sleep.assert_called_with(bot.check_interval) @@ -107,6 +109,7 @@ class TestMainEntry(unittest.TestCase): def test_config_missing_vars(self): """Test Config raises ValueError when env vars are missing""" from main import Config + with self.assertRaises(ValueError): Config.from_env() @@ -123,6 +126,7 @@ class TestMainEntry(unittest.TestCase): def test_config_no_feeds(self): """Test Config raises ValueError when no feeds are configured""" from main import Config + with self.assertRaises(ValueError): Config.from_env() @@ -139,6 +143,7 @@ class TestMainEntry(unittest.TestCase): def test_config_feed_file_error(self): """Test Config handles missing/bad feeds file gracefully (logs warning but continues check)""" from main import Config + # Should raise ValueError ultimately because no feeds are found, # but cover the file reading path with self.assertRaises(ValueError) as cm: