|
""" |
|
client.py – Base HTTP client for CoinDesk API. |
|
|
|
This module provides the BaseClient class that handles HTTP requests |
|
to the CoinDesk API with proper authentication and error handling. |
|
""" |
|
|
|
import requests |
|
import json |
|
from typing import Dict, Any, Optional |
|
from urllib.parse import urljoin, urlencode |
|
import config |
|
|
|
|
|
class APIError(Exception): |
|
"""Custom exception for API errors.""" |
|
def __init__(self, message: str, status_code: int = None, response: Any = None): |
|
self.message = message |
|
self.status_code = status_code |
|
self.response = response |
|
super().__init__(self.message) |
|
|
|
|
|
class BaseClient: |
|
""" |
|
Base HTTP client for CoinDesk API requests. |
|
|
|
Handles authentication, request formatting, and error handling. |
|
""" |
|
|
|
def __init__(self, base_url: str = None, headers: Dict[str, str] = None): |
|
""" |
|
Initialize the base client. |
|
|
|
Args: |
|
base_url: Base URL for the API (defaults to config.BASE_URL) |
|
headers: Default headers (defaults to config.HEADERS) |
|
""" |
|
self.base_url = base_url or config.BASE_URL |
|
self.headers = headers or config.HEADERS.copy() |
|
self.session = requests.Session() |
|
self.session.headers.update(self.headers) |
|
|
|
def _make_request(self, method: str, endpoint: str, params: Dict[str, Any] = None, |
|
data: Dict[str, Any] = None, **kwargs) -> Dict[str, Any]: |
|
""" |
|
Make an HTTP request to the API. |
|
|
|
Args: |
|
method: HTTP method (GET, POST, PUT, DELETE) |
|
endpoint: API endpoint path |
|
params: URL parameters |
|
data: Request body data |
|
**kwargs: Additional arguments for requests |
|
|
|
Returns: |
|
dict: JSON response from the API |
|
|
|
Raises: |
|
APIError: If the request fails or returns an error status |
|
""" |
|
|
|
url = urljoin(self.base_url, endpoint.lstrip('/')) |
|
|
|
|
|
if params: |
|
params = {k: v for k, v in params.items() if v is not None} |
|
|
|
try: |
|
|
|
response = self.session.request( |
|
method=method, |
|
url=url, |
|
params=params, |
|
json=data, |
|
**kwargs |
|
) |
|
|
|
|
|
print(f"[DEBUG] {method} {url}") |
|
if params: |
|
print(f"[DEBUG] Params: {params}") |
|
print(f"[DEBUG] Status: {response.status_code}") |
|
|
|
|
|
if response.status_code == 200: |
|
try: |
|
return response.json() |
|
except json.JSONDecodeError: |
|
|
|
return {"data": response.text, "status": "success"} |
|
else: |
|
|
|
error_message = f"API request failed with status {response.status_code}" |
|
|
|
try: |
|
error_data = response.json() |
|
if 'error' in error_data: |
|
error_message = error_data['error'] |
|
elif 'message' in error_data: |
|
error_message = error_data['message'] |
|
except json.JSONDecodeError: |
|
error_message = f"{error_message}: {response.text}" |
|
|
|
raise APIError( |
|
message=error_message, |
|
status_code=response.status_code, |
|
response=response |
|
) |
|
|
|
except requests.exceptions.RequestException as e: |
|
raise APIError(f"Request failed: {str(e)}") |
|
|
|
def get(self, endpoint: str, params: Dict[str, Any] = None, **kwargs) -> Dict[str, Any]: |
|
""" |
|
Make a GET request. |
|
|
|
Args: |
|
endpoint: API endpoint path |
|
params: URL parameters |
|
**kwargs: Additional arguments for requests |
|
|
|
Returns: |
|
dict: JSON response from the API |
|
""" |
|
return self._make_request('GET', endpoint, params=params, **kwargs) |
|
|
|
def post(self, endpoint: str, data: Dict[str, Any] = None, |
|
params: Dict[str, Any] = None, **kwargs) -> Dict[str, Any]: |
|
""" |
|
Make a POST request. |
|
|
|
Args: |
|
endpoint: API endpoint path |
|
data: Request body data |
|
params: URL parameters |
|
**kwargs: Additional arguments for requests |
|
|
|
Returns: |
|
dict: JSON response from the API |
|
""" |
|
return self._make_request('POST', endpoint, params=params, data=data, **kwargs) |
|
|
|
def put(self, endpoint: str, data: Dict[str, Any] = None, |
|
params: Dict[str, Any] = None, **kwargs) -> Dict[str, Any]: |
|
""" |
|
Make a PUT request. |
|
|
|
Args: |
|
endpoint: API endpoint path |
|
data: Request body data |
|
params: URL parameters |
|
**kwargs: Additional arguments for requests |
|
|
|
Returns: |
|
dict: JSON response from the API |
|
""" |
|
return self._make_request('PUT', endpoint, params=params, data=data, **kwargs) |
|
|
|
def delete(self, endpoint: str, params: Dict[str, Any] = None, **kwargs) -> Dict[str, Any]: |
|
""" |
|
Make a DELETE request. |
|
|
|
Args: |
|
endpoint: API endpoint path |
|
params: URL parameters |
|
**kwargs: Additional arguments for requests |
|
|
|
Returns: |
|
dict: JSON response from the API |
|
""" |
|
return self._make_request('DELETE', endpoint, params=params, **kwargs) |
|
|
|
def close(self): |
|
"""Close the HTTP session.""" |
|
self.session.close() |
|
|
|
def __enter__(self): |
|
"""Context manager entry.""" |
|
return self |
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb): |
|
"""Context manager exit.""" |
|
self.close() |
|
|
|
|
|
|
|
def create_client(base_url: str = None, headers: Dict[str, str] = None) -> BaseClient: |
|
""" |
|
Create a new BaseClient instance. |
|
|
|
Args: |
|
base_url: Base URL for the API |
|
headers: Default headers |
|
|
|
Returns: |
|
BaseClient: Configured client instance |
|
""" |
|
return BaseClient(base_url=base_url, headers=headers) |
|
|
|
|
|
|
|
def test_client(): |
|
"""Test the base client functionality.""" |
|
try: |
|
with create_client() as client: |
|
|
|
response = client.get("/index/cc/v1/markets") |
|
print("Client test successful!") |
|
print(f"Response keys: {list(response.keys()) if isinstance(response, dict) else 'Not a dict'}") |
|
return True |
|
except Exception as e: |
|
print(f"Client test failed: {e}") |
|
return False |
|
|
|
|
|
if __name__ == "__main__": |
|
test_client() |