←back to Blog

A Coding Guide to Build a Production-Ready Asynchronous Python SDK with Rate Limiting, In-Memory Caching, and Authentication

«`html

A Coding Guide to Build a Production-Ready Asynchronous Python SDK with Rate Limiting, In-Memory Caching, and Authentication

This tutorial provides a comprehensive guide for developers looking to create a robust, production-ready Python SDK. It covers the installation and configuration of essential asynchronous HTTP libraries, namely aiohttp and nest-asyncio, and walks through the implementation of core components including structured response objects, token-bucket rate limiting, in-memory caching with TTL, and a clean, dataclass-driven design.

Target Audience Analysis

The target audience for this guide consists primarily of software developers and engineers working on API integrations who are familiar with Python and asynchronous programming. They may belong to startups or established enterprises, seeking to improve their API client implementations.

Pain Points:

  • Difficulty in managing API request rates and avoiding throttling.
  • Need for efficient data retrieval methods with caching mechanisms.
  • Challenges in implementing robust error handling and response management.

Goals:

  • To build a scalable and efficient SDK for API integration.
  • To ensure reliability and resilience in API interactions.
  • To minimize latency and improve response times through caching.

Interests:

  • Modern Python programming practices.
  • Asynchronous programming techniques.
  • API design and development patterns.

Communication Preferences:

  • Clear, concise, and technical content.
  • Examples and code snippets for practical application.
  • Structured documentation for easy navigation.

Installation and Configuration

To begin, install the required libraries with the following command:

!pip install aiohttp nest-asyncio

This command sets up the asynchronous runtime needed to run event loops seamlessly, enabling robust async HTTP requests and rate-limited workflows.

Core Components Implementation

Structured Response Object

from dataclasses import dataclass
from typing import Any, Dict
from datetime import datetime

@dataclass
class APIResponse:
    data: Any
    status_code: int
    headers: Dict[str, str]
    timestamp: datetime
   
    def to_dict(self) -> Dict:
        return asdict(self)

The APIResponse class encapsulates the HTTP response details such as payload, status code, headers, and timestamp.

Rate Limiting

import time

class RateLimiter:
    def __init__(self, max_calls: int = 100, time_window: int = 60):
        self.max_calls = max_calls
        self.time_window = time_window
        self.calls = []
   
    def can_proceed(self) -> bool:
        now = time.time()
        self.calls = [call_time for call_time in self.calls if now - call_time < self.time_window]
        if len(self.calls) < self.max_calls:
            self.calls.append(now)
            return True
        return False
   
    def wait_time(self) -> float:
        if not self.calls:
            return 0
        return max(0, self.time_window - (time.time() - self.calls[0]))

The RateLimiter class enforces a token-bucket policy to manage API request rates.

In-Memory Caching

from datetime import timedelta
import hashlib
import json

class Cache:
    def __init__(self, default_ttl: int = 300):
        self.cache = {}
        self.default_ttl = default_ttl
   
    def _generate_key(self, method: str, url: str, params: Dict = None) -> str:
        key_data = f"{method}:{url}:{json.dumps(params or {}, sort_keys=True)}"
        return hashlib.md5(key_data.encode()).hexdigest()
   
    def get(self, method: str, url: str, params: Dict = None) -> Optional[APIResponse]:
        key = self._generate_key(method, url, params)
        if key in self.cache:
            response, expiry = self.cache[key]
            if datetime.now() < expiry:
                return response
            del self.cache[key]
        return None
   
    def set(self, method: str, url: str, response: APIResponse, params: Dict = None, ttl: int = None):
        key = self._generate_key(method, url, params)
        expiry = datetime.now() + timedelta(seconds=ttl or self.default_ttl)
        self.cache[key] = (response, expiry)

The Cache class provides a lightweight in-memory TTL cache for API responses, automatically evicting stale entries.

Advanced SDK Class

import aiohttp
import logging

class AdvancedSDK:
    def __init__(self, base_url: str, api_key: str = None, rate_limit: int = 100):
        self.base_url = base_url.rstrip('/')
        self.api_key = api_key
        self.session = None
        self.rate_limiter = RateLimiter(max_calls=rate_limit)
        self.cache = Cache()
        self.logger = self._setup_logger()
   
    def _setup_logger(self) -> logging.Logger:
        logger = logging.getLogger(f"SDK-{id(self)}")
        if not logger.handlers:
            handler = logging.StreamHandler()
            formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
            handler.setFormatter(formatter)
            logger.addHandler(handler)
            logger.setLevel(logging.INFO)
        return logger
   
    async def __aenter__(self):
        self.session = aiohttp.ClientSession()
        return self
   
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if self.session:
            await self.session.close()

The AdvancedSDK class integrates all components, managing sessions, headers, and coordinating rate limiting and caching.

Demonstration of SDK Capabilities

async def demo_sdk():
    async with AdvancedSDK("https://jsonplaceholder.typicode.com") as sdk:
        response = await sdk.get("/posts/1")
        print(f"Status: {response.status_code}, Title: {response.data.get('title', 'N/A')}")

The demo_sdk function illustrates the SDK's core features by performing GET requests, showcasing caching and error handling.

Conclusion

This SDK tutorial provides a scalable foundation for any RESTful integration, combining modern Python idioms with practical tooling. By adopting the patterns described, development teams can accelerate the creation of new API clients while ensuring predictability, observability, and resilience.

For further resources and community discussions, please follow us on Twitter and join our Machine Learning subreddit.

```