I've been grokking some of my sample async code and I'm struggling to understand the flow of the data and how often I find myself awaiting.
Am I using await correctly? Also - are my output types correct? I'd imagine some these should be outputting coroutines (but my Pyright/Pylance type hinting keeps telling me that's wrong...)
The functions that require inspection are governed requests, send, governed_send, and governed_sendall. Maybe I'm not understanding what await is doing completely - and how this interacts with function scopes.
The code works correctly; I'd like to understand why and whether I can optimize out some of these awaits or if that's unnecessary.
import time import asyncio import httpx from typing import Any, Callable, Coroutine, Dict, List from rich import print class GovernedClient: def __init__(self): self.client = httpx.AsyncClient() self.loop = asyncio.get_event_loop() self.semaphore = asyncio.BoundedSemaphore(4) def __del__(self) -> None: # Define a destructor that closes the client and the loop. async def close(self: GovernedClient) -> None: # An async function is required to await aclose await self.client.aclose() self.loop.run_until_complete(close(self)) self.loop.close() def govern_requests(fn: Callable) -> Callable: # govern_requests applies semaphore locking to a given callable. async def call(self, *args, **kwargs): async with self.semaphore: return await fn(self, *args, **kwargs) return call async def send(self, method: str, url: str) -> httpx.Response: # A single send. request = httpx.Request(method, url, headers={'accept': 'application/json'}) return await self.client.send(request) @govern_requests async def governed_send(self, method: str, url: str) -> httpx.Response: # Applies semaphore locking via decorator. return await self.send(method, url) def governed_sendall(self, urls: List[str]) -> List[httpx.Response]: async def goverened_sendall(urls: List[str]) -> List[httpx.Response]: start = time.time() awaitables = [self.governed_send('GET', url) for url in urls] responses = [] for response in asyncio.as_completed(awaitables): responses.append(await response) print('Total time: ', int(1000 * (time.time() - start))) return responses return self.loop.run_until_complete(goverened_sendall(urls)) if __name__ == '__main__': URL = 'https://www.icanhazdadjoke.com' bc = GovernedClient() urls = [URL for url in range(20)] responses = bc.governed_sendall(urls) for response in responses: print(response.json())