--- title: Requests HTTP Pattern category: patterns --- # Requests HTTP Pattern ## Rules - Reuse requests.Session when making multiple calls to the same host to benefit from connection pooling. - Wrap outbound HTTP calls with retry/backoff logic and respect Retry-After on 429. - Treat 5xx as transient and retry; surface 4xx as configuration/client errors (do not retry unless 429). - Raise or wrap non-OK responses into domain ProviderError to make behavior consistent across the codebase. ## Examples ### ai_provider.py - 429 handling with Retry-After ```python resp = requests.post(url, json=json, headers=headers, timeout=10) ... if getattr(resp, "status_code", 0) == 429: if attempt == retries: raise ProviderError(f"Provider returned HTTP {resp.status_code}") retry_after = None raw = resp.headers.get("Retry-After") if getattr(resp, "headers", None) else None if raw: try: retry_after = int(raw) except Exception: ... if retry_after is not None: time.sleep(retry_after) continue ``` ### api_client.py - Session + raise_for_status ```python response = self.session.get( base_url, params=params, timeout=config.API_TIMEOUT ) response.raise_for_status() data = response.json() ``` ### pipeline/ai_provider_wrapper.py - Retry/backoff wrapper ```python def _attempt_batch(chunk_texts, start_index): backoff = 0.5 for attempt in range(1, retries + 1): try: emb_chunk = _embedder( chunk_texts, model=model, batch_size=len(chunk_texts) ) return emb_chunk, None except Exception as exc: if attempt == retries: break sleep = backoff * (2 ** (attempt - 1)) time.sleep(sleep) continue ``` ## Anti-Patterns ### Bad: Silent exception swallowing **Problem**: Blindly catching all requests exceptions and returning empty response. **Remediation**: Map network exceptions to retryable vs terminal (ProviderError) and log details. ### Bad: Using print() for errors **Problem**: Using print() for network errors instead of structured logging. **Remediation**: Use `_logger.exception()` instead (see api_client.py needs fixing).