fix: resolve flake8 C901/F401 errors in main.py, gmail_client.py, test_mail_sources.py

Agent-Logs-Url: https://github.com/christianlouis/dmarq/sessions/38868887-87b6-4032-8713-e90e56ae3318

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-29 19:50:50 +00:00
parent e033e11e34
commit 0c05ded74f
3 changed files with 135 additions and 124 deletions
+75 -66
View File
@@ -427,44 +427,40 @@ async def health():
return {"status": "ok", "service": "dmarq"} return {"status": "ok", "service": "dmarq"}
# API endpoint to manually trigger IMAP polling # ---------------------------------------------------------------------------
@app.post("/api/v1/admin/trigger-poll") # Helpers for the manual trigger-poll endpoint
async def trigger_imap_poll(auth: dict = Depends(require_admin_auth)): # ---------------------------------------------------------------------------
"""
Manually trigger IMAP polling for all enabled mail sources (admin only).
Security: Requires either X-API-Key header or Bearer token
""" def _trigger_poll_imap_source(source: MailSource, db) -> dict:
"""Poll a single IMAP source and return a result dict for the API response."""
global last_check_time # pylint: disable=global-statement global last_check_time # pylint: disable=global-statement
results_summary = [] imap_client = IMAPClient(
db = SessionLocal() server=source.server,
try: port=source.port or 993,
enabled_sources = ( username=source.username,
db.query(MailSource).filter(MailSource.enabled == True).all() # noqa: E712 password=source.password,
delete_emails=False,
) )
results = imap_client.fetch_reports(days=7)
if not enabled_sources: last_check_time = datetime.now()
source.last_checked = datetime.utcnow()
db.commit()
return { return {
"success": True,
"message": "No enabled mail sources configured.",
"sources_polled": 0,
"authenticated_by": auth.get("auth_type"),
}
for source in enabled_sources:
if source.method == "GMAIL_API":
if not source.gmail_access_token:
results_summary.append(
{
"source_id": source.id, "source_id": source.id,
"name": source.name, "name": source.name,
"skipped": True, "success": results["success"],
"reason": "Gmail account not yet authorised", "processed": results.get("processed", 0),
"reports_found": results.get("reports_found", 0),
"new_domains": results.get("new_domains", []),
} }
)
continue
try: def _trigger_poll_gmail_source(source: MailSource, db) -> dict:
"""Poll a single GMAIL_API source and return a result dict for the API response."""
global last_check_time # pylint: disable=global-statement
already = GmailClient.load_ingested_ids(source.gmail_ingested_ids) already = GmailClient.load_ingested_ids(source.gmail_ingested_ids)
gmail_client = GmailClient( gmail_client = GmailClient(
client_id=source.gmail_client_id or "", client_id=source.gmail_client_id or "",
@@ -486,9 +482,7 @@ async def trigger_imap_poll(auth: dict = Depends(require_admin_auth)):
source.gmail_refresh_token = refreshed["refresh_token"] source.gmail_refresh_token = refreshed["refresh_token"]
source.last_checked = datetime.utcnow() source.last_checked = datetime.utcnow()
db.commit() db.commit()
return {
results_summary.append(
{
"source_id": source.id, "source_id": source.id,
"name": source.name, "name": source.name,
"success": results["success"], "success": results["success"],
@@ -496,60 +490,75 @@ async def trigger_imap_poll(auth: dict = Depends(require_admin_auth)):
"reports_found": results.get("reports_found", 0), "reports_found": results.get("reports_found", 0),
"new_domains": results.get("new_domains", []), "new_domains": results.get("new_domains", []),
} }
)
def _poll_source_for_trigger(source: MailSource, db) -> dict:
"""Dispatch a single mail source for the manual trigger-poll endpoint.
Returns a result/summary dict that is included in the API response.
"""
if source.method == "GMAIL_API":
if not source.gmail_access_token:
return {
"source_id": source.id,
"name": source.name,
"skipped": True,
"reason": "Gmail account not yet authorised",
}
try:
return _trigger_poll_gmail_source(source, db)
except Exception as e: # pylint: disable=broad-exception-caught except Exception as e: # pylint: disable=broad-exception-caught
logger.error("Error polling Gmail source id=%d: %s", source.id, str(e)) logger.error("Error polling Gmail source id=%d: %s", source.id, str(e))
results_summary.append( return {
{
"source_id": source.id, "source_id": source.id,
"name": source.name, "name": source.name,
"success": False, "success": False,
"error": "Failed to poll. Check server logs for details.", "error": "Failed to poll. Check server logs for details.",
} }
) if source.method == "IMAP":
elif source.method == "IMAP":
try: try:
imap_client = IMAPClient( return _trigger_poll_imap_source(source, db)
server=source.server,
port=source.port or 993,
username=source.username,
password=source.password,
delete_emails=False,
)
results = imap_client.fetch_reports(days=7)
last_check_time = datetime.now()
source.last_checked = datetime.utcnow()
db.commit()
results_summary.append(
{
"source_id": source.id,
"name": source.name,
"success": results["success"],
"processed": results.get("processed", 0),
"reports_found": results.get("reports_found", 0),
"new_domains": results.get("new_domains", []),
}
)
except Exception as e: # pylint: disable=broad-exception-caught except Exception as e: # pylint: disable=broad-exception-caught
logger.error("Error polling mail source id=%d: %s", source.id, str(e)) logger.error("Error polling mail source id=%d: %s", source.id, str(e))
results_summary.append( return {
{
"source_id": source.id, "source_id": source.id,
"name": source.name, "name": source.name,
"success": False, "success": False,
"error": "Failed to poll. Check server logs for details.", "error": "Failed to poll. Check server logs for details.",
} }
) return {
else:
results_summary.append(
{
"source_id": source.id, "source_id": source.id,
"name": source.name, "name": source.name,
"skipped": True, "skipped": True,
"reason": f"method '{source.method}' not yet implemented", "reason": f"method '{source.method}' not yet implemented",
} }
# API endpoint to manually trigger IMAP polling
@app.post("/api/v1/admin/trigger-poll")
async def trigger_imap_poll(auth: dict = Depends(require_admin_auth)):
"""
Manually trigger IMAP polling for all enabled mail sources (admin only).
Security: Requires either X-API-Key header or Bearer token
"""
results_summary = []
db = SessionLocal()
try:
enabled_sources = (
db.query(MailSource).filter(MailSource.enabled == True).all() # noqa: E712
) )
if not enabled_sources:
return {
"success": True,
"message": "No enabled mail sources configured.",
"sources_polled": 0,
"authenticated_by": auth.get("auth_type"),
}
for source in enabled_sources:
results_summary.append(_poll_source_for_trigger(source, db))
finally: finally:
db.close() db.close()
+27 -21
View File
@@ -306,6 +306,31 @@ class GmailClient:
msg = email.message_from_bytes(raw_bytes) msg = email.message_from_bytes(raw_bytes)
return self._process_attachments(msg, stats) return self._process_attachments(msg, stats)
@staticmethod
def _decode_part_filename(part: email.message.Message) -> str:
"""Return the decoded filename for a MIME part (handles RFC 2047 encoding)."""
from email.header import decode_header
raw_name = part.get_filename() or ""
decoded_parts = []
for fragment, charset in decode_header(raw_name):
if isinstance(fragment, bytes):
decoded_parts.append(fragment.decode(charset or "utf-8", errors="replace"))
else:
decoded_parts.append(fragment)
return "".join(decoded_parts)
@staticmethod
def _is_dmarc_attachment(filename: str) -> bool:
"""Return True if *filename* looks like a DMARC aggregate-report file."""
lower = filename.lower()
return (
lower.endswith(".xml")
or lower.endswith(".zip")
or lower.endswith(".gz")
or lower.endswith(".gzip")
)
def _process_attachments(self, msg: email.message.Message, stats: dict) -> int: def _process_attachments(self, msg: email.message.Message, stats: dict) -> int:
"""Walk a parsed email message and extract DMARC report attachments.""" """Walk a parsed email message and extract DMARC report attachments."""
reports_found = 0 reports_found = 0
@@ -314,27 +339,8 @@ class GmailClient:
if part.get_content_disposition() != "attachment": if part.get_content_disposition() != "attachment":
continue continue
filename = part.get_filename() or "" filename = self._decode_part_filename(part)
if hasattr(filename, "encode"): if not self._is_dmarc_attachment(filename):
# Decode RFC 2047-encoded filenames
from email.header import decode_header
parts = decode_header(filename)
decoded_parts = []
for raw, charset in parts:
if isinstance(raw, bytes):
decoded_parts.append(raw.decode(charset or "utf-8", errors="replace"))
else:
decoded_parts.append(raw)
filename = "".join(decoded_parts)
lower = filename.lower()
if not (
lower.endswith(".xml")
or lower.endswith(".zip")
or lower.endswith(".gz")
or lower.endswith(".gzip")
):
continue continue
content = part.get_payload(decode=True) content = part.get_payload(decode=True)
-4
View File
@@ -489,10 +489,6 @@ class TestGmailAPIMailSource:
) )
source_id = create_resp.json()["id"] source_id = create_resp.json()["id"]
# Inject tokens directly into DB via the DB session
from sqlalchemy.orm import Session
from app.models.mail_source import MailSource as MS
# Use the authed_client's DB override — patch the ORM object instead # Use the authed_client's DB override — patch the ORM object instead
mock_service = MagicMock() mock_service = MagicMock()
mock_service.users.return_value.getProfile.return_value.execute.return_value = { mock_service.users.return_value.getProfile.return_value.execute.return_value = {