fix: make ruff lint pass in CI
CI / lint (push) Successful in 4s
CI / build (push) Successful in 9m20s
Build & Push Container Image / build-and-push (push) Failing after 10s
CI / docker (push) Successful in 28s

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.
This commit is contained in:
2026-08-02 16:23:46 -04:00
parent b3e44d8ffb
commit 6d1d274457
2 changed files with 30 additions and 30 deletions
+5
View File
@@ -0,0 +1,5 @@
target-version = "py310"
[lint]
# Blind excepts and silent cleanup in mail parsing are intentional here.
ignore = ["BLE001", "S110"]
+25 -30
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
__version__ = "0.1.0" __version__ = "0.1.0"
import asyncio import asyncio
import hmac
import logging import logging
import os import os
import smtplib import smtplib
@@ -13,16 +13,13 @@ from email import policy
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText from email.mime.text import MIMEText
from email.parser import BytesParser from email.parser import BytesParser
from typing import Optional
import hmac
import httpx import httpx
import uvicorn import uvicorn
import yaml import yaml
from fastmcp import Context, FastMCP
from fastmcp.server.auth import AccessToken, TokenVerifier
from imapclient import IMAPClient from imapclient import IMAPClient
from fastmcp import FastMCP, Context
from fastmcp.server.auth import TokenVerifier, AccessToken
from starlette.middleware import Middleware from starlette.middleware import Middleware
from starlette.responses import JSONResponse, Response from starlette.responses import JSONResponse, Response
from starlette.types import ASGIApp, Receive, Scope, Send from starlette.types import ASGIApp, Receive, Scope, Send
@@ -365,11 +362,11 @@ def parse_email_message(raw: bytes) -> dict:
def build_search_criteria( def build_search_criteria(
from_addr: Optional[str] = None, from_addr: str | None = None,
to_addr: Optional[str] = None, to_addr: str | None = None,
subject: Optional[str] = None, subject: str | None = None,
since: Optional[str] = None, since: str | None = None,
before: Optional[str] = None, before: str | None = None,
) -> list: ) -> list:
criteria = [] criteria = []
if from_addr: if from_addr:
@@ -534,8 +531,8 @@ async def watch_folder(
# silently drop mail that arrived during the reconnect window. Reset only # silently drop mail that arrived during the reconnect window. Reset only
# on first run or when the server's UIDVALIDITY changes (which means UIDs # on first run or when the server's UIDVALIDITY changes (which means UIDs
# have been reassigned and the previous cursor is meaningless). # have been reassigned and the previous cursor is meaningless).
last_seen_uid: Optional[int] = None last_seen_uid: int | None = None
last_uidvalidity: Optional[int] = None last_uidvalidity: int | None = None
while not stop_event.is_set(): while not stop_event.is_set():
client = None client = None
@@ -742,9 +739,7 @@ async def _poll_folder(
mailbox_uids[-1] if mailbox_uids else last_seen_uid, mailbox_uids[-1] if mailbox_uids else last_seen_uid,
) )
except Exception as e: except Exception as e:
logger.warning( logger.warning("[%s/%s] Poll error: %s", account["id"], folder, e)
"[%s/%s] Poll error: %s", account["id"], folder, e
)
raise # reconnect via outer loop raise # reconnect via outer loop
await asyncio.sleep(60) await asyncio.sleep(60)
@@ -842,11 +837,11 @@ async def search_emails(
ctx: Context, ctx: Context,
account_id: str, account_id: str,
folder: str = "INBOX", folder: str = "INBOX",
from_addr: Optional[str] = None, from_addr: str | None = None,
to_addr: Optional[str] = None, to_addr: str | None = None,
subject: Optional[str] = None, subject: str | None = None,
since: Optional[str] = None, since: str | None = None,
before: Optional[str] = None, before: str | None = None,
limit: int = 20, limit: int = 20,
) -> list[dict]: ) -> list[dict]:
accounts = ctx.lifespan_context["accounts"] accounts = ctx.lifespan_context["accounts"]
@@ -941,11 +936,11 @@ async def send_email(
to: str, to: str,
subject: str, subject: str,
body: str, body: str,
cc: Optional[str] = None, cc: str | None = None,
bcc: Optional[str] = None, bcc: str | None = None,
html: Optional[str] = None, html: str | None = None,
reply_to_uid: Optional[int] = None, reply_to_uid: int | None = None,
reply_to_folder: Optional[str] = None, reply_to_folder: str | None = None,
) -> dict: ) -> dict:
accounts = ctx.lifespan_context["accounts"] accounts = ctx.lifespan_context["accounts"]
acc = resolve_account(accounts, account_id) acc = resolve_account(accounts, account_id)
@@ -1023,9 +1018,9 @@ async def create_draft(
to: str, to: str,
subject: str, subject: str,
body: str, body: str,
cc: Optional[str] = None, cc: str | None = None,
bcc: Optional[str] = None, bcc: str | None = None,
html: Optional[str] = None, html: str | None = None,
) -> dict: ) -> dict:
accounts = ctx.lifespan_context["accounts"] accounts = ctx.lifespan_context["accounts"]
acc = resolve_account(accounts, account_id) acc = resolve_account(accounts, account_id)
@@ -1320,7 +1315,7 @@ async def get_server_info(ctx: Context) -> dict:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
if __name__ == "__main__": if __name__ == "__main__":
port = int(os.environ.get("PORT", 3000)) port = int(os.environ.get("PORT", "3000"))
host = "0.0.0.0" host = "0.0.0.0"
logger.info("Starting poke-mail on %s:%d", host, port) logger.info("Starting poke-mail on %s:%d", host, port)
app = mcp.http_app( app = mcp.http_app(