Самый частый паттерн при написании логгирующей http обертки в API автотестах:
import httpx
class LogClient(httpx.Client):
def request(self, *args, **kwargs):
print(args, kwargs)
response = super().request(*args, **kwargs)
print(response.content)
return response
Потом добавляем allure степы:
class LogClient(httpx.Client):
def request(self, *args, **kwargs):
with allure.step(f"Request {args}"):
print(args, kwargs)
allure.attach(request, name="Request")
response = super().request(*args, **kwargs)
allure.attach(response, name="Response")
return response
Метод разрастается, мы опять вносим изменения в класс.
Дальше захотим добавить coverage, метрики - опять изменения.
Может быть есть способ не изменяя класс?
Решение 1: Встроенные hooks в httpx
client = httpx.Client(
event_hooks={
'request': [log_request],
'response': [log_response]
}
)
httpx уже поддерживает event hooks.
Можем передать функции, которые вызовутся до/после запроса.
Решение 2: Своя реализация
А что если захотим свой клиент?
Систему хуков можно внедрить в любой клиент - к БД или другому ресурсу.
Рассмотрим на примере httpx, как будто там нет хуков:
from typing import Callable
import httpx
from httpx import Request, Response
class Client:
def __init__(self):
self._on_request_callbacks = []
self._on_response_callbacks = []
self._client = httpx.Client()
def add_request_hook(
self, callback: Callable[[Request], None]
) -> None:
self._on_request_callbacks.append(callback)
def add_response_hook(
self, callback: Callable[[Response], None]
) -> None:
self._on_response_callbacks.append(callback)
def _run_hooks(self, value, hooks):
for hook in hooks:
try:
hook(value)
except Exception as e:
print(f"Hook error: {e}")
def request(self, method, url, **kwargs) -> Response:
request = self._client.build_request(
method=method, url=url, **kwargs
)
self._run_hooks(request, self._on_request_callbacks)
response = self._client.send(request)
self._run_hooks(response, self._on_response_callbacks)
return response
Создаем колбеки:
def log_req(request):
print(f"→ {request.method} {request.url}")
def log_resp(response):
print(f"← {response.status_code}")
def allure_req(request):
allure.attach(str(request), name="Request")
def allure_resp(response):
allure.attach(response.text, name="Response")
Регистрируем и используем:
client = Client()
client.add_request_hook(log_req)
client.add_request_hook(allure_req)
client.add_response_hook(log_resp)
client.add_response_hook(allure_resp)
client.request("GET", "https://httpbin.org/get")
Результат:
→ GET https://httpbin.org/get
← 200
Что получается?
Мы один раз описали интерфейс для регистрации хуков, и теперь можем добавлять сколько угодно логики, не внося изменений в класс. Код становится более стабильным и расширяемым.
——————————-
📱 TG-сообщество
📱 Обучение
📱 Отзывы






