async def _process_single_attachment(
        self,
        client: httpx.AsyncClient,
        attachment: Dict,
        event_id: str,
        breadcrumbs: List[Breadcrumb],
        att_idx: int,
        total_attachments: int,
    ) -> Optional[OutlookCalendarAttachmentEntity]:
        """Process a single attachment and return the processed entity."""
        attachment_id = attachment["id"]
        attachment_type = attachment.get("@odata.type", "")
        attachment_name = attachment.get("name", "unknown")

        logger.debug(
            f"Processing attachment #{att_idx + 1}/{total_attachments} "
            f"(ID: {attachment_id}, Name: {attachment_name}, Type: {attachment_type})"
        )

        # Only process file attachments
        if "#microsoft.graph.fileAttachment" not in attachment_type:
            logger.debug(
                f"Skipping non-file attachment: {attachment_name} (type: {attachment_type})"
            )
            return None

        try:
            # Get attachment content if not already included
            content_bytes = attachment.get("contentBytes")
            if not content_bytes:
                logger.debug(f"Fetching content for attachment {attachment_id}")
                attachment_url = (
                    f"{self.GRAPH_BASE_URL}/me/events/{event_id}/attachments/{attachment_id}"
                )
                attachment_data = await self._get_with_auth(client, attachment_url)
                content_bytes = attachment_data.get("contentBytes")

                if not content_bytes:
                    logger.warning(f"No content found for attachment {attachment_name}")
                    return None

            # Create file entity
            file_entity = OutlookCalendarAttachmentEntity(
                entity_id=f"{event_id}_attachment_{attachment_id}",
                breadcrumbs=breadcrumbs,
                file_id=attachment_id,
                name=attachment_name,
                mime_type=attachment.get("contentType"),
                size=attachment.get("size", 0),
                download_url=f"outlook://calendar/attachment/{event_id}/{attachment_id}",
                event_id=event_id,
                attachment_id=attachment_id,
                content_type=attachment.get("contentType"),
                is_inline=attachment.get("isInline", False),
                content_id=attachment.get("contentId"),
                last_modified_at=attachment.get("lastModifiedDateTime"),
                metadata={
                    "source": "outlook_calendar",
                    "event_id": event_id,
                    "attachment_id": attachment_id,
                },
            )

            # Decode the base64 data
            try:
                binary_data = base64.b64decode(content_bytes)
            except Exception as e:
                logger.error(f"Error decoding attachment content: {str(e)}")
                return None

            # Process using the BaseSource method
            logger.debug(f"Processing file entity for {attachment_name} with direct content stream")
            processed_entity = await self.process_file_entity_with_content(
                file_entity=file_entity,
                content_stream=self._create_content_stream(binary_data),
                metadata={"source": "outlook_calendar", "event_id": event_id},
            )

            if processed_entity:
                logger.debug(f"Successfully processed attachment: {attachment_name}")
                return processed_entity
            else:
                logger.warning(f"Processing failed for attachment: {attachment_name}")
                return None

        except Exception as e:
            logger.error(f"Error processing attachment {attachment_id}: {str(e)}")
            return None
Process a single attachment and return the processed entity.
backend/airweave/platform/sources/outlook_calendar.py