lib init
This commit is contained in:
0
mail_intake/adapters/__init__.py
Normal file
0
mail_intake/adapters/__init__.py
Normal file
48
mail_intake/adapters/base.py
Normal file
48
mail_intake/adapters/base.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Iterator, Dict, Any
|
||||
|
||||
|
||||
class MailIntakeAdapter(ABC):
|
||||
"""
|
||||
Base adapter interface for mail providers.
|
||||
|
||||
This interface defines the minimal contract required for
|
||||
read-only mail ingestion. No provider-specific concepts
|
||||
should leak beyond implementations of this class.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def iter_message_refs(self, query: str) -> Iterator[Dict[str, str]]:
|
||||
"""
|
||||
Iterate over lightweight message references.
|
||||
|
||||
Must yield dictionaries containing at least:
|
||||
- message_id
|
||||
- thread_id
|
||||
|
||||
Example yield:
|
||||
{
|
||||
"message_id": "...",
|
||||
"thread_id": "..."
|
||||
}
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def fetch_message(self, message_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Fetch a full raw message by message_id.
|
||||
|
||||
Returns the provider-native message payload
|
||||
(e.g., Gmail message JSON).
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def fetch_thread(self, thread_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Fetch a full raw thread by thread_id.
|
||||
|
||||
Returns the provider-native thread payload.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
105
mail_intake/adapters/gmail.py
Normal file
105
mail_intake/adapters/gmail.py
Normal file
@@ -0,0 +1,105 @@
|
||||
from typing import Iterator, Dict, Any
|
||||
|
||||
from googleapiclient.discovery import build
|
||||
from googleapiclient.errors import HttpError
|
||||
|
||||
from mail_intake.adapters.base import MailIntakeAdapter
|
||||
from mail_intake.exceptions import MailIntakeAdapterError
|
||||
from mail_intake.auth.base import MailIntakeAuthProvider
|
||||
|
||||
|
||||
class MailIntakeGmailAdapter(MailIntakeAdapter):
|
||||
"""
|
||||
Gmail read-only adapter.
|
||||
|
||||
This class is the ONLY place where:
|
||||
- googleapiclient is imported
|
||||
- Gmail REST semantics are known
|
||||
- .execute() is called
|
||||
|
||||
It must remain thin and dumb by design.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
auth_provider: MailIntakeAuthProvider,
|
||||
user_id: str = "me",
|
||||
):
|
||||
self._auth_provider = auth_provider
|
||||
self._user_id = user_id
|
||||
self._service = None
|
||||
|
||||
@property
|
||||
def service(self):
|
||||
if self._service is None:
|
||||
try:
|
||||
creds = self._auth_provider.get_credentials()
|
||||
self._service = build("gmail", "v1", credentials=creds)
|
||||
except Exception as exc:
|
||||
raise MailIntakeAdapterError(
|
||||
"Failed to initialize Gmail service"
|
||||
) from exc
|
||||
return self._service
|
||||
|
||||
def iter_message_refs(self, query: str) -> Iterator[Dict[str, str]]:
|
||||
"""
|
||||
Iterate over message references matching the query.
|
||||
|
||||
Yields:
|
||||
{
|
||||
"message_id": "...",
|
||||
"thread_id": "..."
|
||||
}
|
||||
"""
|
||||
try:
|
||||
request = (
|
||||
self.service.users()
|
||||
.messages()
|
||||
.list(userId=self._user_id, q=query)
|
||||
)
|
||||
|
||||
while request is not None:
|
||||
response = request.execute()
|
||||
|
||||
for msg in response.get("messages", []):
|
||||
yield {
|
||||
"message_id": msg["id"],
|
||||
"thread_id": msg["threadId"],
|
||||
}
|
||||
|
||||
request = (
|
||||
self.service.users()
|
||||
.messages()
|
||||
.list_next(request, response)
|
||||
)
|
||||
|
||||
except HttpError as exc:
|
||||
raise MailIntakeAdapterError(
|
||||
"Gmail API error while listing messages"
|
||||
) from exc
|
||||
|
||||
def fetch_message(self, message_id: str) -> Dict[str, Any]:
|
||||
try:
|
||||
return (
|
||||
self.service.users()
|
||||
.messages()
|
||||
.get(userId=self._user_id, id=message_id)
|
||||
.execute()
|
||||
)
|
||||
except HttpError as exc:
|
||||
raise MailIntakeAdapterError(
|
||||
f"Gmail API error while fetching message {message_id}"
|
||||
) from exc
|
||||
|
||||
def fetch_thread(self, thread_id: str) -> Dict[str, Any]:
|
||||
try:
|
||||
return (
|
||||
self.service.users()
|
||||
.threads()
|
||||
.get(userId=self._user_id, id=thread_id)
|
||||
.execute()
|
||||
)
|
||||
except HttpError as exc:
|
||||
raise MailIntakeAdapterError(
|
||||
f"Gmail API error while fetching thread {thread_id}"
|
||||
) from exc
|
||||
Reference in New Issue
Block a user