from typing import Any from pydantic import BaseModel, EmailStr, Field class EmailAddress(BaseModel): address: EmailStr name: str | None = None def as_graph_recipient(self) -> dict[str, Any]: payload: dict[str, Any] = {"address": str(self.address)} if self.name: payload["name"] = self.name return {"emailAddress": payload} class FileAttachment(BaseModel): name: str content_type: str = "application/octet-stream" content_bytes_base64: str = Field(..., description="Base64 encoded file content.") def as_graph_attachment(self) -> dict[str, Any]: return { "@odata.type": "#microsoft.graph.fileAttachment", "name": self.name, "contentType": self.content_type, "contentBytes": self.content_bytes_base64, } class SendMailRequest(BaseModel): subject: str body: str to: list[EmailAddress] cc: list[EmailAddress] = Field(default_factory=list) bcc: list[EmailAddress] = Field(default_factory=list) reply_to: list[EmailAddress] = Field(default_factory=list) body_content_type: str = Field("HTML", pattern="^(HTML|Text)$") save_to_sent_items: bool = True attachments: list[FileAttachment] = Field(default_factory=list) class CalendarAttendee(BaseModel): email: EmailStr name: str | None = None type: str = Field("required", pattern="^(required|optional|resource)$") def as_graph_attendee(self) -> dict[str, Any]: email_address: dict[str, Any] = {"address": str(self.email)} if self.name: email_address["name"] = self.name return {"emailAddress": email_address, "type": self.type} class DateTimeTimeZone(BaseModel): date_time: str = Field(..., alias="dateTime") time_zone: str = Field("UTC", alias="timeZone") class CalendarEventRequest(BaseModel): subject: str body: str | None = None body_content_type: str = Field("HTML", pattern="^(HTML|Text)$") start: DateTimeTimeZone end: DateTimeTimeZone location: str | None = None attendees: list[CalendarAttendee] = Field(default_factory=list) is_online_meeting: bool = False online_meeting_provider: str | None = Field(None, pattern="^(teamsForBusiness|skypeForBusiness|skypeForConsumer)$") class DriveUploadRequest(BaseModel): content_base64: str content_type: str = "application/octet-stream" class GraphCollectionResponse(BaseModel): value: list[dict[str, Any]] next_link: str | None = Field(None, alias="@odata.nextLink")