1919import shutil
2020import socket
2121import subprocess
22- import time
2322from collections import defaultdict
23+ from itertools import chain
2424from typing import Any
2525
26- from requests import get , post
27- from requests .exceptions import ConnectionError as ReqConnectionError
26+ from requests import Session , post
27+ from requests .adapters import HTTPAdapter
28+ from urllib3 .util .retry import Retry
2829
2930from opentelemetry .semconv .schemas import Schemas
3031
@@ -50,19 +51,27 @@ def _extract_violations(report: dict) -> list:
5051 """
5152 raw : list [dict ] = []
5253
53- def collect (obj : Any ) -> None :
54- if isinstance (obj , dict ):
55- if "live_check_result" in obj :
56- for advice in obj ["live_check_result" ].get ("all_advice" , []):
57- if advice .get ("level" ) == "violation" :
58- raw .append (advice )
59- for val in obj .values ():
60- collect (val )
61- elif isinstance (obj , list ):
62- for item in obj :
63- collect (item )
54+ def _collect (obj : Any ) -> list [dict ]:
55+ match obj :
56+ case {"live_check_result" : {"all_advice" : advices }, ** _rest }:
57+ violations = [
58+ a for a in advices if a .get ("level" ) == "violation"
59+ ]
60+ return violations + list (
61+ chain .from_iterable (_collect (v ) for v in obj .values ())
62+ )
63+ case dict ():
64+ return list (
65+ chain .from_iterable (_collect (v ) for v in obj .values ())
66+ )
67+ case list ():
68+ return list (
69+ chain .from_iterable (_collect (item ) for item in obj )
70+ )
71+ case _:
72+ return []
6473
65- collect (report )
74+ raw = _collect (report )
6675
6776 groups : dict [tuple , list ] = defaultdict (list )
6877 for violation in raw :
@@ -96,7 +105,7 @@ def collect(obj: Any) -> None:
96105
97106
98107def _format_violations (violations : list ) -> str :
99- """Format violations list as human-readable text (mirrors violations.j2 output) ."""
108+ """Format violations list as human-readable text."""
100109 lines = []
101110 for violation in violations :
102111 signal = ""
@@ -314,26 +323,29 @@ def start(self, timeout: int = 60) -> "WeaverLiveCheck":
314323 return self
315324
316325 def _wait_for_ready (self , timeout : int = 60 ) -> None :
317- for attempt in range (timeout ):
326+ retry = Retry (
327+ total = timeout ,
328+ backoff_factor = 1 ,
329+ backoff_max = 1 ,
330+ # Any non-2xx response from /health means weaver isn't ready yet.
331+ status_forcelist = list (range (300 , 600 )),
332+ allowed_methods = ["GET" ],
333+ )
334+ session = Session ()
335+ session .mount ("http://" , HTTPAdapter (max_retries = retry ))
336+ try :
337+ response = session .get (
338+ f"http://localhost:{ self ._admin_port } /health" , timeout = 5
339+ )
340+ response .raise_for_status ()
341+ except Exception as exc : # pylint: disable=broad-except
318342 if self ._process is not None and self ._process .poll () is not None :
319343 raise RuntimeError (
320344 f"WeaverLiveCheck process exited unexpectedly (code { self ._process .returncode } )"
321- )
322- try :
323- response = get (
324- f"http://localhost:{ self ._admin_port } /health" , timeout = 5
325- )
326- if response .status_code == 200 :
327- return
328- logger .debug (
329- "Health check returned %s, try %s" ,
330- response .status_code ,
331- attempt ,
332- )
333- except ReqConnectionError as exc :
334- logger .debug ("Health check connection error: %s" , exc )
335- time .sleep (1 )
336- raise TimeoutError ("WeaverLiveCheck did not become ready in time" )
345+ ) from exc
346+ raise TimeoutError (
347+ "WeaverLiveCheck did not become ready in time"
348+ ) from exc
337349
338350 @property
339351 def otlp_endpoint (self ) -> str :
0 commit comments