pytest-testcontainers 0.2.0__tar.gz → 0.2.2__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.
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/CHANGELOG.md +27 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/PKG-INFO +80 -4
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/README.md +79 -3
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/pyproject.toml +1 -1
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/src/pytest_testcontainers/__init__.py +3 -1
- pytest_testcontainers-0.2.2/src/pytest_testcontainers/_internal/docker_health.py +105 -0
- pytest_testcontainers-0.2.2/src/pytest_testcontainers/_internal/git_identity.py +80 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/src/pytest_testcontainers/errors.py +8 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/src/pytest_testcontainers/makers.py +4 -5
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/src/pytest_testcontainers/plugin.py +18 -0
- pytest_testcontainers-0.2.2/src/pytest_testcontainers/reuse.py +319 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/tests/conftest.py +17 -0
- pytest_testcontainers-0.2.2/tests/test_docker_context.py +113 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/tests/test_errors.py +13 -0
- pytest_testcontainers-0.2.2/tests/test_git_identity.py +56 -0
- pytest_testcontainers-0.2.2/tests/test_makers_naming.py +71 -0
- pytest_testcontainers-0.2.2/tests/test_naming_docker.py +59 -0
- pytest_testcontainers-0.2.2/tests/test_plugin_options.py +39 -0
- pytest_testcontainers-0.2.2/tests/test_reuse.py +345 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/tests/test_reuse_mode.py +2 -2
- pytest_testcontainers-0.2.0/src/pytest_testcontainers/_internal/docker_health.py +0 -59
- pytest_testcontainers-0.2.0/src/pytest_testcontainers/reuse.py +0 -209
- pytest_testcontainers-0.2.0/tests/test_reuse.py +0 -179
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/.gitignore +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/LICENSE +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/src/pytest_testcontainers/_internal/__init__.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/src/pytest_testcontainers/_internal/clean_session_admin.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/src/pytest_testcontainers/_internal/conn_info.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/src/pytest_testcontainers/_internal/port_resolver.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/src/pytest_testcontainers/containers.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/src/pytest_testcontainers/fixtures.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/tests/test_disabled.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/tests/test_disabled_fixture.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/tests/test_docker_not_running.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/tests/test_fixtures_clean_session_mongo.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/tests/test_fixtures_clean_session_psql.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/tests/test_fixtures_clean_session_redis.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/tests/test_fixtures_session.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/tests/test_makers_generic.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/tests/test_makers_mongo.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/tests/test_makers_mysql.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/tests/test_makers_no_docker.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/tests/test_makers_postgres.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/tests/test_makers_redis.py +0 -0
- {pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/tests/test_port_resolver.py +0 -0
|
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.2] - 2026-06-29
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Restored the `reuse_name_for` name in `pytest_testcontainers.reuse` as a
|
|
15
|
+
backward-compatible alias of `container_name_for`. The template-based
|
|
16
|
+
container-naming feature renamed the function, and 0.2.1 shipped that rename —
|
|
17
|
+
which broke `pytest-testcontainers-django` < 0.2.5 (it imports the old name)
|
|
18
|
+
at import time. The alias keeps existing installs working; the default
|
|
19
|
+
`reuse_mode=True` matches the old function's byte-stable behavior.
|
|
20
|
+
|
|
21
|
+
## [0.2.1] - 2026-06-29
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **`Docker daemon is not reachable` even though `docker ps` worked**, on setups
|
|
26
|
+
where the daemon lives on a non-default socket (OrbStack, colima, a stopped
|
|
27
|
+
Docker Desktop). `docker.from_env()` — used by docker-py, by
|
|
28
|
+
testcontainers' own client, and by Ryuk's socket bind-mount — honors
|
|
29
|
+
`DOCKER_HOST` but, unlike the `docker` CLI, ignores the active
|
|
30
|
+
`docker context`, so it fell back to `/var/run/docker.sock` (absent or a
|
|
31
|
+
dangling symlink) and crashed with `FileNotFoundError`. The plugin now
|
|
32
|
+
exports `DOCKER_HOST` from the active context before any client is built, so
|
|
33
|
+
the whole process resolves Docker the way the CLI does. An explicit
|
|
34
|
+
`DOCKER_HOST` still wins; with no resolvable context it falls back to the
|
|
35
|
+
platform default socket.
|
|
36
|
+
|
|
10
37
|
## [0.2.0] - 2026-06-18
|
|
11
38
|
|
|
12
39
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest-testcontainers
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Named pytest fixtures and a maker convention on top of testcontainers-python.
|
|
5
5
|
Project-URL: Homepage, https://github.com/iplweb/pytest-testcontainers
|
|
6
6
|
Project-URL: Repository, https://github.com/iplweb/pytest-testcontainers
|
|
@@ -455,6 +455,53 @@ Every plumbing concern — daemon ping, reuse name, atexit cleanup,
|
|
|
455
455
|
Ryuk-disable-when-reuse — applies. `args`/`kwargs` go to the upstream
|
|
456
456
|
constructor verbatim.
|
|
457
457
|
|
|
458
|
+
## Container names
|
|
459
|
+
|
|
460
|
+
Every container the plugin starts is named so you can find it in `docker ps`
|
|
461
|
+
(previously default-mode containers got random Docker names like
|
|
462
|
+
`heuristic_cannon`). The name is built from a template:
|
|
463
|
+
|
|
464
|
+
```
|
|
465
|
+
{project}-tc-{service}-{branch}-{dirtag}-{worker}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
e.g. `myproj-tc-psql-feature-x-3f9ac1b2-master`.
|
|
469
|
+
|
|
470
|
+
Placeholders:
|
|
471
|
+
|
|
472
|
+
| Placeholder | Meaning |
|
|
473
|
+
|-------------|---------|
|
|
474
|
+
| `{project}` | Project name (see precedence below). |
|
|
475
|
+
| `{service}` | Service slug (`psql`, `redis`, `mysql`, `mongo`, `rabbitmq`, or derived from the container class). |
|
|
476
|
+
| `{branch}` | Current git branch (read from `.git/HEAD`, no subprocess). Detached HEAD → 12-char commit SHA. No repo → empty. |
|
|
477
|
+
| `{dir}` | Worktree-root basename. Readable, not collision-proof. |
|
|
478
|
+
| `{dirtag}` | 8-hex hash of the worktree-root absolute path. Disambiguates the same branch checked out in two directories. |
|
|
479
|
+
| `{worker}` | xdist worker id (`master` when not under xdist). |
|
|
480
|
+
| `{rand}` | 4 random hex chars. Empty in reuse mode; auto-appended in default mode when the template omits it. |
|
|
481
|
+
|
|
482
|
+
Empty placeholders collapse cleanly (no `--`, no dangling dashes). Names are
|
|
483
|
+
sanitized to Docker's charset and capped at 255 chars (trim + checksum).
|
|
484
|
+
|
|
485
|
+
Override the template (CLI > env > pyproject > built-in default):
|
|
486
|
+
|
|
487
|
+
```bash
|
|
488
|
+
pytest --testcontainers-name-template='{project}-{service}-{branch}'
|
|
489
|
+
# or
|
|
490
|
+
export PYTEST_TESTCONTAINERS_NAME_TEMPLATE='{project}-{service}-{branch}'
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
```toml
|
|
494
|
+
# pyproject.toml
|
|
495
|
+
[tool.pytest_testcontainers]
|
|
496
|
+
name_template = "{project}-{service}-{branch}"
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
The `{project}` component resolves as: `--testcontainers-project` →
|
|
500
|
+
`PYTEST_TESTCONTAINERS_PROJECT` → `PROJECT_NAME` → `COMPOSE_PROJECT_NAME` →
|
|
501
|
+
`pyproject.toml [project].name` → cwd basename.
|
|
502
|
+
|
|
503
|
+
An unknown placeholder raises `NameTemplateError` listing the valid ones.
|
|
504
|
+
|
|
458
505
|
## Reuse mode
|
|
459
506
|
|
|
460
507
|
For iterative dev loops where you don't want to pay container-start
|
|
@@ -467,9 +514,11 @@ pytest --testcontainers-reuse tests/
|
|
|
467
514
|
```
|
|
468
515
|
|
|
469
516
|
What changes:
|
|
470
|
-
- Each container gets a stable name
|
|
471
|
-
(
|
|
472
|
-
|
|
517
|
+
- Each container gets a stable, identifiable name from the template
|
|
518
|
+
(default `{project}-tc-{service}-{branch}-{dirtag}-{worker}`), so reuse is
|
|
519
|
+
scoped per project **and** per git branch **and** per directory. Switching
|
|
520
|
+
branch or worktree yields a *new* container; the previous one lingers
|
|
521
|
+
(stopped) until you run `pytest --testcontainers-clean`.
|
|
473
522
|
- Ryuk (testcontainers' reaper) is disabled so the named containers
|
|
474
523
|
survive between runs.
|
|
475
524
|
- On the next run we look up by name — found-and-running gets bound
|
|
@@ -491,6 +540,10 @@ own `PYTEST_TESTCONTAINERS_PROJECT` to avoid name collisions. The
|
|
|
491
540
|
plugin doesn't auto-namespace by PID — that would defeat the "reuse
|
|
492
541
|
across runs" point.
|
|
493
542
|
|
|
543
|
+
> **Upgrading from 0.1.0:** reuse-mode names now encode branch + directory, so
|
|
544
|
+
> containers reused from an older version are not found by name — a fresh one
|
|
545
|
+
> is created and the old one lingers until `pytest --testcontainers-clean`.
|
|
546
|
+
|
|
494
547
|
## Configuration
|
|
495
548
|
|
|
496
549
|
No TOML config table. The handful of toggles read at maker-call time:
|
|
@@ -502,6 +555,9 @@ No TOML config table. The handful of toggles read at maker-call time:
|
|
|
502
555
|
| `PYTEST_TESTCONTAINERS=0` | Disable plugin fixtures (raise UsageError). |
|
|
503
556
|
| `PYTEST_TESTCONTAINERS_REUSE=1` | Reuse named containers across runs. |
|
|
504
557
|
| `PYTEST_TESTCONTAINERS_PROJECT=<name>` | Override the `<project>` part of reuse names. |
|
|
558
|
+
| `PROJECT_NAME=<name>` | Generic project-name source (below the plugin var). |
|
|
559
|
+
| `COMPOSE_PROJECT_NAME=<name>` | docker-compose's project var (below `PROJECT_NAME`). |
|
|
560
|
+
| `PYTEST_TESTCONTAINERS_NAME_TEMPLATE=<t>` | Override the container-name template. |
|
|
505
561
|
| `PYTEST_TESTCONTAINERS_NO_DAEMON_CHECK=1` | Skip Docker daemon ping (rare). |
|
|
506
562
|
| `PYTEST_TESTCONTAINERS_QUIET=1` | Suppress one-shot informational advisories. |
|
|
507
563
|
|
|
@@ -513,6 +569,7 @@ No TOML config table. The handful of toggles read at maker-call time:
|
|
|
513
569
|
| `--testcontainers-reuse` | `PYTEST_TESTCONTAINERS_REUSE=1` |
|
|
514
570
|
| `--testcontainers-no-reuse` | force fresh-each-run mode |
|
|
515
571
|
| `--testcontainers-project=NAME` | `PYTEST_TESTCONTAINERS_PROJECT=NAME` |
|
|
572
|
+
| `--testcontainers-name-template=T` | `PYTEST_TESTCONTAINERS_NAME_TEMPLATE=T` |
|
|
516
573
|
| `--testcontainers-clean` | prune `<project>-tc-*` and exit 0 |
|
|
517
574
|
|
|
518
575
|
CLI > env > defaults.
|
|
@@ -534,6 +591,25 @@ Options:
|
|
|
534
591
|
Underlying error: ...
|
|
535
592
|
```
|
|
536
593
|
|
|
594
|
+
### `docker ps` works but pytest says the daemon is unreachable
|
|
595
|
+
|
|
596
|
+
You're almost certainly on a non-default **Docker context** — OrbStack,
|
|
597
|
+
colima, or a Docker Desktop install where `/var/run/docker.sock` is missing
|
|
598
|
+
or a dangling symlink. `docker.from_env()` (used by docker-py *and* by
|
|
599
|
+
testcontainers internally) honors `DOCKER_HOST` but, unlike the `docker`
|
|
600
|
+
CLI, ignores the active `docker context`. The plugin now resolves the active
|
|
601
|
+
context's endpoint for you and exports `DOCKER_HOST` before any container is
|
|
602
|
+
started, so it talks to the same daemon the CLI does.
|
|
603
|
+
|
|
604
|
+
If it still fails, point it at the daemon explicitly — an explicit
|
|
605
|
+
`DOCKER_HOST` always wins:
|
|
606
|
+
|
|
607
|
+
```bash
|
|
608
|
+
docker context inspect -f '{{.Endpoints.docker.Host}}' # see the endpoint
|
|
609
|
+
export DOCKER_HOST=unix://$HOME/.orbstack/run/docker.sock # e.g. OrbStack
|
|
610
|
+
export DOCKER_HOST=unix://$HOME/.colima/default/docker.sock # e.g. colima
|
|
611
|
+
```
|
|
612
|
+
|
|
537
613
|
When a stopped reused container can't be brought back (typically
|
|
538
614
|
because the previously-mapped port is now held by something else):
|
|
539
615
|
|
|
@@ -383,6 +383,53 @@ Every plumbing concern — daemon ping, reuse name, atexit cleanup,
|
|
|
383
383
|
Ryuk-disable-when-reuse — applies. `args`/`kwargs` go to the upstream
|
|
384
384
|
constructor verbatim.
|
|
385
385
|
|
|
386
|
+
## Container names
|
|
387
|
+
|
|
388
|
+
Every container the plugin starts is named so you can find it in `docker ps`
|
|
389
|
+
(previously default-mode containers got random Docker names like
|
|
390
|
+
`heuristic_cannon`). The name is built from a template:
|
|
391
|
+
|
|
392
|
+
```
|
|
393
|
+
{project}-tc-{service}-{branch}-{dirtag}-{worker}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
e.g. `myproj-tc-psql-feature-x-3f9ac1b2-master`.
|
|
397
|
+
|
|
398
|
+
Placeholders:
|
|
399
|
+
|
|
400
|
+
| Placeholder | Meaning |
|
|
401
|
+
|-------------|---------|
|
|
402
|
+
| `{project}` | Project name (see precedence below). |
|
|
403
|
+
| `{service}` | Service slug (`psql`, `redis`, `mysql`, `mongo`, `rabbitmq`, or derived from the container class). |
|
|
404
|
+
| `{branch}` | Current git branch (read from `.git/HEAD`, no subprocess). Detached HEAD → 12-char commit SHA. No repo → empty. |
|
|
405
|
+
| `{dir}` | Worktree-root basename. Readable, not collision-proof. |
|
|
406
|
+
| `{dirtag}` | 8-hex hash of the worktree-root absolute path. Disambiguates the same branch checked out in two directories. |
|
|
407
|
+
| `{worker}` | xdist worker id (`master` when not under xdist). |
|
|
408
|
+
| `{rand}` | 4 random hex chars. Empty in reuse mode; auto-appended in default mode when the template omits it. |
|
|
409
|
+
|
|
410
|
+
Empty placeholders collapse cleanly (no `--`, no dangling dashes). Names are
|
|
411
|
+
sanitized to Docker's charset and capped at 255 chars (trim + checksum).
|
|
412
|
+
|
|
413
|
+
Override the template (CLI > env > pyproject > built-in default):
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
pytest --testcontainers-name-template='{project}-{service}-{branch}'
|
|
417
|
+
# or
|
|
418
|
+
export PYTEST_TESTCONTAINERS_NAME_TEMPLATE='{project}-{service}-{branch}'
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
```toml
|
|
422
|
+
# pyproject.toml
|
|
423
|
+
[tool.pytest_testcontainers]
|
|
424
|
+
name_template = "{project}-{service}-{branch}"
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
The `{project}` component resolves as: `--testcontainers-project` →
|
|
428
|
+
`PYTEST_TESTCONTAINERS_PROJECT` → `PROJECT_NAME` → `COMPOSE_PROJECT_NAME` →
|
|
429
|
+
`pyproject.toml [project].name` → cwd basename.
|
|
430
|
+
|
|
431
|
+
An unknown placeholder raises `NameTemplateError` listing the valid ones.
|
|
432
|
+
|
|
386
433
|
## Reuse mode
|
|
387
434
|
|
|
388
435
|
For iterative dev loops where you don't want to pay container-start
|
|
@@ -395,9 +442,11 @@ pytest --testcontainers-reuse tests/
|
|
|
395
442
|
```
|
|
396
443
|
|
|
397
444
|
What changes:
|
|
398
|
-
- Each container gets a stable name
|
|
399
|
-
(
|
|
400
|
-
|
|
445
|
+
- Each container gets a stable, identifiable name from the template
|
|
446
|
+
(default `{project}-tc-{service}-{branch}-{dirtag}-{worker}`), so reuse is
|
|
447
|
+
scoped per project **and** per git branch **and** per directory. Switching
|
|
448
|
+
branch or worktree yields a *new* container; the previous one lingers
|
|
449
|
+
(stopped) until you run `pytest --testcontainers-clean`.
|
|
401
450
|
- Ryuk (testcontainers' reaper) is disabled so the named containers
|
|
402
451
|
survive between runs.
|
|
403
452
|
- On the next run we look up by name — found-and-running gets bound
|
|
@@ -419,6 +468,10 @@ own `PYTEST_TESTCONTAINERS_PROJECT` to avoid name collisions. The
|
|
|
419
468
|
plugin doesn't auto-namespace by PID — that would defeat the "reuse
|
|
420
469
|
across runs" point.
|
|
421
470
|
|
|
471
|
+
> **Upgrading from 0.1.0:** reuse-mode names now encode branch + directory, so
|
|
472
|
+
> containers reused from an older version are not found by name — a fresh one
|
|
473
|
+
> is created and the old one lingers until `pytest --testcontainers-clean`.
|
|
474
|
+
|
|
422
475
|
## Configuration
|
|
423
476
|
|
|
424
477
|
No TOML config table. The handful of toggles read at maker-call time:
|
|
@@ -430,6 +483,9 @@ No TOML config table. The handful of toggles read at maker-call time:
|
|
|
430
483
|
| `PYTEST_TESTCONTAINERS=0` | Disable plugin fixtures (raise UsageError). |
|
|
431
484
|
| `PYTEST_TESTCONTAINERS_REUSE=1` | Reuse named containers across runs. |
|
|
432
485
|
| `PYTEST_TESTCONTAINERS_PROJECT=<name>` | Override the `<project>` part of reuse names. |
|
|
486
|
+
| `PROJECT_NAME=<name>` | Generic project-name source (below the plugin var). |
|
|
487
|
+
| `COMPOSE_PROJECT_NAME=<name>` | docker-compose's project var (below `PROJECT_NAME`). |
|
|
488
|
+
| `PYTEST_TESTCONTAINERS_NAME_TEMPLATE=<t>` | Override the container-name template. |
|
|
433
489
|
| `PYTEST_TESTCONTAINERS_NO_DAEMON_CHECK=1` | Skip Docker daemon ping (rare). |
|
|
434
490
|
| `PYTEST_TESTCONTAINERS_QUIET=1` | Suppress one-shot informational advisories. |
|
|
435
491
|
|
|
@@ -441,6 +497,7 @@ No TOML config table. The handful of toggles read at maker-call time:
|
|
|
441
497
|
| `--testcontainers-reuse` | `PYTEST_TESTCONTAINERS_REUSE=1` |
|
|
442
498
|
| `--testcontainers-no-reuse` | force fresh-each-run mode |
|
|
443
499
|
| `--testcontainers-project=NAME` | `PYTEST_TESTCONTAINERS_PROJECT=NAME` |
|
|
500
|
+
| `--testcontainers-name-template=T` | `PYTEST_TESTCONTAINERS_NAME_TEMPLATE=T` |
|
|
444
501
|
| `--testcontainers-clean` | prune `<project>-tc-*` and exit 0 |
|
|
445
502
|
|
|
446
503
|
CLI > env > defaults.
|
|
@@ -462,6 +519,25 @@ Options:
|
|
|
462
519
|
Underlying error: ...
|
|
463
520
|
```
|
|
464
521
|
|
|
522
|
+
### `docker ps` works but pytest says the daemon is unreachable
|
|
523
|
+
|
|
524
|
+
You're almost certainly on a non-default **Docker context** — OrbStack,
|
|
525
|
+
colima, or a Docker Desktop install where `/var/run/docker.sock` is missing
|
|
526
|
+
or a dangling symlink. `docker.from_env()` (used by docker-py *and* by
|
|
527
|
+
testcontainers internally) honors `DOCKER_HOST` but, unlike the `docker`
|
|
528
|
+
CLI, ignores the active `docker context`. The plugin now resolves the active
|
|
529
|
+
context's endpoint for you and exports `DOCKER_HOST` before any container is
|
|
530
|
+
started, so it talks to the same daemon the CLI does.
|
|
531
|
+
|
|
532
|
+
If it still fails, point it at the daemon explicitly — an explicit
|
|
533
|
+
`DOCKER_HOST` always wins:
|
|
534
|
+
|
|
535
|
+
```bash
|
|
536
|
+
docker context inspect -f '{{.Endpoints.docker.Host}}' # see the endpoint
|
|
537
|
+
export DOCKER_HOST=unix://$HOME/.orbstack/run/docker.sock # e.g. OrbStack
|
|
538
|
+
export DOCKER_HOST=unix://$HOME/.colima/default/docker.sock # e.g. colima
|
|
539
|
+
```
|
|
540
|
+
|
|
465
541
|
When a stopped reused container can't be brought back (typically
|
|
466
542
|
because the previously-mapped port is now held by something else):
|
|
467
543
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pytest-testcontainers"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "Named pytest fixtures and a maker convention on top of testcontainers-python."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
{pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/src/pytest_testcontainers/__init__.py
RENAMED
|
@@ -7,6 +7,7 @@ from pytest_testcontainers.errors import (
|
|
|
7
7
|
CleanSessionFixtureError,
|
|
8
8
|
ContainerStartError,
|
|
9
9
|
DockerNotRunningError,
|
|
10
|
+
NameTemplateError,
|
|
10
11
|
PytestTestcontainersError,
|
|
11
12
|
ReuseConflictError,
|
|
12
13
|
)
|
|
@@ -24,6 +25,7 @@ __all__ = [
|
|
|
24
25
|
"ContainerStartError",
|
|
25
26
|
"DbConnInfo",
|
|
26
27
|
"DockerNotRunningError",
|
|
28
|
+
"NameTemplateError",
|
|
27
29
|
"PytestTestcontainersError",
|
|
28
30
|
"ReuseConflictError",
|
|
29
31
|
"make_container",
|
|
@@ -34,4 +36,4 @@ __all__ = [
|
|
|
34
36
|
"make_redis",
|
|
35
37
|
]
|
|
36
38
|
|
|
37
|
-
__version__ = "0.
|
|
39
|
+
__version__ = "0.2.2"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Docker daemon health check — single source of truth for daemon ping.
|
|
2
|
+
|
|
3
|
+
The cache stores **only successful pings**. A successful ping flips a
|
|
4
|
+
process-wide flag; subsequent maker calls in the same process skip the
|
|
5
|
+
probe. A failed ping does NOT poison the cache, so a transient daemon
|
|
6
|
+
restart inside a long-running pytest session does not permanently
|
|
7
|
+
disable the plugin.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import threading
|
|
15
|
+
|
|
16
|
+
from pytest_testcontainers.errors import DockerNotRunningError
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger("pytest_testcontainers")
|
|
19
|
+
|
|
20
|
+
_lock = threading.Lock()
|
|
21
|
+
_ping_ok: bool = False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _is_truthy(value: str | None) -> bool:
|
|
25
|
+
return (value or "").strip().lower() in {"1", "true", "yes", "on"}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def reset_cache() -> None:
|
|
29
|
+
"""Reset the success cache. Used in tests."""
|
|
30
|
+
global _ping_ok
|
|
31
|
+
with _lock:
|
|
32
|
+
_ping_ok = False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def ensure_docker_host_env() -> None:
|
|
36
|
+
"""Export ``DOCKER_HOST`` from the active ``docker context`` when unset.
|
|
37
|
+
|
|
38
|
+
``docker.from_env()`` — used by docker-py, by testcontainers' own client,
|
|
39
|
+
and by Ryuk's socket bind-mount — honors ``DOCKER_HOST`` but, unlike the
|
|
40
|
+
``docker`` CLI, ignores the active ``docker context``. On OrbStack /
|
|
41
|
+
colima / a stopped Docker Desktop the daemon lives on a non-default socket
|
|
42
|
+
and ``/var/run/docker.sock`` is absent or a dangling symlink, so container
|
|
43
|
+
launch crashes with ``FileNotFoundError`` even though ``docker ps`` works.
|
|
44
|
+
|
|
45
|
+
Exporting ``DOCKER_HOST`` once — before any client is built — makes the
|
|
46
|
+
whole process resolve Docker the way the CLI does. No-op when
|
|
47
|
+
``DOCKER_HOST`` is already set (an explicit choice always wins) or when no
|
|
48
|
+
context endpoint resolves (fall back to the platform default socket).
|
|
49
|
+
"""
|
|
50
|
+
if os.environ.get("DOCKER_HOST"):
|
|
51
|
+
return
|
|
52
|
+
ctx = _active_docker_context()
|
|
53
|
+
host = getattr(ctx, "Host", None) if ctx is not None else None
|
|
54
|
+
if host:
|
|
55
|
+
os.environ["DOCKER_HOST"] = host
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _active_docker_context(): # type: ignore[no-untyped-def]
|
|
59
|
+
"""Return the active ``docker context`` object, or ``None``.
|
|
60
|
+
|
|
61
|
+
Resolution failures (older SDK without context support, malformed
|
|
62
|
+
``~/.docker/config.json``, missing context) are logged and swallowed so
|
|
63
|
+
the caller falls back to the platform default socket.
|
|
64
|
+
"""
|
|
65
|
+
try:
|
|
66
|
+
from docker.context import ContextAPI
|
|
67
|
+
|
|
68
|
+
return ContextAPI.get_current_context()
|
|
69
|
+
except Exception:
|
|
70
|
+
logger.debug("could not resolve active docker context", exc_info=True)
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def check_docker_daemon() -> None:
|
|
75
|
+
"""Ping the Docker daemon once per process (on success).
|
|
76
|
+
|
|
77
|
+
Raises :class:`DockerNotRunningError` (chained from the underlying
|
|
78
|
+
docker-py exception) if the daemon cannot be reached.
|
|
79
|
+
"""
|
|
80
|
+
global _ping_ok
|
|
81
|
+
if _ping_ok:
|
|
82
|
+
return
|
|
83
|
+
# Honor the active `docker context` before any client (ours, testcontainers'
|
|
84
|
+
# or Ryuk's) is built — even when the ping itself is skipped, so the launch
|
|
85
|
+
# path still resolves the right socket on OrbStack / colima / Docker Desktop.
|
|
86
|
+
ensure_docker_host_env()
|
|
87
|
+
if _is_truthy(os.environ.get("PYTEST_TESTCONTAINERS_NO_DAEMON_CHECK")):
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
import docker # local import: never trigger at module-import time
|
|
92
|
+
except ImportError as exc: # pragma: no cover — docker is a hard dep
|
|
93
|
+
raise DockerNotRunningError(
|
|
94
|
+
"docker-py is not installed; cannot ping Docker daemon"
|
|
95
|
+
) from exc
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
client = docker.from_env()
|
|
99
|
+
client.ping()
|
|
100
|
+
except Exception as exc:
|
|
101
|
+
# Surface the underlying daemon failure with a concrete cause.
|
|
102
|
+
raise DockerNotRunningError(f"docker.from_env().ping() failed: {exc!r}") from exc
|
|
103
|
+
|
|
104
|
+
with _lock:
|
|
105
|
+
_ping_ok = True
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Filesystem-only git identity helpers: worktree root + current branch.
|
|
2
|
+
|
|
3
|
+
Pure reads — no subprocess, no docker, no import-time side effects. Used by
|
|
4
|
+
``reuse.py`` to compute the ``{branch}``, ``{dir}`` and ``{dirtag}`` name
|
|
5
|
+
components. Handles both a normal clone (``.git`` is a directory) and a
|
|
6
|
+
linked worktree (``.git`` is a file containing ``gitdir: <path>``).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def worktree_root(start: Path) -> Path:
|
|
15
|
+
"""Return the directory that owns the working tree containing ``start``.
|
|
16
|
+
|
|
17
|
+
Walks upward from ``start`` looking for a ``.git`` entry (a directory in a
|
|
18
|
+
normal clone, a file in a linked worktree — either marks the root). Falls
|
|
19
|
+
back to ``start.resolve()`` when no ``.git`` is found.
|
|
20
|
+
"""
|
|
21
|
+
current = start.resolve()
|
|
22
|
+
for candidate in [current, *current.parents]:
|
|
23
|
+
if (candidate / ".git").exists():
|
|
24
|
+
return candidate
|
|
25
|
+
return current
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _gitdir_for(root: Path) -> Path | None:
|
|
29
|
+
"""Resolve the gitdir that holds ``HEAD`` for the worktree at ``root``.
|
|
30
|
+
|
|
31
|
+
Normal clone: ``<root>/.git`` is a directory → that directory.
|
|
32
|
+
Linked worktree: ``<root>/.git`` is a file ``gitdir: <path>`` → ``<path>``
|
|
33
|
+
(resolved relative to ``root`` when the recorded path is relative).
|
|
34
|
+
Returns ``None`` when neither shape is readable.
|
|
35
|
+
"""
|
|
36
|
+
dot_git = root / ".git"
|
|
37
|
+
if dot_git.is_dir():
|
|
38
|
+
return dot_git
|
|
39
|
+
if dot_git.is_file():
|
|
40
|
+
try:
|
|
41
|
+
content = dot_git.read_text(encoding="utf-8", errors="replace").strip()
|
|
42
|
+
except OSError:
|
|
43
|
+
return None
|
|
44
|
+
prefix = "gitdir:"
|
|
45
|
+
if content.startswith(prefix):
|
|
46
|
+
target = content[len(prefix) :].strip()
|
|
47
|
+
if target:
|
|
48
|
+
resolved = Path(target)
|
|
49
|
+
if not resolved.is_absolute():
|
|
50
|
+
resolved = (root / resolved).resolve()
|
|
51
|
+
return resolved
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def current_branch(start: Path) -> str | None:
|
|
56
|
+
"""Return the current git branch name, or ``None``.
|
|
57
|
+
|
|
58
|
+
Reads ``HEAD`` from the resolved gitdir without spawning git:
|
|
59
|
+
- ``ref: refs/heads/<b>`` → ``<b>``
|
|
60
|
+
- any other ``ref: .../<x>`` → last path component ``<x>``
|
|
61
|
+
- raw SHA (detached HEAD) → first 12 chars
|
|
62
|
+
- unreadable / missing / empty → ``None``
|
|
63
|
+
"""
|
|
64
|
+
root = worktree_root(start)
|
|
65
|
+
gitdir = _gitdir_for(root)
|
|
66
|
+
if gitdir is None:
|
|
67
|
+
return None
|
|
68
|
+
try:
|
|
69
|
+
content = (gitdir / "HEAD").read_text(encoding="utf-8", errors="replace").strip()
|
|
70
|
+
except OSError:
|
|
71
|
+
return None
|
|
72
|
+
if not content:
|
|
73
|
+
return None
|
|
74
|
+
if content.startswith("ref:"):
|
|
75
|
+
ref = content[len("ref:") :].strip()
|
|
76
|
+
marker = "refs/heads/"
|
|
77
|
+
if marker in ref:
|
|
78
|
+
return ref.split(marker, 1)[1] or None
|
|
79
|
+
return ref.rsplit("/", 1)[-1] or None
|
|
80
|
+
return content[:12]
|
{pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/src/pytest_testcontainers/errors.py
RENAMED
|
@@ -30,6 +30,14 @@ class ReuseConflictError(PytestTestcontainersError):
|
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
class NameTemplateError(PytestTestcontainersError):
|
|
34
|
+
"""The configured container-name template referenced an unknown placeholder.
|
|
35
|
+
|
|
36
|
+
Raised at name-resolution time (during fixture/maker setup, before any
|
|
37
|
+
container starts). The message enumerates the valid placeholders.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
|
|
33
41
|
class CleanSessionFixtureError(PytestTestcontainersError):
|
|
34
42
|
"""A clean-session fixture failed to issue an admin command.
|
|
35
43
|
|
{pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/src/pytest_testcontainers/makers.py
RENAMED
|
@@ -27,8 +27,8 @@ from pytest_testcontainers.containers import (
|
|
|
27
27
|
start_or_raise,
|
|
28
28
|
)
|
|
29
29
|
from pytest_testcontainers.reuse import (
|
|
30
|
+
container_name_for,
|
|
30
31
|
is_reuse_enabled,
|
|
31
|
-
reuse_name_for,
|
|
32
32
|
sanitize_component,
|
|
33
33
|
)
|
|
34
34
|
|
|
@@ -76,7 +76,7 @@ def _make_generic(
|
|
|
76
76
|
check_docker_daemon()
|
|
77
77
|
|
|
78
78
|
reuse_on = is_reuse_enabled() or reuse_name is not None
|
|
79
|
-
name =
|
|
79
|
+
name = container_name_for(service_slug, override=reuse_name, reuse_mode=reuse_on)
|
|
80
80
|
|
|
81
81
|
if reuse_on:
|
|
82
82
|
disable_ryuk_once()
|
|
@@ -84,7 +84,7 @@ def _make_generic(
|
|
|
84
84
|
instance: DockerContainer | None = None
|
|
85
85
|
is_bound_to_existing = False
|
|
86
86
|
|
|
87
|
-
if reuse_on
|
|
87
|
+
if reuse_on:
|
|
88
88
|
existing = find_existing_container(name)
|
|
89
89
|
if existing is not None:
|
|
90
90
|
status = getattr(existing, "status", "")
|
|
@@ -106,8 +106,7 @@ def _make_generic(
|
|
|
106
106
|
|
|
107
107
|
if instance is None:
|
|
108
108
|
instance = container_cls(*constructor_args, **constructor_kwargs)
|
|
109
|
-
|
|
110
|
-
instance.with_name(name)
|
|
109
|
+
instance.with_name(name)
|
|
111
110
|
_apply_env(instance, env)
|
|
112
111
|
|
|
113
112
|
image_for_msg = constructor_kwargs.get("image") or (
|
{pytest_testcontainers-0.2.0 → pytest_testcontainers-0.2.2}/src/pytest_testcontainers/plugin.py
RENAMED
|
@@ -50,6 +50,16 @@ def pytest_addoption(parser: pytest.Parser) -> None:
|
|
|
50
50
|
dest="testcontainers_project",
|
|
51
51
|
help="Override project name used in reuse-name prefix.",
|
|
52
52
|
)
|
|
53
|
+
group.addoption(
|
|
54
|
+
"--testcontainers-name-template",
|
|
55
|
+
action="store",
|
|
56
|
+
default=None,
|
|
57
|
+
dest="testcontainers_name_template",
|
|
58
|
+
help=(
|
|
59
|
+
"Template for container names. Placeholders: {project} {service} "
|
|
60
|
+
"{branch} {dir} {dirtag} {worker} {rand}."
|
|
61
|
+
),
|
|
62
|
+
)
|
|
53
63
|
group.addoption(
|
|
54
64
|
"--testcontainers-clean",
|
|
55
65
|
action="store_true",
|
|
@@ -65,6 +75,9 @@ def pytest_configure(config: pytest.Config) -> None:
|
|
|
65
75
|
project = config.getoption("testcontainers_project")
|
|
66
76
|
if project:
|
|
67
77
|
reuse.set_cli_project(project)
|
|
78
|
+
name_template = config.getoption("testcontainers_name_template")
|
|
79
|
+
if name_template:
|
|
80
|
+
reuse.set_cli_name_template(name_template)
|
|
68
81
|
if config.getoption("testcontainers_no_reuse"):
|
|
69
82
|
reuse.set_cli_reuse(False)
|
|
70
83
|
elif config.getoption("testcontainers_reuse"):
|
|
@@ -82,6 +95,11 @@ def pytest_cmdline_main(config: pytest.Config) -> int | None:
|
|
|
82
95
|
except ImportError:
|
|
83
96
|
sys.stderr.write("[pytest-testcontainers] docker-py not installed\n")
|
|
84
97
|
return 1
|
|
98
|
+
# Honor the active `docker context` (OrbStack / colima / Docker Desktop)
|
|
99
|
+
# before from_env(), same as the launch path.
|
|
100
|
+
from pytest_testcontainers._internal.docker_health import ensure_docker_host_env
|
|
101
|
+
|
|
102
|
+
ensure_docker_host_env()
|
|
85
103
|
try:
|
|
86
104
|
client = docker.from_env()
|
|
87
105
|
client.ping()
|