Spaces:
Paused
Paused
| """ | |
| Cloudflare API Client | |
| Handles authentication and base HTTP operations for Cloudflare services | |
| """ | |
| import asyncio | |
| import json | |
| from typing import Any, Dict, Optional, Union | |
| import aiohttp | |
| from app.logger import logger | |
| class CloudflareClient: | |
| """Base client for Cloudflare API operations""" | |
| def __init__( | |
| self, | |
| api_token: str, | |
| account_id: str, | |
| worker_url: Optional[str] = None, | |
| timeout: int = 30, | |
| ): | |
| self.api_token = api_token | |
| self.account_id = account_id | |
| self.worker_url = worker_url | |
| self.timeout = timeout | |
| self.base_url = "https://api.cloudflare.com/client/v4" | |
| # HTTP headers for API requests | |
| self.headers = { | |
| "Authorization": f"Bearer {api_token}", | |
| "Content-Type": "application/json", | |
| } | |
| async def _make_request( | |
| self, | |
| method: str, | |
| url: str, | |
| data: Optional[Dict[str, Any]] = None, | |
| headers: Optional[Dict[str, str]] = None, | |
| use_worker: bool = False, | |
| ) -> Dict[str, Any]: | |
| """Make HTTP request to Cloudflare API or Worker""" | |
| # Use worker URL if specified and use_worker is True | |
| if use_worker and self.worker_url: | |
| full_url = f"{self.worker_url.rstrip('/')}/{url.lstrip('/')}" | |
| else: | |
| full_url = f"{self.base_url}/{url.lstrip('/')}" | |
| request_headers = self.headers.copy() | |
| if headers: | |
| request_headers.update(headers) | |
| timeout = aiohttp.ClientTimeout(total=self.timeout) | |
| try: | |
| async with aiohttp.ClientSession(timeout=timeout) as session: | |
| async with session.request( | |
| method=method.upper(), | |
| url=full_url, | |
| headers=request_headers, | |
| json=data if data else None, | |
| ) as response: | |
| response_text = await response.text() | |
| try: | |
| response_data = ( | |
| json.loads(response_text) if response_text else {} | |
| ) | |
| except json.JSONDecodeError: | |
| response_data = {"raw_response": response_text} | |
| if not response.ok: | |
| logger.error( | |
| f"Cloudflare API error: {response.status} - {response_text}" | |
| ) | |
| raise CloudflareError( | |
| f"HTTP {response.status}: {response_text}", | |
| response.status, | |
| response_data, | |
| ) | |
| return response_data | |
| except asyncio.TimeoutError: | |
| logger.error(f"Timeout making request to {full_url}") | |
| raise CloudflareError(f"Request timeout after {self.timeout}s") | |
| except aiohttp.ClientError as e: | |
| logger.error(f"HTTP client error: {e}") | |
| raise CloudflareError(f"Client error: {e}") | |
| async def get( | |
| self, | |
| url: str, | |
| headers: Optional[Dict[str, str]] = None, | |
| use_worker: bool = False, | |
| ) -> Dict[str, Any]: | |
| """Make GET request""" | |
| return await self._make_request( | |
| "GET", url, headers=headers, use_worker=use_worker | |
| ) | |
| async def post( | |
| self, | |
| url: str, | |
| data: Optional[Dict[str, Any]] = None, | |
| headers: Optional[Dict[str, str]] = None, | |
| use_worker: bool = False, | |
| ) -> Dict[str, Any]: | |
| """Make POST request""" | |
| return await self._make_request( | |
| "POST", url, data=data, headers=headers, use_worker=use_worker | |
| ) | |
| async def put( | |
| self, | |
| url: str, | |
| data: Optional[Dict[str, Any]] = None, | |
| headers: Optional[Dict[str, str]] = None, | |
| use_worker: bool = False, | |
| ) -> Dict[str, Any]: | |
| """Make PUT request""" | |
| return await self._make_request( | |
| "PUT", url, data=data, headers=headers, use_worker=use_worker | |
| ) | |
| async def delete( | |
| self, | |
| url: str, | |
| headers: Optional[Dict[str, str]] = None, | |
| use_worker: bool = False, | |
| ) -> Dict[str, Any]: | |
| """Make DELETE request""" | |
| return await self._make_request( | |
| "DELETE", url, headers=headers, use_worker=use_worker | |
| ) | |
| async def upload_file( | |
| self, | |
| url: str, | |
| file_data: bytes, | |
| content_type: str = "application/octet-stream", | |
| headers: Optional[Dict[str, str]] = None, | |
| use_worker: bool = False, | |
| ) -> Dict[str, Any]: | |
| """Upload file data""" | |
| # Use worker URL if specified and use_worker is True | |
| if use_worker and self.worker_url: | |
| full_url = f"{self.worker_url.rstrip('/')}/{url.lstrip('/')}" | |
| else: | |
| full_url = f"{self.base_url}/{url.lstrip('/')}" | |
| upload_headers = { | |
| "Authorization": f"Bearer {self.api_token}", | |
| "Content-Type": content_type, | |
| } | |
| if headers: | |
| upload_headers.update(headers) | |
| timeout = aiohttp.ClientTimeout( | |
| total=self.timeout * 2 | |
| ) # Longer timeout for uploads | |
| try: | |
| async with aiohttp.ClientSession(timeout=timeout) as session: | |
| async with session.put( | |
| url=full_url, headers=upload_headers, data=file_data | |
| ) as response: | |
| response_text = await response.text() | |
| try: | |
| response_data = ( | |
| json.loads(response_text) if response_text else {} | |
| ) | |
| except json.JSONDecodeError: | |
| response_data = {"raw_response": response_text} | |
| if not response.ok: | |
| logger.error( | |
| f"File upload error: {response.status} - {response_text}" | |
| ) | |
| raise CloudflareError( | |
| f"Upload failed: HTTP {response.status}", | |
| response.status, | |
| response_data, | |
| ) | |
| return response_data | |
| except asyncio.TimeoutError: | |
| logger.error(f"Timeout uploading file to {full_url}") | |
| raise CloudflareError(f"Upload timeout after {self.timeout * 2}s") | |
| except aiohttp.ClientError as e: | |
| logger.error(f"Upload client error: {e}") | |
| raise CloudflareError(f"Upload error: {e}") | |
| def get_account_url(self, endpoint: str) -> str: | |
| """Get URL for account-scoped endpoint""" | |
| return f"accounts/{self.account_id}/{endpoint}" | |
| def get_worker_url(self, endpoint: str) -> str: | |
| """Get URL for worker endpoint""" | |
| if not self.worker_url: | |
| raise CloudflareError("Worker URL not configured") | |
| return endpoint | |
| class CloudflareError(Exception): | |
| """Cloudflare API error""" | |
| def __init__( | |
| self, | |
| message: str, | |
| status_code: Optional[int] = None, | |
| response_data: Optional[Dict[str, Any]] = None, | |
| ): | |
| super().__init__(message) | |
| self.status_code = status_code | |
| self.response_data = response_data or {} | |
| def __str__(self) -> str: | |
| if self.status_code: | |
| return f"CloudflareError({self.status_code}): {super().__str__()}" | |
| return f"CloudflareError: {super().__str__()}" | |