|
3 | 3 | import asyncio |
4 | 4 | import os |
5 | 5 | import uuid |
6 | | -from typing import AsyncGenerator |
7 | | -from urllib.parse import urlparse, urlunparse |
| 6 | +from typing import Any, AsyncGenerator |
| 7 | +from urllib.parse import quote, urlparse, urlunparse |
8 | 8 |
|
9 | 9 | import asyncpg |
10 | 10 | import psycopg |
|
19 | 19 | except ModuleNotFoundError: |
20 | 20 | uvloop = None # type: ignore[assignment] |
21 | 21 |
|
22 | | -from testcontainers.postgres import PostgresContainer |
| 22 | +from testcontainers.core.container import DockerContainer |
| 23 | +from testcontainers.core.wait_strategies import LogMessageWaitStrategy |
23 | 24 |
|
24 | 25 | from pgqueuer.db import SyncPsycopgDriver |
25 | 26 |
|
26 | 27 |
|
| 28 | +class PGQueuerPostgresContainer(DockerContainer): |
| 29 | + """Postgres container with modern wait strategy support.""" |
| 30 | + |
| 31 | + def __init__( |
| 32 | + self, |
| 33 | + image: str = "postgres:latest", |
| 34 | + port: int = 5432, |
| 35 | + username: str | None = None, |
| 36 | + password: str | None = None, |
| 37 | + dbname: str | None = None, |
| 38 | + *, |
| 39 | + driver: str | None = "psycopg2", |
| 40 | + **kwargs: Any, |
| 41 | + ) -> None: |
| 42 | + super().__init__(image=image, **kwargs) |
| 43 | + self.port = port |
| 44 | + self.username = username or os.environ.get("POSTGRES_USER", "test") |
| 45 | + self.password = password or os.environ.get("POSTGRES_PASSWORD", "test") |
| 46 | + self.dbname = dbname or os.environ.get("POSTGRES_DB", "test") |
| 47 | + self.driver_suffix = f"+{driver}" if driver else "" |
| 48 | + |
| 49 | + self.with_exposed_ports(port) |
| 50 | + self.with_env("POSTGRES_USER", self.username) |
| 51 | + self.with_env("POSTGRES_PASSWORD", self.password) |
| 52 | + self.with_env("POSTGRES_DB", self.dbname) |
| 53 | + self.waiting_for(LogMessageWaitStrategy("database system is ready to accept connections")) |
| 54 | + |
| 55 | + def get_connection_url(self, host: str | None = None) -> str: |
| 56 | + if self._container is None: |
| 57 | + msg = "container has not been started" |
| 58 | + raise RuntimeError(msg) |
| 59 | + |
| 60 | + host = host or self.get_container_host_ip() |
| 61 | + port = self.get_exposed_port(self.port) |
| 62 | + quoted_password = quote(self.password, safe=" +") |
| 63 | + return ( |
| 64 | + f"postgresql{self.driver_suffix}://{self.username}:{quoted_password}" |
| 65 | + f"@{host}:{port}/{self.dbname}" |
| 66 | + ) |
| 67 | + |
| 68 | + |
27 | 69 | @pytest.fixture(scope="session", autouse=True) |
28 | 70 | def event_loop_policy() -> asyncio.AbstractEventLoopPolicy: |
29 | 71 | """Provide uvloop if available; fallback to default policy.""" |
@@ -63,7 +105,7 @@ async def postgres_container() -> AsyncGenerator[str, None]: |
63 | 105 | ] + (["-c", "vacuum_buffer_usage_limit=8MB"] if int(postgres_version) >= 16 else []) |
64 | 106 |
|
65 | 107 | container = ( |
66 | | - PostgresContainer(f"postgres:{postgres_version}", driver=None) |
| 108 | + PGQueuerPostgresContainer(f"postgres:{postgres_version}", driver=None) |
67 | 109 | .with_command(commands) |
68 | 110 | .with_kwargs(tmpfs={"/var/lib/pg/data": "rw"}) |
69 | 111 | .with_envs(PGDATA="/var/lib/pg/data") |
|
0 commit comments