A very fast, lightweight Python asyncio HTTP/1.1, HTTP/2, and WebSocket client.
The repository is hosted on GitHub.
For full documentation, please see aiosonic docs.
- Keepalive support and smart pool of connections
- Multipart file uploads
- Handling of chunked responses and requests
- Connection timeouts and automatic decompression
- Automatic redirect following
- Fully type-annotated
- WebSocket support
- HTTP proxy support
- Sessions with cookie persistence
- Elegant key/value cookies
- (Nearly) 100% test coverage
- HTTP/2 (BETA; enabled with a flag)
- Python >= 3.8 (or PyPy 3.8+)
pip install aiosonicBelow is an example demonstrating basic HTTP client usage:
import asyncio
import aiosonic
import json
async def run():
    client = aiosonic.HTTPClient()
    # Sample GET request
    response = await client.get('https://www.google.com/')
    assert response.status_code == 200
    assert 'Google' in (await response.text())
    # POST data as multipart form
    url = "https://postman-echo.com/post"
    posted_data = {'foo': 'bar'}
    response = await client.post(url, data=posted_data)
    assert response.status_code == 200
    data = json.loads(await response.content())
    assert data['form'] == posted_data
    # POST data as JSON
    response = await client.post(url, json=posted_data)
    assert response.status_code == 200
    data = json.loads(await response.content())
    assert data['json'] == posted_data
    # GET request with timeouts
    from aiosonic.timeout import Timeouts
    timeouts = Timeouts(sock_read=10, sock_connect=3)
    response = await client.get('https://www.google.com/', timeouts=timeouts)
    assert response.status_code == 200
    assert 'Google' in (await response.text())
    print('HTTP client success')
if __name__ == '__main__':
    asyncio.run(run())Below is an example demonstrating how to use aiosonic's WebSocket support:
import asyncio
from aiosonic import WebSocketClient
async def main():
    # Replace with your WebSocket server URL
    ws_url = "ws://localhost:8080"
    async with WebSocketClient() as client:
        async with await client.connect(ws_url) as ws:
            # Send a text message
            await ws.send_text("Hello WebSocket")
            
            # Receive an echo response
            response = await ws.receive_text()
            print("Received:", response)
            
            # Send a ping and wait for the pong
            await ws.ping(b"keep-alive")
            pong = await ws.receive_pong()
            print("Pong received:", pong)
            # You can have a "reader" task like this:
            async def ws_reader(conn):
                async for msg in conn:
                    # handle the message...
                    # msg is an instance of aiosonic.web_socket_client.Message dataclass.
                    pass
            asyncio.create_task(ws_reader(ws))
            
            # Gracefully close the connection (optional)
            await ws.close(code=1000, reason="Normal closure")
if __name__ == "__main__":
    asyncio.run(main())You can easily wrap apis with AioSonicBaseClient class
import asyncio
import json
from aiosonic.base_client import AioSonicBaseClient
class GitHubAPI(AioSonicBaseClient):
    base_url = "https://api.github.com"
    default_headers = {
        "Accept": "application/vnd.github+json",
        "X-GitHub-Api-Version": "2022-11-28",
        # "Authorization": "Bearer YOUR_GITHUB_TOKEN",
    }
    async def users(self, username: str, **kwargs):
        # base_url and headers are applied internally.
        return await self.get(f"/users/{username}", **kwargs)
    
    async def update_repo(self, owner: str, repo: str, description: str):
        data = {
            "name": repo,
            "description": description,
        }
        return await self.put(f"/repos/{owner}/{repo}", json=data)
async def main():
    # You can pass an existing aiosonic.HTTPClient() instance in the constructor.
    # If not provided, AioSonicBaseClient will create a new instance automatically.
    github = GitHubAPI()
    # Call the custom 'users' method to get data for user "sonic182"
    user_data = await github.users("sonic182")
    print(json.dumps(user_data, indent=2))
if __name__ == '__main__':
    asyncio.run(main())Note: You may wanna do a singleton of your clients implementations in order to reuse the internal HTTPClient instance, and it's pool of connections (efficient usage of the client), an example:
class SingletonMixin:
    _instances = {}
    def __new__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__new__(cls)
        return cls._instances[cls]
class GitHubAPI(AioSonicBaseClient, SingletonMixin):
    base_url = "https://api.github.com"
    # ... the rest of the code
# now, each instance of the class will be the first created
gh = GitHubAPI()
g2 = GitHubAPI()
gh == gh2A simple performance benchmark script is included in the tests folder. For example:
python scripts/performance.pyExample output:
{
  "aiohttp": "5000 requests in 558.31 ms",
  "aiosonic": "5000 requests in 563.95 ms",
  "requests": "5000 requests in 10306.90 ms",
  "aiosonic_cyclic": "5000 requests in 642.15 ms",
  "httpx": "5000 requests in 7920.04 ms"
}aiosonic is 1457.99% faster than requests aiosonic is -1.38% faster than aiosonic cyclic
Note:
These benchmarks are basic and machine-dependent. They are intended as a rough comparison.
- HTTP/2:
- Stable HTTP/2 release
 
- Better documentation
- International domains and URLs (IDNA + cache)
- Basic/Digest authentication
Install development dependencies with Poetry:
poetry installIt is recommended to install Poetry in a separate virtual environment (via apt, pacman, etc.) rather than in your development environment. You can configure Poetry to use an in-project virtual environment by running:
poetry config virtualenvs.in-project truepoetry run pytest- Fork the repository.
- Create a branch named feature/your_feature.
- Commit your changes, push, and submit a pull request.
Thanks for contributing!