""" Low-level HTTP client for Gitea API operations. This module handles the actual HTTP requests to Gitea API using subprocess + curl for maximum compatibility and minimal dependencies. """ import json import subprocess from subprocess import PIPE from typing import Dict, Any, Optional, List from .exceptions import GiteaError, GiteaApiError, GiteaAuthError from .config import GiteaConfig class GiteaHttpClient: """Low-level HTTP client for Gitea API.""" def __init__(self, config: GiteaConfig): self.config = config def get(self, url: str, params: Optional[Dict[str, str]] = None) -> Dict[str, Any]: """Make GET request to Gitea API.""" if params: param_string = '&'.join(f"{k}={v}" for k, v in params.items()) url = f"{url}?{param_string}" return self._make_request('GET', url) def post(self, url: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: """Make POST request to Gitea API.""" self._require_auth() return self._make_request('POST', url, data) def patch(self, url: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: """Make PATCH request to Gitea API.""" self._require_auth() return self._make_request('PATCH', url, data) def delete(self, url: str) -> Dict[str, Any]: """Make DELETE request to Gitea API.""" self._require_auth() return self._make_request('DELETE', url) def _make_request(self, method: str, url: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: """Make HTTP request using curl.""" cmd = ['curl', '-s', '-X', method] # Add authentication if available if self.config.auth_token: cmd.extend(['-H', f'Authorization: token {self.config.auth_token}']) # Add content type for requests with data if data is not None: cmd.extend(['-H', 'Content-Type: application/json']) cmd.extend(['-d', json.dumps(data)]) cmd.append(url) try: result = subprocess.run( cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True, check=True ) if result.returncode != 0: raise GiteaApiError(f"HTTP request failed: {result.stderr}") # Handle empty responses if not result.stdout.strip(): return {} response_data = json.loads(result.stdout) # Check for API error responses if isinstance(response_data, dict): if 'message' in response_data: # This could be an error or just a response with a message field # We need to distinguish based on context or HTTP status if any(error_word in response_data['message'].lower() for error_word in ['error', 'not found', 'forbidden', 'unauthorized']): raise GiteaApiError(response_data['message']) return response_data except subprocess.CalledProcessError as e: raise GiteaApiError(f"HTTP request failed: {e.stderr}") except json.JSONDecodeError as e: raise GiteaError(f"Failed to parse API response: {e}") def _require_auth(self): """Ensure authentication token is available.""" if not self.config.auth_token: raise GiteaAuthError("Authentication token required for this operation")