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 to use. One of |
|
|
Ordered list of middleware callables. Defaults to |
|
|
Default headers applied to all requests. Overridden by per-request |
|
|
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
head()
Send a HEAD request. Returns the same headers as a GET request but with no response body.
Useful for checking resource existence or retrieving metadata such as Content-Length
without downloading the full body.
response = client.head("https://api.example.com/todos/1")
print(response.status_code) # 200
print(response.headers["content-length"]) # e.g. "83"
print(response.body) # b'' (always empty for HEAD)
Returns: Response — body is always b"", headers are preserved.
Note
Per the HTTP/1.1 specification, HEAD responses must not include a body. lightreq
enforces this by stripping the body before returning the response, while preserving
all headers including Content-Length.
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:
Default headers: Set via the
headersparameter in theHttpClientconstructor, applied to all requests.Per-request headers: Passed as
headersin**kwargsof 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),
]
)