From 9a161ddeb693b90ffa1262b28095b6b13aae67e6 Mon Sep 17 00:00:00 2001 From: Adam Shimali Date: Tue, 28 Oct 2025 17:01:35 +0000 Subject: [PATCH 1/2] fix: convert Pydantic HttpUrl to string for httpx proxy config Pydantic v2's HttpUrl is not an httpx.URL and when passed directly to httpx.AsyncHTTPTransport(proxy=...), httpx tries to access .url attribute which doesn't exist in Pydantic v2. This causes runtime error if config.http_proxy is defined. httpx.AsyncHTTPTransport accepts httpx.URL, str, or httpx.Proxy Solution: wrap config.http_proxy with str() before passing to httpx --- app/common/http_client.py | 8 ++++---- app/common/test_http_client.py | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/app/common/http_client.py b/app/common/http_client.py index e721b3c..45c97bb 100644 --- a/app/common/http_client.py +++ b/app/common/http_client.py @@ -8,13 +8,13 @@ logger = getLogger(__name__) async_proxy_mounts = { - "http://": httpx.AsyncHTTPTransport(proxy=config.http_proxy), - "https://": httpx.AsyncHTTPTransport(proxy=config.http_proxy) + "http://": httpx.AsyncHTTPTransport(proxy=str(config.http_proxy)), + "https://": httpx.AsyncHTTPTransport(proxy=str(config.http_proxy)) } if config.http_proxy else {} sync_proxy_mounts = { - "http://": httpx.HTTPTransport(proxy=config.http_proxy), - "https://": httpx.HTTPTransport(proxy=config.http_proxy) + "http://": httpx.HTTPTransport(proxy=str(config.http_proxy)), + "https://": httpx.HTTPTransport(proxy=str(config.http_proxy)) } if config.http_proxy else {} diff --git a/app/common/test_http_client.py b/app/common/test_http_client.py index 8fe6baa..e2d6b18 100644 --- a/app/common/test_http_client.py +++ b/app/common/test_http_client.py @@ -27,3 +27,27 @@ def test_trace_id_set(): ) resp = client.get("http://localhost:1234/test") assert resp.text == "trace-id-value" + + +def test_create_client_with_proxy(monkeypatch): + import importlib + + from pydantic import HttpUrl + + # Set http_proxy config before reloading http_client + monkeypatch.setattr( + "app.config.config.http_proxy", HttpUrl("http://proxy.example.com:8080") + ) + + # Reload the http_client module to trigger creation of proxy_mounts with HttpUrl set + # This would fail with the old code (AttributeError: 'HttpUrl' object has no attribute 'url') + import app.common.http_client + + importlib.reload(app.common.http_client) + + # Verify clients can be created + client = app.common.http_client.create_client() + assert client is not None + + async_client = app.common.http_client.create_async_client() + assert async_client is not None \ No newline at end of file From 121a0892436d5dcd483bbdca87cf8029b7d5437b Mon Sep 17 00:00:00 2001 From: Adam Shimali Date: Tue, 28 Oct 2025 17:13:52 +0000 Subject: [PATCH 2/2] fix: ruff format and lint with fixes --- app/common/http_client.py | 30 +++++++++++++++++++----------- app/common/test_http_client.py | 2 +- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/app/common/http_client.py b/app/common/http_client.py index 45c97bb..3edbba6 100644 --- a/app/common/http_client.py +++ b/app/common/http_client.py @@ -7,15 +7,23 @@ logger = getLogger(__name__) -async_proxy_mounts = { - "http://": httpx.AsyncHTTPTransport(proxy=str(config.http_proxy)), - "https://": httpx.AsyncHTTPTransport(proxy=str(config.http_proxy)) -} if config.http_proxy else {} - -sync_proxy_mounts = { - "http://": httpx.HTTPTransport(proxy=str(config.http_proxy)), - "https://": httpx.HTTPTransport(proxy=str(config.http_proxy)) -} if config.http_proxy else {} +async_proxy_mounts = ( + { + "http://": httpx.AsyncHTTPTransport(proxy=str(config.http_proxy)), + "https://": httpx.AsyncHTTPTransport(proxy=str(config.http_proxy)), + } + if config.http_proxy + else {} +) + +sync_proxy_mounts = ( + { + "http://": httpx.HTTPTransport(proxy=str(config.http_proxy)), + "https://": httpx.HTTPTransport(proxy=str(config.http_proxy)), + } + if config.http_proxy + else {} +) async def async_hook_request_tracing(request): @@ -42,7 +50,7 @@ def create_async_client(request_timeout: int = 30) -> httpx.AsyncClient: """ client_kwargs = { "timeout": request_timeout, - "event_hooks": {"request": [async_hook_request_tracing]} + "event_hooks": {"request": [async_hook_request_tracing]}, } if config.http_proxy: @@ -63,7 +71,7 @@ def create_client(request_timeout: int = 30) -> httpx.Client: """ client_kwargs = { "timeout": request_timeout, - "event_hooks": {"request": [hook_request_tracing]} + "event_hooks": {"request": [hook_request_tracing]}, } if config.http_proxy: diff --git a/app/common/test_http_client.py b/app/common/test_http_client.py index e2d6b18..c8a5a43 100644 --- a/app/common/test_http_client.py +++ b/app/common/test_http_client.py @@ -50,4 +50,4 @@ def test_create_client_with_proxy(monkeypatch): assert client is not None async_client = app.common.http_client.create_async_client() - assert async_client is not None \ No newline at end of file + assert async_client is not None