HTTP Client

HttpClient is the core interface of lightreq. It wraps an adapter and an optional middleware chain into a single unified client for making HTTP requests.

from src.client import HttpClient
from src.middleware import logging, retry

client = HttpClient(
    adapter="requests",
    middleware=[logging(), retry(retries=3)]
)

response = client.get("https://api.example.com/data")
client.close()

Constructor

HttpClient(adapter="urllib", middleware=None, headers=None, **config)

Parameters:

Parameter

Type

Description

adapter

str

Adapter to use. One of "urllib" or "requests". Defaults to "urllib".

middleware

list

Ordered list of middleware callables. Defaults to None.

headers

dict

Default headers applied to all requests. Overridden by per-request headers passed to HTTP methods. Defaults to None.

config

any

Additional configuration passed through to the underlying adapter.

Raises: ValueError if the specified adapter name is not recognized.

# raises ValueError: Adapter httpx not found
client = HttpClient(adapter="httpx")

Note

When the timeout middleware is present in the middleware list, HttpClient automatically suppresses the adapter’s own timeout parameter to prevent a double-timer conflict. See Middleware for details.


HTTP Methods

All HTTP methods accept a url positional argument and optional **kwargs such as headers, data, and timeout. They all return a Response object. Per-request headers are merged with default headers set in the HttpClient constructor, where per-request values take precedence over defaults for matching keys.

get()

Send a GET request. Used to retrieve a resource without side effects.

response = client.get("https://api.example.com/todos/1")
response = client.get("https://api.example.com/todos/1", headers={"Accept": "application/json"})

Returns: Response


post()

Send a POST request. Used to create a new resource.

response = client.post(
    "https://api.example.com/todos",
    data={"title": "Buy milk", "completed": False}
)

Returns: Response


put()

Send a PUT request. Used to replace an existing resource entirely.

response = client.put(
    "https://api.example.com/todos/1",
    data={"title": "Buy milk", "completed": True}
)

Returns: Response


patch()

Send a PATCH request. Used to partially update an existing resource.

response = client.patch(
    "https://api.example.com/todos/1",
    data={"completed": True}
)

Returns: Response


delete()

Send a DELETE request. Used to remove a resource.

response = client.delete("https://api.example.com/todos/1")

Returns: Response



options()

Send an OPTIONS request. Used to query which HTTP methods are supported by a resource.

response = client.options("https://api.example.com/todos")

print(response.headers.get("Allow"))  # e.g. "GET, POST, HEAD, OPTIONS"

Returns: Response


close()

Closes the client and releases all underlying adapter resources, such as connection pools.

client.close()

Should be called when the client is no longer needed, especially when using the "requests" adapter which maintains a persistent connection pool.

# recommended pattern — always close the client after use
try:
    response = client.get("https://api.example.com/data")
    response.raise_for_status()
finally:
    client.close()

Note

Not all adapters require explicit cleanup. close() checks whether the underlying adapter supports it before attempting to close. It is safe to call on any adapter.


Usage Patterns

Basic request with error handling

client = HttpClient(adapter="requests")

try:
    response = client.get("https://api.example.com/data")
    response.raise_for_status()
    print(response.json())
except Exception as e:
    print("Request failed:", e)
finally:
    client.close()

Passing custom headers

Headers can be configured at two levels:

  1. Default headers: Set via the headers parameter in the HttpClient constructor, applied to all requests.

  2. Per-request headers: Passed as headers in **kwargs of any HTTP method. Merged with default headers, with per-request values overriding defaults for matching keys (not replacing all defaults).

from src.client import HttpClient

# Set default headers for all requests
client = HttpClient(
    adapter="requests",
    headers={"User-Agent": "lightreq/1.0", "Accept": "application/json"}
)

# Per-request headers: adds Authorization, overrides Accept for this request only
response = client.get(
    "https://api.example.com/protected",
    headers={"Authorization": "Bearer <token>", "Accept": "application/xml"}
)

# Default headers are still used for subsequent requests
client.get("https://api.example.com/data")  # Uses default User-Agent and Accept

Sending JSON data

response = client.post(
    "https://api.example.com/todos",
    data={"title": "Buy milk", "completed": False},
    headers={"Content-Type": "application/json"}
)

print(response.status_code)  # 201
print(response.json())

Using with middleware

For a full guide on available middlewares, ordering rules, and cookbook recipes, see Middleware.

from src.middleware import logging, retry, timeout, rate_limit

client = HttpClient(
    adapter="requests",
    middleware=[
        logging(),
        rate_limit(calls=10, period=1.0),
        retry(retries=3, delay=0.5),
        timeout(seconds=5.0),
    ]
)