# Copyright (c) 2025 Patrick Motsch # All rights reserved. """Abstract base classes for the Provider-Connector architecture (1:n). One ProviderConnector per vendor (e.g. MsftConnector, GoogleConnector). Each ProviderConnector exposes n ServiceAdapters (e.g. SharepointAdapter, OutlookAdapter). All ServiceAdapters share the same access token from the UserConnection. """ from abc import ABC, abstractmethod from dataclasses import dataclass, field from typing import List, Optional, Union @dataclass class DownloadResult: """Rich return type for ServiceAdapter.download() when metadata is available.""" data: bytes = field(default=b"", repr=False) fileName: str = "" mimeType: str = "" class ServiceAdapter(ABC): """Standardized operations for a single service of a provider.""" @abstractmethod async def browse( self, path: str, filter: Optional[str] = None, limit: Optional[int] = None, ) -> list: """List items (files/folders) at the given path. ``limit`` is an optional upper bound for the number of returned entries. Adapters that talk to paginated APIs should keep paging until either the API is exhausted OR ``limit`` is reached. ``None`` means "use the adapter's sensible default" (NOT "unlimited") so an over-eager caller cannot accidentally pull millions of records. Adapters that have no pagination (single page result) may ignore this parameter. """ ... @abstractmethod async def download(self, path: str) -> Union[bytes, DownloadResult]: """Download a file. Return bytes or DownloadResult with metadata.""" ... @abstractmethod async def upload(self, path: str, data: bytes, fileName: str) -> dict: """Upload a file to the given path. Returns metadata of the created entry.""" ... @abstractmethod async def search( self, query: str, path: Optional[str] = None, limit: Optional[int] = None, ) -> list: """Search for items matching the query. See :meth:`browse` for the semantics of ``limit``. """ ... class ProviderConnector(ABC): """One connector per provider. Manages a UserConnection + token. Provides access to n services of the provider.""" def __init__(self, connection, accessToken: str): self.connection = connection self.accessToken = accessToken @abstractmethod def getAvailableServices(self) -> List[str]: """Which services does this provider offer?""" ... @abstractmethod def getServiceAdapter(self, service: str) -> ServiceAdapter: """Return the ServiceAdapter for a specific service.""" ...