Source code for pyiv_common.network

"""requests-backed NetworkClient for pyiv.

This module provides ``RequestsClient``, a ``pyiv.network.NetworkClient``
that uses the requests library. urllib HTTP/HTTPS clients stay in core.

**What Problem Does This Solve?**

Many apps already depend on requests (sessions, adapters, timeouts).
Core pyiv cannot import it. This extra implements the same
``request(method, url, headers, data, timeout)`` shape as core clients.

**Real-World Use Cases:**
- Inject an HTTP client that uses the requests stack
- Swap urllib ``HTTPClient`` for ``RequestsClient`` in Config

Example:

    >>> from pyiv_common.network import RequestsClient
    >>> client = RequestsClient()
    >>> client.handler_type
    'http'
    >>> client.request("GET", "ftp://example.com")
    Traceback (most recent call last):
        ...
    ValueError: RequestsClient only supports http:// and https:// URLs, got: ftp://example.com
"""

from typing import Any, Dict, Optional, Union
from urllib.parse import urlparse

import requests  # type: ignore[import-untyped]

from pyiv.network.base import NetworkClient


[docs] class RequestsClient(NetworkClient): """HTTP(S) client using the requests library. ``handler_type`` is ``"http"`` so it can replace urllib ``HTTPClient`` on the NETWORK_CLIENT chain. Both ``http://`` and ``https://`` URLs are accepted. Example: >>> from pyiv import ChainType, Config, get_injector >>> from pyiv_common.network import RequestsClient >>> class MyConfig(Config): ... def configure(self): ... self.register_chain_handler( ... ChainType.NETWORK_CLIENT, "http", RequestsClient ... ) >>> injector = get_injector(MyConfig) >>> client = injector.inject_chain_handler(ChainType.NETWORK_CLIENT, "http") >>> isinstance(client, RequestsClient) True """ @property def handler_type(self) -> str: """Return the handler type identifier ("http").""" return "http"
[docs] def request( self, method: str, url: str, headers: Optional[Dict[str, str]] = None, data: Optional[Union[str, bytes]] = None, timeout: Optional[float] = None, ) -> Dict[str, Any]: """Make an HTTP request with requests. Args: method: HTTP method (GET, POST, PUT, DELETE, etc.) url: The URL to request (must be http:// or https://) headers: Optional dictionary of HTTP headers data: Optional request body (string or bytes) timeout: Optional timeout in seconds Returns: Dictionary containing: - status: HTTP status code - headers: Response headers dictionary - body: Response body (bytes) - url: Final URL after redirects Raises: ValueError: If URL is not http:// or https:// requests.RequestException: If the request fails """ parsed = urlparse(url) if parsed.scheme not in ("http", "https"): raise ValueError(f"RequestsClient only supports http:// and https:// URLs, got: {url}") if data is not None and isinstance(data, str): data = data.encode("utf-8") response = requests.request( method=method.upper(), url=url, headers=headers, data=data, timeout=timeout, ) return { "status": response.status_code, "headers": dict(response.headers), "body": response.content, "url": response.url, }