1616 ADDITIONAL_TRANSIENT_ERRORS .append (DBAPIError )
1717except ImportError :
1818 logger .debug ("SQLAlchemy not available, skipping DBAPIError handling" )
19+ SQL_TRANSIENT_EXCEPTIONS = (TimeoutError , ConnectionError , * ADDITIONAL_TRANSIENT_ERRORS )
1920
2021
21- class DatabaseConnectionWaitStrategy (WaitStrategy ):
22+ class ExceptionsWaitStrategy (WaitStrategy ):
2223 """
23- Wait strategy for database connection readiness using SqlContainer._connect() .
24+ Generic wait strategy that retries a callable until it succeeds or times out .
2425
25- This strategy implements retry logic and calls SqlContainer._connect()
26- repeatedly until it succeeds or times out.
26+ This strategy can be used with any container method that needs retry logic
27+ for handling transient errors. It calls the provided callable repeatedly
28+ until it succeeds or the timeout is reached.
2729 """
2830
29- def __init__ (self , sql_container : "SqlContainer" ):
31+ def __init__ (self , callable_func : callable , transient_exceptions : Optional [ tuple ] = None ):
3032 super ().__init__ ()
31- self .sql_container = sql_container
33+ self .callable_func = callable_func
34+ self .transient_exceptions = transient_exceptions or (TimeoutError , ConnectionError )
3235
3336 def wait_until_ready (self , container : WaitStrategyTarget ) -> None :
3437 """
35- Test database connectivity with retry logic by calling SqlContainer._connect() .
38+ Execute the callable with retry logic until it succeeds or times out .
3639
3740 Raises:
38- TimeoutError: If connection fails after timeout
39- Exception: Any non-transient errors from _connect()
41+ TimeoutError: If callable fails after timeout
42+ Exception: Any non-transient errors from the callable
4043 """
4144 import time
4245
4346 start_time = time .time ()
4447
45- transient_exceptions = (TimeoutError , ConnectionError , * ADDITIONAL_TRANSIENT_ERRORS )
46-
4748 while True :
4849 if time .time () - start_time > self ._startup_timeout :
4950 raise TimeoutError (
50- f"Database connection failed after { self ._startup_timeout } s timeout. "
51- f"Hint: Check if the database container is ready and accessible ."
51+ f"Callable failed after { self ._startup_timeout } s timeout. "
52+ f"Hint: Check if the container is ready and the operation can succeed ."
5253 )
5354
5455 try :
55- self .sql_container . _connect ()
56+ self .callable_func ()
5657 return
57- except transient_exceptions as e :
58- logger .debug (f"Database connection attempt failed: { e } , retrying in { self ._poll_interval } s..." )
58+ except self . transient_exceptions as e :
59+ logger .debug (f"Callable attempt failed: { e } , retrying in { self ._poll_interval } s..." )
5960 except Exception as e :
60- logger .error (f"Database connection test failed with non-transient error: { e } " )
61+ logger .error (f"Callable failed with non-transient error: { e } " )
6162 raise
6263
6364 time .sleep (self ._poll_interval )
@@ -69,7 +70,7 @@ class SqlContainer(DockerContainer):
6970
7071 This class can serve as a base for database-specific container implementations.
7172 It provides connection management, URL construction, and basic lifecycle methods.
72- Database connection readiness is automatically handled by DatabaseConnectionWaitStrategy .
73+ Database connection readiness is automatically handled by ExceptionsWaitStrategy .
7374 """
7475
7576 def _connect (self ) -> None :
@@ -183,8 +184,7 @@ def start(self) -> "SqlContainer":
183184
184185 try :
185186 self ._configure ()
186- # Set up database connection wait strategy before starting
187- self .waiting_for (DatabaseConnectionWaitStrategy (self ))
187+ self .waiting_for (ExceptionsWaitStrategy (self ._connect , SQL_TRANSIENT_EXCEPTIONS ))
188188 super ().start ()
189189 self ._transfer_seed ()
190190 logger .info ("Database container started successfully" )
0 commit comments