|
1 | 1 | import sys |
2 | | -from dataclasses import asdict, dataclass, field, fields, is_dataclass |
| 2 | +from dataclasses import asdict, dataclass, field |
3 | 3 | from functools import cached_property |
4 | 4 | from json import loads |
5 | 5 | from logging import getLogger, warning |
|
11 | 11 | from types import TracebackType |
12 | 12 | from typing import Any, Callable, Literal, Optional, TypeVar, Union, cast |
13 | 13 |
|
| 14 | +from testcontainers.core.docker_client import ContainerInspectInfo, _ignore_properties |
14 | 15 | from testcontainers.core.exceptions import ContainerIsNotRunning, NoSuchPortExposed |
15 | 16 | from testcontainers.core.waiting_utils import WaitStrategy |
16 | 17 |
|
|
20 | 21 | logger = getLogger(__name__) |
21 | 22 |
|
22 | 23 |
|
23 | | -def _ignore_properties(cls: type[_IPT], dict_: Any) -> _IPT: |
24 | | - """omits extra fields like @JsonIgnoreProperties(ignoreUnknown = true) |
25 | | -
|
26 | | - https://gist.github.com/alexanderankin/2a4549ac03554a31bef6eaaf2eaf7fd5""" |
27 | | - if isinstance(dict_, cls): |
28 | | - return dict_ |
29 | | - if not is_dataclass(cls): |
30 | | - raise TypeError(f"Expected a dataclass type, got {cls}") |
31 | | - class_fields = {f.name for f in fields(cls)} |
32 | | - filtered = {k: v for k, v in dict_.items() if k in class_fields} |
33 | | - return cast("_IPT", cls(**filtered)) |
34 | | - |
35 | | - |
36 | | -@dataclass |
37 | | -class ContainerState: |
38 | | - """Container state from docker inspect.""" |
39 | | - |
40 | | - Status: Optional[str] = None |
41 | | - Running: Optional[bool] = None |
42 | | - Paused: Optional[bool] = None |
43 | | - Restarting: Optional[bool] = None |
44 | | - OOMKilled: Optional[bool] = None |
45 | | - Dead: Optional[bool] = None |
46 | | - Pid: Optional[int] = None |
47 | | - ExitCode: Optional[int] = None |
48 | | - Error: Optional[str] = None |
49 | | - StartedAt: Optional[str] = None |
50 | | - FinishedAt: Optional[str] = None |
51 | | - |
52 | | - |
53 | | -@dataclass |
54 | | -class ContainerConfig: |
55 | | - """Container config from docker inspect.""" |
56 | | - |
57 | | - Hostname: Optional[str] = None |
58 | | - User: Optional[str] = None |
59 | | - Env: Optional[list[str]] = None |
60 | | - Cmd: Optional[list[str]] = None |
61 | | - Image: Optional[str] = None |
62 | | - WorkingDir: Optional[str] = None |
63 | | - Entrypoint: Optional[list[str]] = None |
64 | | - ExposedPorts: Optional[dict[str, Any]] = None |
65 | | - Labels: Optional[dict[str, str]] = None |
66 | | - |
67 | | - |
68 | | -@dataclass |
69 | | -class Network: |
70 | | - """Individual network from docker inspect.""" |
71 | | - |
72 | | - IPAddress: Optional[str] = None |
73 | | - Gateway: Optional[str] = None |
74 | | - NetworkID: Optional[str] = None |
75 | | - EndpointID: Optional[str] = None |
76 | | - MacAddress: Optional[str] = None |
77 | | - Aliases: Optional[list[str]] = None |
78 | | - |
79 | | - |
80 | | -@dataclass |
81 | | -class NetworkSettings: |
82 | | - """Network settings from docker inspect.""" |
83 | | - |
84 | | - Bridge: Optional[str] = None |
85 | | - IPAddress: Optional[str] = None |
86 | | - Gateway: Optional[str] = None |
87 | | - Ports: Optional[dict[str, Any]] = None |
88 | | - Networks: Optional[dict[str, Network]] = None |
89 | | - |
90 | | - def get_networks(self) -> Optional[dict[str, Network]]: |
91 | | - """Get networks for the container.""" |
92 | | - return self.Networks |
93 | | - |
94 | | - |
95 | | -@dataclass |
96 | | -class ContainerInspectInfo: |
97 | | - """Container information from docker inspect.""" |
98 | | - |
99 | | - Id: Optional[str] = None |
100 | | - Name: Optional[str] = None |
101 | | - Created: Optional[str] = None |
102 | | - Path: Optional[str] = None |
103 | | - Args: Optional[list[str]] = None |
104 | | - Image: Optional[str] = None |
105 | | - State: Optional[ContainerState] = None |
106 | | - Config: Optional[ContainerConfig] = None |
107 | | - network_settings: Optional[NetworkSettings] = None |
108 | | - Mounts: Optional[list[dict[str, Any]]] = None |
109 | | - |
110 | | - @classmethod |
111 | | - def from_dict(cls, data: dict[str, Any]) -> "ContainerInspectInfo": |
112 | | - """Create from docker inspect JSON.""" |
113 | | - return cls( |
114 | | - Id=data.get("Id"), |
115 | | - Name=data.get("Name"), |
116 | | - Created=data.get("Created"), |
117 | | - Path=data.get("Path"), |
118 | | - Args=data.get("Args"), |
119 | | - Image=data.get("Image"), |
120 | | - State=_ignore_properties(ContainerState, data.get("State", {})) if data.get("State") else None, |
121 | | - Config=_ignore_properties(ContainerConfig, data.get("Config", {})) if data.get("Config") else None, |
122 | | - network_settings=cls._parse_network_settings(data.get("NetworkSettings", {})) |
123 | | - if data.get("NetworkSettings") |
124 | | - else None, |
125 | | - Mounts=data.get("Mounts"), |
126 | | - ) |
127 | | - |
128 | | - @classmethod |
129 | | - def _parse_network_settings(cls, data: dict[str, Any]) -> Optional[NetworkSettings]: |
130 | | - """Parse NetworkSettings with Networks as Network objects.""" |
131 | | - if not data: |
132 | | - return None |
133 | | - |
134 | | - networks_data = data.get("Networks", {}) |
135 | | - networks = {} |
136 | | - for name, net_data in networks_data.items(): |
137 | | - networks[name] = _ignore_properties(Network, net_data) |
138 | | - |
139 | | - return NetworkSettings( |
140 | | - Bridge=data.get("Bridge"), |
141 | | - IPAddress=data.get("IPAddress"), |
142 | | - Gateway=data.get("Gateway"), |
143 | | - Ports=data.get("Ports"), |
144 | | - Networks=networks, |
145 | | - ) |
146 | | - |
147 | | - def get_network_settings(self) -> Optional[NetworkSettings]: |
148 | | - """Get network settings for the container.""" |
149 | | - return self.network_settings |
150 | | - |
151 | | - |
152 | 24 | @dataclass |
153 | 25 | class PublishedPortModel: |
154 | 26 | """ |
|
0 commit comments