Рубрики
Без рубрики

Исправление запросов HTTP -крючков с помощью пользовательских аргументов

Оригинальный пост: https://seds.nl/posts/http-mooks-with-custom-arguments/ Я работаю над проектом, когда … Tagged with Python, Django, Tutorial.

Оригинальный пост : https://seds.nl/posts/http-hooks-with-custom-arguments/

Я работаю над проектом, где у нас есть много функций, интегрированных с внешними API. Функции, в которых HTTP -запросы отправляются, мы регистрируем имя, заголовки и данные текущего вызывающего абонента, в случае, если нам нужно что -либо отлаживать. Например:

import logging
import requests
LOGGER = logging.getLogger("external")

def create_external_services():
    ...
    LOGGER.info(f"[func] | Request {data}")
    response = requests.post("..")
    LOGGER.info(f"[func] | Response {response.status_code} {response.text}")
    ...

def delete_external_services():
    LOGGER.info(f"[func] | Request {data}")
    response = requests.delete("..")
    LOGGER.info(f"[func] | Response {response.status_code} {response.text}")

Пока все хорошо, но это становится безобразным, когда у вас есть куча функций, которые регистрируют http одну или несколько запросов и ответов.

Python’s Запросы имеет крючковая система Это позволяет нам манипулировать части процесса запроса или обработки событий сигнала. Однако крюк не может получить пользовательский аргумент. Запросы Требуется крючки, чтобы иметь текущее определение аргумента: def Hook (ответ, *args, ** kwargs) , однако, вы не можете пройти индивидуальность Kwargs к крюку как Запросы поднимает TypeError Если есть Kwarg не признан.

То, как я решил эту проблему, было сначала создать декоратор крючков.

from typing import Callable
import functools
import logging

def patch_http(
    logger: logging.Logger = None,
    level: int = logging.INFO,
    log_hook: Callable = log_hook,
) -> Callable:
    if logger is None:
        logger = logging.getLogger("http.client")

    def decorate_http(func):
        @functools.wraps(func)
        def log_wrapper(*args, **kwargs):
            # fake func and logger attribute to log_hook
            log_hook.func = func
            log_hook.logger = logger
            log_hook.level = level
            return func(*args, **kwargs)

        return log_wrapper

    return decorate_http

Этот декоратор украшает функции, в которых Запросы используется. Это позволяет нам использовать пользовательский журнал, уровень журнала и функцию log_hook, если это необходимо. log_wrapper Создает 3 фиктивных атрибута: Func который содержит адрес вызывающего абонента, журнала и уровня журнала. log_hook требует некоторого взлома, как мы могли бы использовать его без необходимости украшенной функции.

def log_hook(req, *args, **kwargs):
    if not hasattr(log_hook, "func"):
        log_hook.func = None
    if not hasattr(log_hook, "logger"):
        setattr(log_hook, "logger", logging.getLogger("http.client"))
    if not hasattr(log_hook, "level"):
        setattr(log_hook, "level", logging.INFO)

    log_hook.logger.log(
        log_hook.level,
        "[{}] | Request | Payload: {}".format(
            log_hook.func.__name__ if callable(log_hook.func) else "",
            req.request.data if hasattr(req.request, "data") else {},
        ),
    )

    log_hook.logger.log(
        log_hook.level,
        "[{}] | Response status {} | Response {}".format(
            log_hook.func.__name__ if callable(log_hook.func) else "",
            req.status_code,
            req.content,
        ),
    )
    return req

Первые линии – это то, что позволяет использовать крючок независимо от декоратора, с недостатком отсутствия именователя функции. Фактический призыв к регистрации выполняется с помощью атрибута, ранее созданного нашим patch_http декоратор, однако, если декоратор не используется, он по умолчанию в предварительно определенном http.client регистратор.

Теперь фактическое изменение нашего кода:

import logging
import requests
LOGGER = logging.getLogger("external")

@patch_http(logger=LOGGER)
def create_external_services():
    response = requests.post("..", hooks={"response": log_hook})

@patch_http(logger=LOGGER)
def delete_external_services():
    response = requests.post("..", hooks={"response": log_hook})

Я до сих пор не полностью убежден, если использование декоратора для исправления крючка является самым чистым способом, однако он позволяет нам изменить сообщения журнала для всех запросов в одном месте без необходимости дублировать код или легко добавлять пользовательскую логику ко всем запросам.

Например, скажем, мы хотим войти в систему, только если код состояния HTTP 404 возвращается в create_external_services . Мы могли бы изменить наш декоратор, чтобы создать wead_statuses и проверьте код состояния ответа в log_hook перед регистрацией.

from typing import Callable, Tuple
import functools
import logging

def patch_http(
    logger: logging.Logger = None,
    level: int = logging.INFO,
    log_hook: Callable = log_hook,
    expected_statuses: Tuple[int] = (200, 201)
) -> Callable:
    if logger is None:
        logger = logging.getLogger("http.client")

    def decorate_http(func):
        @functools.wraps(func)
        def log_wrapper(*args, **kwargs):
            log_hook.expected_statuses = expected_statuses
            log_hook.func = func
            log_hook.logger = logger
            log_hook.level = level
            return func(*args, **kwargs)

        return log_wrapper

    return decorate_http

def log_hook(req, *args, **kwargs):
    ...
    if not hasattr(log_hook, "expected_statuses"):
        setattr(log_hook, "expected_statuses", (200,))

    if req.status_code in log_hook.expected_statuses:
        log_hook.logger.log(
            log_hook.level,
            "[{}] | Request | Payload: {}".format(
                log_hook.func.__name__ if callable(log_hook.func) else "",
                req.request.data if hasattr(req.request, "data") else {},
             ),
        )
        ....
    return req

Оригинал: “https://dev.to/seds/patching-requests-http-hooks-with-custom-arguments-3lj4”