# request with auth
url = 'http://httpbin.org/basic-auth/user/passwd'
username = 'user'
password = 'passwd'

response = requests.get(url, auth=(username, password))
data = response.json()

print(data)
```

The simplicity and readability of `Requests` code compared to `urllib` is immediately apparent. `Requests` abstracts away much of the complexity, handling things like authentication headers and JSON responses with ease.

Some key features that made `Requests` the de facto standard include:

1. **Automatic content decoding**: `Requests` automatically decodes the response content based on the Content-Type header.
2. **Session persistence**: The `Session` object allows you to persist certain parameters across requests.
3. **Elegant error handling**: `Requests` raises intuitive exceptions for network problems and HTTP errors.
4. **Automatic decompression**: `Requests` automatically decompresses gzip-encoded responses.

However, as Python evolved and the use cases for Python expanded, new needs arose that `Requests` wasn't designed to address. In particular, Asynchronous rose as a need which led to the introduction of `asyncio` in Python 3.4.

## `AIOHTTP`: Built for Asyncio

[`AIOHTTP`](https://github.com/aio-libs/aiohttp), first released in October 2014, was one of the first libraries to fully embrace Python's asyncio framework. Designed from the ground up for asynchronous operations, it's an excellent choice for high-performance, concurrent applications. Today, `AIOHTTP` is widely used, with around [six million downloads per day](https://web.archive.org/web/20250510011435/https://pypistats.org/packages/aiohttp) as of May 2024.

`AIOHTTP` has several key features that set it apart from Requests:

1. **Purely asynchronous**: All operations in `AIOHTTP` are async, allowing for efficient handling of many concurrent connections.
2. **Both client and server**: `AIOHTTP` can be used to create both HTTP clients and servers.
3. **WebSocket support**: It offers full support for WebSocket connections.

Install `AIOHTTP` using pip:

```bash
pip install aiohttp
```

Here's a basic example of using `AIOHTTP`:

```python filename="aiohttp_basic.py"
import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    async with aiohttp.ClientSession() as session:
        html = await fetch(session, 'https://api.github.com')
        print(html)

asyncio.run(main())
```

To really test `AIOHTTP`'s capabilities, you need to run multiple requests concurrently. Here's an example that fetches multiple URLs concurrently:

```python filename="aiohttp_multiple.py"
import asyncio
import aiohttp
import time

urls = [
    "https://httpbin.org/delay/1",
    "https://httpbin.org/delay/2",
    "https://httpbin.org/delay/3",
    "https://httpbin.org/delay/1",
    "https://httpbin.org/delay/2",
]

async def fetch(session, url, i):
    try:
        start_time = time.perf_counter()
        async with session.get(url) as response:
            await response.text()
            elapsed = time.perf_counter() - start_time
            print(f"Request {i} completed in {elapsed:.2f}s")
    except asyncio.TimeoutError:
        print(f"Request {i} timed out")

async def async_requests():
    start_time = time.perf_counter()
    async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) as session:
        tasks = [fetch(session, url, i) for i, url in enumerate(urls, 1)]
        await asyncio.gather(*tasks)

    total_time = time.perf_counter() - start_time
    print(f"\nTotal time: {total_time:.2f}s")

if __name__ == "__main__":
    asyncio.run(async_requests())
```

In this example, we're fetching five URLs concurrently, each with a different server-side delay. The script will output the time taken for each request to complete, as well as the total time taken:

```bash filename="output"
Request 1 completed in 2.22s
Request 4 completed in 2.22s
Request 5 completed in 3.20s
Request 2 completed in 3.20s
Request 3 completed in 4.30s

Total time: 4.31s
```

For comparison, here's how you might achieve the same thing using Requests:

```python filename="requests_multiple.py"
import requests
import time

urls = [
    "https://httpbin.org/delay/1",
    "https://httpbin.org/delay/2",
    "https://httpbin.org/delay/3",
    "https://httpbin.org/delay/1",
    "https://httpbin.org/delay/2",
]

def sync_requests():
    start_time = time.time()
    with requests.Session() as session:
        session.timeout = 10.0
        for i, url in enumerate(urls, 1):
            try:
                response = session.get(url)
                print(f"Request {i} completed in {response.elapsed.total_seconds():.2f}s")
            except requests.Timeout:
                print(f"Request {i} timed out")

    total_time = time.time() - start_time
    print(f"\nTotal time: {total_time:.2f}s")

if __name__ == "__main__":
    sync_requests()
```

The output will be similar to the `AIOHTTP` example, but the total time taken will be significantly longer due to the synchronous nature of Requests.

```bash filename="output"
Request 1 completed in 3.04s
Request 2 completed in 2.57s
Request 3 completed in 3.26s
Request 4 completed in 1.23s
Request 5 completed in 2.49s

Total time: 12.61s
```

As you can see, `AIOHTTP`'s asynchronous nature allows it to complete all requests in roughly the time it takes to complete the slowest request, while Requests waits for each request to complete sequentially.

While `AIOHTTP` is a powerful library for asynchronous operations, it doesn't provide a synchronous API like Requests. This is where HTTPX comes in.

## `HTTPX`: The Best of Both Worlds

[HTTPX](https://github.com/encode/httpx), released by Tom Christie (the author of Django REST framework) in August 2019, aims to combine the best features of Requests and `AIOHTTP`. It provides a synchronous API similar to Requests but also supports asynchronous operations.

Key features of `HTTPX` include:

1. **Familiar Requests-like API**: `HTTPX` maintains a similar API to Requests, making it easy for developers to transition.
2. **Both sync and async support**: Unlike Requests or `AIOHTTP`, `HTTPX` supports both synchronous and asynchronous operations.
3. **HTTP/2 support**: `HTTPX` natively supports HTTP/2, allowing for more efficient communication with modern web servers.
4. **Type annotations**: `HTTPX` is fully type-annotated, which improves IDE support and helps catch errors early.

To install `HTTPX`, use pip:

```bash
pip install httpx
```

Here's a basic example of using `HTTPX` synchronously:

```python filename="httpx_sync.py"
import httpx

response = httpx.get('https://api.github.com')
print(response.status_code)
print(response.json())
```

The code is almost identical to the Requests example, making it easy to switch between the two libraries. However, HTTPX also supports asynchronous operations:

```python filename="httpx_async.py"
import asyncio
import httpx
import time

urls = [
    "https://httpbin.org/delay/1",
    "https://httpbin.org/delay/2",
    "https://httpbin.org/delay/3",
    "https://httpbin.org/delay/1",
    "https://httpbin.org/delay/2",
]

async def fetch(client, url, i):
    response = await client.get(url)
    print(f"Request {i} completed in {response.elapsed.total_seconds():.2f}s")

async def async_requests():
    start_time = time.time()
    async with httpx.AsyncClient(timeout=10.0) as client:
        tasks = [fetch(client, url, i) for i, url in enumerate(urls, 1)]
        await asyncio.gather(*tasks)

    total_time = time.time() - start_time
    print(f"\nTotal time: {total_time:.2f}s")

if __name__ == "__main__":
    asyncio.run(async_requests())
```

The output will be similar to the AIOHTTP example:

```bash filename="output"
Request 1 completed in 1.96s
Request 4 completed in 1.96s
Request 5 completed in 3.00s
Request 2 completed in 3.30s
Request 3 completed in 4.42s

Total time: 4.44s
```

HTTPX's ability to switch seamlessly between synchronous and asynchronous operations makes it a versatile choice for a wide range of applications. It's especially useful when you need to interact with both synchronous and asynchronous code within the same project.

This brings us to the question: which library should you choose for your next Python project? That depends on your specific requirements.

## Choosing the Right HTTP Client for Your Project

Here's a quick comparison of the key features of Requests, AIOHTTP, and HTTPX:

<Table
  title="HTTP Client Feature Comparison"
  columns={[
    { key: "feature", header: "Feature / Characteristic" },
    { key: "requests", header: "Requests" },
    { key: "aiohttp", header: "AIOHTTP" },
    { key: "httpx", header: "HTTPX" }
  ]}
  data={[
    { feature: "Synchronous operations", requests: "✅", aiohttp: "❌", httpx: "✅" },
    { feature: "Asynchronous operations", requests: "❌", aiohttp: "✅", httpx: "✅" },
    { feature: "Built-in HTTP/2 support", requests: "❌", aiohttp: "❌", httpx: "✅" },
    { feature: "WebSocket support", requests: "❌", aiohttp: "✅", httpx: "Via addon" },
    { feature: "Type hints", requests: "Partial", aiohttp: "✅", httpx: "✅" },
    { feature: "Retries with backoff", requests: "Via addon", aiohttp: "✅", httpx: "✅" },
    { feature: "SOCKS proxies", requests: "Via addon", aiohttp: "Via addon", httpx: "✅" },
    { feature: "Event hooks", requests: "✅", aiohttp: "❌", httpx: "✅" },
    { feature: "Brotli support", requests: "Via addon", aiohttp: "✅", httpx: "✅" },
    { feature: "Asynchronous DNS lookup", requests: "❌", aiohttp: "✅", httpx: "✅" }
  ]}
  rowKeyField="feature"
/>

## Recommendations

1. If you're working on a simple script or a project that doesn't require asynchronous operations, stick with **`Requests`**. Its simplicity and wide adoption make it an excellent choice for straightforward HTTP tasks.

2. For high-performance asyncio applications, especially those dealing with many concurrent connections or requiring WebSocket support, **`AIOHTTP`** is your best bet. It's particularly well-suited for building scalable web services.

3. If you need the flexibility to use both synchronous and asynchronous code, or if you're looking to future-proof your application with `HTTP/2` support, go with **`HTTPX`**. It's also a great choice if you're familiar with `Requests` but want to start incorporating async operations into your project.

## How Speakeasy Uses `HTTPX`

When creating Python SDKs, Speakeasy includes HTTPX as the default HTTP client. This choice allows developers to use our SDKs for synchronous and asynchronous operations.

For example, here's how you might use the [Mistral Python SDK](https://github.com/mistralai/client-python) created by Speakeasy to make requests.

First, install the SDK:

```bash
pip install mistralai
```

Set your [Mistral API key](https://console.mistral.ai/api-keys/) as an environment variable:

```bash
export MISTRAL_API_KEY="your-api-key"
```

Here's how you might use the SDK to make a synchronous request:

```python filename="mistral_sync.py"
from mistralai import Mistral
import os

s = Mistral(
    api_key=os.getenv("MISTRAL_API_KEY", ""),
)

res = s.chat.complete(model="mistral-small-latest", messages=[
    {
        "content": "Who is the best French painter? Answer in one short sentence.",
        "role": "user",
    },
])

if res is not None and res.choices:
    print(res.choices[0].message.content)
```

And here's the same SDK and request using the asynchronous API:

```python filename="mistral_async.py"
import asyncio
from mistralai import Mistral
import os

async def main():
    s = Mistral(
        api_key=os.getenv("MISTRAL_API_KEY", ""),
    )
    res = await s.chat.complete_async(model="mistral-small-latest", messages=[
        {
            "content": "Who is the best French painter? Answer in one short sentence.",
            "role": "user",
        },
    ])
    if res is not None:
        print(res.choices[0].message.content)

asyncio.run(main())
```

Note how the asynchronous version uses the `_async` suffix for the method name, but otherwise the code is almost identical. This consistency makes it easy to switch between synchronous and asynchronous operations as needed.

You'll also notice that there is no need to instantiate a different client object for the asynchronous version. SDKs created by Speakeasy allow developers to use the same client object for both synchronous and asynchronous operations. By abstracting away the differences between the modes of operation in HTTPX, Speakeasy reduces boilerplate code and makes your SDKs more user-friendly.

To illustrate the value of mixing synchronous and asynchronous operations, consider a scenario where you need to make a synchronous request to fetch some data, then use that data to make multiple asynchronous requests. HTTPX's unified API makes this kind of mixed-mode operation straightforward.

```python filename="mistral_mixed.py"
import asyncio
from mistralai import Mistral
import os