Оригинальный пост : 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”