mirror of
https://github.com/wassname/openrouter-python-sdk-retry-errors.git
synced 2026-07-30 12:20:57 +08:00
latest updates
This commit is contained in:
@@ -142,16 +142,21 @@ def serialize_multipart_form(
|
||||
if field_metadata.file:
|
||||
if isinstance(val, List):
|
||||
# Handle array of files
|
||||
array_field_name = f_name + "[]"
|
||||
for file_obj in val:
|
||||
if not _is_set(file_obj):
|
||||
continue
|
||||
|
||||
file_name, content, content_type = _extract_file_properties(file_obj)
|
||||
|
||||
file_name, content, content_type = _extract_file_properties(
|
||||
file_obj
|
||||
)
|
||||
|
||||
if content_type is not None:
|
||||
files.append((f_name + "[]", (file_name, content, content_type)))
|
||||
files.append(
|
||||
(array_field_name, (file_name, content, content_type))
|
||||
)
|
||||
else:
|
||||
files.append((f_name + "[]", (file_name, content)))
|
||||
files.append((array_field_name, (file_name, content)))
|
||||
else:
|
||||
# Handle single file
|
||||
file_name, content, content_type = _extract_file_properties(val)
|
||||
@@ -161,11 +166,16 @@ def serialize_multipart_form(
|
||||
else:
|
||||
files.append((f_name, (file_name, content)))
|
||||
elif field_metadata.json:
|
||||
files.append((f_name, (
|
||||
None,
|
||||
marshal_json(val, request_field_types[name]),
|
||||
"application/json",
|
||||
)))
|
||||
files.append(
|
||||
(
|
||||
f_name,
|
||||
(
|
||||
None,
|
||||
marshal_json(val, request_field_types[name]),
|
||||
"application/json",
|
||||
),
|
||||
)
|
||||
)
|
||||
else:
|
||||
if isinstance(val, List):
|
||||
values = []
|
||||
@@ -175,7 +185,8 @@ def serialize_multipart_form(
|
||||
continue
|
||||
values.append(_val_to_string(value))
|
||||
|
||||
form[f_name + "[]"] = values
|
||||
array_field_name = f_name + "[]"
|
||||
form[array_field_name] = values
|
||||
else:
|
||||
form[f_name] = _val_to_string(val)
|
||||
return media_type, form, files
|
||||
|
||||
@@ -27,12 +27,13 @@ from .forms import _populate_form
|
||||
def get_query_params(
|
||||
query_params: Any,
|
||||
gbls: Optional[Any] = None,
|
||||
allow_empty_value: Optional[List[str]] = None,
|
||||
) -> Dict[str, List[str]]:
|
||||
params: Dict[str, List[str]] = {}
|
||||
|
||||
globals_already_populated = _populate_query_params(query_params, gbls, params, [])
|
||||
globals_already_populated = _populate_query_params(query_params, gbls, params, [], allow_empty_value)
|
||||
if _is_set(gbls):
|
||||
_populate_query_params(gbls, None, params, globals_already_populated)
|
||||
_populate_query_params(gbls, None, params, globals_already_populated, allow_empty_value)
|
||||
|
||||
return params
|
||||
|
||||
@@ -42,6 +43,7 @@ def _populate_query_params(
|
||||
gbls: Any,
|
||||
query_param_values: Dict[str, List[str]],
|
||||
skip_fields: List[str],
|
||||
allow_empty_value: Optional[List[str]] = None,
|
||||
) -> List[str]:
|
||||
globals_already_populated: List[str] = []
|
||||
|
||||
@@ -69,6 +71,16 @@ def _populate_query_params(
|
||||
globals_already_populated.append(name)
|
||||
|
||||
f_name = field.alias if field.alias is not None else name
|
||||
|
||||
allow_empty_set = set(allow_empty_value or [])
|
||||
should_include_empty = f_name in allow_empty_set and (
|
||||
value is None or value == [] or value == ""
|
||||
)
|
||||
|
||||
if should_include_empty:
|
||||
query_param_values[f_name] = [""]
|
||||
continue
|
||||
|
||||
serialization = metadata.serialization
|
||||
if serialization is not None:
|
||||
serialized_parms = _get_serialized_params(
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
import asyncio
|
||||
import random
|
||||
import time
|
||||
from typing import List
|
||||
from datetime import datetime
|
||||
from email.utils import parsedate_to_datetime
|
||||
from typing import List, Optional
|
||||
|
||||
import httpx
|
||||
|
||||
@@ -51,9 +53,11 @@ class Retries:
|
||||
|
||||
class TemporaryError(Exception):
|
||||
response: httpx.Response
|
||||
retry_after: Optional[int]
|
||||
|
||||
def __init__(self, response: httpx.Response):
|
||||
self.response = response
|
||||
self.retry_after = _parse_retry_after_header(response)
|
||||
|
||||
|
||||
class PermanentError(Exception):
|
||||
@@ -63,6 +67,62 @@ class PermanentError(Exception):
|
||||
self.inner = inner
|
||||
|
||||
|
||||
def _parse_retry_after_header(response: httpx.Response) -> Optional[int]:
|
||||
"""Parse Retry-After header from response.
|
||||
|
||||
Returns:
|
||||
Retry interval in milliseconds, or None if header is missing or invalid.
|
||||
"""
|
||||
retry_after_header = response.headers.get("retry-after")
|
||||
if not retry_after_header:
|
||||
return None
|
||||
|
||||
try:
|
||||
seconds = float(retry_after_header)
|
||||
return round(seconds * 1000)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
retry_date = parsedate_to_datetime(retry_after_header)
|
||||
delta = (retry_date - datetime.now(retry_date.tzinfo)).total_seconds()
|
||||
return round(max(0, delta) * 1000)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _get_sleep_interval(
|
||||
exception: Exception,
|
||||
initial_interval: int,
|
||||
max_interval: int,
|
||||
exponent: float,
|
||||
retries: int,
|
||||
) -> float:
|
||||
"""Get sleep interval for retry with exponential backoff.
|
||||
|
||||
Args:
|
||||
exception: The exception that triggered the retry.
|
||||
initial_interval: Initial retry interval in milliseconds.
|
||||
max_interval: Maximum retry interval in milliseconds.
|
||||
exponent: Base for exponential backoff calculation.
|
||||
retries: Current retry attempt count.
|
||||
|
||||
Returns:
|
||||
Sleep interval in seconds.
|
||||
"""
|
||||
if (
|
||||
isinstance(exception, TemporaryError)
|
||||
and exception.retry_after is not None
|
||||
and exception.retry_after > 0
|
||||
):
|
||||
return exception.retry_after / 1000
|
||||
|
||||
sleep = (initial_interval / 1000) * exponent**retries + random.uniform(0, 1)
|
||||
return min(sleep, max_interval / 1000)
|
||||
|
||||
|
||||
def retry(func, retries: Retries):
|
||||
if retries.config.strategy == "backoff":
|
||||
|
||||
@@ -183,8 +243,10 @@ def retry_with_backoff(
|
||||
return exception.response
|
||||
|
||||
raise
|
||||
sleep = (initial_interval / 1000) * exponent**retries + random.uniform(0, 1)
|
||||
sleep = min(sleep, max_interval / 1000)
|
||||
|
||||
sleep = _get_sleep_interval(
|
||||
exception, initial_interval, max_interval, exponent, retries
|
||||
)
|
||||
time.sleep(sleep)
|
||||
retries += 1
|
||||
|
||||
@@ -211,7 +273,9 @@ async def retry_with_backoff_async(
|
||||
return exception.response
|
||||
|
||||
raise
|
||||
sleep = (initial_interval / 1000) * exponent**retries + random.uniform(0, 1)
|
||||
sleep = min(sleep, max_interval / 1000)
|
||||
|
||||
sleep = _get_sleep_interval(
|
||||
exception, initial_interval, max_interval, exponent, retries
|
||||
)
|
||||
await asyncio.sleep(sleep)
|
||||
retries += 1
|
||||
|
||||
Reference in New Issue
Block a user