Skip to content

Instantly share code, notes, and snippets.

@si3mshady
Forked from bpgould/http_requests.py
Created May 21, 2023 21:04
Show Gist options
  • Select an option

  • Save si3mshady/b0766ed2b81b2c91837d12d287e9b6cb to your computer and use it in GitHub Desktop.

Select an option

Save si3mshady/b0766ed2b81b2c91837d12d287e9b6cb to your computer and use it in GitHub Desktop.

Revisions

  1. @bpgould bpgould created this gist May 21, 2023.
    152 changes: 152 additions & 0 deletions http_requests.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,152 @@
    """
    This module provides functionality for making HTTP requests. It leverages the `aiohttp`
    library for asynchronous HTTP requests, and the `backoff` library to implement exponential
    backoff in case of failed requests.
    The module defines a child logger for logging purposes and implements two methods, `on_backoff`
    and `on_giveup`, which log information about the retry attempts and when the retry attempts are
    given up respectively.
    The `http_request` function is the primary function of the module, making an HTTP request with the
    provided parameters. If the request fails due to an `aiohttp.ClientError`, the function will retry
    the request using an exponential backoff strategy, up to a maximum of 5 times or a total of 60
    seconds.
    The function logs the result of the HTTP request, either indicating success and the status code of
    the response or raising an exception if the response cannot be parsed as JSON or if the response
    status code indicates a client error.
    """

    import json
    import logging
    import aiohttp
    import backoff

    # set up child logger for module
    logger = logging.getLogger(__name__)


    def on_backoff(backoff_event):
    """
    Logs a warning message whenever a backoff event occurs due to a failed HTTP request.
    Parameters
    ----------
    backoff_event : dict
    A dictionary containing detailed information about the backoff event.
    It includes the following keys:
    'wait' (the delay before the next retry),
    'tries' (the number of attempts made),
    'exception' (the exception that caused the backoff),
    'target' (the function where the exception was raised),
    'args' (the arguments passed to the target function),
    'kwargs' (the keyword arguments passed to the target function),
    and 'elapsed' (the time elapsed since the first attempt).
    """
    logger.warning(
    "http backoff event",
    extra={
    "Retrying in, seconds": {backoff_event["wait"]},
    "Attempt number": {backoff_event["tries"]},
    "Exception": {backoff_event["exception"]},
    "Target function": {backoff_event["target"].__name__},
    "Args": {backoff_event["args"]},
    "Kwargs": {backoff_event["kwargs"]},
    "Elapsed time": {backoff_event["elapsed"]},
    },
    )


    def on_giveup(giveup_event):
    """
    Logs an error message when a series of HTTP requests fail and the retry attempts are given up.
    Parameters
    ----------
    giveup_event : dict
    A dictionary containing detailed information about the event when retries are given up.
    It includes the following keys:
    'tries' (the number of attempts made),
    'exception' (the exception that caused the retries to be given up),
    'target' (the function where the exception was raised),
    'args' (the arguments passed to the target function),
    'kwargs' (the keyword arguments passed to the target function),
    and 'elapsed' (the time elapsed since the first attempt).
    """
    logger.error(
    "http giveup event",
    extra={
    "Giving up after retries exceeded": giveup_event["tries"],
    "Exception": giveup_event["exception"],
    "Target function": giveup_event["target"].__name__,
    "Args": giveup_event["args"],
    "Kwargs": giveup_event["kwargs"],
    "Elapsed time": giveup_event["elapsed"],
    },
    )


    @backoff.on_exception(
    backoff.expo,
    aiohttp.ClientError,
    jitter=backoff.random_jitter,
    max_tries=5,
    max_time=60,
    on_backoff=on_backoff,
    on_giveup=on_giveup,
    )
    async def http_request(verb, url, query_params, headers, payload):
    """
    Performs an HTTP request, retrying on `aiohttp.ClientError` exceptions with an exponential
    backoff strategy.
    Parameters
    ----------
    verb : str
    The HTTP method for the request, such as 'GET', 'POST', etc.
    url : str
    The URL for the HTTP request.
    query_params : dict
    The query parameters to be included in the request.
    headers : dict
    The headers to be included in the request.
    payload : dict
    The payload (body) of the request, which will be sent as JSON.
    Returns
    -------
    None
    Raises
    ------
    ValueError
    If the response from the HTTP request cannot be parsed as JSON.
    aiohttp.ClientResponseError
    If the HTTP request returns a response with a status code indicating a client error.
    Note
    ----
    The function will automatically retry the request if an `aiohttp.ClientError` is raised.
    It uses an exponential backoff strategy with a maximum of 5 tries and a total retry duration
    of 60 seconds. The 'on_backoff' function will be called after each failed attempt, and the
    'on_giveup' function will be called if all retry attempts fail.
    """
    async with aiohttp.ClientSession() as session:
    async with session.request(
    method=verb, url=url, params=query_params, headers=headers, json=payload
    ) as response:
    if response.ok:
    try:
    data = await response.json()
    return data
    except json.JSONDecodeError as json_decode_error:
    raise ValueError(
    "Failed to parse response JSON"
    ) from json_decode_error
    else:
    raise aiohttp.ClientResponseError(
    response.request_info,
    response.history,
    status=response.status,
    message=response.reason,
    )