pytest-testcontainers 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.
Files changed (36) hide show
  1. pytest_testcontainers-0.1.0/.gitignore +44 -0
  2. pytest_testcontainers-0.1.0/CHANGELOG.md +35 -0
  3. pytest_testcontainers-0.1.0/LICENSE +21 -0
  4. pytest_testcontainers-0.1.0/PKG-INFO +598 -0
  5. pytest_testcontainers-0.1.0/README.md +527 -0
  6. pytest_testcontainers-0.1.0/pyproject.toml +129 -0
  7. pytest_testcontainers-0.1.0/src/pytest_testcontainers/__init__.py +37 -0
  8. pytest_testcontainers-0.1.0/src/pytest_testcontainers/_internal/__init__.py +1 -0
  9. pytest_testcontainers-0.1.0/src/pytest_testcontainers/_internal/clean_session_admin.py +329 -0
  10. pytest_testcontainers-0.1.0/src/pytest_testcontainers/_internal/conn_info.py +29 -0
  11. pytest_testcontainers-0.1.0/src/pytest_testcontainers/_internal/docker_health.py +59 -0
  12. pytest_testcontainers-0.1.0/src/pytest_testcontainers/_internal/port_resolver.py +20 -0
  13. pytest_testcontainers-0.1.0/src/pytest_testcontainers/containers.py +181 -0
  14. pytest_testcontainers-0.1.0/src/pytest_testcontainers/errors.py +61 -0
  15. pytest_testcontainers-0.1.0/src/pytest_testcontainers/fixtures.py +295 -0
  16. pytest_testcontainers-0.1.0/src/pytest_testcontainers/makers.py +312 -0
  17. pytest_testcontainers-0.1.0/src/pytest_testcontainers/plugin.py +104 -0
  18. pytest_testcontainers-0.1.0/src/pytest_testcontainers/reuse.py +209 -0
  19. pytest_testcontainers-0.1.0/tests/conftest.py +76 -0
  20. pytest_testcontainers-0.1.0/tests/test_disabled.py +34 -0
  21. pytest_testcontainers-0.1.0/tests/test_disabled_fixture.py +15 -0
  22. pytest_testcontainers-0.1.0/tests/test_docker_not_running.py +80 -0
  23. pytest_testcontainers-0.1.0/tests/test_errors.py +46 -0
  24. pytest_testcontainers-0.1.0/tests/test_fixtures_clean_session_mongo.py +15 -0
  25. pytest_testcontainers-0.1.0/tests/test_fixtures_clean_session_psql.py +28 -0
  26. pytest_testcontainers-0.1.0/tests/test_fixtures_clean_session_redis.py +29 -0
  27. pytest_testcontainers-0.1.0/tests/test_fixtures_session.py +25 -0
  28. pytest_testcontainers-0.1.0/tests/test_makers_generic.py +17 -0
  29. pytest_testcontainers-0.1.0/tests/test_makers_mongo.py +14 -0
  30. pytest_testcontainers-0.1.0/tests/test_makers_mysql.py +14 -0
  31. pytest_testcontainers-0.1.0/tests/test_makers_no_docker.py +25 -0
  32. pytest_testcontainers-0.1.0/tests/test_makers_postgres.py +34 -0
  33. pytest_testcontainers-0.1.0/tests/test_makers_redis.py +14 -0
  34. pytest_testcontainers-0.1.0/tests/test_port_resolver.py +21 -0
  35. pytest_testcontainers-0.1.0/tests/test_reuse.py +179 -0
  36. pytest_testcontainers-0.1.0/tests/test_reuse_mode.py +42 -0
@@ -0,0 +1,44 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ *.egg
7
+ build/
8
+ dist/
9
+ .eggs/
10
+ pip-wheel-metadata/
11
+
12
+ # Virtualenvs
13
+ .venv/
14
+ venv/
15
+ env/
16
+
17
+ # uv
18
+ .uv/
19
+
20
+ # Testing
21
+ .pytest_cache/
22
+ .coverage
23
+ .coverage.*
24
+ htmlcov/
25
+ coverage.xml
26
+ .tox/
27
+ .nox/
28
+
29
+ # Editors / OS
30
+ .idea/
31
+ .vscode/
32
+ .DS_Store
33
+ *.swp
34
+ *.swo
35
+
36
+ # Type checkers / linters
37
+ .mypy_cache/
38
+ .ruff_cache/
39
+ .pyright_cache/
40
+
41
+ # Local
42
+ .envrc
43
+ .env
44
+ .env.local
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-05-08
11
+
12
+ Initial release.
13
+
14
+ ### Added
15
+
16
+ - Maker functions for Postgres, Redis, MySQL, MongoDB, RabbitMQ and a generic
17
+ `make_container()` escape hatch — each is a context manager wrapping the
18
+ upstream `testcontainers-python` class.
19
+ - Session-scoped fixtures: `tc_psql`, `tc_redis`, `tc_mysql`, `tc_mongo`,
20
+ `tc_rabbitmq`.
21
+ - Function-scoped fixtures: `tc_psql_func`, `tc_redis_func`, `tc_mysql_func`,
22
+ `tc_mongo_func`, `tc_rabbitmq_func`.
23
+ - Clean-session fixtures (session container + per-test fresh state):
24
+ `tc_psql_db`, `tc_mysql_db`, `tc_mongo_db`, `tc_redis_clean`.
25
+ - Clean-session admin commands run via Python client
26
+ (`psycopg`/`pymysql`/`pymongo`) when importable; `docker exec` fallback
27
+ otherwise. Redis clean-session is exec-only.
28
+ - Reuse mode (`--testcontainers-reuse` / `PYTEST_TESTCONTAINERS_REUSE=1`):
29
+ named per-worker containers (`<project>-tc-<service>-<worker_id>`), Ryuk
30
+ disabled, port-conflict detection on stopped-container restart.
31
+ - `--testcontainers-clean` to remove all `<project>-tc-*` containers.
32
+ - Normalized `pytest.UsageError` when Docker daemon is unreachable.
33
+ - `atexit` safety net for cleanup when `pytest_unconfigure` is skipped.
34
+ - Optional dependency extras: `clients-postgres`, `clients-mysql`,
35
+ `clients-mongo`, aggregate `clients`.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michał Pasternak and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,598 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-testcontainers
3
+ Version: 0.1.0
4
+ Summary: Named pytest fixtures and a maker convention on top of testcontainers-python.
5
+ Project-URL: Homepage, https://github.com/iplweb/pytest-testcontainers
6
+ Project-URL: Repository, https://github.com/iplweb/pytest-testcontainers
7
+ Project-URL: Issues, https://github.com/iplweb/pytest-testcontainers/issues
8
+ Project-URL: Changelog, https://github.com/iplweb/pytest-testcontainers/blob/main/CHANGELOG.md
9
+ Author-email: Michał Pasternak <michal.dtz@gmail.com>
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 Michał Pasternak and contributors
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: docker,fixtures,integration-testing,mongodb,mysql,postgres,pytest,pytest-plugin,rabbitmq,redis,testcontainers
33
+ Classifier: Development Status :: 4 - Beta
34
+ Classifier: Framework :: Pytest
35
+ Classifier: Intended Audience :: Developers
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: OS Independent
38
+ Classifier: Programming Language :: Python
39
+ Classifier: Programming Language :: Python :: 3
40
+ Classifier: Programming Language :: Python :: 3.10
41
+ Classifier: Programming Language :: Python :: 3.11
42
+ Classifier: Programming Language :: Python :: 3.12
43
+ Classifier: Programming Language :: Python :: 3.13
44
+ Classifier: Topic :: Software Development :: Testing
45
+ Classifier: Topic :: Software Development :: Testing :: Mocking
46
+ Requires-Python: >=3.10
47
+ Requires-Dist: docker>=6.1
48
+ Requires-Dist: pytest<9,>=7.4
49
+ Requires-Dist: testcontainers<5,>=4.7
50
+ Requires-Dist: tomli>=2; python_version < '3.11'
51
+ Provides-Extra: clients
52
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'clients'
53
+ Requires-Dist: pymongo>=4.6; extra == 'clients'
54
+ Requires-Dist: pymysql>=1.1; extra == 'clients'
55
+ Provides-Extra: clients-mongo
56
+ Requires-Dist: pymongo>=4.6; extra == 'clients-mongo'
57
+ Provides-Extra: clients-mysql
58
+ Requires-Dist: pymysql>=1.1; extra == 'clients-mysql'
59
+ Provides-Extra: clients-postgres
60
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'clients-postgres'
61
+ Provides-Extra: dev
62
+ Requires-Dist: pika; extra == 'dev'
63
+ Requires-Dist: pre-commit; extra == 'dev'
64
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'dev'
65
+ Requires-Dist: pymongo>=4.6; extra == 'dev'
66
+ Requires-Dist: pymysql>=1.1; extra == 'dev'
67
+ Requires-Dist: pytest-xdist; extra == 'dev'
68
+ Requires-Dist: redis; extra == 'dev'
69
+ Requires-Dist: ruff; extra == 'dev'
70
+ Description-Content-Type: text/markdown
71
+
72
+ # pytest-testcontainers
73
+
74
+ [![CI](https://github.com/iplweb/pytest-testcontainers/actions/workflows/ci.yml/badge.svg)](https://github.com/iplweb/pytest-testcontainers/actions/workflows/ci.yml)
75
+ [![PyPI version](https://img.shields.io/pypi/v/pytest-testcontainers.svg)](https://pypi.org/project/pytest-testcontainers/)
76
+ [![Python versions](https://img.shields.io/pypi/pyversions/pytest-testcontainers.svg)](https://pypi.org/project/pytest-testcontainers/)
77
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
78
+
79
+ **Named pytest fixtures + a maker convention on top of
80
+ [testcontainers-python](https://testcontainers-python.readthedocs.io/),
81
+ plus clean-session "fresh DB per test" fixtures that get you per-test isolation
82
+ without per-test container start.**
83
+
84
+ `testcontainers-python` is a great library, but it ships **no pytest
85
+ fixtures** and **no pytest entry point**. Every project that wants
86
+ `def test_db(pg): …` ends up rewriting the same ~150 lines of
87
+ `conftest.py`: session-scoped fixtures times five services, times
88
+ correct xdist worker handling, times reuse mode with stable names and
89
+ Ryuk disabled, times a normalized "Docker is not running" error.
90
+
91
+ `pytest-testcontainers` ships those 150 lines.
92
+
93
+ ## Why you might want this
94
+
95
+ - **Trivial defaults**: `def test_x(tc_psql): …` boots `postgres:16`
96
+ once per worker, gives you back a `PostgresContainer`, and tears it
97
+ down at session end.
98
+ - **Custom images in one line**: write your own fixture against
99
+ `make_postgres(image="acme/pg-with-extensions:16")` — the rest of
100
+ your test code is unchanged because the maker returns the same
101
+ upstream class your test already knows.
102
+ - **Clean-session "fresh DB per test"**: `tc_psql_db` creates a
103
+ `test_<hex>` database before the test and drops it after — same
104
+ isolation as a per-test container, **~480× faster**.
105
+ - **Reuse mode for fast dev loops**: `--testcontainers-reuse` keeps
106
+ named containers alive between pytest runs (Ryuk disabled, stable
107
+ per-worker names) so iterative dev doesn't pay the container-start
108
+ tax every iteration.
109
+ - **Normalized errors**: "Docker daemon not reachable?" gets you a
110
+ human-readable `pytest.UsageError` with remediation, not a wall of
111
+ docker-py traceback.
112
+ - **No magic**: just `@pytest.fixture` decorators on top of the
113
+ upstream classes. No env-var injection, no conftest re-import dance,
114
+ no `pytest_load_initial_conftests` cleverness. Read the source in 15
115
+ minutes.
116
+
117
+ ## Installation
118
+
119
+ ### Using uv (recommended)
120
+
121
+ ```bash
122
+ uv add --group dev pytest-testcontainers
123
+ ```
124
+
125
+ ### Using pip
126
+
127
+ ```bash
128
+ pip install pytest-testcontainers
129
+ ```
130
+
131
+ For the clean-session fast path (auto-detects clients; no install needed
132
+ for basic functionality):
133
+
134
+ ```bash
135
+ # all clean-session fast paths in one shot
136
+ pip install pytest-testcontainers[clients]
137
+
138
+ # or pick the ones you need
139
+ pip install pytest-testcontainers[clients-postgres]
140
+ pip install pytest-testcontainers[clients-mysql]
141
+ pip install pytest-testcontainers[clients-mongo]
142
+ ```
143
+
144
+ You also need a working Docker daemon — Docker Desktop, colima, Rancher
145
+ Desktop, or Podman with the Docker socket all work. No platform-specific
146
+ extra steps; if `docker.from_env().ping()` works, this plugin works.
147
+
148
+ ## Quickstart
149
+
150
+ ```python
151
+ def test_my_thing(tc_psql):
152
+ host = tc_psql.get_container_host_ip()
153
+ port = int(tc_psql.get_exposed_port(5432))
154
+ # connect with your library of choice (psycopg, SQLAlchemy, asyncpg, ...)
155
+ ```
156
+
157
+ That's the whole "hello world." `tc_psql` is the upstream
158
+ `PostgresContainer` — every method/attribute on it works the same as
159
+ when you build it yourself.
160
+
161
+ ## Services × variants
162
+
163
+ Each service ships three fixture variants plus a maker function:
164
+
165
+ | Service | Maker | Session fixture | Function-scoped fixture | Clean-session fixture |
166
+ |----------|------------------|------------------|-----------------------|-------------------|
167
+ | Postgres | `make_postgres` | `tc_psql` | `tc_psql_func` | `tc_psql_db` |
168
+ | Redis | `make_redis` | `tc_redis` | `tc_redis_func` | `tc_redis_clean` |
169
+ | MySQL | `make_mysql` | `tc_mysql` | `tc_mysql_func` | `tc_mysql_db` |
170
+ | MongoDB | `make_mongo` | `tc_mongo` | `tc_mongo_func` | `tc_mongo_db` |
171
+ | RabbitMQ | `make_rabbitmq` | `tc_rabbitmq` | `tc_rabbitmq_func` | — |
172
+ | (any) | `make_container(Cls, **kw)` | — | — | — |
173
+
174
+ - **Session** = one container per worker, lazy first-request start,
175
+ shared across all tests. The right default.
176
+ - **Function-scoped** = fresh container per test. **Almost always wrong**;
177
+ see the scope ladder below.
178
+ - **Clean-session** = session container + per-test fresh DB / keyspace.
179
+ What most users who think they need a function-scoped fixture actually
180
+ want.
181
+ - **`make_container(Cls, ...)`** is the escape hatch for services we
182
+ don't ship a fixture for (Kafka, Localstack, your bespoke image).
183
+
184
+ ## Image overrides — write a custom fixture in one line
185
+
186
+ You don't override defaults in some config file — you write your own
187
+ fixture using the maker:
188
+
189
+ ```python
190
+ import pytest
191
+ from pytest_testcontainers import make_postgres
192
+
193
+ # Postgres 18.
194
+ @pytest.fixture(scope="session")
195
+ def pg_18():
196
+ with make_postgres(image="postgres:18") as pg:
197
+ yield pg
198
+
199
+ # Specific minor version (pinning for reproducibility).
200
+ @pytest.fixture(scope="session")
201
+ def pg_pinned():
202
+ with make_postgres(image="postgres:16.13") as pg:
203
+ yield pg
204
+
205
+ # Custom user-built image (e.g. one with extensions baked in).
206
+ @pytest.fixture(scope="session")
207
+ def my_psql():
208
+ with make_postgres(image="acme/pg-with-postgis:16") as pg:
209
+ yield pg
210
+
211
+ # Custom image + extra env (image-specific tuning knobs).
212
+ @pytest.fixture(scope="session")
213
+ def my_psql_fast():
214
+ with make_postgres(
215
+ image="acme/pg-with-postgis:16",
216
+ env={
217
+ "POSTGRES_INITDB_ARGS": "--encoding=UTF8",
218
+ "POSTGRES_HOST_AUTH_METHOD": "trust",
219
+ },
220
+ ) as pg:
221
+ yield pg
222
+
223
+ # Custom credentials (must match what the app expects).
224
+ @pytest.fixture(scope="session")
225
+ def my_psql_creds():
226
+ with make_postgres(
227
+ image="acme/pg:16",
228
+ username="appuser",
229
+ password="appsecret",
230
+ database="appdb",
231
+ ) as pg:
232
+ yield pg
233
+ ```
234
+
235
+ Same pattern for `make_redis`, `make_mysql`, `make_mongo`,
236
+ `make_rabbitmq`. **Swapping the image is a one-line change**; the rest
237
+ of your test code is unchanged because the maker returns a vanilla
238
+ `testcontainers-python` instance with the same API.
239
+
240
+ ### Override the built-in `tc_psql` directly
241
+
242
+ If you want *one* customized PG everywhere instead of writing a new
243
+ fixture name, redefine `tc_psql` in your own `conftest.py` — pytest's
244
+ nearest-conftest resolution takes the user's version and silently
245
+ shadows the plugin's:
246
+
247
+ ```python
248
+ # conftest.py
249
+ import pytest
250
+ from pytest_testcontainers import make_postgres
251
+
252
+ @pytest.fixture(scope="session")
253
+ def tc_psql():
254
+ with make_postgres(image="acme/pg:16", username="bpp", password="pw", database="bpp") as pg:
255
+ yield pg
256
+ ```
257
+
258
+ ## Container scope: when to use what
259
+
260
+ This is the educational core. **Read this before reaching for
261
+ `tc_psql_func`.**
262
+
263
+ ### Default: session fixture (`tc_psql`)
264
+
265
+ One container per worker, shared across all tests. Cheapest, fastest.
266
+ The right default.
267
+
268
+ Use for:
269
+ - Read-heavy tests.
270
+ - Tests that wrap their work in transactions and roll back
271
+ (pytest-django's `db` fixture; SQLAlchemy savepoints).
272
+ - Tests that delete their own rows in teardown.
273
+
274
+ Don't use for:
275
+ - DDL-heavy tests (`CREATE TABLE` / `ALTER TABLE` / `CREATE EXTENSION`)
276
+ unless rolled back inside a transaction.
277
+ - Tests that mutate global server state (`ALTER SYSTEM SET …`,
278
+ replication slots, prepared statements that persist).
279
+
280
+ ### Clean-session: session container + fresh DB (`tc_psql_db`)
281
+
282
+ Same container as above, but each test gets a brand-new database
283
+ created and dropped around it.
284
+
285
+ Use for:
286
+ - DDL-heavy suites (Django migration tests; schema-change tests).
287
+ - Tests that vacuum, reset sequences with autocommit, or do anything
288
+ else that fights with transactional isolation.
289
+ - Replication slot / `pg_stat_*` tests where you want a clean view.
290
+
291
+ Cost: ~50–200ms per test for `CREATE DATABASE` / `DROP DATABASE`. Two
292
+ orders of magnitude faster than per-test container start.
293
+
294
+ This is what most users who think they need a function-scoped container
295
+ actually want.
296
+
297
+ ### Function-scoped container (`tc_psql_func`) — **avoid**
298
+
299
+ Fresh container per test. Available, **strongly discouraged**.
300
+
301
+ The math, with concrete numbers:
302
+
303
+ > **Container start = 3–5 seconds for Postgres.**
304
+ > **100 tests × 5s = 8 minutes wasted on container startup.**
305
+ > **The clean-session pattern gives the same isolation ~480× faster**
306
+ > (50ms per fresh DB vs 5s per fresh container).
307
+
308
+ Use ONLY when:
309
+
310
+ 1. Test mutates global server config that can't be reset (`ALTER
311
+ SYSTEM SET …` followed by a restart requirement).
312
+ 2. Test corrupts container-level state (broken cluster, FS corruption,
313
+ killed `postmaster`, etc.).
314
+ 3. You're testing the testcontainer machinery itself.
315
+
316
+ If your reasoning is "I want isolation between tests," that's the
317
+ clean-session pattern, not this.
318
+
319
+ ### Why we don't ship "shared DB + transaction rollback"
320
+
321
+ (Sometimes called "Variant B".) **Doesn't work for**:
322
+ - DDL tests (migrations, schema changes) — DDL implicit-commits in
323
+ many SQL dialects, breaking the rollback-around-each-test model.
324
+ - `VACUUM`, sequence operations with autocommit.
325
+ - Replication state.
326
+ - Anything cross-connection.
327
+
328
+ If you genuinely want this pattern for your read-heavy suite, write
329
+ your own fixture against `tc_psql` — but this plugin doesn't try to
330
+ ship a one-size-fits-all transactional layer.
331
+
332
+ ## Surprising thing #1: xdist `scope="session"` is per-worker
333
+
334
+ If you only read one section, read this one.
335
+
336
+ Under `pytest-xdist`, each worker process runs its own pytest session.
337
+ `@pytest.fixture(scope="session")` therefore means **"one per
338
+ worker"**, NOT "one for the whole pytest invocation". With `-n 8`,
339
+ you get **8 containers**, one per worker:
340
+
341
+ ```
342
+ $ pytest -n 4 tests/ # 4 workers
343
+ [gw0] container started: postgres on localhost:54321
344
+ [gw1] container started: postgres on localhost:54322
345
+ [gw2] container started: postgres on localhost:54323
346
+ [gw3] container started: postgres on localhost:54324
347
+ ```
348
+
349
+ Many users assume the opposite (because "session" sounds like
350
+ "global"). It isn't.
351
+
352
+ If you need **one container shared across all workers**, you need to
353
+ start it before pytest forks workers — that's eager-start machinery
354
+ that lives in
355
+ [`pytest-testcontainers-django`](https://pypi.org/project/pytest-testcontainers-django/)
356
+ (if you're a Django user) or in a wrapper script:
357
+
358
+ ```bash
359
+ # wrapper.sh — start once, pass coordinates to all workers
360
+ docker run -d --rm --name shared-pg \
361
+ -e POSTGRES_PASSWORD=test \
362
+ -p 54321:5432 postgres:16
363
+ export SHARED_PG_HOST=localhost
364
+ export SHARED_PG_PORT=54321
365
+ trap "docker rm -f shared-pg" EXIT
366
+ PYTEST_TESTCONTAINERS=0 pytest -n 8 tests/
367
+ ```
368
+
369
+ ```python
370
+ # conftest.py — each worker reads env, no container started by us
371
+ import os, pytest
372
+
373
+ @pytest.fixture(scope="session")
374
+ def shared_pg():
375
+ return {
376
+ "host": os.environ["SHARED_PG_HOST"],
377
+ "port": int(os.environ["SHARED_PG_PORT"]),
378
+ "username": "postgres",
379
+ "password": "test",
380
+ }
381
+ ```
382
+
383
+ `PYTEST_TESTCONTAINERS=0` (or `--no-testcontainers`) disables our
384
+ `tc_*` fixtures so they don't try to start their own per-worker
385
+ container alongside yours.
386
+
387
+ ## Clean-session pattern walkthrough — `tc_psql_db`
388
+
389
+ `tc_psql_db` yields a `DbConnInfo` (host/port/user/pass/db) for a
390
+ brand-new Postgres database created on the session-scoped `tc_psql`
391
+ container. The DB is dropped after the test:
392
+
393
+ ```python
394
+ from pytest_testcontainers import DbConnInfo
395
+
396
+ def test_isolated_schema(tc_psql_db: DbConnInfo):
397
+ # tc_psql_db.database is e.g. "test_a3f9b2c1d4e5"
398
+ url = tc_psql_db.url() # "postgresql://test:test@localhost:5xxxx/test_a3f9b2c1d4e5"
399
+
400
+ import psycopg
401
+ with psycopg.connect(**dataclasses.asdict(tc_psql_db)) as conn:
402
+ conn.execute("CREATE TABLE widgets (id serial PRIMARY KEY)")
403
+ # …
404
+ # On teardown, DROP DATABASE … WITH (FORCE) wipes everything.
405
+ ```
406
+
407
+ The DB name is `test_<12-hex>` (`secrets.token_hex(6)` — ~6e13 unique
408
+ values per session, more than enough for any conceivable test count).
409
+ Same shape for `tc_mysql_db`, `tc_mongo_db`. `tc_redis_clean`
410
+ `FLUSHALL`-s the session Redis container after each test instead.
411
+
412
+ ### How admin commands run
413
+
414
+ We need to issue six single-shot commands behind the clean-session fixtures
415
+ (`CREATE DATABASE`, `DROP DATABASE`, `dropDatabase()`, `FLUSHALL`).
416
+ Two paths:
417
+
418
+ 1. **Python client fast path** (~5ms/call) — used automatically when
419
+ `psycopg` / `pymysql` / `pymongo` is importable.
420
+ 2. **`docker exec` fallback** (~50–100ms/call) — used otherwise. Zero
421
+ host-side client deps. Works out of the box.
422
+
423
+ Both raise the same `CleanSessionFixtureError` so user code catches one
424
+ exception type. To get the fast path everywhere in one install:
425
+
426
+ ```bash
427
+ pip install pytest-testcontainers[clients]
428
+ ```
429
+
430
+ Or per-service: `pytest-testcontainers[clients-postgres]`,
431
+ `[clients-mysql]`, `[clients-mongo]`. Redis is exec-only by design — for
432
+ a single `FLUSHALL`, opening a TCP connection costs more than just
433
+ exec-ing the in-container CLI.
434
+
435
+ The fallback emits a one-shot stderr advisory the first time it runs
436
+ in a session; suppress with `PYTEST_TESTCONTAINERS_QUIET=1`.
437
+
438
+ ## Building custom services with `make_container`
439
+
440
+ For services we don't ship a maker for, use the generic escape hatch:
441
+
442
+ ```python
443
+ import pytest
444
+ from testcontainers.kafka import KafkaContainer
445
+ from pytest_testcontainers import make_container
446
+
447
+ @pytest.fixture(scope="session")
448
+ def tc_kafka():
449
+ with make_container(KafkaContainer, image="confluentinc/cp-kafka:7.5.0") as k:
450
+ yield k
451
+ ```
452
+
453
+ Every plumbing concern — daemon ping, reuse name, atexit cleanup,
454
+ Ryuk-disable-when-reuse — applies. `args`/`kwargs` go to the upstream
455
+ constructor verbatim.
456
+
457
+ ## Reuse mode
458
+
459
+ For iterative dev loops where you don't want to pay container-start
460
+ on every pytest run:
461
+
462
+ ```bash
463
+ PYTEST_TESTCONTAINERS_REUSE=1 pytest tests/
464
+ # or
465
+ pytest --testcontainers-reuse tests/
466
+ ```
467
+
468
+ What changes:
469
+ - Each container gets a stable name `<project>-tc-<service>-<worker>`
470
+ (e.g. `myproject-tc-psql-master`). The project name comes from
471
+ `pyproject.toml [project].name`.
472
+ - Ryuk (testcontainers' reaper) is disabled so the named containers
473
+ survive between runs.
474
+ - On the next run we look up by name — found-and-running gets bound
475
+ immediately; found-stopped gets restarted; not-found falls through
476
+ to fresh start.
477
+
478
+ To clean up:
479
+
480
+ ```bash
481
+ pytest --testcontainers-clean
482
+ ```
483
+
484
+ This stops and removes all `<project>-tc-*` containers and exits with
485
+ code 0 — a one-liner equivalent of the docker-py `containers.list +
486
+ remove(force=True)` loop.
487
+
488
+ **Two pytest invocations running concurrently**: each must use its
489
+ own `PYTEST_TESTCONTAINERS_PROJECT` to avoid name collisions. The
490
+ plugin doesn't auto-namespace by PID — that would defeat the "reuse
491
+ across runs" point.
492
+
493
+ ## Configuration
494
+
495
+ No TOML config table. The handful of toggles read at maker-call time:
496
+
497
+ ### Environment variables
498
+
499
+ | Variable | Effect |
500
+ |-------------------------------------------|---------------------------------------------|
501
+ | `PYTEST_TESTCONTAINERS=0` | Disable plugin fixtures (raise UsageError). |
502
+ | `PYTEST_TESTCONTAINERS_REUSE=1` | Reuse named containers across runs. |
503
+ | `PYTEST_TESTCONTAINERS_PROJECT=<name>` | Override the `<project>` part of reuse names. |
504
+ | `PYTEST_TESTCONTAINERS_NO_DAEMON_CHECK=1` | Skip Docker daemon ping (rare). |
505
+ | `PYTEST_TESTCONTAINERS_QUIET=1` | Suppress one-shot informational advisories. |
506
+
507
+ ### CLI flags
508
+
509
+ | Flag | Same as |
510
+ |---------------------------------|--------------------------------------|
511
+ | `--no-testcontainers` | `PYTEST_TESTCONTAINERS=0` |
512
+ | `--testcontainers-reuse` | `PYTEST_TESTCONTAINERS_REUSE=1` |
513
+ | `--testcontainers-no-reuse` | force fresh-each-run mode |
514
+ | `--testcontainers-project=NAME` | `PYTEST_TESTCONTAINERS_PROJECT=NAME` |
515
+ | `--testcontainers-clean` | prune `<project>-tc-*` and exit 0 |
516
+
517
+ CLI > env > defaults.
518
+
519
+ ## Error scenarios
520
+
521
+ When Docker is not running you get a normalized `pytest.UsageError`,
522
+ not a wall of docker-py traceback:
523
+
524
+ ```
525
+ [pytest-testcontainers] Docker daemon is not reachable.
526
+ Is Docker Desktop / colima / Rancher Desktop running?
527
+
528
+ Options:
529
+ - start it and re-run pytest, OR
530
+ - disable the plugin: --no-testcontainers (or PYTEST_TESTCONTAINERS=0), OR
531
+ - point at remote Docker via DOCKER_HOST.
532
+
533
+ Underlying error: ...
534
+ ```
535
+
536
+ When a stopped reused container can't be brought back (typically
537
+ because the previously-mapped port is now held by something else):
538
+
539
+ ```
540
+ [pytest-testcontainers] Cannot start existing container 'myproj-tc-psql-master': ...
541
+
542
+ Most common cause: another process now holds the port this
543
+ container was previously bound to. To start fresh:
544
+ docker rm -f myproj-tc-psql-master
545
+ Or pass --testcontainers-no-reuse for this run.
546
+ ```
547
+
548
+ When a clean-session admin command fails (`CREATE DATABASE` etc.), you get
549
+ `CleanSessionFixtureError` with `command`, `stderr`, and (for the
550
+ docker-exec path) `exit_code` — surfaced like any other in-test
551
+ exception.
552
+
553
+ ## Comparison
554
+
555
+ | Tool | Backend | Lifecycle | Notes |
556
+ |--------------------------------|-----------------|------------------------|--------------------------------------------------|
557
+ | `testcontainers-python` | docker-py | manual | The library this plugin sits on. No fixtures. |
558
+ | **`pytest-testcontainers`** | testcontainers | session (lazy) / func | **This.** Fixtures, makers, clean-session fixtures. |
559
+ | `pytest-docker-compose` | compose | per-test (default) | Different abstraction. Complementary, not a dup. |
560
+ | `pytest-docker` | compose | per-test | Compose-driven; not testcontainers-driven. |
561
+ | `pytest-container` | testinfra | image-under-test | For testing image content, not dependency setup. |
562
+
563
+ If you need intra-network communication between several testcontainers,
564
+ a compose-based plugin is the better choice. Each service we start is
565
+ reachable on its mapped host port — that's enough for >95% of test
566
+ setups.
567
+
568
+ ## Supported versions
569
+
570
+ ### Python
571
+
572
+ | Python | 3.10 | 3.11 | 3.12 | 3.13 |
573
+ |--------|:----:|:----:|:----:|:----:|
574
+ | | ✓ | ✓ | ✓ | ✓ |
575
+
576
+ CI runs the full test matrix on every supported version. 3.13 is the
577
+ default we develop against.
578
+
579
+ ### pytest
580
+
581
+ | pytest | 7.x | 8.x |
582
+ |--------|:---:|:---:|
583
+ | | ✓ | ✓ |
584
+
585
+ Tested separately in CI; floor is `pytest>=7.4`.
586
+
587
+ ### Other runtime requirements
588
+
589
+ - testcontainers-python ≥ 4.7 (modern `wait_strategies` API)
590
+ - docker-py ≥ 6.1 (we use `docker.errors.NotFound`,
591
+ `containers.exec_run(demux=True)`)
592
+ - A working Docker daemon: Docker Desktop / colima / Rancher Desktop /
593
+ Podman with the Docker socket / remote Docker via `DOCKER_HOST`.
594
+
595
+ ## License
596
+
597
+ MIT — see [LICENSE](LICENSE). Matches `testcontainers-python`'s
598
+ license.