Skip to content

Commit 6ea18d9

Browse files
committed
Remove SqlAlchemy dependency from postgres container
Create a new generic database subclass - DependencyFreeDbContainer Remove test that was testing sqlalchemy support for driver types Add tests for supported versions of Postgres
1 parent efec6cb commit 6ea18d9

File tree

3 files changed

+54
-41
lines changed

3 files changed

+54
-41
lines changed

core/testcontainers/core/generic.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,24 @@
2525
pass
2626

2727

28+
class DependencyFreeDbContainer(DockerContainer):
29+
"""
30+
A generic database without any package dependencies
31+
"""
32+
def start(self) -> 'DbContainer':
33+
self._configure()
34+
super().start()
35+
self._verify_connection()
36+
return self
37+
38+
def _verify_connection(self) -> 'DependencyFreeDbContainer':
39+
"""override this method to ensure the databse is running and accepting connections"""
40+
raise NotImplementedError
41+
42+
def _configure(self) -> None:
43+
raise NotImplementedError
44+
45+
2846
class DbContainer(DockerContainer):
2947
"""
3048
Generic database container.

postgres/testcontainers/postgres/__init__.py

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,17 @@
1111
# License for the specific language governing permissions and limitations
1212
# under the License.
1313
import os
14-
import re
15-
from datetime import UTC, datetime
1614
from time import sleep
1715
from typing import Optional
1816

1917
from testcontainers.core.config import MAX_TRIES, SLEEP_TIME
20-
from testcontainers.core.generic import DbContainer
18+
from testcontainers.core.generic import DependencyFreeDbContainer
2119
from testcontainers.core.utils import raise_for_deprecated_parameter
22-
from testcontainers.core.waiting_utils import wait_container_is_ready
20+
from testcontainers.core.waiting_utils import (wait_container_is_ready,
21+
wait_for_logs)
2322

2423

25-
class PostgresContainer(DbContainer):
24+
class PostgresContainer(DependencyFreeDbContainer):
2625
"""
2726
Postgres database container.
2827
@@ -63,33 +62,34 @@ def _configure(self) -> None:
6362
self.with_env("POSTGRES_PASSWORD", self.password)
6463
self.with_env("POSTGRES_DB", self.dbname)
6564

66-
def get_connection_url(self, host=None) -> str:
67-
return super()._create_connection_url(
68-
dialect=f"postgresql+{self.driver}", username=self.username,
69-
password=self.password, dbname=self.dbname, host=host,
70-
port=self.port,
71-
)
72-
7365
@wait_container_is_ready()
74-
def _connect(self) -> None:
75-
count = 0
66+
def _verify_connection(self) -> None:
67+
d = wait_for_logs(self, ".*database system is ready to accept connections.*", MAX_TRIES, SLEEP_TIME)
68+
print(f"took {d} seconds to start db")
7669

77-
# ALTERNATE IMPLEMENTATION based on comments from @HofmeisterAn
78-
# expr = re.compile(r'(?P<ts>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{9}Z).*')
79-
# timestamp = datetime.now()
70+
count = 0
8071
while count < MAX_TRIES:
8172
status, _ = self.exec(f"pg_isready -hlocalhost -p{self.port} -U{self.username}")
8273
if status == 0:
8374
return
8475

85-
# ALTERNATE IMPLEMENTATION based on comments from @HofmeisterAn
86-
# stdout = self._container.logs(stderr = False, timestamps = True, since = timestamp)
87-
# lines = stdout.decode("utf-8").split("\n")
88-
# for line in lines:
89-
# if m:= re.match(expr, line):
90-
# timestamp = datetime.fromisoformat(m.groupdict()["ts"]).replace(tzinfo=None)
91-
# if "database system is ready to accept connections" in line:
92-
# return
93-
76+
print(f"pg_isready returned {status}")
9477
sleep(SLEEP_TIME)
95-
count += 1
78+
count += 1
79+
80+
raise RuntimeError("Postgres could not get into a ready state")
81+
# # ALTERNATE IMPLEMENTATION based on comments from @HofmeisterAn
82+
# # expr = re.compile(r'(?P<ts>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{9}Z).*')
83+
# # timestamp = datetime.now()
84+
85+
# # ALTERNATE IMPLEMENTATION based on comments from @HofmeisterAn
86+
# # stdout = self._container.logs(stderr = False, timestamps = True, since = timestamp)
87+
# # lines = stdout.decode("utf-8").split("\n")
88+
# # for line in lines:
89+
# # if m:= re.match(expr, line):
90+
# # timestamp = datetime.fromisoformat(m.groupdict()["ts"]).replace(tzinfo=None)
91+
# # if "database system is ready to accept connections" in line:
92+
# # return
93+
94+
# sleep(SLEEP_TIME)
95+
# count += 1

postgres/tests/test_postgres.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
1-
import sqlalchemy
21
from testcontainers.postgres import PostgresContainer
32

43

54
def test_docker_run_postgres():
6-
postgres_container = PostgresContainer("postgres:9.5")
7-
with postgres_container as postgres:
8-
engine = sqlalchemy.create_engine(postgres.get_connection_url())
9-
with engine.begin() as connection:
10-
result = connection.execute(sqlalchemy.text("select version()"))
11-
for row in result:
12-
assert row[0].lower().startswith("postgresql 9.5")
5+
# https://www.postgresql.org/support/versioning/
6+
supported_versions = ["11", "12", "13", "14", "latest"]
137

8+
for version in supported_versions:
9+
postgres_container = PostgresContainer(f"postgres:{version}")
10+
with postgres_container as postgres:
11+
status, msg = postgres.exec(f"pg_isready -hlocalhost -p{postgres.port} -U{postgres.username}")
12+
13+
assert msg.decode("utf-8").endswith("accepting connections\n")
14+
assert status == 0
1415

15-
def test_docker_run_postgres_with_driver_pg8000():
16-
postgres_container = PostgresContainer("postgres:9.5", driver="pg8000")
17-
with postgres_container as postgres:
18-
engine = sqlalchemy.create_engine(postgres.get_connection_url())
19-
with engine.begin() as connection:
20-
connection.execute(sqlalchemy.text("select 1=1"))

0 commit comments

Comments
 (0)