Merge pull request #109 from christianlouis/copilot/fix-mypy-type-errors

fix: resolve 21 mypy type errors across backend modules
This commit is contained in:
Christian Krakau-Louis
2026-03-28 20:53:11 +01:00
committed by GitHub
9 changed files with 24 additions and 23 deletions
+1
View File
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Test connection always showed success; add per-account test endpoint
([`441e8b5`](https://github.com/christianlouis/InboxConverge/commit/441e8b54e958686fe52414393d715a18dfcda77c))
- Fixed 21 mypy type errors across `notification_service.py`, `mail_processor.py`, `auth.py`, `tasks.py`, `providers.py`, `mail_accounts.py`, and `main.py`
## v0.3.0 (2026-03-28)
+2 -2
View File
@@ -153,13 +153,13 @@ async def login(
# Domain restriction — superusers always bypass
if not user.is_superuser:
_check_domain_allowed(user.email)
_check_domain_allowed(str(user.email))
# Update last login
user.last_login_at = datetime.now(timezone.utc) # type: ignore[assignment]
# Auto-promote to superuser if this is the configured admin email
if not user.is_superuser and _is_admin_email(user.email):
if not user.is_superuser and _is_admin_email(str(user.email)):
user.is_superuser = True # type: ignore[assignment]
logger.info(f"Auto-promoted admin user: {user.email}")
@@ -75,7 +75,7 @@ async def create_mail_account(
"pro": settings.TIER_PRO_MAX_ACCOUNTS,
"enterprise": settings.TIER_ENTERPRISE_MAX_ACCOUNTS,
}
max_accounts = tier_limits.get(current_user.subscription_tier.value, 1)
max_accounts = tier_limits.get(current_user.subscription_tier.value, 1) # type: ignore[assignment]
if len(existing_accounts) >= max_accounts:
raise HTTPException(
@@ -320,7 +320,7 @@ async def test_existing_mail_connection(
)
try:
password = decrypt_credential(account.encrypted_password)
password = decrypt_credential(str(account.encrypted_password))
except Exception:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+1 -1
View File
@@ -266,7 +266,7 @@ async def save_gmail_credential(
gmail_email=credential_in.gmail_email,
encrypted_access_token=encrypted_access,
encrypted_refresh_token=encrypted_refresh,
scopes=build_gmail_credential_scopes(),
scopes=build_gmail_credential_scopes(granted_scopes=[]),
is_valid=True,
last_verified_at=datetime.now(timezone.utc),
)
+1 -1
View File
@@ -36,7 +36,7 @@ logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
"""Application lifespan handler for startup and shutdown events."""
# Startup
logger.info(f"Starting {settings.APP_NAME} v{settings.APP_VERSION}")
+5 -5
View File
@@ -183,18 +183,18 @@ class MailProcessor:
if self.account.protocol == MailProtocol.POP3_SSL:
context = ssl.create_default_context()
pop_conn = poplib.POP3_SSL(
self.account.host,
self.account.port,
str(self.account.host),
int(self.account.port),
context=context,
timeout=30,
)
else:
pop_conn = poplib.POP3(
self.account.host, self.account.port, timeout=30
pop_conn = poplib.POP3( # type: ignore[assignment]
str(self.account.host), int(self.account.port), timeout=30
)
# Authenticate
pop_conn.user(self.account.username)
pop_conn.user(str(self.account.username))
pop_conn.pass_(self.password)
# Retrieve UIDL map: {msg_number: uid_string}
+2 -2
View File
@@ -60,7 +60,7 @@ async def send_user_notification(
continue
try:
success = await _send_apprise(config.apprise_url or "", title, body)
success = await _send_apprise(str(config.apprise_url or ""), title, body)
if success:
sent += 1
except Exception as exc:
@@ -99,7 +99,7 @@ async def send_admin_notification(
sent = 0
for config in configs:
try:
success = await _send_apprise(config.apprise_url or "", title, body)
success = await _send_apprise(str(config.apprise_url or ""), title, body)
if success:
sent += 1
except Exception as exc:
+9 -10
View File
@@ -137,6 +137,7 @@ async def process_mail_account(account_id: int):
gmail_service = None
smtp_config = None
gmail_cred = None
if use_gmail_api:
# Get user's Gmail credentials
@@ -238,7 +239,7 @@ async def process_mail_account(account_id: int):
error_msg: str | None = None
try:
if use_gmail_api and gmail_service:
if use_gmail_api and gmail_service and gmail_cred:
# Inject via Gmail API (preferred)
label_ids = await gmail_service.build_import_label_ids(
import_label_templates=gmail_cred.import_label_templates,
@@ -286,7 +287,7 @@ async def process_mail_account(account_id: int):
try:
await send_user_notification(
db=db,
user_id=account.user_id,
user_id=int(account.user_id),
title="InboxRescue: Gmail Authorization Expired",
body=f"Your Gmail credentials for account '{account.name}' have been revoked. Please re-authorize Gmail access in Settings.",
notify_on_error=True,
@@ -347,7 +348,7 @@ async def process_mail_account(account_id: int):
run.emails_failed = emails_failed # type: ignore[assignment]
run.completed_at = datetime.now(timezone.utc) # type: ignore[assignment]
run.duration_seconds = (
run.completed_at - _as_utc(run.started_at)
run.completed_at - _as_utc(run.started_at) # type: ignore[arg-type]
).total_seconds()
run.status = "completed" if emails_failed == 0 else "partial_failure" # type: ignore[assignment]
@@ -366,7 +367,7 @@ async def process_mail_account(account_id: int):
try:
await send_user_notification(
db=db,
user_id=account.user_id,
user_id=int(account.user_id),
title="InboxRescue: Mail Forwarding Failures",
body=f"Mail account '{account.name}': {emails_failed} email(s) failed to forward.",
notify_on_error=True,
@@ -440,7 +441,7 @@ async def process_mail_account(account_id: int):
try:
await send_user_notification(
db=db,
user_id=account.user_id,
user_id=int(account.user_id),
title="InboxRescue: Mail Processing Error",
body=f"Error processing mail account '{account.name}': {e}",
notify_on_error=True,
@@ -488,7 +489,7 @@ async def process_all_enabled_accounts():
)
stale_run.completed_at = datetime.now(timezone.utc) # type: ignore[assignment]
stale_run.duration_seconds = ( # type: ignore[assignment]
stale_run.completed_at - _as_utc(stale_run.started_at)
stale_run.completed_at - _as_utc(stale_run.started_at) # type: ignore[arg-type]
).total_seconds()
if stale_runs:
await db.commit()
@@ -571,12 +572,10 @@ async def cleanup_old_logs(days_to_keep: int = 30):
stale_runs = stale_result.scalars().all()
for stale_run in stale_runs:
stale_run.status = "failed" # type: ignore[assignment]
stale_run.error_message = ( # type: ignore[assignment]
"Run timed out or worker was killed before completion"
)
stale_run.error_message = "Run timed out or worker was killed before completion" # type: ignore[assignment]
stale_run.completed_at = datetime.now(timezone.utc) # type: ignore[assignment]
stale_run.duration_seconds = ( # type: ignore[assignment]
stale_run.completed_at - _as_utc(stale_run.started_at)
stale_run.completed_at - _as_utc(stale_run.started_at) # type: ignore[arg-type]
).total_seconds()
if stale_runs:
+1
View File
@@ -5,6 +5,7 @@ Comprehensive task breakdown for repository improvements and production readines
## ✅ Recently Completed
- [x] **Pull Now**: Added "Pull Now" button on Accounts page that immediately queues a `process_mail_account` Celery task via `POST /mail-accounts/{id}/pull-now`. Button shows spinner while in flight and is disabled for inactive accounts.
- [x] Fixed 21 mypy type errors: `Column[T]` vs native type mismatches in `notification_service.py`, `mail_processor.py`, `auth.py`, `tasks.py`, `providers.py`, `mail_accounts.py`, and `main.py` (`lifespan` parameter rename).
- [x] **Provider logos rework**: Logos now displayed as full-width banner strips at the top of each account card using `next/image fill + object-contain`. Handles all aspect ratios (1:1 square to 6:1 wordmark) without distortion. Proton Mail added.
- [x] **Proton Mail provider**: Added Proton Mail preset in backend and ProviderWizard frontend. Domains: proton.me, protonmail.com, protonmail.ch, pm.me. Auto-detect and IMAP/POP3 Bridge settings included.
- [x] Redesigned user-facing Logs page to mailbox-centric "Mailbox Activity" view: shows last check status per account + only successful pulls, suppressing noise from empty polling cycles.