Skip to main content

⚡ High-performance async HTTP client in Rust with Python bindings for blazing-fast batch requests.

Project description

rusty-req

A high-performance asynchronous request library based on Rust and Python, suitable for scenarios that require high-throughput concurrent HTTP requests. It implements the core concurrent logic in Rust and packages it into a Python module using PyO3 and maturin, combining Rust's performance with Python's ease of use.

🚀 Features

  • Dual Request Modes: Supports both batch concurrent requests (fetch_requests) and single asynchronous requests (fetch_single).
  • High Performance: Built with Rust, Tokio, and a shared reqwest client for maximum throughput.
  • Highly Customizable: Allows custom headers, parameters/body, per-request timeouts, and tags.
  • Flexible Concurrency Modes: Choose between SELECT_ALL (default, get results as they complete) and JOIN_ALL (wait for all requests to finish) to fit your use case.
  • Smart Response Handling: Automatically decompresses gzip, brotli, and deflate encoded responses.
  • Global Timeout Control: Use total_timeout in batch requests to prevent hangs.
  • Detailed Results: Each response includes the HTTP status, body, metadata (like processing time), and any exceptions.
  • Debug Mode: An optional debug mode (set_debug(True)) prints detailed request/response information.

🔧 Installation

pip install rusty-req

Or build from source:

# This will compile the Rust code and create a .whl file
maturin build --release

# Install from the generated wheel
pip install target/wheels/rusty_req-*.whl

Development & Debugging

cargo watch -s "maturin develop"

📦 Example Usage

1. Fetching a Single Request (fetch_single)

Perfect for making a single asynchronous call and awaiting its result.

import asyncio
import pprint
import rusty_req

async def single_request_example():
    """Demonstrates how to use fetch_single for a POST request."""
    print("🚀 Fetching a single POST request to httpbin.org...")

    # Enable debug mode to see detailed logs in the console
    rusty_req.set_debug(True)

    response = await rusty_req.fetch_single(
        url="https://httpbin.org/post",
        method="POST",
        params={"user_id": 123, "source": "example"},
        headers={"X-Client-Version": "1.0"},
        tag="my-single-post"
    )

    print("\n✅ Request finished. Response:")
    pprint.pprint(response)

if __name__ == "__main__":
    asyncio.run(single_request_example())

2. Fetching Batch Requests (fetch_requests)

The core feature for handling a large number of requests concurrently. This example simulates a simple load test.

import asyncio
import time
import rusty_req
from rusty_req import ConcurrencyMode

async def batch_requests_example():
    """Demonstrates 100 concurrent requests with a global timeout."""
    requests = [
        rusty_req.RequestItem(
            url="https://httpbin.org/delay/2",  # This endpoint waits 2 seconds
            method="GET",
            timeout=2.9,  # Per-request timeout, should succeed
            tag=f"test-req-{i}",
        )
        for i in range(100)
    ]

    # Disable debug logs for cleaner output
    rusty_req.set_debug(False)

    print("🚀 Starting 100 concurrent requests...")
    start_time = time.perf_counter()

    # Set a global timeout of 3.0 seconds. Some requests will be cut off.
    responses = await rusty_req.fetch_requests(
        requests,
        total_timeout=3.0,
        mode=ConcurrencyMode.SELECT_ALL # Explicitly use SELECT_ALL mode
    )

    total_time = time.perf_counter() - start_time

    # --- Process results ---
    success_count = 0
    failed_count = 0
    for r in responses:
        # Check the 'exception' field to see if the request was successful
        if r.get("exception") and r["exception"].get("type"):
            failed_count += 1
        else:
            success_count += 1

    print("\n📊 Load Test Summary:")
    print(f"⏱️  Total time taken: {total_time:.2f}s")
    print(f"✅ Successful requests: {success_count}")
    print(f"⚠️ Failed or timed-out requests: {failed_count}")

if __name__ == "__main__":
    asyncio.run(batch_requests_example())

3. Understanding Concurrency Modes (SELECT_ALL vs JOIN_ALL)

The fetch_requests function supports two powerful concurrency strategies. Choosing the right one is key to building robust applications.

  • ConcurrencyMode.SELECT_ALL (Default): Best-Effort Collector This mode operates on a "first come, first served" or "best-effort" basis. It aims to collect as many successful results as possible within the given total_timeout.

    • It returns results as soon as they complete.
    • If the total_timeout is reached, it gracefully returns all the requests that have already succeeded, while marking any still-pending requests as timed out.
    • A failure in one request does not affect others.
  • ConcurrencyMode.JOIN_ALL: Transactional (All-or-Nothing) This mode treats the entire batch of requests as a single, atomic transaction. It is much stricter.

    • It waits for all submitted requests to complete first.
    • It then inspects the results.
    • Success Case: Only if every single request was successful will it return the complete list of successful results.
    • Failure Case: If even one request fails for any reason (e.g., its individual timeout, a network error, or a non-2xx status code), this mode will discard all results and return a list where every request is marked as a global failure.

Quick Comparison

Aspect ConcurrencyMode.SELECT_ALL (Default) ConcurrencyMode.JOIN_ALL
Failure Handling Tolerant. One failure does not affect other successful requests. Strict / Atomic. One failure causes the entire batch to fail.
Primary Use Case Maximizing throughput; getting as much data as possible. Tasks that must succeed or fail as a single unit (e.g., transactions).
Result Order By completion time (fastest first). By original submission order.
"When do I get results?" As they complete, one by one. All at once, only after every request has finished and been validated.

Code Example

The example below clearly demonstrates the difference in behavior.

import asyncio
import rusty_req
from rusty_req import ConcurrencyMode

async def concurrency_modes_example():
    """Demonstrates the difference between SELECT_ALL and JOIN_ALL modes."""
    # Note: We are using an endpoint that returns 500 to force a failure.
    requests = [
        rusty_req.RequestItem(url="https://httpbin.org/delay/2", tag="should_succeed"),
        rusty_req.RequestItem(url="https://httpbin.org/status/500", tag="will_fail"),
        rusty_req.RequestItem(url="https://httpbin.org/delay/1", tag="should_also_succeed"),
    ]

    # --- 1. Test SELECT_ALL ---
    print("--- 🚀 Testing SELECT_ALL (Best-Effort) ---")
    results_select = await rusty_req.fetch_requests(
        requests,
        mode=ConcurrencyMode.SELECT_ALL,
        total_timeout=3.0
    )

    print("Results:")
    for res in results_select:
        tag = res.get("meta", {}).get("tag")
        status = res.get("http_status")
        err_type = res.get("exception", {}).get("type")
        print(f"  - Tag: {tag}, Status: {status}, Exception: {err_type}")

    print("\n" + "="*50 + "\n")

    # --- 2. Test JOIN_ALL ---
    print("--- 🚀 Testing JOIN_ALL (All-or-Nothing) ---")
    results_join = await rusty_req.fetch_requests(
        requests,
        mode=ConcurrencyMode.JOIN_ALL,
        total_timeout=3.0
    )

    print("Results:")
    for res in results_join:
        tag = res.get("meta", {}).get("tag")
        status = res.get("http_status")
        err_type = res.get("exception", {}).get("type")
        print(f"  - Tag: {tag}, Status: {status}, Exception: {err_type}")

if __name__ == "__main__":
    asyncio.run(concurrency_modes_example())

The expected output from the script above:

--- 🚀 Testing SELECT_ALL (Best-Effort) ---
Results:
  - Tag: should_also_succeed, Status: 200, Exception: None
  - Tag: will_fail, Status: 500, Exception: HttpStatusError
  - Tag: should_succeed, Status: 200, Exception: None

==================================================

--- 🚀 Testing JOIN_ALL (All-or-Nothing) ---
Results:
  - Tag: should_succeed, Status: 0, Exception: GlobalTimeout
  - Tag: will_fail, Status: 0, Exception: GlobalTimeout
  - Tag: should_also_succeed, Status: 0, Exception: GlobalTimeout

🧱 Data Structures

RequestItem Parameters

Field Type Required Description
url str The target URL.
method str The HTTP method.
params dict / None No For GET/DELETE, converted to URL query parameters. For POST/PUT/PATCH, sent as a JSON body.
headers dict / None No Custom HTTP headers.
timeout float Timeout for this individual request in seconds. Defaults to 30s.
tag str No An arbitrary tag to help identify or index the response.

fetch_requests Parameters

Field Type Required Description
requests List[RequestItem] A list of RequestItem objects to be executed concurrently.
total_timeout float No A global timeout in seconds for the entire batch operation.
mode ConcurrencyMode No The concurrency strategy. SELECT_ALL (default) for best-effort collection. JOIN_ALL for atomic (all-or-nothing) execution. See Section 3 for a detailed comparison.

Response Dictionary Format

Both fetch_single and fetch_requests return a dictionary (or a list of dictionaries) with a consistent structure.

Example of a successful response:

{
    "http_status": 200,
    "response": "{\"data\": \"...\", \"headers\": {\"...\"}}",
    "meta": {
        "process_time": "0.4531",
        "request_time": "2025-08-08 03:15:01 -> 2025-08-08 03:15:01",
        "tag": "my-single-post"
    },
    "exception": {}
}

Example of a failed response (e.g., timeout):

{
    "http_status": 0,
    "response": "",
    "meta": {
        "process_time": "3.0012",
        "request_time": "2025-08-08 03:15:05 -> 2025-08-08 03:15:08",
        "tag": "test-req-50"
    },
    "exception": {
        "type": "Timeout",
        "message": "Request timeout after 3.00 seconds"
    }
}

📄 License

This project is licensed under the MIT License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

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

rusty_req-0.3.0-cp312-cp312-win_amd64.whl (1.6 MB view details)

Uploaded CPython 3.12Windows x86-64

rusty_req-0.3.0-cp312-cp312-manylinux_2_28_x86_64.whl (3.7 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.28+ x86-64

rusty_req-0.3.0-cp312-cp312-macosx_10_12_universal2.whl (3.7 MB view details)

Uploaded CPython 3.12macOS 10.12+ universal2 (ARM64, x86-64)

rusty_req-0.3.0-cp311-cp311-win_amd64.whl (1.6 MB view details)

Uploaded CPython 3.11Windows x86-64

rusty_req-0.3.0-cp311-cp311-manylinux_2_28_x86_64.whl (3.7 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.28+ x86-64

rusty_req-0.3.0-cp311-cp311-macosx_10_12_universal2.whl (3.7 MB view details)

Uploaded CPython 3.11macOS 10.12+ universal2 (ARM64, x86-64)

rusty_req-0.3.0-cp310-cp310-win_amd64.whl (1.6 MB view details)

Uploaded CPython 3.10Windows x86-64

rusty_req-0.3.0-cp310-cp310-manylinux_2_28_x86_64.whl (3.7 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.28+ x86-64

rusty_req-0.3.0-cp310-cp310-macosx_10_12_universal2.whl (3.7 MB view details)

Uploaded CPython 3.10macOS 10.12+ universal2 (ARM64, x86-64)

rusty_req-0.3.0-cp39-cp39-win_amd64.whl (1.6 MB view details)

Uploaded CPython 3.9Windows x86-64

rusty_req-0.3.0-cp39-cp39-manylinux_2_28_x86_64.whl (3.7 MB view details)

Uploaded CPython 3.9manylinux: glibc 2.28+ x86-64

rusty_req-0.3.0-cp39-cp39-macosx_10_12_universal2.whl (3.7 MB view details)

Uploaded CPython 3.9macOS 10.12+ universal2 (ARM64, x86-64)

File details

Details for the file rusty_req-0.3.0-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: rusty_req-0.3.0-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 1.6 MB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for rusty_req-0.3.0-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 8198ad088b0f52752c54dafcc994f80ea5aa849dfcd740d7261296c445d8a2cf
MD5 0ec05f5c997d84958e3a0442ca789e85
BLAKE2b-256 aa74dac98bcf150379b00adf977712b3d23a18d1f805457f6b5d685fc9225be1

See more details on using hashes here.

File details

Details for the file rusty_req-0.3.0-cp312-cp312-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for rusty_req-0.3.0-cp312-cp312-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 166d440d0884376678ab6cf5a135e541e0149f8e3b4aa4d173c76d91aef46299
MD5 27dc08532d5fa5f5fa3f96d81fef0dd8
BLAKE2b-256 4ca9a42b83992179d54176c81389f46d82633dbf5633720f507e199eee459506

See more details on using hashes here.

File details

Details for the file rusty_req-0.3.0-cp312-cp312-macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for rusty_req-0.3.0-cp312-cp312-macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 491e4030bd0eb1c5ada3ebbe1b0b9147124a022072cf0b44418458fe872fc4da
MD5 b13e1d6ee4ad84c8a0d9091bbb1015fb
BLAKE2b-256 7649f78014e4a8e6621bd8e087dc5f2b31be26b732bfcede5be6446fa4b41918

See more details on using hashes here.

File details

Details for the file rusty_req-0.3.0-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: rusty_req-0.3.0-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 1.6 MB
  • Tags: CPython 3.11, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for rusty_req-0.3.0-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 a409df41e294ff170dccc1dd4e19a362b07bba90140cd51b39e202b2ae52c4ee
MD5 705fc2e3860bbd7d70c2a58130f3a4a0
BLAKE2b-256 13b6f54eaa2c248a1e02cbb881e94f7001142b366cbee3ac8dcbec8a9680fa0a

See more details on using hashes here.

File details

Details for the file rusty_req-0.3.0-cp311-cp311-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for rusty_req-0.3.0-cp311-cp311-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 0bb2f5fb4a1cbb3c12be790564a7b1758a254f97835523c00b0be11c2852f6ef
MD5 3e603213c9a1cd9c43fd21cb2bd6fe8b
BLAKE2b-256 5c9c0c637e4e1a00fde03a4c9970bde4b24b6735004831d816b026731fde5e9a

See more details on using hashes here.

File details

Details for the file rusty_req-0.3.0-cp311-cp311-macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for rusty_req-0.3.0-cp311-cp311-macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 b480388976a53b6080348ab02cbf2ba652070a23d3e566fa658491d204f42175
MD5 f471d303c1f366b045cd3afd70fde87a
BLAKE2b-256 c5c660f371b95191c74b1c5e350da28d1950730ec25185a9469d1fd4a849b6d7

See more details on using hashes here.

File details

Details for the file rusty_req-0.3.0-cp310-cp310-win_amd64.whl.

File metadata

  • Download URL: rusty_req-0.3.0-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 1.6 MB
  • Tags: CPython 3.10, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for rusty_req-0.3.0-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 b2e691a993f67b234d92cc3a506b145c04b3000915610f2d33c011a66d9e2bd5
MD5 49faced4131d0ba5311f8f42adcad1c1
BLAKE2b-256 753b6a3cdc9215fee1af373775038568354f6b8e080f9801628122e8f964adcf

See more details on using hashes here.

File details

Details for the file rusty_req-0.3.0-cp310-cp310-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for rusty_req-0.3.0-cp310-cp310-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 999f1c6d2e008c4b3a938e6512fd2c38cdf77c0e75a8c914fc0704d3237bc62a
MD5 90c4aed4defdba538c5d1d97f4f36e6c
BLAKE2b-256 f4c6a2193c6c4a7e2ad936ea079a9f0e593232ab4f93ca300c15994f16c1f8e9

See more details on using hashes here.

File details

Details for the file rusty_req-0.3.0-cp310-cp310-macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for rusty_req-0.3.0-cp310-cp310-macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 fc1f96e4d9f98d36b0395b7155ebd916f678af087265186c39286301defdc72a
MD5 c16daaa63b60cbb63a35b5166c676b17
BLAKE2b-256 6589c0403b1e5d1e3933600179ca0cb57223bc24e12aa69f60e4cde80c06b5d9

See more details on using hashes here.

File details

Details for the file rusty_req-0.3.0-cp39-cp39-win_amd64.whl.

File metadata

  • Download URL: rusty_req-0.3.0-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 1.6 MB
  • Tags: CPython 3.9, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for rusty_req-0.3.0-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 51c0073151289d9100d9ca8a5601f1618730f5b3e4d90f9328e88a8807d3ee41
MD5 b3bd4b0aa54618dee81f687ab8947e51
BLAKE2b-256 ab128bb4088dea18a220349046eaf074adbf9a01eab06874b0832ace72d1d476

See more details on using hashes here.

File details

Details for the file rusty_req-0.3.0-cp39-cp39-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for rusty_req-0.3.0-cp39-cp39-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 f8195f50d7d6ab4c82daefde3e4cbe8d8866e358c26c93b85a3f09d0e1d2094a
MD5 27def277228a58b2255d564bfd3d5f97
BLAKE2b-256 45bcb11876d31ebc6baecc801136e309e9672ca55dbb108715ad1edf8a024db8

See more details on using hashes here.

File details

Details for the file rusty_req-0.3.0-cp39-cp39-macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for rusty_req-0.3.0-cp39-cp39-macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 a09bff2c8fd6189adee1300b422b12044043eb8d34dce5486ce9e37e3b3f42b5
MD5 e167392ab596ee5add7b194bfcd1d870
BLAKE2b-256 f41a569c7a91c57dc2e59f9a180098b9458c6a79326a01f3c683121d3158fc93

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 Pingdom Monitoring Sentry Error logging StatusPage Status page