"""Connector for CRUD sharepoint operations.""" import asyncio from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass from datetime import datetime from io import BytesIO from typing import Optional from office365.sharepoint.client_context import ClientContext from office365.sharepoint.files.file import File @dataclass class ConnectorSharepoint: ctx: ClientContext @classmethod async def create(cls, ctx: ClientContext) -> "ConnectorSharepoint": """Creates an instance of the Sharepoint connector. Params: ctx: The ClientContext instance. Returns: ConnectorSharepoint: An instance of the Sharepoint connector. """ return cls(ctx=ctx) @classmethod def get_client_context_from_username_password( cls, site_url: str, username: str, password: str ) -> ClientContext: """Creates a ClientContext instance from username and password. Params: site_url: The URL of the SharePoint site. username: The username for authentication. password: The password for authentication. Returns: ClientContext: An instance of the ClientContext. """ return ClientContext(site_url).with_user_credentials(username, password) @classmethod def get_client_context_from_app( cls, site_url: str, client_id: str, client_secret: str ) -> ClientContext: """Creates a ClientContext instance from client ID and client secret. Params: site_url: The URL of the SharePoint site. client_id: The client ID for authentication. client_secret: The client secret for authentication. Returns: ClientContext: An instance of the ClientContext. """ return ClientContext(site_url).with_client_credentials( client_id=client_id, client_secret=client_secret ) def copy_file( self, *, source_folder: str, source_file: str, dest_folder: str, dest_file: str ) -> bool: """Copy a file from one SharePoint location to another. Params: source_folder: Source folder path (server-relative) source_file: Source file name dest_folder: Destination folder path (server-relative) dest_file: Destination file name Returns: bool: True if successful, False otherwise """ source_path = f"{source_folder.rstrip('/')}/{source_file}" dest_path = f"{dest_folder.rstrip('/')}/{dest_file}" source_file_obj = self.ctx.web.get_file_by_server_relative_url(source_path) source_file_obj.copyto(dest_path).execute_query() return True async def copy_file_async( self, *, source_folder: str, source_file: str, dest_folder: str, dest_file: str ) -> bool: """Copy a file from one SharePoint location to another (async version). Params: source_folder: Source folder path (server-relative) source_file: Source file name dest_folder: Destination folder path (server-relative) dest_file: Destination file name Returns: bool: True if successful, False otherwise """ loop = asyncio.get_event_loop() with ThreadPoolExecutor() as executor: return await loop.run_in_executor( executor, lambda: self.copy_file( source_folder=source_folder, source_file=source_file, dest_folder=dest_folder, dest_file=dest_file, ), ) def read_file(self, *, folder_path: str, file_name: str) -> bytes: """Read a file from SharePoint and return its content as bytes. Params: folder_path: Folder path (server-relative) file_name: File name Returns: bytes: File content as bytes """ file_path = f"{folder_path.rstrip('/')}/{file_name}" response = File.open_binary(self.ctx, file_path) return response.content async def read_file_async(self, *, folder_path: str, file_name: str) -> bytes: """Read a file from SharePoint and return its content as bytes (async version). Params: folder_path: Folder path (server-relative) file_name: File name Returns: bytes: File content as bytes """ loop = asyncio.get_event_loop() with ThreadPoolExecutor() as executor: return await loop.run_in_executor( executor, lambda: self.read_file(folder_path=folder_path, file_name=file_name), ) def overwrite_file( self, *, folder_path: str, file_name: str, content: bytes ) -> bool: """Write content to a SharePoint file, overwriting if it exists. Params: folder_path: Target folder path (server-relative) file_name: Target file name content: File content as bytes Returns: bool: True if successful, False otherwise """ target_folder = self.ctx.web.get_folder_by_server_relative_url(folder_path) target_folder.upload_file(file_name, content).execute_query() return True async def overwrite_file_async( self, *, folder_path: str, file_name: str, content: bytes ) -> bool: """Write content to a SharePoint file, overwriting if it exists (async version). Params: folder_path: Target folder path (server-relative) file_name: Target file name content: File content as bytes Returns: bool: True if successful, False otherwise """ loop = asyncio.get_event_loop() with ThreadPoolExecutor() as executor: return await loop.run_in_executor( executor, lambda: self.overwrite_file( folder_path=folder_path, file_name=file_name, content=content, ), )