fix: A42 terminé — client httpx partagé par boucle (v7.28.0)
- `app/services/http_client.py` : `async with shared_client(timeout=15) as client:` remplace les 49 créations `async with httpx.AsyncClient(` de 14 fichiers (gitea ×21, providers oidc/oauth ×11, calendar ×4, automations ×3…) — le pool de connexions est réutilisé au lieu d'être recréé à chaque appel. __aexit__ no-op (le client partagé ne se ferme pas à la sortie). - Cache par (boucle d'event, kwargs) en WeakKeyDictionary : un AsyncClient n'est JAMAIS partagé entre deux loops (piège des tests « Event loop is closed ») — une boucle par test = client propre collecté avec la boucle. Clé = kwargs triés, repr() pour les valeurs non hashables (`headers=` dict → TypeError rattrapé par la suite). - Laissés délibérément : github_adapter (transport MockTransport injecté), webhook_outbound (client « own_client » fermé par la fonction). - Tests : `test_http_client_shared_and_loop_scoped` (réutilisation mêmes kwargs / cloisonné kwargs / cloisonné loop) ; le stub des webhooks patche aussi la fabrique `http_client.httpx` + purge du cache (avant : webhook_outbound.httpx patché mais la fabrique partagée créait un vrai client → réseau réel dans les tests). suite **1091/1091** · ruff OK · docs à jour
This commit is contained in:
@@ -7,7 +7,7 @@ import time
|
||||
from abc import ABC, abstractmethod
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import httpx
|
||||
from app.services.http_client import shared_client
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -76,7 +76,7 @@ class GiteaProvider(OAuthProvider):
|
||||
"grant_type": "authorization_code",
|
||||
"redirect_uri": redirect_uri or self.redirect_uri,
|
||||
}
|
||||
async with httpx.AsyncClient(timeout=15) as client:
|
||||
async with shared_client(timeout=15) as client:
|
||||
r = await client.post(url, json=data, headers={"Accept": "application/json"})
|
||||
if r.status_code != 200:
|
||||
logger.error("Gitea token exchange failed: %s", r.text)
|
||||
@@ -85,7 +85,7 @@ class GiteaProvider(OAuthProvider):
|
||||
|
||||
async def get_user(self, access_token: str) -> dict | None:
|
||||
url = f"{self.base}/api/v1/user"
|
||||
async with httpx.AsyncClient(timeout=15) as client:
|
||||
async with shared_client(timeout=15) as client:
|
||||
r = await client.get(url, headers={"Authorization": f"token {access_token}"})
|
||||
if r.status_code != 200:
|
||||
return None
|
||||
@@ -100,7 +100,7 @@ class GiteaProvider(OAuthProvider):
|
||||
|
||||
async def list_repositories(self, access_token: str) -> list[dict]:
|
||||
repos = []
|
||||
async with httpx.AsyncClient(timeout=30) as client:
|
||||
async with shared_client(timeout=30) as client:
|
||||
for page in range(1, 6):
|
||||
r = await client.get(
|
||||
f"{self.base}/api/v1/user/repos",
|
||||
@@ -158,7 +158,7 @@ class GitHubProvider(OAuthProvider):
|
||||
)
|
||||
|
||||
async def exchange_code(self, code: str, redirect_uri: str | None = None) -> dict | None:
|
||||
async with httpx.AsyncClient(timeout=15) as client:
|
||||
async with shared_client(timeout=15) as client:
|
||||
r = await client.post(
|
||||
self.token_url,
|
||||
data={
|
||||
@@ -179,7 +179,7 @@ class GitHubProvider(OAuthProvider):
|
||||
|
||||
async def get_user(self, access_token: str) -> dict | None:
|
||||
url = f"{self.api_url}/user"
|
||||
async with httpx.AsyncClient(timeout=15) as client:
|
||||
async with shared_client(timeout=15) as client:
|
||||
r = await client.get(
|
||||
url,
|
||||
headers={"Authorization": f"Bearer {access_token}", "Accept": "application/vnd.github.v3+json"},
|
||||
@@ -197,7 +197,7 @@ class GitHubProvider(OAuthProvider):
|
||||
|
||||
async def list_repositories(self, access_token: str) -> list[dict]:
|
||||
repos = []
|
||||
async with httpx.AsyncClient(timeout=30) as client:
|
||||
async with shared_client(timeout=30) as client:
|
||||
for page in range(1, 6):
|
||||
r = await client.get(
|
||||
f"{self.api_url}/user/repos",
|
||||
|
||||
Reference in New Issue
Block a user