trodo-python 2.23.3__tar.gz → 2.23.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {trodo_python-2.23.3 → trodo_python-2.23.4}/PKG-INFO +1 -1
- {trodo_python-2.23.3 → trodo_python-2.23.4}/pyproject.toml +1 -1
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/__init__.py +1 -1
- trodo_python-2.23.4/trodo/api/_retry.py +30 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/api/async_client.py +8 -4
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/api/http_client.py +17 -10
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo_python.egg-info/PKG-INFO +1 -1
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo_python.egg-info/SOURCES.txt +1 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/README.md +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/setup.cfg +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_anon_distinct_id.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_auto_instrument_fixes.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_cross_process_session.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_datasets.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_end_run.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_error_enrichment.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_experiments.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_framework_deference.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_get_prompt_version_types.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_instrumentor_resolution.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_llm_usage_cost.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_multi_agent_shape.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_processor_methods.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_prompt_trace.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_prompts.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_register_otel.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_start_run.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_user_traits.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/tests/test_wrap_agent_unchanged.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/api/__init__.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/api/endpoints.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/auto/__init__.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/auto/auto_event_manager.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/client.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/managers/__init__.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/managers/dataset_manager.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/managers/experiment_manager.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/managers/group_manager.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/managers/people_manager.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/managers/prompt_manager.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/managers/user_manager.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/otel/__init__.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/otel/auto_instrument.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/otel/context.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/otel/helpers.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/otel/processor.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/otel/prompt_trace.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/otel/register.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/otel/transport.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/otel/wrap_agent.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/prompts/__init__.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/prompts/compile.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/prompts/template.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/prompts/types.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/queue/__init__.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/queue/batch_flusher.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/queue/event_queue.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/session/__init__.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/session/server_session.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/session/session_manager.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/types.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/user_context.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/util/__init__.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo/util/lru.py +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo_python.egg-info/dependency_links.txt +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo_python.egg-info/requires.txt +0 -0
- {trodo_python-2.23.3 → trodo_python-2.23.4}/trodo_python.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""When to retry a request, and after how long. Mirrors the Node SDK."""
|
|
2
|
+
import random
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def retry_delay(status: int, retry_after: Optional[str], attempt: int) -> Optional[float]:
|
|
7
|
+
"""Seconds to wait before retrying, or None when not to retry.
|
|
8
|
+
|
|
9
|
+
Retries a 5xx, and a 429 that says when (Retry-After): a rate limit is
|
|
10
|
+
temporary, an exhausted allowance (429 without Retry-After) is not. A
|
|
11
|
+
server that says Retry-After is honoured (capped at 30 s). Otherwise
|
|
12
|
+
exponential backoff with jitter, so a burst of clients hit by the same
|
|
13
|
+
outage does not retry in lockstep and knock it over again.
|
|
14
|
+
"""
|
|
15
|
+
retryable = status >= 500 or (status == 429 and retry_after is not None)
|
|
16
|
+
if not retryable:
|
|
17
|
+
return None
|
|
18
|
+
try:
|
|
19
|
+
seconds = float(retry_after) if retry_after is not None else None
|
|
20
|
+
except ValueError:
|
|
21
|
+
seconds = None
|
|
22
|
+
if seconds is not None and seconds >= 0:
|
|
23
|
+
return min(seconds, 30.0) + random.random()
|
|
24
|
+
return jittered_backoff(attempt)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def jittered_backoff(attempt: int) -> float:
|
|
28
|
+
"""2**attempt seconds, somewhere in its upper half."""
|
|
29
|
+
base = float(2 ** attempt)
|
|
30
|
+
return base / 2 + random.random() * (base / 2)
|
|
@@ -7,6 +7,7 @@ import asyncio
|
|
|
7
7
|
from typing import Any, Callable, Dict, Optional
|
|
8
8
|
|
|
9
9
|
from ..types import ApiResult, EventPayload
|
|
10
|
+
from ._retry import jittered_backoff, retry_delay
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class AsyncHttpClient:
|
|
@@ -51,9 +52,12 @@ class AsyncHttpClient:
|
|
|
51
52
|
try:
|
|
52
53
|
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
53
54
|
resp = await client.post(url, json=body, headers=headers)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
delay = (
|
|
56
|
+
retry_delay(resp.status_code, resp.headers.get('retry-after'), attempt)
|
|
57
|
+
if attempt < self.retries else None
|
|
58
|
+
)
|
|
59
|
+
if delay is not None:
|
|
60
|
+
self._log(f"Retry {attempt + 1} after {delay:.1f}s (status {resp.status_code})")
|
|
57
61
|
await asyncio.sleep(delay)
|
|
58
62
|
return await self._request(path, body, attempt + 1)
|
|
59
63
|
try:
|
|
@@ -62,7 +66,7 @@ class AsyncHttpClient:
|
|
|
62
66
|
return {}
|
|
63
67
|
except Exception as exc:
|
|
64
68
|
if attempt < self.retries:
|
|
65
|
-
await asyncio.sleep(
|
|
69
|
+
await asyncio.sleep(jittered_backoff(attempt))
|
|
66
70
|
return await self._request(path, body, attempt + 1)
|
|
67
71
|
self._log(f"Error: {exc}")
|
|
68
72
|
if self.on_error:
|
|
@@ -10,6 +10,7 @@ import requests
|
|
|
10
10
|
|
|
11
11
|
from ..types import ApiResult, EventPayload
|
|
12
12
|
from . import endpoints
|
|
13
|
+
from ._retry import jittered_backoff, retry_delay
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class HttpClient:
|
|
@@ -43,9 +44,12 @@ class HttpClient:
|
|
|
43
44
|
self._log(f"POST {url}")
|
|
44
45
|
try:
|
|
45
46
|
resp = self._session.post(url, json=body, timeout=self.timeout)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
delay = (
|
|
48
|
+
retry_delay(resp.status_code, resp.headers.get('retry-after'), attempt)
|
|
49
|
+
if attempt < self.retries else None
|
|
50
|
+
)
|
|
51
|
+
if delay is not None:
|
|
52
|
+
self._log(f"Retry {attempt + 1} after {delay:.1f}s (status {resp.status_code})")
|
|
49
53
|
time.sleep(delay)
|
|
50
54
|
return self._request(path, body, attempt + 1)
|
|
51
55
|
if resp.status_code >= 400:
|
|
@@ -72,8 +76,8 @@ class HttpClient:
|
|
|
72
76
|
return {}
|
|
73
77
|
except Exception as exc:
|
|
74
78
|
if attempt < self.retries:
|
|
75
|
-
delay =
|
|
76
|
-
self._log(f"Retry {attempt + 1} after {delay}s (network error)")
|
|
79
|
+
delay = jittered_backoff(attempt)
|
|
80
|
+
self._log(f"Retry {attempt + 1} after {delay:.1f}s (network error)")
|
|
77
81
|
time.sleep(delay)
|
|
78
82
|
return self._request(path, body, attempt + 1)
|
|
79
83
|
self._log(f"Error: {exc}")
|
|
@@ -91,9 +95,12 @@ class HttpClient:
|
|
|
91
95
|
self._log(f"GET {url}")
|
|
92
96
|
try:
|
|
93
97
|
resp = self._session.get(url, params=params or {}, timeout=self.timeout)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
delay = (
|
|
99
|
+
retry_delay(resp.status_code, resp.headers.get('retry-after'), attempt)
|
|
100
|
+
if attempt < self.retries else None
|
|
101
|
+
)
|
|
102
|
+
if delay is not None:
|
|
103
|
+
self._log(f"Retry {attempt + 1} after {delay:.1f}s (status {resp.status_code})")
|
|
97
104
|
time.sleep(delay)
|
|
98
105
|
return self._get(path, params, attempt + 1)
|
|
99
106
|
try:
|
|
@@ -108,8 +115,8 @@ class HttpClient:
|
|
|
108
115
|
return data
|
|
109
116
|
except Exception as exc:
|
|
110
117
|
if attempt < self.retries:
|
|
111
|
-
delay =
|
|
112
|
-
self._log(f"Retry {attempt + 1} after {delay}s (network error)")
|
|
118
|
+
delay = jittered_backoff(attempt)
|
|
119
|
+
self._log(f"Retry {attempt + 1} after {delay:.1f}s (network error)")
|
|
113
120
|
time.sleep(delay)
|
|
114
121
|
return self._get(path, params, attempt + 1)
|
|
115
122
|
self._log(f"Error: {exc}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|