TGViewer
Валерий | AQA Engineer | Автотестирование на Python | REST, gRPC, GraphQL Валерий | AQA Engineer | Автотестирование на Python | REST, gRPC, GraphQL @aqa_engineer · 1.52K subscribers
Post #454 1.32K
Как сделать удобно расширяемый http клиент?

Самый частый паттерн при написании логгирующей 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-сообщество

📱 Обучение

📱 Отзывы
  • 🔥 11
More from @aqa_engineer
  1. Sep 2, 2026💙💙💙💙💙 Маленькое напоминание о большой дате: где стоит провести День программиста⬆️ Ар…
  2. Sep 2, 2026Привет, давно меня не было пригар по работе, что совсем не успеваю ничего. За что прошу пр…
  3. Jul 16, 2026👩‍💻 В мае-июне я проходила курс «Автоматизация тестирования Rest API Advanced (Python)»…
  4. Jun 10, 2026Привет! Уже на следующей неделе у меня стартуют. Автоматизация тестирования брокеров сообщ…
  5. Jun 8, 2026Несколько месяцев назад я спросил, какой технический тренинг вам был бы действительно инте…
  6. Jun 6, 2026Материалы для тех, кто в теме. Одна из самых больших проблем при развитии в IT - это возмо…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →