Skip to main content
Glama
base.py3.55 kB
"""Base client for Nextcloud operations with shared authentication.""" import logging import time from abc import ABC from functools import wraps from httpx import AsyncClient, HTTPStatusError, RequestError, codes logger = logging.getLogger(__name__) def retry_on_429(func): """This decorator handles the 429 response from REST APIs The `func` is assumed to be a method that is similar to `httpx.Client.get`, and returns an `httpx.Response` object. In the case of `Too Many Requests` HTTP response, the function will wait for a couple of seconds and retry the request. """ MAX_RETRIES = 5 @wraps(func) async def wrapper(*args, **kwargs): retries = 0 while retries < MAX_RETRIES: try: # Make GET API call retries += 1 response = await func(*args, **kwargs) break except HTTPStatusError as e: # If we get a '429 Client Error: Too Many Requests' # error we wait a couple of seconds and do a retry if e.response.status_code == codes.TOO_MANY_REQUESTS: logger.warning( f"429 Client Error: Too Many Requests, Number of attempts: {retries}" ) time.sleep(5) elif e.response.status_code == 404: # 404 errors are often expected (e.g., checking if attachments exist) # Log as debug instead of warning logger.debug( f"HTTPStatusError {e.response.status_code}: {e}, Number of attempts: {retries}" ) raise else: logger.warning( f"HTTPStatusError {e.response.status_code}: {e}, Number of attempts: {retries}" ) raise except RequestError as e: logger.warning( f"RequestError {e.request.url}: {e}, Number of attempts: {retries}" ) raise # If for loop ends without break statement else: logger.warning("All API call retries failed") raise RuntimeError( f"Maximum number of retries ({MAX_RETRIES}) exceeded without success" ) return response return wrapper class BaseNextcloudClient(ABC): """Base class for all Nextcloud app clients.""" def __init__(self, http_client: AsyncClient, username: str): """Initialize with shared HTTP client and username. Args: http_client: Authenticated AsyncClient instance username: Nextcloud username for WebDAV operations """ self._client = http_client self.username = username def _get_webdav_base_path(self) -> str: """Helper to get the base WebDAV path for the authenticated user.""" return f"/remote.php/dav/files/{self.username}" @retry_on_429 async def _make_request(self, method: str, url: str, **kwargs): """Common request wrapper with logging and error handling. Args: method: HTTP method url: Request URL **kwargs: Additional request parameters Returns: Response object """ logger.debug(f"Making {method} request to {url}") response = await self._client.request(method, url, **kwargs) response.raise_for_status() return response

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/No-Smoke/nextcloud-mcp-comprehensive'

If you have feedback or need assistance with the MCP directory API, please join our Discord server