using doc forge
This commit is contained in:
@@ -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
17
mail_intake/__init__.pyi
Normal 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",
|
||||
]
|
||||
4
mail_intake/adapters/__init__.pyi
Normal file
4
mail_intake/adapters/__init__.pyi
Normal file
@@ -0,0 +1,4 @@
|
||||
from .base import MailIntakeAdapter
|
||||
from .gmail import MailIntakeGmailAdapter
|
||||
|
||||
__all__ = ["MailIntakeAdapter", "MailIntakeGmailAdapter"]
|
||||
10
mail_intake/adapters/base.pyi
Normal file
10
mail_intake/adapters/base.pyi
Normal 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]: ...
|
||||
11
mail_intake/adapters/gmail.pyi
Normal file
11
mail_intake/adapters/gmail.pyi
Normal 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]: ...
|
||||
4
mail_intake/auth/__init__.pyi
Normal file
4
mail_intake/auth/__init__.pyi
Normal file
@@ -0,0 +1,4 @@
|
||||
from .base import MailIntakeAuthProvider
|
||||
from .google import MailIntakeGoogleAuth
|
||||
|
||||
__all__ = ["MailIntakeAuthProvider", "MailIntakeGoogleAuth"]
|
||||
8
mail_intake/auth/base.pyi
Normal file
8
mail_intake/auth/base.pyi
Normal 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: ...
|
||||
7
mail_intake/auth/google.pyi
Normal file
7
mail_intake/auth/google.pyi
Normal 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
9
mail_intake/config.pyi
Normal 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: ...
|
||||
5
mail_intake/credentials/__init__.pyi
Normal file
5
mail_intake/credentials/__init__.pyi
Normal file
@@ -0,0 +1,5 @@
|
||||
from .store import CredentialStore
|
||||
from .pickle import PickleCredentialStore
|
||||
from .redis import RedisCredentialStore
|
||||
|
||||
__all__ = ["CredentialStore", "PickleCredentialStore", "RedisCredentialStore"]
|
||||
11
mail_intake/credentials/pickle.pyi
Normal file
11
mail_intake/credentials/pickle.pyi
Normal 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: ...
|
||||
15
mail_intake/credentials/redis.pyi
Normal file
15
mail_intake/credentials/redis.pyi
Normal 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: ...
|
||||
@@ -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
|
||||
|
||||
12
mail_intake/credentials/store.pyi
Normal file
12
mail_intake/credentials/store.pyi
Normal 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: ...
|
||||
4
mail_intake/exceptions.pyi
Normal file
4
mail_intake/exceptions.pyi
Normal file
@@ -0,0 +1,4 @@
|
||||
class MailIntakeError(Exception): ...
|
||||
class MailIntakeAuthError(MailIntakeError): ...
|
||||
class MailIntakeAdapterError(MailIntakeError): ...
|
||||
class MailIntakeParsingError(MailIntakeError): ...
|
||||
3
mail_intake/ingestion/__init__.pyi
Normal file
3
mail_intake/ingestion/__init__.pyi
Normal file
@@ -0,0 +1,3 @@
|
||||
from .reader import MailIntakeReader
|
||||
|
||||
__all__ = ["MailIntakeReader"]
|
||||
10
mail_intake/ingestion/reader.pyi
Normal file
10
mail_intake/ingestion/reader.pyi
Normal 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: ...
|
||||
4
mail_intake/models/__init__.pyi
Normal file
4
mail_intake/models/__init__.pyi
Normal file
@@ -0,0 +1,4 @@
|
||||
from .message import MailIntakeMessage
|
||||
from .thread import MailIntakeThread
|
||||
|
||||
__all__ = ["MailIntakeMessage", "MailIntakeThread"]
|
||||
14
mail_intake/models/message.pyi
Normal file
14
mail_intake/models/message.pyi
Normal 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: ...
|
||||
12
mail_intake/models/thread.pyi
Normal file
12
mail_intake/models/thread.pyi
Normal 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: ...
|
||||
5
mail_intake/parsers/__init__.pyi
Normal file
5
mail_intake/parsers/__init__.pyi
Normal 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"]
|
||||
3
mail_intake/parsers/body.pyi
Normal file
3
mail_intake/parsers/body.pyi
Normal file
@@ -0,0 +1,3 @@
|
||||
from typing import Dict, Any
|
||||
|
||||
def extract_body(payload: Dict[str, Any]) -> str: ...
|
||||
4
mail_intake/parsers/headers.pyi
Normal file
4
mail_intake/parsers/headers.pyi
Normal 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]]: ...
|
||||
1
mail_intake/parsers/subject.pyi
Normal file
1
mail_intake/parsers/subject.pyi
Normal file
@@ -0,0 +1 @@
|
||||
def normalize_subject(subject: str) -> str: ...
|
||||
Reference in New Issue
Block a user