Skip to main content

snowland-http

PyPI version PyPI downloads License Python CI

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), and stdlib (sync, built on the Python standard library urllib — requires no third-party install). Select via backend= or use backend="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 async acquire_async().
  • Parallel requests: thread pool (ThreadPoolExecutor) for sync, and asyncio.gather + Semaphore for 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 items may be a dict ({"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 to False to raise immediately.

Rate limiting

RateLimitConfig(max_rate, burst)

  • max_rate: maximum requests per second. max_rate <= 0 disables 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 charset declared in the Content-Type header wins (e.g. text/html; charset=gbk);
  • when no charset is present, the default is ISO-8859-1 (latin-1) per RFC 7231;
  • decoding is strict (no silent errors="replace"): an invalid body raises UnicodeDecodeError so mojibake is never hidden.

You can override the resolution by passing encoding= when constructing a response (used internally by the backends).

Backend constraints

  • requests supports sync APIs only (calling request_async raises AsyncRequiredError).
  • aiohttp supports async APIs only (calling request raises AsyncRequiredError).
  • httpx supports both.
  • stdlib (urllib) supports sync APIs only (calling request_async raises AsyncRequiredError).

Development & CI

  • Tests run on master and dev branches (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 the PYPI_API_TOKEN repository 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

snowland_http-0.2.0.tar.gz (18.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

snowland_http-0.2.0-py3-none-any.whl (17.6 kB view details)

Uploaded Python 3

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

Hashes for snowland_http-0.2.0.tar.gz
Algorithm Hash digest
SHA256 65d04f7a089e2a6d8566d96a866312c81ca74487fda17a5ec03b6d318d432456
MD5 36369222179b4f4b889c2fa7f0337a7a
BLAKE2b-256 84cfd9aa67874690a5560d238e9f8522a98beca0d6f4f334365a12b35f069cb8

See more details on using hashes here.

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

Hashes for snowland_http-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 adf44d59253713d054c3a1e03ac86f91fd770dedd9aea66e7cbbf6f8aa2ee320
MD5 d16635dc129547828bfec157d0183002
BLAKE2b-256 a4ba7b8503e6cd6efd8665f352a042a7da52388617afe938a9473373d5e95408

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page