From 70b930a820486c2f3107b63d4b11e2858915488b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Reuh=20Fildadut?= Date: Sat, 11 Oct 2025 17:38:48 +0200 Subject: [PATCH] refactor: add missing type hints --- src/feather/client.py | 31 ++++++++++++++----------------- src/feather/config.py | 5 ++--- src/feather/data.py | 23 ++++++++++------------- src/feather/feather.py | 16 ++++++---------- 4 files changed, 32 insertions(+), 43 deletions(-) diff --git a/src/feather/client.py b/src/feather/client.py index 012bd07..5e5881d 100644 --- a/src/feather/client.py +++ b/src/feather/client.py @@ -12,7 +12,9 @@ from feather.data import Article, ArticleId, Category class ClientSession(ABC): - config: Config + @abstractmethod + def __init__(self, config: Config): + self.config: Config = config @abstractmethod def set_read_flag(self, article_ids: list[ArticleId], read: bool = True): @@ -36,21 +38,19 @@ class ClientSession(ABC): pass -label_name = re.compile("user/.*/label/(.*)") +label_name_re = re.compile("user/.*/label/(.*)") class GReaderSession(ClientSession): """Google Reader API client""" - greader: google_reader.Client - auth_token: str - csrf_token: str - def __init__(self, config: Config): - self.config = config - self.greader = google_reader.Client(config.server_url) - self.auth_token = self.greader.login(config.server_user, config.server_password) - self.csrf_token = self.greader.get_token(self.auth_token) + self.config: Config = config + self.greader: google_reader.Client = google_reader.Client(config.server_url) + self.auth_token: str = self.greader.login( + config.server_user, config.server_password + ) + self.csrf_token: str = self.greader.get_token(self.auth_token) def set_read_flag(self, article_ids: list[ArticleId], read: bool = True): if read: @@ -76,7 +76,7 @@ class GReaderSession(ClientSession): ] categories = [] for category in tags: - category_name = category.label or label_name.search(category.id).group(1) + category_name = category.label or label_name_re.search(category.id).group(1) category_id = category.id categories.append(Category(id=category_id, title=category_name)) return categories @@ -134,19 +134,16 @@ class GReaderArticle(Article): class TTRSession(ClientSession): """Tiny Tiny RSS API client""" - ttrss: TTRClient - feeds: dict - def __init__(self, config: Config): - self.config = config - self.ttrss = TTRClient( + self.config: Config = config + self.ttrss: TTRClient = TTRClient( config.server_url, config.server_user, config.server_password, auto_login=True, ) self.ttrss.login() - self.feeds = {} + self.feeds: dict = {} def set_read_flag(self, article_ids: list[ArticleId], read: bool = True): if read: diff --git a/src/feather/config.py b/src/feather/config.py index a23ce8e..e7c9314 100644 --- a/src/feather/config.py +++ b/src/feather/config.py @@ -6,14 +6,13 @@ from zoneinfo import ZoneInfo from pathlib import Path from jinja2 import Template +default_config_path: Path = Path(__file__).parent / "config.default.toml" + class ConfigurationError(ValueError): pass -default_config_path = Path(__file__).parent / "config.default.toml" - - class Config: def __init__(self): with default_config_path.open("rb") as f: diff --git a/src/feather/data.py b/src/feather/data.py index 7c01449..5cb9591 100644 --- a/src/feather/data.py +++ b/src/feather/data.py @@ -48,20 +48,17 @@ type CategoryId = int | str class Category: - id: CategoryId # category id - title: str # category name - parents: list[Category] # list of parent categories - order: int = 0 # category display order, starting from 1 (0 if unknown) - - def fromdict(d): + def fromdict(d) -> Category: parents = [Category.fromdict(parent) for parent in d["parents"]] return Category(d["id"], d["title"], parents, d["order"]) def __init__(self, id, title, parents=[], order=0): - self.id = id - self.title = title - self.parents = parents - self.order = order + self.id: CategoryId = id # category unique id + self.title: str = title # category name + self.parents: list[Category] = parents # list of parent categories + self.order: int = ( + order # category display order, starting from 1 (0 if unknown) + ) def asdict(self): return { @@ -106,7 +103,7 @@ class Article(ABC): language: str = "" # article language image_url: str = "" # article main image - def _get_html_path(self): + def _get_html_path(self) -> Path: config = self.config # Category directory path @@ -205,7 +202,7 @@ class Article(ABC): """Delete the JSON file associated with this article.""" self.json_path.unlink() - def has_html(self): + def has_html(self) -> bool: """Check if the HTML file associated with the article exists on disk.""" if self.html_path is None: return False @@ -257,7 +254,7 @@ class Article(ABC): self.compute_fields() # recompute formatted datetime & paths from the current configuration self.write() # rewrite JSON & HTML - def was_updated(self, old_article: Article): + def was_updated(self, old_article: Article) -> bool: """Returns true if the article is different from a previous version in a way that would require regeneration""" return old_article._get_template_dict() != self._get_template_dict() diff --git a/src/feather/feather.py b/src/feather/feather.py index 9b824f3..1c393d4 100755 --- a/src/feather/feather.py +++ b/src/feather/feather.py @@ -2,20 +2,17 @@ import asyncio import signal +from typing import Iterable from feather.config import Config -from feather.client import GReaderSession, TTRSession, ClientSession, ArticleId +from feather.client import GReaderSession, TTRSession, ClientSession, Article, ArticleId from feather.data import FileArticle class FeatherApp: - config: Config - def __init__(self, config: Config): - self.config = config - self._client_session = None - - _client_session: ClientSession + self.config: Config = config + self._client_session: ClientSession = None def get_client_session(self) -> ClientSession: """Connect to the server and return a ClientSession object; return an existing ClientSession if we are already connected""" @@ -32,7 +29,7 @@ class FeatherApp: ) return self._client_session - def iter_articles(self): + def iter_articles(self) -> Iterable[Article]: """Iterate over all the articles in local storage""" config = self.config for json_path in config.json_root.glob("*.json"): @@ -175,9 +172,8 @@ class FeatherApp: async def daemon(self): """Start the synchronization daemon""" - config = self.config print( - f"Started in daemon mode; changes will be downloaded from the server every {config.daemon_sync_down_every}s and uploaded every {config.daemon_sync_up_every}s" + f"Started in daemon mode; changes will be downloaded from the server every {self.config.daemon_sync_down_every}s and uploaded every {self.config.daemon_sync_up_every}s" ) async with asyncio.TaskGroup() as tg: tup = tg.create_task(self.daemon_sync_up_loop())