- Modular architecture with separate modules for scraping, parsing, security, validation, and caching - Comprehensive security measures including HTML sanitization, rate limiting, and input validation - Robust error handling with custom exceptions and retry logic - HTTP caching with ETags and Last-Modified headers for efficiency - Pre-compiled regex patterns for improved performance - Comprehensive test suite with 66 tests covering all major functionality - Docker support for containerized deployment - Configuration management with environment variable support - Working parser that successfully extracts 32 articles from Warhammer Community 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
59 lines
2.0 KiB
Python
59 lines
2.0 KiB
Python
"""RSS feed generation functionality."""
|
|
|
|
import os
|
|
import logging
|
|
from typing import List, Dict, Any
|
|
from feedgen.feed import FeedGenerator
|
|
|
|
from .config import Config
|
|
from .validation import validate_output_path
|
|
from .exceptions import FileOperationError
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def generate_rss_feed(articles: List[Dict[str, Any]], feed_url: str) -> bytes:
|
|
"""Generate RSS feed from articles list."""
|
|
logger.info(f"Generating RSS feed for {len(articles)} articles")
|
|
|
|
# Initialize the RSS feed generator
|
|
fg = FeedGenerator()
|
|
fg.title(Config.FEED_TITLE)
|
|
fg.link(href=feed_url)
|
|
fg.description(Config.FEED_DESCRIPTION)
|
|
|
|
# Add the sorted articles to the RSS feed
|
|
for article in articles:
|
|
fe = fg.add_entry()
|
|
fe.title(article['title'])
|
|
fe.link(href=article['link'])
|
|
fe.pubDate(article['date'])
|
|
|
|
# Generate the RSS feed
|
|
return fg.rss_str(pretty=True)
|
|
|
|
|
|
def save_rss_feed(rss_content: bytes, output_dir: str) -> str:
|
|
"""Save RSS feed to file."""
|
|
try:
|
|
rss_path = validate_output_path(os.path.join(output_dir, Config.RSS_FILENAME), output_dir)
|
|
with open(rss_path, 'wb') as f:
|
|
f.write(rss_content)
|
|
logger.info(f'RSS feed saved to: {rss_path}')
|
|
return rss_path
|
|
except Exception as e:
|
|
raise FileOperationError(f"Failed to save RSS feed: {e}")
|
|
|
|
|
|
def save_debug_html(html_content: str, output_dir: str) -> None:
|
|
"""Save HTML content for debugging purposes."""
|
|
try:
|
|
from bs4 import BeautifulSoup
|
|
soup = BeautifulSoup(html_content, 'html.parser')
|
|
html_path = validate_output_path(os.path.join(output_dir, Config.DEBUG_HTML_FILENAME), output_dir)
|
|
with open(html_path, 'w', encoding='utf-8') as f:
|
|
f.write(soup.prettify())
|
|
logger.info(f'Debug HTML saved to: {html_path}')
|
|
except Exception as e:
|
|
# HTML saving is not critical, just log the error
|
|
logger.warning(f"Failed to save debug HTML: {e}") |