pyreqwest Documentation

repository·main·Indexed 18 days ago

https://github.com/markussintonen/pyreqwest

A high-performance, Rust-based HTTP client for Python inspired by the reqwest crate. Version 0.12.2 provides synchronous and asynchronous APIs with full type safety, built-in mocking via ClientMocker for pytest, and support for Python 3.13+ free threading. Features include a builder pattern for client configuration, zero-copy data handling via the buffer protocol, and configurable tokio runtime threading models.

Tokens
9.1K
Snippets
33
Records
44
Agent score
63%

What's inside pyreqwest

  1. Use zero-copy data handling with the buffer protocol

    main

    To avoid unnecessary memory copying, pyreqwest utilizes the Python buffer protocol.

    • Request Bodies: Returned as pyreqwest.bytes.Bytes. This is a bytes-like type. To pass this data to other libraries without copying, wrap it in a memoryview().
    • Avoid Copies: Using bytes(Bytes) or bytearray(Bytes) will trigger a copy of the underlying buffer. Use memoryview(Bytes) for zero-copy access.
    • Request Retries: Methods like Request.copy() create zero-copy views, making them efficient for middleware-driven retries.

    Note on Ownership: pyreqwest often transfers ownership of data structures. Once a method like Request.send() is called, the Request instance becomes unusable.

    # Efficient zero-copy access to response data
    response = client.get("https://example.com")
    body = response.body  # This is pyreqwest.bytes.Bytes
    
    # Pass to another function without copying
    process_data(memoryview(body))
    
    # This WOULD cause a copy (avoid if performance is critical)
    copy_of_data = bytes(body)
  2. Thread safety and Python 3.13+ free threading

    main

    pyreqwest supports Python 3.13+ free threading. Understanding which objects are thread-safe is critical for concurrent programming.

    Thread-safe objects

    You can safely share these across multiple threads without additional locking:

    • Core Clients: Client, SyncClient, CookieStore.
    • Immutable/Simple Types: Url, HeaderMap, Bytes, Mime, Cookie.

    Non-thread-safe objects

    Do not share these across threads or mutate them concurrently from multiple threads:

    • Builders: ClientBuilder, SyncClientBuilder.
    • Requests/Responses: ConsumedRequest, Response, SyncConsumedRequest, SyncResponse.
  3. Run pyreqwest benchmarks

    main

    To run the project's internal benchmarks, use the make bench command. These benchmarks are executed against an embedded server to minimize network interference and ensure latency measurements are accurate.

    Benchmark Environment Details:

    • Python Version: 3.14
    • Protocol: HTTP/1.1 with TLS
    • Hardware: Apple M3 Max (36GB RAM, OS 15.7.3)
    make bench
  4. Configure the async runtime threading model

    main

    By default, pyreqwest uses a global single-threaded tokio runtime. While sufficient for most use cases, you can switch to a multithreaded runtime if you are handling many concurrent requests or processing large responses.

    You can enable multithreading in two ways:

    1. Per Client: Use ClientBuilder.runtime_multithreaded(bool) to configure a specific client.
    2. Globally: Use pyreqwest.runtime.runtime_multithreaded_default(bool) to set the default for the entire library.

    For more granular control, use the pyreqwest.runtime module.

    # Enable multithreaded runtime for a specific client
    client = ClientBuilder().runtime_multithreaded(True).build()
    
    # OR enable it globally
    import pyreqwest.runtime
    pyreqwest.runtime.runtime_multithreaded_default(True)
  5. Explore the pyreqwest module structure

    main

    The pyreqwest library is organized into several submodules that handle different aspects of HTTP communication. You can access these via the main package import.

    Key submodules include:

    • client: For managing Client and SyncClient instances.
    • request: For constructing Request and RequestBuilder objects.
    • response: For handling Response and SyncResponse objects.
    • http: Provides core types like Url, Mime, and HeaderMap (which implements Python's MutableMapping).
    • cookie: For managing Cookie and CookieStore.
    • multipart: For building multipart/form-data requests using FormBuilder and PartBuilder.
    • middleware: For implementing custom request/response logic using Next or SyncNext.
    • proxy: For configuring proxy settings via ProxyBuilder.
    • runtime: For configuring the underlying execution environment (e.g., worker threads).
    • logging: For managing logs via flush_logs.
  6. How Client and SyncClient manage lifecycle

    main

    Both client types are designed to be used as context managers to ensure that underlying connections and resources are cleaned up via the close() method.

    • Client (Async): Use async with Client() as client: to automatically call close() when the block exits. This triggers a cancellation of internal tasks.
    • SyncClient (Sync): Use with SyncClient() as client: to automatically call close() when the block exits.
  7. How ResponseBodyReader and SyncResponseBodyReader work together

    main

    The library uses a class hierarchy to provide both async and sync interfaces for the same underlying data stream:

    1. BaseResponseBodyReader: The core implementation containing the Mutex<BodyReader> and the RuntimeHandle. It provides the actual async methods (bytes, read, read_chunk).
    2. ResponseBodyReader: A subclass of the base reader designed for use in async Python environments.
    3. SyncResponseBodyReader: A subclass of the base reader designed for use in synchronous Python environments. It uses the RuntimeHandle to blocking_spawn the underlying async operations, making them callable as standard synchronous functions.
  8. Use CookieStore as a reqwest CookieStore implementation

    main

    The CookieStore is designed to be compatible with the reqwest::cookie::CookieStore trait. When used within the pyreqwest ecosystem, it automatically handles:

    1. Response Cookies: Via set_cookies, it parses Cookie headers from HTTP responses and stores them in the CookieStore for the given URL.
    2. Request Cookies: Via cookies, it generates the appropriate Cookie header value for outgoing HTTP requests based on the cookies stored for the target URL.
  9. How middleware and JSON handlers work

    main

    Middleware

    Middleware allows you to intercept requests and responses.

    • In ClientBuilder (Async), middleware must be an async function.
    • In SyncClientBuilder (Sync), middleware must be a regular synchronous function.
    • Use .with_middleware(middleware_func) to register them.

    JSON Handlers

    You can override the default JSON serialization/deserialization logic using .json_handler().

    • Async Client: loads must be an async function; dumps must be a synchronous function.
    • Sync Client: loads must be a synchronous function; dumps must be a synchronous function.

    Example for Async Client:

    import json
    import asyncio
    
    async def async_loads(data):
        return json.loads(data)
    
    def sync_dumps(obj):
        return json.dumps(obj)
    
    builder = ClientBuilder().json_handler(loads=async_loads, dumps=sync_dumps)
  10. How Response objects work and their lifecycle

    main

    A Response object in pyreqwest is a wrapper around an underlying HTTP response. It manages the lifecycle of the response body through a body_reader.

    Consumption and Ownership

    • Single Consumption: Most body-reading methods (bytes(), text(), json()) consume the underlying body reader. Once called, the body is cached, but the reader is closed. Attempting to read the body again will result in a PyRuntimeError stating the response has already been consumed.
    • Taking Ownership: You can use take() to move the underlying BaseResponse out of its current container, or take_body_reader() to explicitly take the reader.
    • Subclasses:
      • Response is used for asynchronous requests.
      • SyncResponse is used for synchronous requests.
      • Both inherit from BaseResponse which contains the shared metadata logic.
  11. Mock HTTP requests in pytest

    main

    Use the ClientMocker fixture provided by pyreqwest.pytest_plugin to mock requests in your tests. You can define expected paths and response bodies using .with_body_text(). The mocker also tracks the number of calls via .get_call_count().

    from pyreqwest.client import ClientBuilder
    from pyreqwest.pytest_plugin import ClientMocker
    
    async def test_client(client_mocker: ClientMocker) -> None:
        client_mocker.get(path="/api").with_body_text("Hello Mock")
    
        async with ClientBuilder().build() as client:
            response = await client.get("http://example.invalid/api").build().send()
            assert response.status == 200 and await response.text() == "Hello Mock"
            assert client_mocker.get_call_count() == 1