fix: resolve all mypy type errors across service modules

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Justin Visser 2026-02-27 15:49:27 +01:00
parent f11947aad2
commit 74249fa24c
10 changed files with 39 additions and 31 deletions

View file

@ -232,7 +232,7 @@ async def _request_arxiv_feed(
timeout_value = _timeout_seconds(timeout_seconds)
headers = {"User-Agent": f"scholar-scraper/1.0 (mailto:{_contact_email(request_email)})"}
async with httpx.AsyncClient(timeout=timeout_value, follow_redirects=True, headers=headers) as client:
return await client.get(_ARXIV_API_URL, params=params)
return await client.get(_ARXIV_API_URL, params=params) # type: ignore[arg-type]
return await run_with_global_arxiv_limit(
fetch=_fetch,

View file

@ -3,7 +3,7 @@ from __future__ import annotations
from datetime import UTC, datetime
from typing import Any
from sqlalchemy import delete, exists, func, select, update
from sqlalchemy import CursorResult, delete, exists, func, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.models import DataRepairJob, IngestionQueueItem, Publication, ScholarProfile, ScholarPublication
@ -152,7 +152,7 @@ def _job_summary(
async def _delete_links_for_targets(db_session: AsyncSession, *, target_scholar_profile_ids: list[int]) -> int:
result = await db_session.execute(
result: CursorResult[Any] = await db_session.execute( # type: ignore[assignment]
delete(ScholarPublication).where(ScholarPublication.scholar_profile_id.in_(target_scholar_profile_ids))
)
return int(result.rowcount or 0)
@ -167,7 +167,7 @@ async def _delete_queue_for_targets(
stmt = delete(IngestionQueueItem).where(IngestionQueueItem.scholar_profile_id.in_(target_scholar_profile_ids))
if user_id is not None:
stmt = stmt.where(IngestionQueueItem.user_id == user_id)
result = await db_session.execute(stmt)
result: CursorResult[Any] = await db_session.execute(stmt) # type: ignore[assignment]
return int(result.rowcount or 0)
@ -180,7 +180,7 @@ async def _reset_scholar_tracking_state(
stmt = update(ScholarProfile).where(ScholarProfile.id.in_(target_scholar_profile_ids))
if user_id is not None:
stmt = stmt.where(ScholarProfile.user_id == user_id)
result = await db_session.execute(
result: CursorResult[Any] = await db_session.execute( # type: ignore[assignment]
stmt.values(
baseline_completed=False,
last_initial_page_fingerprint_sha256=None,
@ -193,7 +193,7 @@ async def _reset_scholar_tracking_state(
async def _delete_orphan_publications(db_session: AsyncSession) -> int:
result = await db_session.execute(
result: CursorResult[Any] = await db_session.execute( # type: ignore[assignment]
delete(Publication).where(~exists(select(1).where(ScholarPublication.publication_id == Publication.id)))
)
return int(result.rowcount or 0)

View file

@ -4,6 +4,7 @@ import asyncio
import logging
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
from typing import Any
from sqlalchemy import select
@ -158,7 +159,7 @@ class SchedulerService:
continue
await self._run_candidate(candidate)
async def _load_candidate_rows(self) -> list[tuple]:
async def _load_candidate_rows(self) -> list[Any]:
session_factory = get_session_factory()
async with session_factory() as session:
result = await session.execute(
@ -173,10 +174,10 @@ class SchedulerService:
.where(User.is_active.is_(True), UserSetting.auto_run_enabled.is_(True))
.order_by(UserSetting.user_id.asc())
)
return result.all()
return list(result.all())
@staticmethod
def _candidate_from_row(row: tuple, *, now_utc: datetime) -> _AutoRunCandidate | None:
def _candidate_from_row(row: Any, *, now_utc: datetime) -> _AutoRunCandidate | None:
user_id, run_interval_minutes, request_delay_seconds, cooldown_until, cooldown_reason = row
if cooldown_until is not None and cooldown_until.tzinfo is None:
cooldown_until = cooldown_until.replace(tzinfo=UTC)

View file

@ -547,7 +547,7 @@ async def run_scholar_iteration(
queue_delay_seconds: int,
) -> RunProgress:
progress = RunProgress()
scholar_kwargs = {
scholar_kwargs: dict[str, Any] = {
"request_delay_seconds": request_delay_seconds,
"network_error_retries": network_error_retries,
"retry_backoff_seconds": retry_backoff_seconds,

View file

@ -23,7 +23,7 @@ def _serialize_export_scholar(profile: ScholarProfile) -> dict[str, Any]:
}
def _serialize_export_publication(row: tuple[Any, ...]) -> dict[str, Any]:
def _serialize_export_publication(row: Any) -> dict[str, Any]:
(
scholar_id,
cluster_id,

View file

@ -2,6 +2,7 @@ from __future__ import annotations
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
from typing import Any
from sqlalchemy import Select, and_, func, literal, or_, select, union_all
from sqlalchemy.ext.asyncio import AsyncSession
@ -43,7 +44,7 @@ def _retry_item_label(display_name: str | None, scholar_id: str | None) -> str:
def _retry_item_from_publication(
publication: Publication,
*,
link_row: tuple | None,
link_row: Any | None,
) -> PublicationListItem:
if link_row is None:
scholar_profile_id = 0
@ -270,7 +271,7 @@ class PdfQueueListItem:
display_identifier: DisplayIdentifier | None = None
def _queue_item_from_row(row: tuple) -> PdfQueueListItem:
def _queue_item_from_row(row: Any) -> PdfQueueListItem:
return PdfQueueListItem(
publication_id=int(row[0]),
title=str(row[1] or ""),
@ -292,7 +293,7 @@ def _queue_item_from_row(row: tuple) -> PdfQueueListItem:
async def _hydrated_queue_items(
db_session: AsyncSession,
*,
rows: list[tuple],
rows: list[Any],
) -> list[PdfQueueListItem]:
items = [_queue_item_from_row(row) for row in rows]
return await identifier_service.overlay_pdf_queue_items_with_display_identifiers(

View file

@ -1,8 +1,9 @@
from __future__ import annotations
from datetime import datetime
from typing import Any
from sqlalchemy import Select, case, func, select
from sqlalchemy import Select, case, false as sa_false, func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.models import (
@ -124,7 +125,7 @@ def publications_query(
stmt = stmt.where(ScholarPublication.is_read.is_(False))
if mode == MODE_LATEST:
if latest_run_id is None:
return stmt.where(False)
return stmt.where(sa_false())
stmt = stmt.where(ScholarPublication.first_seen_run_id == latest_run_id)
if snapshot_before is not None:
stmt = stmt.where(ScholarPublication.created_at <= snapshot_before)
@ -195,7 +196,7 @@ async def get_publication_item_for_user(
def publication_list_item_from_row(
row: tuple,
row: Any,
*,
latest_run_id: int | None,
) -> PublicationListItem:
@ -232,7 +233,7 @@ def publication_list_item_from_row(
)
def unread_item_from_row(row: tuple) -> UnreadPublicationItem:
def unread_item_from_row(row: Any) -> UnreadPublicationItem:
(
publication_id,
scholar_profile_id,

View file

@ -1,6 +1,8 @@
from __future__ import annotations
from sqlalchemy import select, tuple_, update
from typing import Any
from sqlalchemy import CursorResult, select, tuple_, update
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.models import ScholarProfile, ScholarPublication
@ -34,7 +36,7 @@ async def mark_all_unread_as_read_for_user(
)
.values(is_read=True)
)
result = await db_session.execute(stmt)
result: CursorResult[Any] = await db_session.execute(stmt) # type: ignore[assignment]
await db_session.commit()
return int(result.rowcount or 0)
@ -62,7 +64,7 @@ async def mark_selected_as_read_for_user(
)
.values(is_read=True)
)
result = await db_session.execute(stmt)
result: CursorResult[Any] = await db_session.execute(stmt) # type: ignore[assignment]
await db_session.commit()
return int(result.rowcount or 0)
@ -85,6 +87,6 @@ async def set_publication_favorite_for_user(
)
.values(is_favorite=bool(is_favorite))
)
result = await db_session.execute(stmt)
result: CursorResult[Any] = await db_session.execute(stmt) # type: ignore[assignment]
await db_session.commit()
return int(result.rowcount or 0)

View file

@ -1,5 +1,7 @@
from __future__ import annotations
from typing import Any
from sqlalchemy import and_, select
from app.db.models import IngestionQueueItem, ScholarProfile
@ -38,7 +40,7 @@ def queue_item_select(*, user_id: int):
)
def queue_list_item_from_row(row: tuple) -> QueueListItem:
def queue_list_item_from_row(row: Any) -> QueueListItem:
(
item_id,
scholar_profile_id,

View file

@ -1,6 +1,7 @@
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from typing import cast
from sqlalchemy import delete, func, select
from sqlalchemy.ext.asyncio import AsyncSession
@ -136,14 +137,14 @@ def _deserialize_parsed_author_search_page(payload: object) -> ParsedAuthorSearc
state_reason=str(payload.get("state_reason", "")).strip() or "unknown",
candidates=[
ScholarSearchCandidate(
scholar_id=item["scholar_id"],
display_name=item["display_name"],
affiliation=item["affiliation"],
email_domain=item["email_domain"],
cited_by_count=item["cited_by_count"],
interests=item["interests"],
profile_url=item["profile_url"],
profile_image_url=item["profile_image_url"],
scholar_id=cast(str, item["scholar_id"]),
display_name=cast(str, item["display_name"]),
affiliation=cast("str | None", item["affiliation"]),
email_domain=cast("str | None", item["email_domain"]),
cited_by_count=cast("int | None", item["cited_by_count"]),
interests=cast("list[str]", item["interests"]),
profile_url=cast(str, item["profile_url"]),
profile_image_url=cast("str | None", item["profile_image_url"]),
)
for item in normalized_candidates
],