from dataclasses import dataclass, field import requests from django.conf import settings from .exceptions import ExternalAPIRequestError from .mock_loader import MockLoader from .services import ServiceRegistry @dataclass class AdapterResponse: status_code: int data: object headers: dict = field(default_factory=dict) is_mock: bool = False class ExternalAPIAdapter: def __init__(self, service_registry=None, mock_loader=None): self.service_registry = service_registry or ServiceRegistry() self.mock_loader = mock_loader or MockLoader() def request(self, service_name, path, method="GET", payload=None, query=None, headers=None): request_method = method.upper() self._validate_method(request_method) service = self.service_registry.get(service_name) if getattr(settings, "USE_EXTERNAL_API_MOCK", False): mock_response = self.mock_loader.load(service_name=service_name, path=path, method=request_method) return AdapterResponse( status_code=mock_response.status_code, data=mock_response.data, headers={"X-Mock-File": mock_response.file_path}, is_mock=True, ) return self._call_real_api( service=service, path=path, method=request_method, payload=payload, query=query, headers=headers, ) def _call_real_api(self, service, path, method, payload=None, query=None, headers=None): base_url = service.get("base_url", "").rstrip("/") api_key = service.get("api_key", "") if not base_url: raise ExternalAPIRequestError("External service base_url is not configured.") url = f"{base_url}/{str(path).lstrip('/')}" request_headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", } if headers: request_headers.update(headers) try: response = requests.request( method=method, url=url, json=payload, params=query, headers=request_headers, timeout=getattr(settings, "EXTERNAL_API_TIMEOUT", 30), ) except requests.RequestException as exc: raise ExternalAPIRequestError(f"External API request failed for '{url}': {exc}") from exc try: response_data = response.json() except ValueError: response_data = response.text return AdapterResponse( status_code=response.status_code, data=response_data, headers=dict(response.headers), is_mock=False, ) @staticmethod def _validate_method(method): supported_methods = {"GET", "POST", "PUT", "DELETE"} if method not in supported_methods: raise ValueError(f"Unsupported HTTP method '{method}'. Supported methods: {sorted(supported_methods)}") _default_adapter = ExternalAPIAdapter() def request(service_name, path, method="GET", payload=None, query=None, headers=None): return _default_adapter.request( service_name=service_name, path=path, method=method, payload=payload, query=query, headers=headers, )