pytest-docker-compose-v2 0.2.0__tar.gz → 0.3.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 (27) hide show
  1. {pytest_docker_compose_v2-0.2.0 → pytest_docker_compose_v2-0.3.0}/.github/workflows/release.yaml +3 -3
  2. {pytest_docker_compose_v2-0.2.0 → pytest_docker_compose_v2-0.3.0}/.github/workflows/test.yaml +5 -3
  3. pytest_docker_compose_v2-0.3.0/Justfile +76 -0
  4. {pytest_docker_compose_v2-0.2.0 → pytest_docker_compose_v2-0.3.0}/PKG-INFO +92 -4
  5. {pytest_docker_compose_v2-0.2.0 → pytest_docker_compose_v2-0.3.0}/README.md +89 -1
  6. {pytest_docker_compose_v2-0.2.0 → pytest_docker_compose_v2-0.3.0}/pyproject.toml +7 -4
  7. pytest_docker_compose_v2-0.3.0/pytest_docker_compose/__init__.py +1 -0
  8. {pytest_docker_compose_v2-0.2.0 → pytest_docker_compose_v2-0.3.0}/pytest_docker_compose/plugin.py +44 -32
  9. pytest_docker_compose_v2-0.3.0/tests/pytest_docker_compose_tests/my_network/a_buildable_container/requirements.txt +3 -0
  10. pytest_docker_compose_v2-0.3.0/tests/pytest_docker_compose_tests/my_network/docker-compose.yml +33 -0
  11. {pytest_docker_compose_v2-0.2.0 → pytest_docker_compose_v2-0.3.0}/tests/pytest_docker_compose_tests/test_function_scoping_fixtures.py +0 -1
  12. {pytest_docker_compose_v2-0.2.0 → pytest_docker_compose_v2-0.3.0}/tests/pytest_docker_compose_tests/test_module_scoping_fixtures.py +0 -1
  13. pytest_docker_compose_v2-0.3.0/tests/pytest_docker_compose_tests/test_wait_for_healthcheck.py +48 -0
  14. pytest_docker_compose_v2-0.3.0/uv.lock +1261 -0
  15. pytest_docker_compose_v2-0.2.0/pytest_docker_compose/__init__.py +0 -1
  16. pytest_docker_compose_v2-0.2.0/tests/pytest_docker_compose_tests/my_network/a_buildable_container/requirements.txt +0 -3
  17. pytest_docker_compose_v2-0.2.0/tests/pytest_docker_compose_tests/my_network/docker-compose.yml +0 -16
  18. pytest_docker_compose_v2-0.2.0/uv.lock +0 -976
  19. {pytest_docker_compose_v2-0.2.0 → pytest_docker_compose_v2-0.3.0}/.devcontainer/Dockerfile +0 -0
  20. {pytest_docker_compose_v2-0.2.0 → pytest_docker_compose_v2-0.3.0}/.devcontainer/devcontainer.json +0 -0
  21. {pytest_docker_compose_v2-0.2.0 → pytest_docker_compose_v2-0.3.0}/.gitignore +0 -0
  22. {pytest_docker_compose_v2-0.2.0 → pytest_docker_compose_v2-0.3.0}/LICENSE +0 -0
  23. {pytest_docker_compose_v2-0.2.0 → pytest_docker_compose_v2-0.3.0}/tests/pytest_docker_compose_tests/my_network/a_buildable_container/Dockerfile +0 -0
  24. {pytest_docker_compose_v2-0.2.0 → pytest_docker_compose_v2-0.3.0}/tests/pytest_docker_compose_tests/my_network/a_buildable_container/an_api.py +0 -0
  25. {pytest_docker_compose_v2-0.2.0 → pytest_docker_compose_v2-0.3.0}/tests/pytest_docker_compose_tests/my_network/extra-service.yml +0 -0
  26. {pytest_docker_compose_v2-0.2.0 → pytest_docker_compose_v2-0.3.0}/tests/pytest_docker_compose_tests/test_multiple_compose_files.py +0 -0
  27. {pytest_docker_compose_v2-0.2.0 → pytest_docker_compose_v2-0.3.0}/tests/pytest_docker_compose_tests/test_wrong_scoping.py +0 -0
@@ -16,9 +16,9 @@ jobs:
16
16
  id-token: write
17
17
  steps:
18
18
  - name: Check out repository
19
- uses: actions/checkout@v5
19
+ uses: actions/checkout@v7
20
20
  - name: Set up uv
21
- uses: astral-sh/setup-uv@v7
21
+ uses: astral-sh/setup-uv@v10.2.0
22
22
  with:
23
23
  enable-cache: true
24
24
  python-version: '3.13'
@@ -28,7 +28,7 @@ jobs:
28
28
  - name: Run tests
29
29
  run: uv run pytest
30
30
  - name: Create GitHub release
31
- uses: softprops/action-gh-release@v1
31
+ uses: softprops/action-gh-release@v3
32
32
  with:
33
33
  name: Release ${{ github.ref }}
34
34
  tag_name: ${{ github.ref }}
@@ -18,14 +18,14 @@ jobs:
18
18
  pytest-version: ['7', '8', '9']
19
19
  steps:
20
20
  - name: Check out repository
21
- uses: actions/checkout@v5
21
+ uses: actions/checkout@v7
22
22
  # needed because we use git describe to get a bumpver compatible GITHASH containing version
23
23
  # alternatively can look at using HEXHASH instead. Note that these are purposefully not
24
24
  # documented in the bumpver project, see issues/PRs for more info.
25
25
  # with:
26
26
  # fetch-depth: 0
27
27
  - name: Set up uv
28
- uses: astral-sh/setup-uv@v7
28
+ uses: astral-sh/setup-uv@v10.2.0
29
29
  with:
30
30
  enable-cache: true
31
31
  python-version: ${{ matrix.python-version }}
@@ -34,7 +34,9 @@ jobs:
34
34
  - name: Install pytest ${{ matrix.pytest-version }}
35
35
  run: uv pip install "pytest>=${{ matrix.pytest-version }},<$(( ${{ matrix.pytest-version }} + 1 ))"
36
36
  - name: Run tests
37
- run: uv run pytest
37
+ run: uv run --no-sync pytest
38
+ - name: Run healthcheck wait tests
39
+ run: uv run --no-sync pytest -m wait_for_healthcheck --docker-compose ./tests/pytest_docker_compose_tests/my_network --docker-compose-wait
38
40
  # NOTE: publishing to test repository is disabled until I figure out a way to do it reliably
39
41
  # this means that we need to avoid version clashes as well as local versions
40
42
  # see: https://github.com/pypa/packaging.python.org/issues/804
@@ -0,0 +1,76 @@
1
+ set shell := ["bash", "-euo", "pipefail", "-c"]
2
+
3
+ # Generate Markdown release notes for VERSION without modifying the repository.
4
+ release-notes version:
5
+ #!/usr/bin/env bash
6
+ set -euo pipefail
7
+
8
+ version="{{ version }}"
9
+ if git show-ref --verify --quiet "refs/tags/${version}"; then
10
+ revision="${version}"
11
+ previous="$(git describe --tags --abbrev=0 "${version}^")"
12
+ else
13
+ revision="HEAD"
14
+ previous="$(git describe --tags --abbrev=0 HEAD)"
15
+ fi
16
+
17
+ uv run git-cliff "${previous}..${revision}" \
18
+ --tag "${version}" \
19
+ --config keepachangelog \
20
+ --strip all
21
+
22
+ # Add generated notes to an existing GitHub release (requires the GitHub CLI).
23
+ publish-release-notes version:
24
+ #!/usr/bin/env bash
25
+ set -euo pipefail
26
+
27
+ command -v gh >/dev/null || {
28
+ echo "The GitHub CLI (gh) is required to publish release notes." >&2
29
+ exit 1
30
+ }
31
+
32
+ notes_file="$(mktemp)"
33
+ trap 'rm -f "${notes_file}"' EXIT
34
+ uv run just release-notes "{{ version }}" > "${notes_file}"
35
+ gh release edit "{{ version }}" --notes-file "${notes_file}"
36
+
37
+ # Bump VERSION_KIND (patch, minor, or major), commit, tag, and push the release.
38
+ release version_kind:
39
+ #!/usr/bin/env bash
40
+ set -euo pipefail
41
+
42
+ version_kind="{{ version_kind }}"
43
+ case "${version_kind}" in
44
+ patch|minor|major) ;;
45
+ *)
46
+ echo "Version kind must be one of: patch, minor, major." >&2
47
+ exit 2
48
+ ;;
49
+ esac
50
+
51
+ if [[ "$(git branch --show-current)" != "main" ]]; then
52
+ echo "Releases must be created from the main branch." >&2
53
+ exit 1
54
+ fi
55
+ if [[ -n "$(git status --porcelain)" ]]; then
56
+ echo "The working tree must be clean before creating a release." >&2
57
+ exit 1
58
+ fi
59
+
60
+ git fetch origin main --tags
61
+ if [[ "$(git rev-parse HEAD)" != "$(git rev-parse origin/main)" ]]; then
62
+ echo "Local main must match origin/main before creating a release." >&2
63
+ exit 1
64
+ fi
65
+
66
+ uv run pytest
67
+ uv run bumpver update "--${version_kind}" --ignore-vcs-tag --no-fetch
68
+ version="$(uv run python -c 'from pytest_docker_compose import __version__; print(__version__)')"
69
+
70
+ uv run just release-notes "${version}"
71
+ git push origin main
72
+ git push origin "${version}"
73
+
74
+ echo
75
+ echo "Release ${version} pushed. Once GitHub creates the release, publish its notes with:"
76
+ echo " uv run just publish-release-notes ${version}"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: pytest-docker-compose-v2
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Manages Docker containers during your integration tests
5
5
  Project-URL: repository, https://github.com/radusuciu/pytest-docker-compose-v2
6
6
  Author: Radu Suciu, Thomas Meckel, Roald Storm, Phoenix Zerin
@@ -22,7 +22,7 @@ Classifier: Programming Language :: Python :: 3.14
22
22
  Classifier: Topic :: Software Development :: Testing
23
23
  Requires-Python: >=3.10
24
24
  Requires-Dist: pytest<10,>=7.2.2
25
- Requires-Dist: python-on-whales<1,>=0.69.0
25
+ Requires-Dist: python-on-whales<1,>=0.71.0
26
26
  Description-Content-Type: text/markdown
27
27
 
28
28
  [![PyPI pyversions](https://img.shields.io/pypi/pyversions/pytest-docker-compose-v2.svg)](https://pypi.python.org/pypi/pytest-docker-compose-v2/)
@@ -103,7 +103,53 @@ The fixtures called `[scope]_scoped_container_getter` will wait until every cont
103
103
 
104
104
  However, just because a container is up does not mean that the services running on it are ready to accept incoming requests yet!
105
105
 
106
- If your tests need to wait for a particular condition (for example, to wait for an HTTP health check endpoint to send back a 200 response), make sure that your fixtures account for this.
106
+ #### Option 1: Using Docker Compose Healthchecks (Recommended)
107
+
108
+ The plugin can use Docker Compose's native `--wait` functionality with healthchecks defined in your `docker-compose.yml`. This lets Docker Compose handle waiting for services to be ready.
109
+
110
+ First, define healthchecks in your `docker-compose.yml`:
111
+
112
+ ```yaml
113
+ services:
114
+ my_api_service:
115
+ build: ./api
116
+ ports:
117
+ - "5000:5000"
118
+ depends_on:
119
+ my_db:
120
+ condition: service_healthy
121
+ healthcheck:
122
+ test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
123
+ interval: 5s
124
+ timeout: 5s
125
+ retries: 10
126
+ start_period: 10s
127
+ my_db:
128
+ image: postgres:15
129
+ healthcheck:
130
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
131
+ interval: 5s
132
+ timeout: 5s
133
+ retries: 10
134
+ ```
135
+
136
+ Then opt in to waiting when you run pytest:
137
+
138
+ ```shell
139
+ pytest --docker-compose-wait
140
+ ```
141
+
142
+ You can also specify a timeout (in seconds):
143
+
144
+ ```shell
145
+ pytest --docker-compose-wait --docker-compose-wait-timeout=120
146
+ ```
147
+
148
+ Waiting remains opt-in for backward compatibility. It is planned to become the default in version 1.0.
149
+
150
+ #### Option 2: Python-based Wait Fixtures
151
+
152
+ If your tests need to wait for a particular condition (for example, to wait for an HTTP health check endpoint to send back a 200 response), you can implement custom wait logic in your fixtures.
107
153
 
108
154
  Here's an example of a fixture called `wait_for_api` that waits for an HTTP service to come online before a test called `test_read_and_write` can run.
109
155
 
@@ -221,5 +267,47 @@ pytest --docker-compose-remove-volumes
221
267
 
222
268
  This option will be ignored if the plugin is not used. Again, this option can also be added to the `pytest.ini` file.
223
269
 
270
+ ### Command Line Options Summary
271
+
272
+ | Option | Description |
273
+ |--------|-------------|
274
+ | `--docker-compose` | Path to docker-compose.yml file or directory containing one. Multiple files can be specified with commas. |
275
+ | `--docker-compose-no-build` | Skip building Docker images before running tests. |
276
+ | `--docker-compose-remove-volumes` | Remove container volumes after tests complete. |
277
+ | `--use-running-containers` | Use already running containers instead of starting new ones. |
278
+ | `--docker-compose-wait` | Wait for services to be healthy before running tests (requires healthcheck definitions). |
279
+ | `--docker-compose-wait-timeout` | Timeout in seconds when waiting for services to be healthy. |
280
+
224
281
  For more examples on how to use this plugin look at the testing suite of this plugin itself! It will give you some examples for configuring `pyproject.toml` and how to use the different fixtures to run docker containers.
225
282
 
283
+ ## Releasing
284
+
285
+ Release commands are managed with `just`, which is installed by the development
286
+ dependency group:
287
+
288
+ ```shell
289
+ uv sync
290
+ ```
291
+
292
+ Preview the release notes for a version without modifying the repository:
293
+
294
+ ```shell
295
+ uv run just release-notes 0.3.0
296
+ ```
297
+
298
+ Create and push a release from a clean, up-to-date `main` branch by choosing a
299
+ semantic version increment:
300
+
301
+ ```shell
302
+ uv run just release patch
303
+ uv run just release minor
304
+ uv run just release major
305
+ ```
306
+
307
+ The command runs the tests, updates the package version, creates the version
308
+ commit and tag, and pushes both. Once GitHub Actions has created the GitHub
309
+ Release, its generated notes can be added with:
310
+
311
+ ```shell
312
+ uv run just publish-release-notes 0.3.0
313
+ ```
@@ -76,7 +76,53 @@ The fixtures called `[scope]_scoped_container_getter` will wait until every cont
76
76
 
77
77
  However, just because a container is up does not mean that the services running on it are ready to accept incoming requests yet!
78
78
 
79
- If your tests need to wait for a particular condition (for example, to wait for an HTTP health check endpoint to send back a 200 response), make sure that your fixtures account for this.
79
+ #### Option 1: Using Docker Compose Healthchecks (Recommended)
80
+
81
+ The plugin can use Docker Compose's native `--wait` functionality with healthchecks defined in your `docker-compose.yml`. This lets Docker Compose handle waiting for services to be ready.
82
+
83
+ First, define healthchecks in your `docker-compose.yml`:
84
+
85
+ ```yaml
86
+ services:
87
+ my_api_service:
88
+ build: ./api
89
+ ports:
90
+ - "5000:5000"
91
+ depends_on:
92
+ my_db:
93
+ condition: service_healthy
94
+ healthcheck:
95
+ test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
96
+ interval: 5s
97
+ timeout: 5s
98
+ retries: 10
99
+ start_period: 10s
100
+ my_db:
101
+ image: postgres:15
102
+ healthcheck:
103
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
104
+ interval: 5s
105
+ timeout: 5s
106
+ retries: 10
107
+ ```
108
+
109
+ Then opt in to waiting when you run pytest:
110
+
111
+ ```shell
112
+ pytest --docker-compose-wait
113
+ ```
114
+
115
+ You can also specify a timeout (in seconds):
116
+
117
+ ```shell
118
+ pytest --docker-compose-wait --docker-compose-wait-timeout=120
119
+ ```
120
+
121
+ Waiting remains opt-in for backward compatibility. It is planned to become the default in version 1.0.
122
+
123
+ #### Option 2: Python-based Wait Fixtures
124
+
125
+ If your tests need to wait for a particular condition (for example, to wait for an HTTP health check endpoint to send back a 200 response), you can implement custom wait logic in your fixtures.
80
126
 
81
127
  Here's an example of a fixture called `wait_for_api` that waits for an HTTP service to come online before a test called `test_read_and_write` can run.
82
128
 
@@ -194,5 +240,47 @@ pytest --docker-compose-remove-volumes
194
240
 
195
241
  This option will be ignored if the plugin is not used. Again, this option can also be added to the `pytest.ini` file.
196
242
 
243
+ ### Command Line Options Summary
244
+
245
+ | Option | Description |
246
+ |--------|-------------|
247
+ | `--docker-compose` | Path to docker-compose.yml file or directory containing one. Multiple files can be specified with commas. |
248
+ | `--docker-compose-no-build` | Skip building Docker images before running tests. |
249
+ | `--docker-compose-remove-volumes` | Remove container volumes after tests complete. |
250
+ | `--use-running-containers` | Use already running containers instead of starting new ones. |
251
+ | `--docker-compose-wait` | Wait for services to be healthy before running tests (requires healthcheck definitions). |
252
+ | `--docker-compose-wait-timeout` | Timeout in seconds when waiting for services to be healthy. |
253
+
197
254
  For more examples on how to use this plugin look at the testing suite of this plugin itself! It will give you some examples for configuring `pyproject.toml` and how to use the different fixtures to run docker containers.
198
255
 
256
+ ## Releasing
257
+
258
+ Release commands are managed with `just`, which is installed by the development
259
+ dependency group:
260
+
261
+ ```shell
262
+ uv sync
263
+ ```
264
+
265
+ Preview the release notes for a version without modifying the repository:
266
+
267
+ ```shell
268
+ uv run just release-notes 0.3.0
269
+ ```
270
+
271
+ Create and push a release from a clean, up-to-date `main` branch by choosing a
272
+ semantic version increment:
273
+
274
+ ```shell
275
+ uv run just release patch
276
+ uv run just release minor
277
+ uv run just release major
278
+ ```
279
+
280
+ The command runs the tests, updates the package version, creates the version
281
+ commit and tag, and pushes both. Once GitHub Actions has created the GitHub
282
+ Release, its generated notes can be added with:
283
+
284
+ ```shell
285
+ uv run just publish-release-notes 0.3.0
286
+ ```
@@ -10,7 +10,7 @@ authors = [
10
10
  ]
11
11
  dependencies = [
12
12
  "pytest>=7.2.2,<10",
13
- "python-on-whales>=0.69.0,<1"
13
+ "python-on-whales>=0.71.0,<1"
14
14
  ]
15
15
  requires-python = ">=3.10"
16
16
  readme = "README.md"
@@ -58,6 +58,7 @@ dev = [
58
58
  "tox>=4.30.3",
59
59
  "tox-uv>=1.28.1",
60
60
  "pytest>=8,<10",
61
+ "rust-just>=1.58.0",
61
62
  ]
62
63
 
63
64
  [tool.hatch.version]
@@ -67,23 +68,24 @@ path = "pytest_docker_compose/__init__.py"
67
68
  packages = ["pytest_docker_compose"]
68
69
 
69
70
  [tool.pytest.ini_options]
70
- addopts = "-m 'not (should_fail or multiple_compose_files)' '--docker-compose' './tests/pytest_docker_compose_tests/my_network' '--docker-compose-no-build'"
71
+ addopts = "-m 'not (should_fail or multiple_compose_files or wait_for_healthcheck)' '--docker-compose' './tests/pytest_docker_compose_tests/my_network' '--docker-compose-no-build'"
71
72
  markers = [
72
73
  "should_fail: marks a set of test that should throw an error when run",
73
74
  "multiple_compose_files: marks a test that uses more than one compose file",
75
+ "wait_for_healthcheck: marks tests that use --docker-compose-wait with healthchecks",
74
76
  ]
75
77
  minversion = "7.2"
76
78
 
77
79
  [tool.ruff]
78
80
  line-length = 88
79
- target-version = "py311"
81
+ target-version = "py310"
80
82
 
81
83
  [tool.ruff.lint.mccabe]
82
84
  # Unlike Flake8, default to a complexity level of 10.
83
85
  max-complexity = 10
84
86
 
85
87
  [tool.bumpver]
86
- current_version = "0.2.0"
88
+ current_version = "0.3.0"
87
89
  version_pattern = "MAJOR.MINOR.PATCH[GITHASH]"
88
90
  commit_message = "bump version {old_version} -> {new_version}"
89
91
  commit = true
@@ -122,4 +124,5 @@ commands=
122
124
  docker compose -f tests/pytest_docker_compose_tests/my_network/docker-compose.yml up -d
123
125
  pytest --use-running-containers
124
126
  docker compose -f tests/pytest_docker_compose_tests/my_network/docker-compose.yml down -v
127
+ pytest -m wait_for_healthcheck --docker-compose ./tests/pytest_docker_compose_tests/my_network --docker-compose-wait
125
128
  """
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -11,8 +11,6 @@ from python_on_whales.components.container.cli_wrapper import Container
11
11
  class ContainersAlreadyExist(Exception):
12
12
  """Raised when running containers are unexpectedly found"""
13
13
 
14
- pass
15
-
16
14
 
17
15
  __all__ = [
18
16
  "DockerComposePlugin",
@@ -125,6 +123,21 @@ class DockerComposePlugin:
125
123
  "instead of calling 'docker-compose up'",
126
124
  )
127
125
 
126
+ group.addoption(
127
+ "--docker-compose-wait",
128
+ action="store_true",
129
+ default=False,
130
+ help="Wait for services to be healthy before running tests "
131
+ "(requires healthcheck definitions in docker-compose.yml)",
132
+ )
133
+
134
+ group.addoption(
135
+ "--docker-compose-wait-timeout",
136
+ type=int,
137
+ default=None,
138
+ help="Timeout in seconds when waiting for services to be healthy",
139
+ )
140
+
128
141
  @pytest.fixture(scope="session")
129
142
  def docker_project(self, request):
130
143
  """
@@ -144,10 +157,8 @@ class DockerComposePlugin:
144
157
 
145
158
  if not docker_compose.is_file():
146
159
  raise ValueError(
147
- "Unable to find `{docker_compose}` "
148
- "for integration tests.".format(
149
- docker_compose=docker_compose.absolute(),
150
- ),
160
+ f"Unable to find `{docker_compose.absolute()}` "
161
+ "for integration tests.",
151
162
  )
152
163
 
153
164
  compose_files.append(docker_compose)
@@ -183,11 +194,14 @@ class DockerComposePlugin:
183
194
  )
184
195
  )
185
196
  current_containers = project.compose.ps()
186
- project.compose.up(detach=True, quiet=True)
187
- containers = [
188
- key for key, value in project.compose.config().services.items()
189
- ]
190
- if not len(current_containers) == len(containers):
197
+ project.compose.up(
198
+ detach=True,
199
+ quiet=True,
200
+ wait=request.config.getoption("--docker-compose-wait"),
201
+ wait_timeout=request.config.getoption("--docker-compose-wait-timeout"),
202
+ )
203
+ containers = list(project.compose.config().services.keys())
204
+ if len(current_containers) != len(containers):
191
205
  warnings.warn(
192
206
  UserWarning(
193
207
  "You used the '--use-running-containers' but "
@@ -197,11 +211,9 @@ class DockerComposePlugin:
197
211
  )
198
212
  else:
199
213
  if any(
200
- (
201
- container
202
- for container in project.compose.ps()
203
- if container.state.running
204
- )
214
+ container
215
+ for container in project.compose.ps()
216
+ if container.state.running
205
217
  ):
206
218
  raise ContainersAlreadyExist(
207
219
  "There are already existing containers, please remove all "
@@ -228,18 +240,21 @@ class DockerComposePlugin:
228
240
  now = datetime.now(timezone.utc)
229
241
  if not request.config.getoption("--use-running-containers"):
230
242
  if any(
231
- (
232
- container
233
- for container in docker_project.compose.ps()
234
- if container.state.running
235
- )
243
+ container
244
+ for container in docker_project.compose.ps()
245
+ if container.state.running
236
246
  ):
237
247
  raise ContainersAlreadyExist(
238
248
  "pytest-docker-compose tried to start containers but there are"
239
- " already running containers: %s, you probably scoped your"
240
- " tests wrong" % docker_project.compose.ps()
249
+ f" already running containers: {docker_project.compose.ps()},"
250
+ " you probably scoped your tests wrong"
241
251
  )
242
- docker_project.compose.up(detach=True, quiet=True)
252
+ docker_project.compose.up(
253
+ detach=True,
254
+ quiet=True,
255
+ wait=request.config.getoption("--docker-compose-wait"),
256
+ wait_timeout=request.config.getoption("--docker-compose-wait-timeout"),
257
+ )
243
258
  if not any(docker_project.compose.ps()):
244
259
  raise ValueError("`docker-compose` didn't launch any containers!")
245
260
 
@@ -249,7 +264,7 @@ class DockerComposePlugin:
249
264
  if request.config.getoption("--verbose"):
250
265
  containers = docker_project.compose.ps()
251
266
  for container in sorted(containers, key=lambda c: c.name):
252
- header = "Logs from {name}:".format(name=container.name)
267
+ header = f"Logs from {container.name}:"
253
268
  print(header, "\n", "=" * len(header))
254
269
  print(
255
270
  container.logs(since=now) or "(no logs)",
@@ -261,16 +276,13 @@ class DockerComposePlugin:
261
276
  volumes=request.config.getoption("--docker-compose-remove-volumes")
262
277
  )
263
278
 
264
- scoped_containers_fixture.__wrapped__.__doc__ = (
265
- """
279
+ scoped_containers_fixture.__wrapped__.__doc__ = f"""
266
280
  Spins up the containers for the Docker project and returns an
267
281
  object that can retrieve the containers. The returned containers
268
282
  all have one additional attribute called network_info to simplify
269
283
  accessing the hostnames and exposed port numbers for each container.
270
- This set of containers is scoped to '%s'
284
+ This set of containers is scoped to '{scope}'
271
285
  """
272
- % scope
273
- )
274
286
  return scoped_containers_fixture
275
287
 
276
288
 
@@ -307,8 +319,8 @@ class ContainerGetter:
307
319
  if not containers_running.get(key):
308
320
  warnings.warn(
309
321
  UserWarning(
310
- "The service '%s' only has a stopped container, "
311
- "it stopped with '%s'" % (key, container.state.status)
322
+ f"The service '{key}' only has a stopped container, "
323
+ f"it stopped with '{container.state.status}'"
312
324
  )
313
325
  )
314
326
  network_info = create_network_info_for_container(container)
@@ -0,0 +1,3 @@
1
+ uvicorn==0.53.0
2
+ psycopg2-binary==2.9.13
3
+ fastapi==0.141.1
@@ -0,0 +1,33 @@
1
+ services:
2
+ my_api_service:
3
+ build: ./a_buildable_container
4
+ ports:
5
+ - "5000:5000"
6
+ depends_on:
7
+ my_db:
8
+ condition: service_healthy
9
+ my_short_lived_service:
10
+ condition: service_completed_successfully
11
+ restart: on-failure
12
+ healthcheck:
13
+ test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/')"]
14
+ interval: 2s
15
+ timeout: 5s
16
+ retries: 10
17
+ start_period: 5s
18
+ my_db:
19
+ image: postgres:11.2-alpine
20
+ ports:
21
+ - "5432:5432"
22
+ environment:
23
+ POSTGRES_HOST_AUTH_METHOD: trust
24
+ healthcheck:
25
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
26
+ interval: 2s
27
+ timeout: 5s
28
+ retries: 10
29
+ start_period: 5s
30
+ my_short_lived_service:
31
+ build: ./a_buildable_container
32
+ entrypoint: /bin/echo
33
+ command: "Echoing"
@@ -5,7 +5,6 @@ import requests
5
5
  from requests.adapters import HTTPAdapter
6
6
  from urllib3.util.retry import Retry
7
7
 
8
-
9
8
  @pytest.fixture(scope="function")
10
9
  def wait_for_api(function_scoped_container_getter):
11
10
  """Wait for the api from my_api_service to become responsive"""
@@ -6,7 +6,6 @@ from requests.adapters import HTTPAdapter
6
6
 
7
7
  import pytest
8
8
 
9
-
10
9
  @pytest.fixture(scope="module")
11
10
  def wait_for_api(module_scoped_container_getter):
12
11
  """Wait for the api from my_api_service to become responsive"""
@@ -0,0 +1,48 @@
1
+ """
2
+ Tests for the --docker-compose-wait feature.
3
+
4
+ These tests verify that when --docker-compose-wait is used, containers
5
+ are healthy (based on their healthcheck definitions) before tests run.
6
+ """
7
+
8
+ import pytest
9
+ import requests
10
+
11
+ pytestmark = pytest.mark.wait_for_healthcheck
12
+
13
+
14
+ @pytest.fixture(scope="function")
15
+ def api_service(function_scoped_container_getter):
16
+ """Get the API service container."""
17
+ container = function_scoped_container_getter.get("my_api_service")
18
+ assert hasattr(container, "network_info")
19
+ assert container.network_info
20
+ return container
21
+
22
+
23
+ def test_container_is_healthy_when_wait_flag_used(api_service):
24
+ """
25
+ When --docker-compose-wait is used, the container should already be healthy
26
+ by the time the test runs, meaning we can immediately make requests without
27
+ any retry logic.
28
+ """
29
+ network_info = api_service.network_info[0]
30
+ api_url = f"http://{network_info.hostname}:{network_info.host_port}/"
31
+
32
+ # With --docker-compose-wait, the service should be immediately available
33
+ # without needing retry logic
34
+ response = requests.get(api_url, timeout=5)
35
+ assert response.status_code == 200
36
+ assert response.json() == {"Hello": "World"}
37
+
38
+
39
+ def test_db_container_is_healthy(function_scoped_container_getter):
40
+ """
41
+ Verify the database container is also healthy when --docker-compose-wait is used.
42
+ """
43
+ container = function_scoped_container_getter.get("my_db")
44
+ # Container should be running and healthy
45
+ assert container.state.running
46
+ # With --docker-compose-wait, the health status should be "healthy"
47
+ assert container.state.health is not None
48
+ assert container.state.health.status == "healthy"