|
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