snowland-http
A rate-limited, parallel HTTP client with pluggable requests / httpx / aiohttp / zero-dependency stdlib (urllib) backends.
Features
- Pluggable transport:
requests(sync only),httpx(sync + async),aiohttp(async only), andstdlib(sync, built on the Python standard libraryurllib— requires no third-party install). Select viabackend=or usebackend="auto"to auto-detect (preference: httpx > aiohttp > requests > stdlib). - Global rate limiting: a token-bucket limiter shared by all parallel workers, so the aggregate request rate never exceeds the configured ceiling. Provides both a blocking
acquire()and an asyncacquire_async(). - Parallel requests: thread pool (
ThreadPoolExecutor) for sync, andasyncio.gather+Semaphorefor async. - Connection lifecycle: explicit
open()/close()(and async counterparts), with context-manager support that opens on enter and closes on exit.
Installation
The three third-party transports (requests / httpx / aiohttp) are optional dependencies, independent of each other — none is required for the package to import (backends are imported lazily). When none is installed, the client automatically falls back to the built-in stdlib backend (pure urllib), so it works in a bare Python environment with zero installs. Install at least one third-party transport to use the corresponding backend:
# Option A: install a transport library directly
pip install requests # or httpx / aiohttp — install at least one
# Option B: install via extras (recommended)
pip install ".[requests]" # sync backend only
pip install ".[httpx]" # sync + async backend (recommended)
pip install ".[aiohttp]" # async backend only
pip install ".[all]" # everything
# ".[stdlib]" is a no-op extra: it documents the always-available urllib backend.
With backend="auto", the client detects installed libraries in the order httpx > aiohttp > requests, and finally falls back to stdlib (no install needed).
Quick start
Sync + rate limiting + parallel
from snowland_http import HttpClient, RateLimitConfig
client = HttpClient(
backend="requests",
rate_limit=RateLimitConfig(max_rate=5, burst=2), # <=5 req/s, burst of 2
)
resp = client.get("https://example.com")
print(resp.status_code, resp.json())
# parallel GET
results = client.get_many(["https://example.com/1", "https://example.com/2"])
for r in results:
print(r if isinstance(r, Exception) else r.status_code)
Async + rate limiting + parallel
import asyncio
from snowland_http import HttpClient, RateLimitConfig
async def main():
client = HttpClient(
backend="httpx",
rate_limit=RateLimitConfig(max_rate=10, burst=5),
)
async with client: # open_async on enter, close_async on exit
results = await client.get_many_async(["https://example.com/1", "https://example.com/2"])
for r in results:
print(r.status_code)
asyncio.run(main())
Zero-dependency (stdlib / urllib)
No third-party package needed — works with a stock Python:
pip install snowland-http # nothing else required
from snowland_http import HttpClient
# backend="auto" falls back to stdlib when requests/httpx/aiohttp are absent,
# or pick it explicitly:
client = HttpClient(backend="stdlib")
resp = client.get("https://example.com")
print(resp.status_code, resp.text)
API
HttpClient(backend="auto", rate_limit=None, max_workers=10, max_concurrency=10)
The backend argument accepts "requests", "httpx", "aiohttp", "stdlib" (or "urllib"), or "auto". With "auto" the client prefers httpx > aiohttp > requests and finally falls back to the dependency-free stdlib backend.
| Method | Description |
|---|---|
request(method, url, **kwargs) |
Single sync request |
get/post/put/delete/head/patch(url, **kwargs) |
Sync convenience methods |
request_many(items, max_workers, return_exceptions) |
Sync parallel (thread pool) |
get_many(urls, method="GET", ...) |
Sync parallel GET |
request_async(method, url, **kwargs) |
Single async request |
get_async/... |
Async convenience methods |
request_many_async(items, max_concurrency, return_exceptions) |
Async parallel |
get_many_async(urls, ...) |
Async parallel GET |
open() / open_async() |
Open / establish connection resources |
close() / close_async() |
Close connection resources |
- Each element of
itemsmay be adict({"method": ..., "url": ..., ...}) or a(method, url, kwargs_dict)tuple. - Parallel methods default to
return_exceptions=True: a single failure is returned as an exception object in the result list rather than aborting the rest. Set it toFalseto raise immediately.
Rate limiting
RateLimitConfig(max_rate, burst)
max_rate: maximum requests per second.max_rate <= 0disables rate limiting entirely (the limiter becomes a no-op and never blocks); it does not raise.burst: how many requests may be sent back-to-back before smoothing kicks in.
Response encoding
HttpResponse.text is decoded according to the HTTP rules, not a hard-coded UTF-8:
- the
charsetdeclared in theContent-Typeheader wins (e.g.text/html; charset=gbk); - when no
charsetis present, the default is ISO-8859-1 (latin-1) per RFC 7231; - decoding is strict (no silent
errors="replace"): an invalid body raisesUnicodeDecodeErrorso mojibake is never hidden.
You can override the resolution by passing encoding= when constructing a response (used internally by the backends).
Backend constraints
requestssupports sync APIs only (callingrequest_asyncraisesAsyncRequiredError).aiohttpsupports async APIs only (callingrequestraisesAsyncRequiredError).httpxsupports both.stdlib(urllib) supports sync APIs only (callingrequest_asyncraisesAsyncRequiredError).
Development & CI
- Tests run on
masteranddevbranches (see.github/workflows/test.yml), across Python 3.8–3.12, installing.[all]so functional/parallel tests execute. - Publishing to PyPI happens on GitHub Release (
release: published) via.github/workflows/release.yml, authenticating with thePYPI_API_TOKENrepository secret.
Run the test suite locally:
pip install -e ".[all]"
python -m unittest discover -s tests -v
License
BSD 3-Clause. See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file snowland_http-0.2.0.tar.gz.
File metadata
- Download URL: snowland_http-0.2.0.tar.gz
- Upload date:
- Size: 18.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
65d04f7a089e2a6d8566d96a866312c81ca74487fda17a5ec03b6d318d432456
|
|
| MD5 |
36369222179b4f4b889c2fa7f0337a7a
|
|
| BLAKE2b-256 |
84cfd9aa67874690a5560d238e9f8522a98beca0d6f4f334365a12b35f069cb8
|
File details
Details for the file snowland_http-0.2.0-py3-none-any.whl.
File metadata
- Download URL: snowland_http-0.2.0-py3-none-any.whl
- Upload date:
- Size: 17.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
adf44d59253713d054c3a1e03ac86f91fd770dedd9aea66e7cbbf6f8aa2ee320
|
|
| MD5 |
d16635dc129547828bfec157d0183002
|
|
| BLAKE2b-256 |
a4ba7b8503e6cd6efd8665f352a042a7da52388617afe938a9473373d5e95408
|