Files
EvoScientist-Multi/EvoScientist/channels/email/probe.py
T
dinos 8b1451cdda refactor(runtime): centralize async bridges under an owned runtime (#376)
* feat(runtime): add application-scoped async runtime

* refactor(cli): use owned runtime for session stats

* refactor(onboard): use the owned async runtime

* docs(runtime): record async bridge ownership

* refactor(middleware): keep sync fallback synchronous

* refactor(mcp): load tools on an owned runtime

* refactor(cli): share owned runtime across entry points

* refactor(channels): make inbound sync bridge explicit

* refactor(stream): run Rich streaming on owned runtime

* chore(runtime): remove nest-asyncio dependency

* refactor(asyncio): require active loops in async code

* docs(runtime): document final event loop ownership

* fix(stream): cancel stalled owned streams

* fix(cli): recover cleanly from stream cancellation

* fix(runtime): drain executor work before shutdown

* fix(runtime): terminate cancelled shell process trees

* fix(models): let fallback bypass selector failures

* fix(cli): reset interrupt handling between turns

* docs: rm implementation spec

* fix(serve): cancel active turns during shutdown

* fix(runtime): protect settlement from waiter cancellation

* fix(backends): reject empty shell commands

* fix(runtime): terminate descendants after shell exit

* fix(mcp): keep standalone discovery off channel loop

* fix(cli): own and settle interactive prompt cancellation

* fix(serve): keep channel sends off runtime loop

* fix(stream): scope cancel context to iterator steps

* refactor(serve): require the owned async runtime

* fix(channels): keep interactive sends off runtime loop

* fix(selector): surface fallback without log spam

* test(runtime): normalize Windows shell marker

* fix(cli): serialize interactive session turns

* fix(shell): bound output drain after termination

* fix(ui): do not retry owned runtime failures

* fix(shell): allow signal-safe registry reentry

* fix(shell): avoid terminating reused process ids

* fix(channels): preserve streaming send order

* fix(cli): report runtime shutdown timeouts cleanly

* fix(mcp): guide async callers to async loader

* docs(runtime): clarify reserved async bridge APIs

* fix(runtime): bound code interpreter cleanup

* test(shell): use active Python for drain regression

---------

Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
2026-07-27 14:17:57 +01:00

93 lines
2.4 KiB
Python

"""Email credential validation."""
import imaplib
import logging
import smtplib
import ssl
logger = logging.getLogger(__name__)
async def validate_email_imap(
host: str,
port: int,
username: str,
password: str,
use_ssl: bool = True,
) -> tuple[bool, str]:
"""Validate IMAP credentials.
Returns:
Tuple of (is_valid, message).
"""
if not host or not username or not password:
return False, "host, username, and password are required"
import asyncio
loop = asyncio.get_running_loop()
def _check():
try:
if use_ssl:
ctx = ssl.create_default_context()
conn = imaplib.IMAP4_SSL(host, port, ssl_context=ctx)
else:
conn = imaplib.IMAP4(host, port)
conn.login(username, password)
conn.logout()
return True, "IMAP credentials valid"
except imaplib.IMAP4.error as e:
return False, f"IMAP auth failed: {e}"
except Exception as e:
return False, f"IMAP error: {e}"
return await loop.run_in_executor(None, _check)
async def validate_email_smtp(
host: str,
port: int,
username: str,
password: str,
use_tls: bool = True,
) -> tuple[bool, str]:
"""Validate SMTP credentials.
Returns:
Tuple of (is_valid, message).
"""
if not host or not username or not password:
return False, "host, username, and password are required"
import asyncio
loop = asyncio.get_running_loop()
def _check():
server = None
try:
if use_tls:
server = smtplib.SMTP(host, port, timeout=10)
server.starttls()
else:
ctx = ssl.create_default_context()
server = smtplib.SMTP_SSL(host, port, context=ctx, timeout=10)
server.login(username, password)
return True, "SMTP credentials valid"
except smtplib.SMTPAuthenticationError:
return False, "SMTP auth failed"
except Exception as e:
return False, f"SMTP error: {e}"
finally:
if server is not None:
try:
server.quit()
except Exception:
try:
server.close()
except Exception:
pass
return await loop.run_in_executor(None, _check)