Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Coded Smorgasbord: High Strung

      September 26, 2025

      Chainguard launches trusted collection of verified JavaScript libraries

      September 26, 2025

      CData launches Connect AI to provide agents access to enterprise data sources

      September 26, 2025

      PostgreSQL 18 adds asynchronous I/O to improve performance

      September 26, 2025

      Distribution Release: Neptune 9.0

      September 25, 2025

      Distribution Release: Kali Linux 2025.3

      September 23, 2025

      Distribution Release: SysLinuxOS 13

      September 23, 2025

      Development Release: MX Linux 25 Beta 1

      September 22, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      PHP 8.5.0 RC 1 available for testing

      September 26, 2025
      Recent

      PHP 8.5.0 RC 1 available for testing

      September 26, 2025

      Terraform Code Generator Using Ollama and CodeGemma

      September 26, 2025

      Beyond Denial: How AI Concierge Services Can Transform Healthcare from Reactive to Proactive

      September 25, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      Distribution Release: Neptune 9.0

      September 25, 2025
      Recent

      Distribution Release: Neptune 9.0

      September 25, 2025

      FOSS Weekly #25.39: Kill Switch Phones, LMDE 7, Zorin OS 18 Beta, Polybar, Apt History and More Linux Stuff

      September 25, 2025

      Distribution Release: Kali Linux 2025.3

      September 23, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»Machine Learning»A Coding Guide to Build a Production-Ready Asynchronous Python SDK with Rate Limiting, In-Memory Caching, and Authentication

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

    June 23, 2025

    In this tutorial, we guide users through building a robust, production-ready Python SDK. It begins by showing how to install and configure essential asynchronous HTTP libraries (aiohttp, nest-asyncio). It then 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. We’ll see how to wrap these pieces up in an AdvancedSDK class that supports async context management, automatic retry/wait-on-rate-limit behavior, JSON/auth headers injection, and convenient HTTP-verb methods. Along the way, a demo harness against JSONPlaceholder illustrates caching efficiency, batch fetching with rate limits, error handling, and even shows how to extend the SDK via a fluent “builder” pattern for custom configuration.

    Copy CodeCopiedUse a different Browser
    import asyncio
    import aiohttp
    import time
    import json
    from typing import Dict, List, Optional, Any, Union
    from dataclasses import dataclass, asdict
    from datetime import datetime, timedelta
    import hashlib
    import logging
    
    
    !pip install aiohttp nest-asyncio

    We install and configure the asynchronous runtime by importing asyncio and aiohttp, alongside utilities for timing, JSON handling, dataclass modeling, caching (via hashlib and datetime), and structured logging. The !pip install aiohttp nest-asyncio line ensures that the notebook can run an event loop seamlessly within Colab, enabling robust async HTTP requests and rate-limited workflows.

    Copy CodeCopiedUse a different Browser
    @dataclass
    class APIResponse:
        """Structured response object"""
        data: Any
        status_code: int
        headers: Dict[str, str]
        timestamp: datetime
       
        def to_dict(self) -> Dict:
            return asdict(self)

    The APIResponse dataclass encapsulates HTTP response details, payload (data), status code, headers, and the timestamp of retrieval into a single, typed object. The to_dict() helper converts the instance into a plain dictionary for easy logging, serialization, or downstream processing.

    Copy CodeCopiedUse a different Browser
    class RateLimiter:
        """Token bucket rate limiter"""
        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 simple token-bucket policy by tracking the timestamps of recent calls and allowing up to max_calls within a rolling time_window. When the limit is reached, can_proceed() returns False, and wait_time() calculates how long to pause before making the next request.

    Copy CodeCopiedUse a different Browser
    class Cache:
        """Simple in-memory cache with TTL"""
        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 by hashing the request signature (method, URL, params) into a unique key. It returns valid cached APIResponse objects before expiry and automatically evicts stale entries after their time-to-live has elapsed.

    Copy CodeCopiedUse a different Browser
    class AdvancedSDK:
        """Advanced SDK with modern Python patterns"""
       
        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):
            """Async context manager entry"""
            self.session = aiohttp.ClientSession()
            return self
       
        async def __aexit__(self, exc_type, exc_val, exc_tb):
            """Async context manager exit"""
            if self.session:
                await self.session.close()
       
        def _get_headers(self) -> Dict[str, str]:
            headers = {'Content-Type': 'application/json'}
            if self.api_key:
                headers['Authorization'] = f'Bearer {self.api_key}'
            return headers
       
        async def _make_request(self, method: str, endpoint: str, params: Dict = None,
                              data: Dict = None, use_cache: bool = True) -> APIResponse:
            """Core request method with rate limiting and caching"""
           
            if use_cache and method.upper() == 'GET':
                cached = self.cache.get(method, endpoint, params)
                if cached:
                    self.logger.info(f"Cache hit for {method} {endpoint}")
                    return cached
           
            if not self.rate_limiter.can_proceed():
                wait_time = self.rate_limiter.wait_time()
                self.logger.warning(f"Rate limit hit, waiting {wait_time:.2f}s")
                await asyncio.sleep(wait_time)
           
            url = f"{self.base_url}/{endpoint.lstrip('/')}"
           
            try:
                async with self.session.request(
                    method=method.upper(),
                    url=url,
                    params=params,
                    json=data,
                    headers=self._get_headers()
                ) as resp:
                    response_data = await resp.json() if resp.content_type == 'application/json' else await resp.text()
                   
                    api_response = APIResponse(
                        data=response_data,
                        status_code=resp.status,
                        headers=dict(resp.headers),
                        timestamp=datetime.now()
                    )
                   
                    if use_cache and method.upper() == 'GET' and 200 <= resp.status < 300:
                        self.cache.set(method, endpoint, api_response, params)
                   
                    self.logger.info(f"{method.upper()} {endpoint} - Status: {resp.status}")
                    return api_response
                   
            except Exception as e:
                self.logger.error(f"Request failed: {str(e)}")
                raise
       
        async def get(self, endpoint: str, params: Dict = None, use_cache: bool = True) -> APIResponse:
            return await self._make_request('GET', endpoint, params=params, use_cache=use_cache)
       
        async def post(self, endpoint: str, data: Dict = None) -> APIResponse:
            return await self._make_request('POST', endpoint, data=data, use_cache=False)
       
        async def put(self, endpoint: str, data: Dict = None) -> APIResponse:
            return await self._make_request('PUT', endpoint, data=data, use_cache=False)
       
        async def delete(self, endpoint: str) -> APIResponse:
            return await self._make_request('DELETE', endpoint, use_cache=False)

    The AdvancedSDK class wraps everything together into a clean, async-first client: it manages an aiohttp session via async context managers, injects JSON and auth headers, and coordinates our RateLimiter and Cache under the hood. Its _make_request method centralizes GET/POST/PUT/DELETE logic, handling cache lookups, rate-limit waits, error logging, and response packing into APIResponse objects, while the get/post/put/delete helpers give us ergonomic, high-level calls.

    Copy CodeCopiedUse a different Browser
    async def demo_sdk():
        """Demonstrate SDK capabilities"""
        print("<img src="https://s.w.org/images/core/emoji/15.1.0/72x72/1f680.png" alt="🚀" class="wp-smiley" /> Advanced SDK Demo")
        print("=" * 50)
       
        async with AdvancedSDK("https://jsonplaceholder.typicode.com") as sdk:
           
            print("n<img src="https://s.w.org/images/core/emoji/15.1.0/72x72/1f4e5.png" alt="📥" class="wp-smiley" /> Testing GET request with caching...")
            response1 = await sdk.get("/posts/1")
            print(f"First request - Status: {response1.status_code}")
            print(f"Title: {response1.data.get('title', 'N/A')}")
           
            response2 = await sdk.get("/posts/1")
            print(f"Second request (cached) - Status: {response2.status_code}")
           
            print("n<img src="https://s.w.org/images/core/emoji/15.1.0/72x72/1f4e4.png" alt="📤" class="wp-smiley" /> Testing POST request...")
            new_post = {
                "title": "Advanced SDK Tutorial",
                "body": "This SDK demonstrates modern Python patterns",
                "userId": 1
            }
            post_response = await sdk.post("/posts", data=new_post)
            print(f"POST Status: {post_response.status_code}")
            print(f"Created post ID: {post_response.data.get('id', 'N/A')}")
           
            print("n<img src="https://s.w.org/images/core/emoji/15.1.0/72x72/26a1.png" alt="⚡" class="wp-smiley" /> Testing batch requests with rate limiting...")
            tasks = []
            for i in range(1, 6):
                tasks.append(sdk.get(f"/posts/{i}"))
           
            results = await asyncio.gather(*tasks)
            print(f"Batch completed: {len(results)} requests")
            for i, result in enumerate(results, 1):
                print(f"  Post {i}: {result.data.get('title', 'N/A')[:30]}...")
           
            print("n<img src="https://s.w.org/images/core/emoji/15.1.0/72x72/274c.png" alt="❌" class="wp-smiley" /> Testing error handling...")
            try:
                error_response = await sdk.get("/posts/999999")
                print(f"Error response status: {error_response.status_code}")
            except Exception as e:
                print(f"Handled error: {type(e).__name__}")
       
        print("n<img src="https://s.w.org/images/core/emoji/15.1.0/72x72/2705.png" alt="✅" class="wp-smiley" /> Demo completed successfully!")
    
    
    async def run_demo():
      """Colab-friendly demo runner"""
      await demo_sdk()

    The demo_sdk coroutine walks through the SDK’s core features, issuing a cached GET request, performing a POST, executing a batch of GETs under rate limiting, and handling errors, against the JSONPlaceholder API, printing status codes and sample data to illustrate each capability. The run_demo helper ensures this demo runs smoothly inside a Colab notebook’s existing event loop.

    Copy CodeCopiedUse a different Browser
    import nest_asyncio
    nest_asyncio.apply()
    
    
    if __name__ == "__main__":
        try:
            asyncio.run(demo_sdk())
        except RuntimeError:
            loop = asyncio.get_event_loop()
            loop.run_until_complete(demo_sdk())
    
    
    class SDKBuilder:
        """Builder pattern for SDK configuration"""
        def __init__(self, base_url: str):
            self.base_url = base_url
            self.config = {}
       
        def with_auth(self, api_key: str):
            self.config['api_key'] = api_key
            return self
       
        def with_rate_limit(self, calls_per_minute: int):
            self.config['rate_limit'] = calls_per_minute
            return self
       
        def build(self) -> AdvancedSDK:
            return AdvancedSDK(self.base_url, **self.config)

    Finally, we apply nest_asyncio to enable nested event loops in Colab, then run the demo via asyncio.run (with a fallback to manual loop execution if needed). It also introduces an SDKBuilder class that implements a fluent builder pattern for easily configuring and instantiating the AdvancedSDK with custom authentication and rate-limit settings.

    In conclusion, this SDK tutorial provides a scalable foundation for any RESTful integration, combining modern Python idioms (dataclasses, async/await, context managers) with practical tooling (rate limiter, cache, structured logging). By adapting the patterns shown here, particularly the separation of concerns between request orchestration, caching, and response modeling, teams can accelerate the development of new API clients while ensuring predictability, observability, and resilience.


    Check out the Codes. All credit for this research goes to the researchers of this project. Also, feel free to follow us on Twitter and don’t forget to join our 100k+ ML SubReddit and Subscribe to our Newsletter.

    The post A Coding Guide to Build a Production-Ready Asynchronous Python SDK with Rate Limiting, In-Memory Caching, and Authentication appeared first on MarkTechPost.

    Source: Read More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleDigital Accessibility Is Rising: Here’s How APAC and LATAM Are Leading the Shift
    Next Article Sakana AI Introduces Reinforcement-Learned Teachers (RLTs): Efficiently Distilling Reasoning in LLMs Using Small-Scale Reinforcement Learning

    Related Posts

    Machine Learning

    How to Evaluate Jailbreak Methods: A Case Study with the StrongREJECT Benchmark

    September 3, 2025
    Machine Learning

    Announcing the new cluster creation experience for Amazon SageMaker HyperPod

    September 3, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    Canvas, meet code: Building Figma’s code layers

    Web Development

    ⚡ Weekly Recap: Chrome 0-Day, Data Wipers, Misused Tools and Zero-Click iPhone Attacks

    Development

    Uber finally launches feature to prioritize women’s safety

    News & Updates

    CVE-2024-7457 – Apple ws.stash.app macOS Privilege Escalation Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    Highlights

    CVE-2025-5404 – Chaitak-Gorai Blogbook GET Parameter Handler Denial of Service Vulnerability

    June 1, 2025

    CVE ID : CVE-2025-5404

    Published : June 1, 2025, 5:15 p.m. | 10 hours, 5 minutes ago

    Description : A vulnerability classified as problematic was found in chaitak-gorai Blogbook up to 92f5cf90f8a7e6566b576fe0952e14e1c6736513. This vulnerability affects unknown code of the file /search.php of the component GET Parameter Handler. The manipulation of the argument Search leads to denial of service. The exploit has been disclosed to the public and may be used. This product is using a rolling release to provide continious delivery. Therefore, no version details for affected nor updated releases are available. The vendor was contacted early about this disclosure but did not respond in any way.

    Severity: 4.3 | MEDIUM

    Visit the link for more details, such as CVSS details, affected products, timeline, and more…

    CVE-2025-9252 – Linksys RE6000 Series Stack-Based Buffer Overflow Vulnerability

    August 20, 2025

    Patch Tuesday, June 2025 Edition

    June 10, 2025

    CVE-2025-37730 – Logstash SSL Verification MitM Vulnerability

    May 6, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.