|  | 
| 1 | 1 | from dataclasses import asdict, dataclass, field, fields | 
| 2 | 2 | from functools import cached_property | 
| 3 | 3 | from json import loads | 
|  | 4 | +from logging import warning | 
| 4 | 5 | from os import PathLike | 
| 5 | 6 | from platform import system | 
| 6 | 7 | from re import split | 
| 7 | 8 | from subprocess import CompletedProcess | 
| 8 | 9 | from subprocess import run as subprocess_run | 
| 9 |  | -from typing import Callable, Literal, Optional, TypeVar, Union | 
|  | 10 | +from typing import Any, Callable, Literal, Optional, TypeVar, Union, cast | 
| 10 | 11 | from urllib.error import HTTPError, URLError | 
| 11 | 12 | from urllib.request import urlopen | 
| 12 | 13 | 
 | 
| 13 | 14 | from testcontainers.core.exceptions import ContainerIsNotRunning, NoSuchPortExposed | 
| 14 | 15 | from testcontainers.core.waiting_utils import wait_container_is_ready | 
| 15 | 16 | 
 | 
| 16 | 17 | _IPT = TypeVar("_IPT") | 
|  | 18 | +_WARNINGS = {"DOCKER_COMPOSE_GET_CONFIG": "get_config is experimental, see testcontainers/testcontainers-python#669"} | 
| 17 | 19 | 
 | 
| 18 | 20 | 
 | 
| 19 | 21 | def _ignore_properties(cls: type[_IPT], dict_: any) -> _IPT: | 
| @@ -258,6 +260,36 @@ def get_logs(self, *services: str) -> tuple[str, str]: | 
| 258 | 260 |         result = self._run_command(cmd=logs_cmd) | 
| 259 | 261 |         return result.stdout.decode("utf-8"), result.stderr.decode("utf-8") | 
| 260 | 262 | 
 | 
|  | 263 | +    def get_config( | 
|  | 264 | +        self, *, path_resolution: bool = True, normalize: bool = True, interpolate: bool = True | 
|  | 265 | +    ) -> dict[str, Any]: | 
|  | 266 | +        """ | 
|  | 267 | +        Parse, resolve and returns compose file via `docker config --format json`. | 
|  | 268 | +        In case of multiple compose files, the returned value will be a merge of all files. | 
|  | 269 | +
 | 
|  | 270 | +        See: https://docs.docker.com/reference/cli/docker/compose/config/ for more details | 
|  | 271 | +
 | 
|  | 272 | +        :param path_resolution: whether to resolve file paths | 
|  | 273 | +        :param normalize: whether to normalize compose model | 
|  | 274 | +        :param interpolate: whether to interpolate environment variables | 
|  | 275 | +
 | 
|  | 276 | +        Returns: | 
|  | 277 | +            Compose file | 
|  | 278 | +
 | 
|  | 279 | +        """ | 
|  | 280 | +        if "DOCKER_COMPOSE_GET_CONFIG" in _WARNINGS: | 
|  | 281 | +            warning(_WARNINGS.pop("DOCKER_COMPOSE_GET_CONFIG")) | 
|  | 282 | +        config_cmd = [*self.compose_command_property, "config", "--format", "json"] | 
|  | 283 | +        if not path_resolution: | 
|  | 284 | +            config_cmd.append("--no-path-resolution") | 
|  | 285 | +        if not normalize: | 
|  | 286 | +            config_cmd.append("--no-normalize") | 
|  | 287 | +        if not interpolate: | 
|  | 288 | +            config_cmd.append("--no-interpolate") | 
|  | 289 | + | 
|  | 290 | +        cmd_output = self._run_command(cmd=config_cmd).stdout | 
|  | 291 | +        return cast(dict[str, Any], loads(cmd_output)) | 
|  | 292 | + | 
| 261 | 293 |     def get_containers(self, include_all=False) -> list[ComposeContainer]: | 
| 262 | 294 |         """ | 
| 263 | 295 |         Fetch information about running containers via `docker compose ps --format json`. | 
|  | 
0 commit comments