|
4 | 4 | import json |
5 | 5 | import shlex |
6 | 6 | import textwrap |
| 7 | +import warnings |
7 | 8 | from datetime import datetime, timedelta |
8 | 9 | from pathlib import Path |
9 | 10 | from typing import ( |
|
16 | 17 | Optional, |
17 | 18 | Sequence, |
18 | 19 | Tuple, |
19 | | - TypedDict, |
20 | 20 | Union, |
21 | 21 | overload, |
22 | 22 | ) |
23 | 23 |
|
24 | 24 | import pydantic |
| 25 | +from typing_extensions import TypeAlias |
25 | 26 |
|
26 | 27 | import python_on_whales.components.image.cli_wrapper |
27 | 28 | import python_on_whales.components.network.cli_wrapper |
|
46 | 47 | ValidPath, |
47 | 48 | ValidPortMapping, |
48 | 49 | custom_parse_object_as, |
49 | | - format_mapping_for_cli, |
50 | 50 | format_port_arg, |
51 | 51 | format_signal_arg, |
52 | 52 | format_time_arg, |
|
58 | 58 | to_seconds, |
59 | 59 | ) |
60 | 60 |
|
61 | | -DockerContainerListFilters = TypedDict( |
62 | | - "DockerContainerListFilters", |
63 | | - { |
64 | | - "id": str, |
65 | | - "name": str, |
66 | | - "label": str, |
67 | | - "exited": int, |
68 | | - "status": Literal[ |
| 61 | +ContainerListFilter: TypeAlias = Union[ |
| 62 | + Tuple[Literal["id"], str], |
| 63 | + Tuple[Literal["name"], str], |
| 64 | + Tuple[Literal["label"], str], |
| 65 | + Tuple[Literal["label!"], str], |
| 66 | + Tuple[Literal["exited"], int], |
| 67 | + Tuple[ |
| 68 | + Literal["status"], |
| 69 | + Literal[ |
69 | 70 | "created", "restarting", "running", "removing", "paused", "exited", "dead" |
70 | 71 | ], |
71 | | - "ancestor": str, |
72 | | - "before": str, # TODO: allow datetime |
73 | | - "since": str, # TODO: allow datetime |
74 | | - "volume": str, # TODO: allow Volumes |
75 | | - "network": str, # TODO: allow Network |
76 | | - "publish": str, |
77 | | - "expose": str, |
78 | | - "health": Literal["starting", "healthy", "unhealthy", "none"], |
79 | | - "isolation": Literal["default", "process", "hyperv"], |
80 | | - "is-task": str, # TODO: allow bool |
81 | | - }, |
82 | | - total=False, |
83 | | -) |
| 72 | + ], |
| 73 | + Tuple[Literal["ancestor"], str], |
| 74 | + Tuple[Literal["before"], str], |
| 75 | + Tuple[Literal["since"], str], |
| 76 | + Tuple[Literal["volume"], str], # TODO: allow Volumes |
| 77 | + Tuple[Literal["network"], str], # TODO: allow Network |
| 78 | + Tuple[Literal["pod"], str], # TODO: allow Pod |
| 79 | + Tuple[Literal["publish"], str], |
| 80 | + Tuple[Literal["expose"], str], |
| 81 | + Tuple[Literal["health"], Literal["starting", "healthy", "unhealthy", "none"]], |
| 82 | + Tuple[Literal["isolation"], Literal["default", "process", "hyperv"]], |
| 83 | + Tuple[Literal["is-task"], str], # TODO: allow bool |
| 84 | + Tuple[Literal["until"], str], # TODO: allow datetime |
| 85 | +] |
84 | 86 |
|
85 | 87 |
|
86 | 88 | class Container(ReloadableObjectFromJson): |
@@ -1179,24 +1181,33 @@ def logs( |
1179 | 1181 | return "".join(x[1].decode() for x in iterator) |
1180 | 1182 |
|
1181 | 1183 | def list( |
1182 | | - self, all: bool = False, filters: DockerContainerListFilters = {} |
| 1184 | + self, |
| 1185 | + all: bool = False, |
| 1186 | + filters: Union[Iterable[ContainerListFilter], Mapping[str, Any]] = (), |
1183 | 1187 | ) -> List[Container]: |
1184 | 1188 | """List the containers on the host. |
1185 | 1189 |
|
1186 | 1190 | Alias: `docker.ps(...)` |
1187 | 1191 |
|
1188 | 1192 | Parameters: |
1189 | 1193 | all: If `True`, also returns containers that are not running. |
| 1194 | + filters: Filters to apply when listing containers. |
1190 | 1195 |
|
1191 | 1196 | # Returns |
1192 | 1197 | A `List[python_on_whales.Container]` |
1193 | 1198 | """ |
| 1199 | + if isinstance(filters, Mapping): |
| 1200 | + filters = filters.items() |
| 1201 | + warnings.warn( |
| 1202 | + "Passing filters as a mapping is deprecated, replace with an " |
| 1203 | + "iterable of tuples instead, as so:\n" |
| 1204 | + f"filters={list(filters)}", |
| 1205 | + DeprecationWarning, |
| 1206 | + ) |
1194 | 1207 | full_cmd = self.docker_cmd |
1195 | 1208 | full_cmd += ["container", "list", "-q", "--no-trunc"] |
1196 | | - full_cmd.add_args_iterable_or_single( |
1197 | | - "--filter", format_mapping_for_cli(filters) |
1198 | | - ) |
1199 | 1209 | full_cmd.add_flag("--all", all) |
| 1210 | + full_cmd.add_args_iterable("--filter", (f"{f[0]}={f[1]}" for f in filters)) |
1200 | 1211 |
|
1201 | 1212 | # TODO: add a test for the fix of is_immutable_id, without it, we get |
1202 | 1213 | # race conditions (we read the attributes of a container but it might not exist. |
@@ -1230,41 +1241,41 @@ def pause( |
1230 | 1241 | @overload |
1231 | 1242 | def prune( |
1232 | 1243 | self, |
1233 | | - filters: Dict[str, str] = {}, |
| 1244 | + filters: Union[Iterable[ContainerListFilter], Mapping[str, Any]] = (), |
1234 | 1245 | stream_logs: Literal[True] = ..., |
1235 | 1246 | ) -> Iterable[Tuple[str, bytes]]: ... |
1236 | 1247 |
|
1237 | 1248 | @overload |
1238 | 1249 | def prune( |
1239 | 1250 | self, |
1240 | | - filters: Dict[str, str] = {}, |
| 1251 | + filters: Union[Iterable[ContainerListFilter], Mapping[str, Any]] = (), |
1241 | 1252 | stream_logs: Literal[False] = ..., |
1242 | 1253 | ) -> None: ... |
1243 | 1254 |
|
1244 | 1255 | def prune( |
1245 | 1256 | self, |
1246 | | - filters: Dict[str, str] = {}, |
| 1257 | + filters: Union[Iterable[ContainerListFilter], Mapping[str, Any]] = (), |
1247 | 1258 | stream_logs: bool = False, |
1248 | 1259 | ): |
1249 | 1260 | """Remove containers that are not running. |
1250 | 1261 |
|
1251 | 1262 | Parameters: |
1252 | | - filters: Filters as strings or list of strings |
| 1263 | + filters: Filters to apply when pruning. |
1253 | 1264 | stream_logs: If `True` this function will return an iterator of strings. |
1254 | 1265 | You can then read the logs as they arrive. If `False` (the default value), then |
1255 | 1266 | the function returns `None`, but when it returns, then the prune operation has already been |
1256 | 1267 | done. |
1257 | 1268 | """ |
1258 | | - if isinstance(filter, list): |
1259 | | - raise TypeError( |
1260 | | - "since python-on-whales 0.38.0, the filter argument is expected to be " |
1261 | | - "a dict, not a list, please replace your function call by " |
1262 | | - "docker.container.prune(filters={...})" |
| 1269 | + if isinstance(filters, Mapping): |
| 1270 | + filters = filters.items() |
| 1271 | + warnings.warn( |
| 1272 | + "Passing filters as a mapping is deprecated, replace with an " |
| 1273 | + "iterable of tuples instead, as so:\n" |
| 1274 | + f"filters={list(filters)}", |
| 1275 | + DeprecationWarning, |
1263 | 1276 | ) |
1264 | 1277 | full_cmd = self.docker_cmd + ["container", "prune", "--force"] |
1265 | | - full_cmd.add_args_iterable_or_single( |
1266 | | - "--filter", format_mapping_for_cli(filters) |
1267 | | - ) |
| 1278 | + full_cmd.add_args_iterable("--filter", (f"{f[0]}={f[1]}" for f in filters)) |
1268 | 1279 | if stream_logs: |
1269 | 1280 | return stream_stdout_and_stderr(full_cmd) |
1270 | 1281 | run(full_cmd) |
|
0 commit comments