refactor: phase 3 - API efficiency improvements
Add typed dataclasses for tuple returns: - types.py: HeadlineItem, FetchResult, Block dataclasses with legacy tuple converters - fetch.py: Add type hints and HeadlineTuple type alias Add pyright for static type checking: - Add pyright to dependencies - Verify type coverage with pyright (0 errors in core modules) This enables: - Named types instead of raw tuples (better IDE support, self-documenting) - Type-safe APIs across modules - Backward compatibility via to_tuple/from_tuple methods Note: Lazy imports skipped for render.py - startup impact is minimal.
This commit is contained in:
@@ -8,6 +8,7 @@ import pathlib
|
||||
import re
|
||||
import urllib.request
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
import feedparser
|
||||
|
||||
@@ -16,9 +17,13 @@ from engine.filter import skip, strip_tags
|
||||
from engine.sources import FEEDS, POETRY_SOURCES
|
||||
from engine.terminal import boot_ln
|
||||
|
||||
# Type alias for headline items
|
||||
HeadlineTuple = tuple[str, str, str]
|
||||
|
||||
|
||||
# ─── SINGLE FEED ──────────────────────────────────────────
|
||||
def fetch_feed(url):
|
||||
def fetch_feed(url: str) -> Any | None:
|
||||
"""Fetch and parse a single RSS feed URL."""
|
||||
try:
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "mainline/0.1"})
|
||||
resp = urllib.request.urlopen(req, timeout=config.FEED_TIMEOUT)
|
||||
@@ -28,8 +33,9 @@ def fetch_feed(url):
|
||||
|
||||
|
||||
# ─── ALL RSS FEEDS ────────────────────────────────────────
|
||||
def fetch_all():
|
||||
items = []
|
||||
def fetch_all() -> tuple[list[HeadlineTuple], int, int]:
|
||||
"""Fetch all RSS feeds and return items, linked count, failed count."""
|
||||
items: list[HeadlineTuple] = []
|
||||
linked = failed = 0
|
||||
for src, url in FEEDS.items():
|
||||
feed = fetch_feed(url)
|
||||
@@ -59,7 +65,7 @@ def fetch_all():
|
||||
|
||||
|
||||
# ─── PROJECT GUTENBERG ────────────────────────────────────
|
||||
def _fetch_gutenberg(url, label):
|
||||
def _fetch_gutenberg(url: str, label: str) -> list[HeadlineTuple]:
|
||||
"""Download and parse stanzas/passages from a Project Gutenberg text."""
|
||||
try:
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "mainline/0.1"})
|
||||
|
||||
60
engine/types.py
Normal file
60
engine/types.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
Shared dataclasses for the mainline application.
|
||||
Provides named types for tuple returns across modules.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class HeadlineItem:
|
||||
"""A single headline item: title, source, and timestamp."""
|
||||
|
||||
title: str
|
||||
source: str
|
||||
timestamp: str
|
||||
|
||||
def to_tuple(self) -> tuple[str, str, str]:
|
||||
"""Convert to tuple for backward compatibility."""
|
||||
return (self.title, self.source, self.timestamp)
|
||||
|
||||
@classmethod
|
||||
def from_tuple(cls, t: tuple[str, str, str]) -> "HeadlineItem":
|
||||
"""Create from tuple for backward compatibility."""
|
||||
return cls(title=t[0], source=t[1], timestamp=t[2])
|
||||
|
||||
|
||||
def items_to_tuples(items: list[HeadlineItem]) -> list[tuple[str, str, str]]:
|
||||
"""Convert list of HeadlineItem to list of tuples."""
|
||||
return [item.to_tuple() for item in items]
|
||||
|
||||
|
||||
def tuples_to_items(tuples: list[tuple[str, str, str]]) -> list[HeadlineItem]:
|
||||
"""Convert list of tuples to list of HeadlineItem."""
|
||||
return [HeadlineItem.from_tuple(t) for t in tuples]
|
||||
|
||||
|
||||
@dataclass
|
||||
class FetchResult:
|
||||
"""Result from fetch_all() or fetch_poetry()."""
|
||||
|
||||
items: list[HeadlineItem]
|
||||
linked: int
|
||||
failed: int
|
||||
|
||||
def to_legacy_tuple(self) -> tuple[list[tuple], int, int]:
|
||||
"""Convert to legacy tuple format for backward compatibility."""
|
||||
return ([item.to_tuple() for item in self.items], self.linked, self.failed)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Block:
|
||||
"""Rendered headline block from make_block()."""
|
||||
|
||||
content: list[str]
|
||||
color: str
|
||||
meta_row_index: int
|
||||
|
||||
def to_legacy_tuple(self) -> tuple[list[str], str, int]:
|
||||
"""Convert to legacy tuple format for backward compatibility."""
|
||||
return (self.content, self.color, self.meta_row_index)
|
||||
Reference in New Issue
Block a user