ry-testkit 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: ry-testkit
3
+ Version: 0.1.0
4
+ Summary: Shared integration test base classes for Redis and Postgres containers
5
+ Author: Ross Yeager
6
+ Requires-Python: >=3.12
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: psycopg2-binary
9
+ Requires-Dist: redis
10
+ Requires-Dist: testcontainers
11
+
12
+ # ry-testkit
13
+
14
+ Shared Redis/Postgres integration test base classes.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install ry-testkit
20
+ ```
@@ -0,0 +1,9 @@
1
+ # ry-testkit
2
+
3
+ Shared Redis/Postgres integration test base classes.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install ry-testkit
9
+ ```
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ry-testkit"
7
+ version = "0.1.0"
8
+ description = "Shared integration test base classes for Redis and Postgres containers"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ authors = [{ name = "Ross Yeager" }]
12
+ dependencies = [
13
+ "psycopg2-binary",
14
+ "redis",
15
+ "testcontainers",
16
+ ]
17
+
18
+ [tool.setuptools]
19
+ package-dir = {"" = "src"}
20
+
21
+ [tool.setuptools.packages.find]
22
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,6 @@
1
+ """Shared test helpers for containerized integration tests."""
2
+
3
+ from .postgres import PostgresOnlyTestBase
4
+ from .redis import RedisOnlyTestBase
5
+
6
+ __all__ = ["PostgresOnlyTestBase", "RedisOnlyTestBase"]
@@ -0,0 +1,83 @@
1
+ import os
2
+ import time
3
+ import typing as T
4
+ import unittest
5
+
6
+ import psycopg2
7
+ from psycopg2 import OperationalError
8
+ from testcontainers.core.container import DockerContainer # type: ignore[import-untyped]
9
+ from testcontainers.core.waiting_utils import wait_for_logs # type: ignore[import-untyped]
10
+
11
+
12
+ class PostgresOnlyTestBase(unittest.TestCase):
13
+ container: T.Optional[DockerContainer] = None
14
+ _started = False
15
+
16
+ IMAGE = os.getenv("TEST_POSTGRES_IMAGE", "postgres:latest")
17
+ USER = os.getenv("TEST_POSTGRES_USER", "test")
18
+ PASSWORD = os.getenv("TEST_POSTGRES_PASSWORD", "test")
19
+ DB = os.getenv("TEST_POSTGRES_DB", "testdb")
20
+ READY_TIMEOUT_S = int(os.getenv("TEST_POSTGRES_READY_TIMEOUT_S", "60"))
21
+ READY_SLEEP_S = float(os.getenv("TEST_POSTGRES_READY_SLEEP_S", "0.5"))
22
+
23
+ @classmethod
24
+ def _dsn(cls) -> str:
25
+ assert cls.container is not None
26
+ return (
27
+ f"dbname={cls.DB} user={cls.USER} password={cls.PASSWORD} "
28
+ f"host={cls.container.get_container_host_ip()} "
29
+ f"port={cls.container.get_exposed_port(5432)}"
30
+ )
31
+
32
+ @classmethod
33
+ def _wait_ready(cls) -> None:
34
+ deadline = time.time() + cls.READY_TIMEOUT_S
35
+ while time.time() < deadline:
36
+ try:
37
+ conn = psycopg2.connect(cls._dsn())
38
+ conn.close()
39
+ return
40
+ except OperationalError:
41
+ time.sleep(cls.READY_SLEEP_S)
42
+ raise RuntimeError("Postgres test container did not become ready")
43
+
44
+ @classmethod
45
+ def setUpClass(cls) -> None:
46
+ super().setUpClass()
47
+ if cls._started:
48
+ return
49
+
50
+ c = DockerContainer(cls.IMAGE)
51
+ c.with_env("POSTGRES_USER", cls.USER)
52
+ c.with_env("POSTGRES_PASSWORD", cls.PASSWORD)
53
+ c.with_env("POSTGRES_DB", cls.DB)
54
+ c.with_exposed_ports(5432)
55
+ c.start()
56
+
57
+ try:
58
+ wait_for_logs(c, "database system is ready to accept connections", timeout=cls.READY_TIMEOUT_S)
59
+ except Exception:
60
+ pass
61
+
62
+ cls.container = c
63
+ cls._wait_ready()
64
+ cls._started = True
65
+
66
+ @classmethod
67
+ def tearDownClass(cls) -> None:
68
+ if cls.container is not None:
69
+ cls.container.stop()
70
+ cls.container = None
71
+ cls._started = False
72
+ super().tearDownClass()
73
+
74
+ @classmethod
75
+ def postgres_connection_info(cls) -> T.Dict[str, T.Any]:
76
+ assert cls.container is not None
77
+ return {
78
+ "host": cls.container.get_container_host_ip(),
79
+ "port": int(cls.container.get_exposed_port(5432)),
80
+ "user": cls.USER,
81
+ "password": cls.PASSWORD,
82
+ "db": cls.DB,
83
+ }
@@ -0,0 +1,75 @@
1
+ import os
2
+ import time
3
+ import typing as T
4
+ import unittest
5
+
6
+ import redis
7
+ from redis import RedisError
8
+ from testcontainers.core.container import DockerContainer # type: ignore[import-untyped]
9
+ from testcontainers.core.waiting_utils import wait_for_logs # type: ignore[import-untyped]
10
+
11
+
12
+ class RedisOnlyTestBase(unittest.TestCase):
13
+ container: T.Optional[DockerContainer] = None
14
+ _started = False
15
+
16
+ IMAGE = os.getenv("TEST_REDIS_IMAGE", "redis:latest")
17
+ READY_TIMEOUT_S = int(os.getenv("TEST_REDIS_READY_TIMEOUT_S", "60"))
18
+ READY_SLEEP_S = float(os.getenv("TEST_REDIS_READY_SLEEP_S", "0.5"))
19
+
20
+ @classmethod
21
+ def _client(cls) -> redis.Redis:
22
+ assert cls.container is not None
23
+ return redis.Redis(
24
+ host=cls.container.get_container_host_ip(),
25
+ port=int(cls.container.get_exposed_port(6379)),
26
+ db=0,
27
+ decode_responses=True,
28
+ )
29
+
30
+ @classmethod
31
+ def _wait_ready(cls) -> None:
32
+ deadline = time.time() + cls.READY_TIMEOUT_S
33
+ while time.time() < deadline:
34
+ try:
35
+ if cls._client().ping():
36
+ return
37
+ except RedisError:
38
+ time.sleep(cls.READY_SLEEP_S)
39
+ raise RuntimeError("Redis test container did not become ready")
40
+
41
+ @classmethod
42
+ def setUpClass(cls) -> None:
43
+ super().setUpClass()
44
+ if cls._started:
45
+ return
46
+
47
+ c = DockerContainer(cls.IMAGE)
48
+ c.with_exposed_ports(6379)
49
+ c.start()
50
+
51
+ try:
52
+ wait_for_logs(c, "Ready to accept connections", timeout=cls.READY_TIMEOUT_S)
53
+ except Exception:
54
+ pass
55
+
56
+ cls.container = c
57
+ cls._wait_ready()
58
+ cls._started = True
59
+
60
+ @classmethod
61
+ def tearDownClass(cls) -> None:
62
+ if cls.container is not None:
63
+ cls.container.stop()
64
+ cls.container = None
65
+ cls._started = False
66
+ super().tearDownClass()
67
+
68
+ @classmethod
69
+ def redis_connection_info(cls) -> T.Dict[str, T.Any]:
70
+ assert cls.container is not None
71
+ return {
72
+ "host": cls.container.get_container_host_ip(),
73
+ "port": int(cls.container.get_exposed_port(6379)),
74
+ "db": 0,
75
+ }
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: ry-testkit
3
+ Version: 0.1.0
4
+ Summary: Shared integration test base classes for Redis and Postgres containers
5
+ Author: Ross Yeager
6
+ Requires-Python: >=3.12
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: psycopg2-binary
9
+ Requires-Dist: redis
10
+ Requires-Dist: testcontainers
11
+
12
+ # ry-testkit
13
+
14
+ Shared Redis/Postgres integration test base classes.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install ry-testkit
20
+ ```
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/ry_testkit/__init__.py
4
+ src/ry_testkit/postgres.py
5
+ src/ry_testkit/redis.py
6
+ src/ry_testkit.egg-info/PKG-INFO
7
+ src/ry_testkit.egg-info/SOURCES.txt
8
+ src/ry_testkit.egg-info/dependency_links.txt
9
+ src/ry_testkit.egg-info/requires.txt
10
+ src/ry_testkit.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ psycopg2-binary
2
+ redis
3
+ testcontainers
@@ -0,0 +1 @@
1
+ ry_testkit