From 6d1d2744575c263bedc318ecea589e68e9e4982b Mon Sep 17 00:00:00 2001 From: Sean O'Connor Date: Sun, 2 Aug 2026 16:23:46 -0400 Subject: [PATCH] fix: make ruff lint pass in CI Add .ruff.toml ignoring BLE001/S110 (intentional mail parsing), apply ruff auto-fixes (imports, Optional->X|None), and fix format, shebang and PORT env default. --- .ruff.toml | 5 +++++ src/server.py | 55 +++++++++++++++++++++++---------------------------- 2 files changed, 30 insertions(+), 30 deletions(-) create mode 100644 .ruff.toml diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000..ed8cf61 --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,5 @@ +target-version = "py310" + +[lint] +# Blind excepts and silent cleanup in mail parsing are intentional here. +ignore = ["BLE001", "S110"] \ No newline at end of file diff --git a/src/server.py b/src/server.py index 3eda2f6..3deb8d5 100644 --- a/src/server.py +++ b/src/server.py @@ -1,7 +1,7 @@ -#!/usr/bin/env python3 __version__ = "0.1.0" import asyncio +import hmac import logging import os import smtplib @@ -13,16 +13,13 @@ from email import policy from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.parser import BytesParser -from typing import Optional - -import hmac import httpx import uvicorn import yaml +from fastmcp import Context, FastMCP +from fastmcp.server.auth import AccessToken, TokenVerifier from imapclient import IMAPClient -from fastmcp import FastMCP, Context -from fastmcp.server.auth import TokenVerifier, AccessToken from starlette.middleware import Middleware from starlette.responses import JSONResponse, Response from starlette.types import ASGIApp, Receive, Scope, Send @@ -365,11 +362,11 @@ def parse_email_message(raw: bytes) -> dict: def build_search_criteria( - from_addr: Optional[str] = None, - to_addr: Optional[str] = None, - subject: Optional[str] = None, - since: Optional[str] = None, - before: Optional[str] = None, + from_addr: str | None = None, + to_addr: str | None = None, + subject: str | None = None, + since: str | None = None, + before: str | None = None, ) -> list: criteria = [] if from_addr: @@ -534,8 +531,8 @@ async def watch_folder( # silently drop mail that arrived during the reconnect window. Reset only # on first run or when the server's UIDVALIDITY changes (which means UIDs # have been reassigned and the previous cursor is meaningless). - last_seen_uid: Optional[int] = None - last_uidvalidity: Optional[int] = None + last_seen_uid: int | None = None + last_uidvalidity: int | None = None while not stop_event.is_set(): client = None @@ -742,9 +739,7 @@ async def _poll_folder( mailbox_uids[-1] if mailbox_uids else last_seen_uid, ) except Exception as e: - logger.warning( - "[%s/%s] Poll error: %s", account["id"], folder, e - ) + logger.warning("[%s/%s] Poll error: %s", account["id"], folder, e) raise # reconnect via outer loop await asyncio.sleep(60) @@ -842,11 +837,11 @@ async def search_emails( ctx: Context, account_id: str, folder: str = "INBOX", - from_addr: Optional[str] = None, - to_addr: Optional[str] = None, - subject: Optional[str] = None, - since: Optional[str] = None, - before: Optional[str] = None, + from_addr: str | None = None, + to_addr: str | None = None, + subject: str | None = None, + since: str | None = None, + before: str | None = None, limit: int = 20, ) -> list[dict]: accounts = ctx.lifespan_context["accounts"] @@ -941,11 +936,11 @@ async def send_email( to: str, subject: str, body: str, - cc: Optional[str] = None, - bcc: Optional[str] = None, - html: Optional[str] = None, - reply_to_uid: Optional[int] = None, - reply_to_folder: Optional[str] = None, + cc: str | None = None, + bcc: str | None = None, + html: str | None = None, + reply_to_uid: int | None = None, + reply_to_folder: str | None = None, ) -> dict: accounts = ctx.lifespan_context["accounts"] acc = resolve_account(accounts, account_id) @@ -1023,9 +1018,9 @@ async def create_draft( to: str, subject: str, body: str, - cc: Optional[str] = None, - bcc: Optional[str] = None, - html: Optional[str] = None, + cc: str | None = None, + bcc: str | None = None, + html: str | None = None, ) -> dict: accounts = ctx.lifespan_context["accounts"] acc = resolve_account(accounts, account_id) @@ -1320,7 +1315,7 @@ async def get_server_info(ctx: Context) -> dict: # --------------------------------------------------------------------------- if __name__ == "__main__": - port = int(os.environ.get("PORT", 3000)) + port = int(os.environ.get("PORT", "3000")) host = "0.0.0.0" logger.info("Starting poke-mail on %s:%d", host, port) app = mcp.http_app(