Skip to content

Commit

Permalink
Set up command construction to support generic iterables and mappings (
Browse files Browse the repository at this point in the history
…#595)

First step in working on
#584.

- `Command.add_args_list()` previously had a misleading name and type
annotation - it is used for accepting either a list or a single item.
Renamed to `Command.add_args_iterable_or_single()`.
- Added `Command.add_args_iterable()` for the case you know you're
dealing with an iterable (more efficient than always creating a new
list).
- Updated `utils.to_list()` to support generic iterables (still always
returns a list, as per the name - my plan is to phase out its use where
it's not needed).
- Updated `utils.format_dict_for_cli()` to support generic mappings.
- Added `Command.add_args_mapping()` as a convenience that wraps
`utils.format_dict_for_cli()`, which will allow tidying up code that
constructs commands.

Next step is to tidy up the components' code that constructs commands,
allowing arguments to be `Iterable` and `Mapping` in place of `List` and
`Dict`. With these changes it should be fairly straightforward, simply
calling the correct method on the `Command` class.

Here's an example of what upcoming changes look like:
LewisGaul/python-on-whales@command-construction...command-construction-image
  • Loading branch information
LewisGaul authored May 29, 2024
1 parent fd18ff4 commit 8b05066
Show file tree
Hide file tree
Showing 13 changed files with 214 additions and 140 deletions.
27 changes: 20 additions & 7 deletions python_on_whales/client_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, Dict, List, Literal, Optional, Union
from typing import Any, Dict, Iterable, List, Literal, Mapping, Optional, Union

import pydantic

from python_on_whales.download_binaries import (
download_docker_cli,
get_docker_binary_path_in_cache,
)
from python_on_whales.utils import to_list

from .utils import ValidPath, run
from . import utils
from .utils import ValidPath, run, to_list

CACHE_VALIDITY_PERIOD = 0.01

Expand All @@ -37,10 +37,23 @@ def add_flag(self, name: str, value: bool):
if value:
self.append(name)

def add_args_list(self, arg_name: str, list_values: list):
for value in to_list(list_values):
def add_args_iterable(self, arg_name: str, values: Iterable[Any]):
for value in values:
self.extend([arg_name, value])

def add_args_iterable_or_single(
self, arg_name: str, iterable_or_single: Union[Iterable[Any], Any]
):
for value in to_list(iterable_or_single):
self.extend([arg_name, value])

def add_args_mapping(
self, arg_name: str, mapping: Mapping[Any, Any], *, separator="="
):
self.add_args_iterable(
arg_name, utils.format_mapping_for_cli(mapping, separator)
)

def __add__(self, other) -> "Command":
return Command(super().__add__(other))

Expand Down Expand Up @@ -144,8 +157,8 @@ def docker_cmd(self) -> Command:
@property
def docker_compose_cmd(self) -> Command:
base_cmd = self.docker_cmd + ["compose"]
base_cmd.add_args_list("--file", self.compose_files)
base_cmd.add_args_list("--profile", self.compose_profiles)
base_cmd.add_args_iterable_or_single("--file", self.compose_files)
base_cmd.add_args_iterable_or_single("--profile", self.compose_profiles)
base_cmd.add_simple_arg("--env-file", self.compose_env_file)
base_cmd.add_simple_arg("--project-name", self.compose_project_name)
base_cmd.add_simple_arg("--project-directory", self.compose_project_directory)
Expand Down
30 changes: 18 additions & 12 deletions python_on_whales/components/buildx/cli_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from python_on_whales.components.buildx.models import BuilderInspectResult
from python_on_whales.utils import (
ValidPath,
format_dict_for_cli,
format_mapping_for_cli,
run,
stream_stdout_and_stderr,
to_list,
Expand Down Expand Up @@ -196,7 +196,7 @@ def bake(
full_cmd += ["--progress", progress]
for file in to_list(files):
full_cmd.add_simple_arg("--file", file)
full_cmd.add_args_list("--set", format_dict_for_cli(set))
full_cmd.add_args_iterable_or_single("--set", format_mapping_for_cli(set))
targets = to_list(targets)
env = dict(variables)
if print:
Expand Down Expand Up @@ -326,16 +326,20 @@ def build(
if progress != "auto" and isinstance(progress, str):
full_cmd += ["--progress", progress]

full_cmd.add_args_list(
"--add-host", format_dict_for_cli(add_hosts, separator=":")
full_cmd.add_args_iterable_or_single(
"--add-host", format_mapping_for_cli(add_hosts, separator=":")
)
full_cmd.add_args_list("--allow", allow)
full_cmd.add_args_iterable_or_single("--allow", allow)
if isinstance(attest, dict):
full_cmd.add_simple_arg("--attest", format_dict_for_buildx(attest))
full_cmd.add_args_list("--build-arg", format_dict_for_cli(build_args))
full_cmd.add_args_list("--build-context", format_dict_for_cli(build_contexts))
full_cmd.add_args_iterable_or_single(
"--build-arg", format_mapping_for_cli(build_args)
)
full_cmd.add_args_iterable_or_single(
"--build-context", format_mapping_for_cli(build_contexts)
)
full_cmd.add_simple_arg("--builder", builder)
full_cmd.add_args_list("--label", format_dict_for_cli(labels))
full_cmd.add_args_iterable_or_single("--label", format_mapping_for_cli(labels))

full_cmd.add_simple_arg("--ssh", ssh)

Expand Down Expand Up @@ -363,14 +367,14 @@ def build(
full_cmd.add_simple_arg("--cache-to", format_dict_for_buildx(cache_to))
else:
full_cmd.add_simple_arg("--cache-to", cache_to)
full_cmd.add_args_list("--secret", to_list(secrets))
full_cmd.add_args_iterable_or_single("--secret", to_list(secrets))
if output != {}:
full_cmd += ["--output", format_dict_for_buildx(output)]
if platforms is not None:
full_cmd += ["--platform", ",".join(platforms)]
full_cmd.add_simple_arg("--network", network)
full_cmd.add_flag("--no-cache", not cache)
full_cmd.add_args_list("--tag", tags)
full_cmd.add_args_iterable_or_single("--tag", tags)

if stream_logs:
if progress in (False, "tty"):
Expand Down Expand Up @@ -558,7 +562,9 @@ def prune(
"""
full_cmd = self.docker_cmd + ["buildx", "prune", "--force"]
full_cmd.add_flag("--all", all)
full_cmd.add_args_list("--filter", format_dict_for_cli(filters))
full_cmd.add_args_iterable_or_single(
"--filter", format_mapping_for_cli(filters)
)
if stream_logs:
return stream_buildx_logs(full_cmd)
run(full_cmd)
Expand Down Expand Up @@ -640,7 +646,7 @@ def removesuffix(base_string: str, suffix: str) -> str:


def format_dict_for_buildx(options: Dict[str, str]) -> str:
return ",".join(format_dict_for_cli(options, separator="="))
return ",".join(format_mapping_for_cli(options, separator="="))


def stream_buildx_logs(full_cmd: list, env: Dict[str, str] = None) -> Iterator[str]:
Expand Down
12 changes: 8 additions & 4 deletions python_on_whales/components/compose/cli_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from python_on_whales.client_config import DockerCLICaller
from python_on_whales.components.compose.models import ComposeConfig, ComposeProject
from python_on_whales.utils import (
format_dict_for_cli,
format_mapping_for_cli,
format_signal_arg,
parse_ls_status_count,
run,
Expand Down Expand Up @@ -89,7 +89,9 @@ def build(
)

full_cmd = self.docker_compose_cmd + ["build"]
full_cmd.add_args_list("--build-arg", format_dict_for_cli(build_args))
full_cmd.add_args_iterable_or_single(
"--build-arg", format_mapping_for_cli(build_args)
)
full_cmd.add_flag("--no-cache", not cache)
full_cmd.add_simple_arg("--progress", progress)
full_cmd.add_flag("--pull", pull)
Expand Down Expand Up @@ -500,7 +502,9 @@ def ls(
"""
full_cmd = self.docker_compose_cmd + ["ls", "--format", "json"]
full_cmd.add_flag("--all", all)
full_cmd.add_args_list("--filter", format_dict_for_cli(filters))
full_cmd.add_args_iterable_or_single(
"--filter", format_mapping_for_cli(filters)
)

return [
ComposeProject(
Expand Down Expand Up @@ -751,7 +755,7 @@ def run(
full_cmd.add_flag("--use-aliases", use_aliases)
full_cmd.add_simple_arg("--user", user)
full_cmd.add_simple_arg("--workdir", workdir)
full_cmd.add_args_list("--label", format_dict_for_cli(labels))
full_cmd.add_args_iterable_or_single("--label", format_mapping_for_cli(labels))
full_cmd.append(service)
full_cmd += command

Expand Down
8 changes: 5 additions & 3 deletions python_on_whales/components/config/cli_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
ConfigSpec,
DockerObjectVersion,
)
from python_on_whales.utils import format_dict_for_cli, run, to_list
from python_on_whales.utils import format_mapping_for_cli, run, to_list


class Config(ReloadableObjectFromJson):
Expand Down Expand Up @@ -96,7 +96,7 @@ def create(
A `python_on_whales.Config` object.
"""
full_cmd = self.docker_cmd + ["config", "create"]
full_cmd.add_args_list("--label", format_dict_for_cli(labels))
full_cmd.add_args_iterable_or_single("--label", format_mapping_for_cli(labels))
full_cmd.add_simple_arg("--template-driver", template_driver)
full_cmd += [name, file]
return Config(self.client_config, run(full_cmd), is_immutable_id=True)
Expand Down Expand Up @@ -135,7 +135,9 @@ def list(self, filters: Dict[str, str] = {}) -> List[Config]:
A `List[python_on_whales.Config]`.
"""
full_cmd = self.docker_cmd + ["config", "list", "--quiet"]
full_cmd.add_args_list("--filter", format_dict_for_cli(filters))
full_cmd.add_args_iterable_or_single(
"--filter", format_mapping_for_cli(filters)
)
output = run(full_cmd)
ids = output.splitlines()
return [Config(self.client_config, id_, is_immutable_id=True) for id_ in ids]
Expand Down
Loading

0 comments on commit 8b05066

Please sign in to comment.