|
14 | 14 |
|
15 | 15 |
|
16 | 16 | class SqlConnectWaitStrategy(WaitStrategy):
|
17 |
| - """ |
18 |
| - Wait strategy that tests database connectivity until it succeeds or times out. |
19 |
| -
|
20 |
| - This strategy performs database connection testing using SQLAlchemy directly, |
21 |
| - handling transient connection errors and providing appropriate retry logic |
22 |
| - for database connectivity testing. |
23 |
| - """ |
| 17 | + """Wait strategy for database connectivity testing using SQLAlchemy.""" |
24 | 18 |
|
25 | 19 | def __init__(self):
|
26 | 20 | super().__init__()
|
27 |
| - self.transient_exceptions = (TimeoutError, ConnectionError, *ADDITIONAL_TRANSIENT_ERRORS) |
| 21 | + # Configure transient exceptions using the framework's built-in method |
| 22 | + self.with_transient_exceptions(TimeoutError, ConnectionError, *ADDITIONAL_TRANSIENT_ERRORS) |
28 | 23 |
|
29 | 24 | def wait_until_ready(self, container: WaitStrategyTarget) -> None:
|
30 |
| - """ |
31 |
| - Test database connectivity with retry logic until it succeeds or times out. |
32 |
| -
|
33 |
| - Args: |
34 |
| - container: The SQL container that must have get_connection_url method |
35 |
| -
|
36 |
| - Raises: |
37 |
| - TimeoutError: If connection fails after timeout |
38 |
| - AttributeError: If container doesn't have get_connection_url method |
39 |
| - ImportError: If SQLAlchemy is not installed |
40 |
| - Exception: Any non-transient errors from connection attempts |
41 |
| - """ |
42 |
| - import time |
43 |
| - |
| 25 | + """Test database connectivity with retry logic until success or timeout.""" |
44 | 26 | if not hasattr(container, "get_connection_url"):
|
45 | 27 | raise AttributeError(f"Container {container} must have a get_connection_url method")
|
46 | 28 |
|
47 | 29 | try:
|
48 | 30 | import sqlalchemy
|
49 | 31 | except ImportError as e:
|
50 |
| - logger.error("SQLAlchemy is required for database connectivity testing") |
51 | 32 | raise ImportError("SQLAlchemy is required for database containers") from e
|
52 | 33 |
|
53 |
| - start_time = time.time() |
54 |
| - |
55 |
| - while True: |
56 |
| - if time.time() - start_time > self._startup_timeout: |
57 |
| - raise TimeoutError( |
58 |
| - f"Database connection failed after {self._startup_timeout}s timeout. " |
59 |
| - f"Hint: Check if the container is ready and the database is accessible." |
60 |
| - ) |
61 |
| - |
| 34 | + def _test_connection() -> bool: |
| 35 | + """Test database connection, returning True if successful.""" |
| 36 | + engine = sqlalchemy.create_engine(container.get_connection_url()) |
62 | 37 | try:
|
63 |
| - connection_url = container.get_connection_url() |
64 |
| - engine = sqlalchemy.create_engine(connection_url) |
65 |
| - |
66 |
| - try: |
67 |
| - with engine.connect(): |
68 |
| - logger.info("Database connection test successful") |
69 |
| - return |
70 |
| - except Exception as e: |
71 |
| - logger.debug(f"Database connection attempt failed: {e}") |
72 |
| - raise |
73 |
| - finally: |
74 |
| - engine.dispose() |
75 |
| - |
76 |
| - except self.transient_exceptions as e: |
77 |
| - logger.debug(f"Connection attempt failed: {e}, retrying in {self._poll_interval}s...") |
78 |
| - except Exception as e: |
79 |
| - logger.error(f"Connection failed with non-transient error: {e}") |
80 |
| - raise |
81 |
| - |
82 |
| - time.sleep(self._poll_interval) |
| 38 | + with engine.connect(): |
| 39 | + logger.info("Database connection successful") |
| 40 | + return True |
| 41 | + finally: |
| 42 | + engine.dispose() |
| 43 | + |
| 44 | + # Use the framework's built-in _poll method with automatic retry and transient exception handling |
| 45 | + result = self._poll(_test_connection) |
| 46 | + if not result: |
| 47 | + raise TimeoutError(f"Database connection failed after {self._startup_timeout}s timeout") |
0 commit comments