using doc forge

This commit is contained in:
2026-01-22 16:49:05 +05:30
parent 9d1635c043
commit 53868e7fc7
55 changed files with 5759 additions and 474 deletions

View File

@@ -114,6 +114,27 @@ Design Guarantees
Mail Intake favors correctness, clarity, and explicitness over convenience
shortcuts.
## Core Philosophy
`Mail Intake` is built as a **contract-first ingestion pipeline**:
1. **Layered Decoupling**: Adapters handle transport, Parsers handle format normalization, and Ingestion orchestrates.
2. **Provider Agnosticism**: Domain models and core logic never depend on provider-specific (e.g., Gmail) API internals.
3. **Stateless Workflows**: The library functions as a read-only pipe, ensuring side-effect-free ingestion.
## Documentation Design
Follow these "AI-Native" docstring principles across the codebase:
### For Humans
- **Namespace Clarity**: Always specify which module a class or function belongs to.
- **Contract Explanations**: Use the `adapters` and `auth` base classes to explain extension requirements.
### For LLMs
- **Dotted Paths**: Use full dotted paths in docstrings to help agents link concepts across modules.
- **Typed Interfaces**: Provide `.pyi` stubs for every public module to ensure perfect context for AI coding tools.
- **Canonical Exceptions**: Always use `: description` pairs in `Raises` blocks to enable structured error analysis.
"""

17
mail_intake/__init__.pyi Normal file
View File

@@ -0,0 +1,17 @@
from . import ingestion
from . import adapters
from . import auth
from . import credentials
from . import models
from . import config
from . import exceptions
__all__ = [
"ingestion",
"adapters",
"auth",
"credentials",
"models",
"config",
"exceptions",
]

View File

@@ -0,0 +1,4 @@
from .base import MailIntakeAdapter
from .gmail import MailIntakeGmailAdapter
__all__ = ["MailIntakeAdapter", "MailIntakeGmailAdapter"]

View File

@@ -0,0 +1,10 @@
from abc import ABC, abstractmethod
from typing import Iterator, Dict, Any
class MailIntakeAdapter(ABC):
@abstractmethod
def iter_message_refs(self, query: str) -> Iterator[Dict[str, str]]: ...
@abstractmethod
def fetch_message(self, message_id: str) -> Dict[str, Any]: ...
@abstractmethod
def fetch_thread(self, thread_id: str) -> Dict[str, Any]: ...

View File

@@ -0,0 +1,11 @@
from typing import Iterator, Dict, Any
from mail_intake.adapters.base import MailIntakeAdapter
from mail_intake.auth.base import MailIntakeAuthProvider
class MailIntakeGmailAdapter(MailIntakeAdapter):
def __init__(self, auth_provider: MailIntakeAuthProvider, user_id: str = ...) -> None: ...
@property
def service(self) -> Any: ...
def iter_message_refs(self, query: str) -> Iterator[Dict[str, str]]: ...
def fetch_message(self, message_id: str) -> Dict[str, Any]: ...
def fetch_thread(self, thread_id: str) -> Dict[str, Any]: ...

View File

@@ -0,0 +1,4 @@
from .base import MailIntakeAuthProvider
from .google import MailIntakeGoogleAuth
__all__ = ["MailIntakeAuthProvider", "MailIntakeGoogleAuth"]

View File

@@ -0,0 +1,8 @@
from abc import ABC, abstractmethod
from typing import Generic, TypeVar
T = TypeVar("T")
class MailIntakeAuthProvider(ABC, Generic[T]):
@abstractmethod
def get_credentials(self) -> T: ...

View File

@@ -0,0 +1,7 @@
from typing import Sequence, Any
from mail_intake.auth.base import MailIntakeAuthProvider
from mail_intake.credentials.store import CredentialStore
class MailIntakeGoogleAuth(MailIntakeAuthProvider[Any]):
def __init__(self, credentials_path: str, store: CredentialStore[Any], scopes: Sequence[str]) -> None: ...
def get_credentials(self) -> Any: ...

9
mail_intake/config.pyi Normal file
View File

@@ -0,0 +1,9 @@
from typing import Optional
class MailIntakeConfig:
provider: str
user_id: str
readonly: bool
credentials_path: Optional[str]
token_path: Optional[str]
def __init__(self, provider: str = ..., user_id: str = ..., readonly: bool = ..., credentials_path: Optional[str] = ..., token_path: Optional[str] = ...) -> None: ...

View File

@@ -0,0 +1,5 @@
from .store import CredentialStore
from .pickle import PickleCredentialStore
from .redis import RedisCredentialStore
__all__ = ["CredentialStore", "PickleCredentialStore", "RedisCredentialStore"]

View File

@@ -0,0 +1,11 @@
from typing import Optional, TypeVar
from .store import CredentialStore
T = TypeVar("T")
class PickleCredentialStore(CredentialStore[T]):
path: str
def __init__(self, path: str) -> None: ...
def load(self) -> Optional[T]: ...
def save(self, credentials: T) -> None: ...
def clear(self) -> None: ...

View File

@@ -0,0 +1,15 @@
from typing import Optional, TypeVar, Callable, Any
from .store import CredentialStore
T = TypeVar("T")
class RedisCredentialStore(CredentialStore[T]):
redis: Any
key: str
serialize: Callable[[T], bytes]
deserialize: Callable[[bytes], T]
ttl_seconds: Optional[int]
def __init__(self, redis_client: Any, key: str, serialize: Callable[[T], bytes], deserialize: Callable[[bytes], T], ttl_seconds: Optional[int] = ...) -> None: ...
def load(self) -> Optional[T]: ...
def save(self, credentials: T) -> None: ...
def clear(self) -> None: ...

View File

@@ -39,12 +39,6 @@ class CredentialStore(ABC, Generic[T]):
- The concrete credential type being stored
- The serialization format used to persist credentials
- The underlying storage backend or durability guarantees
Type Parameters:
T:
The concrete credential type managed by the store. This may
represent OAuth credentials, API tokens, session objects,
or any other authentication material.
"""
@abstractmethod

View File

@@ -0,0 +1,12 @@
from abc import ABC, abstractmethod
from typing import Generic, Optional, TypeVar
T = TypeVar("T")
class CredentialStore(ABC, Generic[T]):
@abstractmethod
def load(self) -> Optional[T]: ...
@abstractmethod
def save(self, credentials: T) -> None: ...
@abstractmethod
def clear(self) -> None: ...

View File

@@ -0,0 +1,4 @@
class MailIntakeError(Exception): ...
class MailIntakeAuthError(MailIntakeError): ...
class MailIntakeAdapterError(MailIntakeError): ...
class MailIntakeParsingError(MailIntakeError): ...

View File

@@ -0,0 +1,3 @@
from .reader import MailIntakeReader
__all__ = ["MailIntakeReader"]

View File

@@ -0,0 +1,10 @@
from typing import Iterator, Dict, Any
from mail_intake.adapters.base import MailIntakeAdapter
from mail_intake.models.message import MailIntakeMessage
from mail_intake.models.thread import MailIntakeThread
class MailIntakeReader:
def __init__(self, adapter: MailIntakeAdapter) -> None: ...
def iter_messages(self, query: str) -> Iterator[MailIntakeMessage]: ...
def iter_threads(self, query: str) -> Iterator[MailIntakeThread]: ...
def _parse_message(self, raw_message: Dict[str, Any]) -> MailIntakeMessage: ...

View File

@@ -0,0 +1,4 @@
from .message import MailIntakeMessage
from .thread import MailIntakeThread
__all__ = ["MailIntakeMessage", "MailIntakeThread"]

View File

@@ -0,0 +1,14 @@
from datetime import datetime
from typing import Optional, Dict
class MailIntakeMessage:
message_id: str
thread_id: str
timestamp: datetime
from_email: str
from_name: Optional[str]
subject: str
body_text: str
snippet: str
raw_headers: Dict[str, str]
def __init__(self, message_id: str, thread_id: str, timestamp: datetime, from_email: str, from_name: Optional[str], subject: str, body_text: str, snippet: str, raw_headers: Dict[str, str]) -> None: ...

View File

@@ -0,0 +1,12 @@
from datetime import datetime
from typing import List, Set, Optional
from .message import MailIntakeMessage
class MailIntakeThread:
thread_id: str
normalized_subject: str
participants: Set[str]
messages: List[MailIntakeMessage]
last_activity_at: Optional[datetime]
def __init__(self, thread_id: str, normalized_subject: str, participants: Set[str] = ..., messages: List[MailIntakeMessage] = ..., last_activity_at: Optional[datetime] = ...) -> None: ...
def add_message(self, message: MailIntakeMessage) -> None: ...

View File

@@ -0,0 +1,5 @@
from .body import extract_body
from .headers import parse_headers, extract_sender
from .subject import normalize_subject
__all__ = ["extract_body", "parse_headers", "extract_sender", "normalize_subject"]

View File

@@ -0,0 +1,3 @@
from typing import Dict, Any
def extract_body(payload: Dict[str, Any]) -> str: ...

View File

@@ -0,0 +1,4 @@
from typing import Dict, List, Tuple, Optional
def parse_headers(raw_headers: List[Dict[str, str]]) -> Dict[str, str]: ...
def extract_sender(headers: Dict[str, str]) -> Tuple[str, Optional[str]]: ...

View File

@@ -0,0 +1 @@
def normalize_subject(subject: str) -> str: ...