fix(bfl): add timeout to polling requests, validate initial POST status code

- Propagate timeout to each polling GET request to prevent indefinite hangs
- Validate HTTP status code of initial POST before parsing JSON
- Fix inline import and add 60s timeout to image URL download in _read_image_bytes
This commit is contained in:
Chesars 2026-03-04 22:20:59 -03:00
parent 6fa9a0e52b
commit f2c75bdbe0
3 changed files with 42 additions and 2 deletions

View file

@ -166,6 +166,7 @@ class BlackForestLabsImageEdit:
initial_response=response,
headers=headers,
sync_client=sync_client,
timeout=timeout,
)
# Transform response
@ -269,6 +270,7 @@ class BlackForestLabsImageEdit:
initial_response=response,
headers=headers,
async_client=async_client,
timeout=timeout,
)
# Transform response
@ -285,6 +287,7 @@ class BlackForestLabsImageEdit:
sync_client: HTTPHandler,
max_wait: float = DEFAULT_MAX_POLLING_TIME,
interval: float = DEFAULT_POLLING_INTERVAL,
timeout: Optional[Union[float, httpx.Timeout]] = None,
) -> httpx.Response:
"""
Poll BFL API until result is ready (sync version).
@ -295,10 +298,18 @@ class BlackForestLabsImageEdit:
sync_client: HTTP client
max_wait: Maximum time to wait in seconds
interval: Polling interval in seconds
timeout: Timeout for each individual polling request
Returns:
Final response with completed result
"""
# Validate initial response status code
if initial_response.status_code >= 400:
raise BlackForestLabsError(
status_code=initial_response.status_code,
message=f"BFL initial request failed: {initial_response.text}",
)
# Parse initial response to get polling URL
try:
response_data = initial_response.json()
@ -332,6 +343,7 @@ class BlackForestLabsImageEdit:
response = sync_client.get(
url=polling_url,
headers=polling_headers,
timeout=timeout,
)
if response.status_code != 200:
@ -367,10 +379,18 @@ class BlackForestLabsImageEdit:
async_client: AsyncHTTPHandler,
max_wait: float = DEFAULT_MAX_POLLING_TIME,
interval: float = DEFAULT_POLLING_INTERVAL,
timeout: Optional[Union[float, httpx.Timeout]] = None,
) -> httpx.Response:
"""
Poll BFL API until result is ready (async version).
"""
# Validate initial response status code
if initial_response.status_code >= 400:
raise BlackForestLabsError(
status_code=initial_response.status_code,
message=f"BFL initial request failed: {initial_response.text}",
)
# Parse initial response to get polling URL
try:
response_data = initial_response.json()
@ -404,6 +424,7 @@ class BlackForestLabsImageEdit:
response = await async_client.get(
url=polling_url,
headers=polling_headers,
timeout=timeout,
)
if response.status_code != 200:

View file

@ -185,8 +185,7 @@ class BlackForestLabsImageEditConfig(BaseImageEditConfig):
elif isinstance(image, str):
if image.startswith(("http://", "https://")):
# Download image from URL
import httpx as _httpx
response = _httpx.get(image)
response = httpx.get(image, timeout=60.0)
return response.content
else:
# Assume it's a file path

View file

@ -163,6 +163,7 @@ class BlackForestLabsImageGeneration:
initial_response=response,
headers=headers,
sync_client=sync_client,
timeout=timeout,
)
# Transform response
@ -265,6 +266,7 @@ class BlackForestLabsImageGeneration:
initial_response=response,
headers=headers,
async_client=async_client,
timeout=timeout,
)
# Transform response
@ -282,10 +284,18 @@ class BlackForestLabsImageGeneration:
sync_client: HTTPHandler,
max_wait: float = DEFAULT_MAX_POLLING_TIME,
interval: float = DEFAULT_POLLING_INTERVAL,
timeout: Optional[Union[float, httpx.Timeout]] = None,
) -> httpx.Response:
"""
Poll BFL API until result is ready (sync version).
"""
# Validate initial response status code
if initial_response.status_code >= 400:
raise BlackForestLabsError(
status_code=initial_response.status_code,
message=f"BFL initial request failed: {initial_response.text}",
)
# Parse initial response to get polling URL
try:
response_data = initial_response.json()
@ -319,6 +329,7 @@ class BlackForestLabsImageGeneration:
response = sync_client.get(
url=polling_url,
headers=polling_headers,
timeout=timeout,
)
if response.status_code != 200:
@ -354,10 +365,18 @@ class BlackForestLabsImageGeneration:
async_client: AsyncHTTPHandler,
max_wait: float = DEFAULT_MAX_POLLING_TIME,
interval: float = DEFAULT_POLLING_INTERVAL,
timeout: Optional[Union[float, httpx.Timeout]] = None,
) -> httpx.Response:
"""
Poll BFL API until result is ready (async version).
"""
# Validate initial response status code
if initial_response.status_code >= 400:
raise BlackForestLabsError(
status_code=initial_response.status_code,
message=f"BFL initial request failed: {initial_response.text}",
)
# Parse initial response to get polling URL
try:
response_data = initial_response.json()
@ -391,6 +410,7 @@ class BlackForestLabsImageGeneration:
response = await async_client.get(
url=polling_url,
headers=polling_headers,
timeout=timeout,
)
if response.status_code != 200: