Generate code to integrate with external APIs
This document provides the generated, production-ready code for integrating with an external RESTful API. This deliverable focuses on providing a robust, flexible, and well-structured Python client that handles common API interaction patterns, including various HTTP methods, error handling, and authentication.
This first step of the "API Integration Builder" workflow delivers a foundational Python client designed to streamline your interactions with a generic RESTful API. The generated code is built with best practices in mind, emphasizing modularity, error handling, and ease of use.
Key Features of the Generated Code:
ApiClient Class: Encapsulates all API interaction logic, making your code clean and maintainable.GET, POST, PUT, and DELETE operations.logging module for better debuggability and operational monitoring.api_client.pyBelow is the Python code for your API client. This code is designed to be highly adaptable; you will need to configure it with your specific API's base URL and authentication details.
import requests
import json
import logging
from typing import Dict, Any, Optional, Union
# --- Configure Logging ---
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# --- Custom Exceptions for API Client ---
class ApiException(Exception):
"""Base exception for API-related errors."""
def __init__(self, message: str, status_code: Optional[int] = None, response_data: Optional[Any] = None):
super().__init__(message)
self.status_code = status_code
self.response_data = response_data
def __str__(self):
detail = f"Status Code: {self.status_code}" if self.status_code else ""
if self.response_data:
detail += f", Response: {json.dumps(self.response_data)}"
return f"{super().__str__()} ({detail})"
class NetworkError(ApiException):
"""Exception for network-related issues (e.g., connection refused, timeout)."""
pass
class ServerError(ApiException):
"""Exception for 5xx HTTP status codes."""
pass
class ClientError(ApiException):
"""Exception for 4xx HTTP status codes."""
pass
class JsonDecodingError(ApiException):
"""Exception for issues decoding JSON response."""
pass
# --- API Client Class ---
class ApiClient:
"""
A robust and configurable client for interacting with RESTful APIs.
Handles HTTP requests, error parsing, and provides a structured way to
communicate with external services.
"""
def __init__(
self,
base_url: str,
api_key: Optional[str] = None,
bearer_token: Optional[str] = None,
default_headers: Optional[Dict[str, str]] = None,
timeout: int = 30
):
"""
Initializes the API client.
Args:
base_url (str): The base URL of the API (e.g., "https://api.example.com/v1").
api_key (Optional[str]): An API key to include in 'X-API-Key' header.
Mutually exclusive with bearer_token.
bearer_token (Optional[str]): A bearer token for 'Authorization' header.
Mutually exclusive with api_key.
default_headers (Optional[Dict[str, str]]): Additional headers to send with every request.
timeout (int): Default timeout for requests in seconds.
"""
if not base_url:
raise ValueError("base_url cannot be empty.")
self.base_url = base_url.rstrip('/')
self.timeout = timeout
self.session = requests.Session()
self.headers = {
"Content-Type": "application/json",
"Accept": "application/json",
}
if default_headers:
self.headers.update(default_headers)
if api_key and bearer_token:
raise ValueError("Cannot provide both api_key and bearer_token. Choose one authentication method.")
elif api_key:
self.headers["X-API-Key"] = api_key
logger.info("API Client initialized with API Key authentication.")
elif bearer_token:
self.headers["Authorization"] = f"Bearer {bearer_token}"
logger.info("API Client initialized with Bearer Token authentication.")
else:
logger.warning("API Client initialized without specific API key or bearer token authentication.")
logger.info(f"API Client initialized for base URL: {self.base_url}")
def _build_url(self, endpoint: str) -> str:
"""Constructs the full URL for an API endpoint."""
return f"{self.base_url}/{endpoint.lstrip('/')}"
def _make_request(
self,
method: str,
endpoint: str,
params: Optional[Dict[str, Any]] = None,
data: Optional[Union[Dict, str]] = None,
json_data: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, str]] = None
) -> Any:
"""
Internal helper to make an HTTP request and handle common responses/errors.
Args:
method (str): HTTP method (GET, POST, PUT, DELETE).
endpoint (str): API endpoint (e.g., "users", "products/123").
params (Optional[Dict[str, Any]]): Query parameters for the URL.
data (Optional[Union[Dict, str]]): Request body for POST/PUT (e.g., form data, raw string).
json_data (Optional[Dict[str, Any]]): Request body for POST/PUT as JSON.
headers (Optional[Dict[str, str]]): Additional headers for this specific request.
Returns:
Any: The JSON response body if successful.
Raises:
NetworkError: For network connectivity issues.
ClientError: For 4xx HTTP status codes.
ServerError: For 5xx HTTP status codes.
JsonDecodingError: If the response cannot be decoded as JSON.
ApiException: For other unexpected API errors.
"""
url = self._build_url(endpoint)
request_headers = self.headers.copy()
if headers:
request_headers.update(headers)
logger.debug(f"Making {method} request to {url} with params: {params}, data: {data}, json: {json_data}, headers: {request_headers}")
try:
response = self.session.request(
method,
url,
params=params,
data=data,
json=json_data,
headers=request_headers,
timeout=self.timeout
)
response.raise_for_status() # Raises HTTPError for bad responses (4xx or 5xx)
# Attempt to parse JSON response
if response.text:
try:
return response.json()
except json.JSONDecodeError as e:
logger.error(f"Failed to decode JSON from response for {url}: {e}. Response text: {response.text}")
raise JsonDecodingError(f"Failed to decode JSON response.", status_code=response.status_code, response_data=response.text)
return None # No content in response
except requests.exceptions.Timeout as e:
logger.error(f"Request to {url} timed out after {self.timeout} seconds: {e}")
raise NetworkError(f"Request timed out.", response_data=str(e))
except requests.exceptions.ConnectionError as e:
logger.error(f"Connection error to {url}: {e}")
raise NetworkError(f"Connection to API failed.", response_data=str(e))
except requests.exceptions.HTTPError as e:
status_code = e.response.status_code
error_message = f"HTTP Error {status_code} for {url}"
response_data = None
try:
response_data = e.response.json()
except json.JSONDecodeError:
response_data = e.response.text # Fallback to raw text if not JSON
logger.error(f"{error_message}: {response_data}")
if 400 <= status_code < 500:
raise ClientError(error_message, status_code=status_code, response_data=response_data)
elif 500 <= status_code < 600:
raise ServerError(error_message, status_code=status_code, response_data=response_data)
else:
raise ApiException(error_message, status_code=status_code, response_data=response_data)
except requests.exceptions.RequestException as e:
logger.error(f"An unexpected request error occurred for {url}: {e}")
raise ApiException(f"An unexpected error occurred during API request.", response_data=str(e))
except Exception as e:
logger.critical(f"An unhandled exception occurred during API request to {url}: {e}", exc_info=True)
raise ApiException(f"An unhandled error occurred: {str(e)}")
def get(
self,
endpoint: str,
params: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, str]] = None
) -> Any:
"""Performs a GET request."""
logger.debug(f"Calling GET {endpoint} with params: {params}")
return self._make_request("GET", endpoint, params=params, headers=headers)
def post(
self,
endpoint: str,
data: Optional[Union[Dict, str]] = None,
json_data: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, str]] = None
) -> Any:
"""Performs a POST request."""
logger.debug(f"Calling POST {endpoint} with data: {data}, json: {json_data}")
return self._make_request("POST", endpoint, data=data, json_data=json_data, headers=headers)
def put(
self,
endpoint: str,
data: Optional[Union[Dict, str]] = None,
json_data: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, str]] = None
) -> Any:
"""Performs a PUT request."""
logger.debug(f"Calling PUT {endpoint} with data: {data}, json: {json_data}")
return self._make_request("PUT", endpoint, data=data, json_data=json_data, headers=headers)
def delete(
self,
endpoint: str,
headers: Optional[Dict[str, str]] = None
) -> Any:
"""Performs a DELETE request."""
logger.debug(f"Calling DELETE {endpoint}")
return self._make_request("DELETE", endpoint, headers=headers)
# --- Example Usage (for demonstration purposes) ---
if __name__ == "__main__":
# IMPORTANT: Replace with your actual API details
# For testing, you can use a public API like JSONPlaceholder
# e.g., "https://jsonplaceholder.typicode.com"
# --- Configuration ---
# Option 1: No specific auth (useful for public APIs or testing)
# API_BASE_URL = "https://jsonplaceholder.typicode.com"
# client = ApiClient(base_url=API_BASE_URL)
# Option 2: API Key authentication (replace with your key)
# API_BASE_URL = "https://api.example.com/v1"
# MY_API_KEY = "your_secret_api_key_here"
# client = ApiClient(base_url=API_BASE_URL, api_key=MY_API_KEY)
# Option 3: Bearer Token authentication (replace with your token)
API_BASE_URL = "https://api.example.com/v1" # Placeholder - update this
MY_BEARER_TOKEN = "your_jwt_bearer_token_here" # Placeholder - update this
client = ApiClient(base_url=API_BASE_URL, bearer_token=MY_BEARER_TOKEN)
# For JSONPlaceholder example, let's use a dummy base URL and no auth for the actual calls
# As the `ApiClient` constructor forces a choice, we'll demonstrate using a public API
# without specific auth for the example calls below.
# For real use, configure `client` once with the correct auth.
jsonplaceholder_client = ApiClient(base_url="https://jsonplaceholder.typicode.com")
print("\n--- Testing GET Request ---")
try:
posts = jsonplaceholder_client.get("posts")
print(f"Fetched {len(posts)} posts. First post title: {posts[0]['title']}")
except ApiException as e:
print(f"Error fetching posts: {e}")
print("\n--- Testing GET Request (Single Resource) ---")
try:
post_id = 1
single_post = jsonplaceholder_client.get(f"posts/{post_id}")
print(f"Fetched post {post_id}: {single_post['title']}")
except ApiException as e:
print(f"Error fetching post {post_id}: {e}")
print("\n--- Testing POST Request ---")
try:
new_post_data = {
"title": "foo",
"body": "bar",
"userId": 1
}
As part of the "API Integration Builder" workflow, we are now executing the "projectmanager → create_project" step. This crucial initial phase lays the foundation for a successful API integration by defining the scope, objectives, technical requirements, and key deliverables.
This document serves as the project initiation output, outlining the framework for your API integration project.
Project Name: [Customer to Define - e.g., "CRM-ERP Data Sync Integration Project"]
Purpose: To establish a robust and secure integration between your internal systems/applications and one or more external APIs, enabling seamless data exchange and automated workflows. This project aims to leverage external services to enhance your business capabilities, streamline operations, and improve data consistency.
Goal of this Step: To formally initiate the API integration project by defining its core parameters, gathering essential preliminary information, and setting clear expectations for the subsequent phases.
The primary objectives for this API Integration Project include:
This section outlines the initial understanding of what will be included and excluded from this integration project. This will be refined in subsequent design phases.
3.1. In-Scope Components:
* [Customer to Specify, e.g., retrieving customer data, submitting order details, syncing product inventory, triggering webhooks].
3.2. Out-of-Scope Components (Unless explicitly added later):
To proceed effectively, we require the following initial information about the target API(s):
* [e.g., "Salesforce REST API"]
* [e.g., "Stripe Payments API"]
* [e.g., https://developer.salesforce.com/docs/api/latest/rest]
* [e.g., https://stripe.com/docs/api]
* [e.g., /services/data/vXX.0/sobjects/Account, /v1/charges]
This section outlines the technical environment and specific requirements for the integration.
* Preferred: [Customer to Specify, e.g., Python, Node.js, Java, C#, Go, or an iPaaS like Zapier, Workato, Mulesoft]
* Rationale (Optional): [e.g., "Aligns with existing tech stack," "Leverages serverless capabilities"]
* Preferred: [Customer to Specify, e.g., AWS Lambda, Azure Functions, Google Cloud Run, Kubernetes, Dedicated VM, On-premise server]
* Region (if cloud-based): [e.g., us-east-1]
* Credential Management: How will API keys/secrets be securely stored and accessed? (e.g., AWS Secrets Manager, Azure Key Vault, HashiCorp Vault)
* Data Encryption: Any specific requirements for data encryption in transit or at rest?
* IP Whitelisting: Is this required by the external API or your internal systems?
* Preferred Tools: [e.g., CloudWatch, Azure Monitor, Splunk, ELK Stack, Prometheus/Grafana]
* Alerting: What conditions should trigger alerts (e.g., failed requests, rate limit breaches)?
Upon completion of this initial phase, you will receive:
Following the successful completion of the "projectmanager → create_project" step, the workflow will proceed as follows:
To ensure the project proceeds smoothly and effectively, we kindly request your prompt attention to the following:
We look forward to collaborating with you to make this API integration project a resounding success!
\n