docs: expand module contract, compress private docstrings

Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
2026-07-03 16:47:32 -04:00
parent f1e52ff1ac
commit 0038f03b9e
5 changed files with 20 additions and 21 deletions
+5 -5
View File
@@ -11,22 +11,22 @@ This reads codes from email; it does not generate them (that is `pyotp`'s job).
`requirements.txt`: `requirements.txt`:
``` ```
aiomail @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.9 aiomail @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.10
# OAuth token providers (Microsoft / Google) need the extra: # OAuth token providers (Microsoft / Google) need the extra:
aiomail[oauth] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.9 aiomail[oauth] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.10
``` ```
Direct: Direct:
```bash ```bash
pip install "aiomail @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.9" pip install "aiomail @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.10"
pip install "aiomail[oauth] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.9" pip install "aiomail[oauth] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.10"
``` ```
Requires `aioimaplib` and `beautifulsoup4` (pulled transitively). The `oauth` Requires `aioimaplib` and `beautifulsoup4` (pulled transitively). The `oauth`
extra adds `aiohttp` for the refresh-token providers. extra adds `aiohttp` for the refresh-token providers.
Drop the `@v0.1.9` suffix from the line above to install the latest unpinned. Drop the `@v0.1.10` suffix from the line above to install the latest unpinned.
## Password auth ## Password auth
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "aiomail" name = "aiomail"
version = "0.1.9" version = "0.1.10"
description = "async IMAP one-time-code retrieval with password/OAuth2 auth and dynamic matching" description = "async IMAP one-time-code retrieval with password/OAuth2 auth and dynamic matching"
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [ dependencies = [
+11 -2
View File
@@ -1,4 +1,13 @@
"""aiomail - async IMAP one-time-code retrieval, password or OAuth2 auth, dynamic matching. see README.""" """aiomail - async IMAP one-time-code retrieval, password or OAuth2 auth, dynamic matching.
facade over auth (PasswordAuth/OAuth2Auth), IMAPClient (connection lifecycle), and
retrieve_otp (folder-scan orchestration); see each module's docstring for detail.
footguns: an IMAPClient instance is not safe for concurrent callers beyond its internal
connect/reconnect lock; sequence-number ids from before a reconnect are invalid after
(pass use_uid=True if ids must survive one); credentials are always caller-supplied via
an injected Auth, never read from config.
"""
from .auth import Auth, OAuth2Auth, PasswordAuth from .auth import Auth, OAuth2Auth, PasswordAuth
from .client import IMAPClient from .client import IMAPClient
from .extract import ( from .extract import (
@@ -26,4 +35,4 @@ __all__ = [
"DEFAULT_FOLDERS", "DEFAULT_FOLDERS",
] ]
__version__ = "0.1.9" __version__ = "0.1.10"
+2 -8
View File
@@ -24,12 +24,7 @@ _LIST_RE = re.compile(rb'^\([^)]*\)\s+(?:"[^"]*"|NIL)\s+(.+)$')
def _folder_name(raw: bytes) -> Optional[str]: def _folder_name(raw: bytes) -> Optional[str]:
"""extract the folder name from a LIST reply line, delimiter-agnostic """extract the folder name from a LIST reply line, or None on no match"""
returns None (not a last-token rsplit fallback) on a non-matching line, so the
tagged completion line aioimaplib appends (e.g. `b"LIST completed."`) is dropped
instead of read as a phantom folder.
"""
match = _LIST_RE.match(raw.strip()) match = _LIST_RE.match(raw.strip())
if not match: if not match:
return None return None
@@ -102,8 +97,7 @@ class IMAPClient:
@staticmethod @staticmethod
async def _discard_mail(mail) -> None: async def _discard_mail(mail) -> None:
"""tear down a half-built IMAP4 without leaking its fire-and-forget connect task (avoids an """tear down a half-built IMAP4 without leaking its fire-and-forget connect task"""
asyncio "Task exception was never retrieved" traceback)"""
task = getattr(mail, "_client_task", None) task = getattr(mail, "_client_task", None)
if task is not None and not task.done(): if task is not None and not task.done():
task.cancel() task.cancel()
+1 -5
View File
@@ -23,11 +23,7 @@ DEFAULT_FOLDERS: Sequence[str] = ("INBOX", "Junk", "Spam", "Archive", "All Mail"
def _server_query(sender: MatchSpec, subject: MatchSpec, match_field: str = "from") -> str: def _server_query(sender: MatchSpec, subject: MatchSpec, match_field: str = "from") -> str:
"""build a narrowing IMAP query from plain-string specs, falling back to ALL for regex/callable specs """build a narrowing IMAP query from plain-string specs, falling back to ALL for regex/callable specs"""
`match_field="to"` searches TO OR FROM (a forwarded code may keep the original
From) so the server query never narrows out a result the client would accept.
"""
parts: List[str] = [] parts: List[str] = []
if isinstance(sender, str): if isinstance(sender, str):
if match_field == "to": if match_field == "to":