diff --git a/podman/api/http_utils.py b/podman/api/http_utils.py index d71629ad..37d895ad 100644 --- a/podman/api/http_utils.py +++ b/podman/api/http_utils.py @@ -7,7 +7,9 @@ from collections.abc import Mapping -def prepare_filters(filters: Union[str, list[str], Mapping[str, str]]) -> Optional[str]: +def prepare_filters( + filters: Union[str, list[str], Mapping[str, Any], None], +) -> Optional[str]: """Return filters as an URL quoted JSON dict[str, list[Any]].""" if filters is None or len(filters) == 0: diff --git a/podman/client.py b/podman/client.py index 2cce2ef4..99964692 100644 --- a/podman/client.py +++ b/podman/client.py @@ -127,11 +127,10 @@ def from_env( environment = environment or os.environ credstore_env = credstore_env or {} - if version == "auto": - version = None + api_version: Optional[str] = None if version == "auto" else version kwargs = { - 'version': version, + 'version': api_version, 'timeout': timeout, 'tls': False, 'credstore_env': credstore_env, diff --git a/podman/domain/containers.py b/podman/domain/containers.py index 2f1e888f..495721d2 100644 --- a/podman/domain/containers.py +++ b/podman/domain/containers.py @@ -116,7 +116,7 @@ def commit(self, repository: str = None, tag: str = None, **kwargs) -> Image: "repo": repository, "tag": tag, } - response = self.client.post("/commit", params=params) + response = self.api.post("/commit", params=params) response.raise_for_status() body = response.json() @@ -128,7 +128,7 @@ def diff(self) -> list[dict[str, int]]: Raises: APIError: when service reports an error """ - response = self.client.get(f"/containers/{self.id}/changes") + response = self.api.get(f"/containers/{self.id}/changes") response.raise_for_status() return response.json() @@ -207,11 +207,11 @@ def exec_run( stream = stream and not detach # create the exec instance - response = self.client.post(f"/containers/{self.name}/exec", data=json.dumps(data)) + response = self.api.post(f"/containers/{self.name}/exec", data=json.dumps(data)) response.raise_for_status() exec_id = response.json()['Id'] # start the exec instance, this will store command output - start_resp = self.client.post( + start_resp = self.api.post( f"/exec/{exec_id}/start", data=json.dumps({"Detach": detach, "Tty": tty}), stream=stream ) start_resp.raise_for_status() @@ -220,7 +220,7 @@ def exec_run( return None, api.stream_frames(start_resp, demux=demux) # get and return exec information - response = self.client.get(f"/exec/{exec_id}/json") + response = self.api.get(f"/exec/{exec_id}/json") response.raise_for_status() if demux: stdout_data, stderr_data = demux_output(start_resp.content) @@ -240,7 +240,7 @@ def export(self, chunk_size: int = api.DEFAULT_CHUNK_SIZE) -> Iterator[bytes]: NotFound: when container has been removed from service APIError: when service reports an error """ - response = self.client.get(f"/containers/{self.id}/export", stream=True) + response = self.api.get(f"/containers/{self.id}/export", stream=True) response.raise_for_status() yield from response.iter_content(chunk_size=chunk_size) @@ -258,7 +258,7 @@ def get_archive( First item is a raw tar data stream. Second item is a dict containing os.stat() information on the specified path. """ - response = self.client.get(f"/containers/{self.id}/archive", params={"path": [path]}) + response = self.api.get(f"/containers/{self.id}/archive", params={"path": [path]}) response.raise_for_status() stat = response.headers.get("x-docker-container-path-stat", None) @@ -267,7 +267,7 @@ def get_archive( def init(self) -> None: """Initialize the container.""" - response = self.client.post(f"/containers/{self.id}/init") + response = self.api.post(f"/containers/{self.id}/init") response.raise_for_status() def inspect(self) -> dict: @@ -276,7 +276,7 @@ def inspect(self) -> dict: Raises: APIError: when service reports an error """ - response = self.client.get(f"/containers/{self.id}/json") + response = self.api.get(f"/containers/{self.id}/json") response.raise_for_status() return response.json() @@ -286,7 +286,7 @@ def kill(self, signal: Union[str, int, None] = None) -> None: Raises: APIError: when service reports an error """ - response = self.client.post(f"/containers/{self.id}/kill", params={"signal": signal}) + response = self.api.post(f"/containers/{self.id}/kill", params={"signal": signal}) response.raise_for_status() def logs(self, **kwargs) -> Union[bytes, Iterator[bytes]]: @@ -317,7 +317,7 @@ def logs(self, **kwargs) -> Union[bytes, Iterator[bytes]]: "until": api.prepare_timestamp(kwargs.get("until")), } - response = self.client.get(f"/containers/{self.id}/logs", stream=stream, params=params) + response = self.api.get(f"/containers/{self.id}/logs", stream=stream, params=params) response.raise_for_status() if stream: @@ -326,7 +326,7 @@ def logs(self, **kwargs) -> Union[bytes, Iterator[bytes]]: def pause(self) -> None: """Pause processes within the container.""" - response = self.client.post(f"/containers/{self.id}/pause") + response = self.api.post(f"/containers/{self.id}/pause") response.raise_for_status() def put_archive(self, path: str, data: bytes = None) -> bool: @@ -349,9 +349,7 @@ def put_archive(self, path: str, data: bytes = None) -> bool: if data is None: data = api.create_tar("/", path) - response = self.client.put( - f"/containers/{self.id}/archive", params={"path": path}, data=data - ) + response = self.api.put(f"/containers/{self.id}/archive", params={"path": path}, data=data) return response.ok def remove(self, **kwargs) -> None: @@ -375,7 +373,7 @@ def rename(self, name: str) -> None: if not name: raise ValueError("'name' is a required argument.") - response = self.client.post(f"/containers/{self.id}/rename", params={"name": name}) + response = self.api.post(f"/containers/{self.id}/rename", params={"name": name}) response.raise_for_status() self.attrs["Name"] = name # shortcut to avoid needing reload() @@ -391,7 +389,7 @@ def resize(self, height: int = None, width: int = None) -> None: "h": height, "w": width, } - response = self.client.post(f"/containers/{self.id}/resize", params=params) + response = self.api.post(f"/containers/{self.id}/resize", params=params) response.raise_for_status() def restart(self, **kwargs) -> None: @@ -405,7 +403,7 @@ def restart(self, **kwargs) -> None: if kwargs.get("timeout"): post_kwargs["timeout"] = float(params["timeout"]) * 1.5 - response = self.client.post(f"/containers/{self.id}/restart", params=params, **post_kwargs) + response = self.api.post(f"/containers/{self.id}/restart", params=params, **post_kwargs) response.raise_for_status() def start(self, **kwargs) -> None: @@ -414,7 +412,7 @@ def start(self, **kwargs) -> None: Keyword Args: detach_keys: Override the key sequence for detaching a container (Podman only) """ - response = self.client.post( + response = self.api.post( f"/containers/{self.id}/start", params={"detachKeys": kwargs.get("detach_keys")} ) response.raise_for_status() @@ -441,7 +439,7 @@ def stats( "stream": stream, } - response = self.client.get("/containers/stats", params=params, stream=stream) + response = self.api.get("/containers/stats", params=params, stream=stream) response.raise_for_status() if stream: @@ -463,7 +461,7 @@ def stop(self, **kwargs) -> None: if kwargs.get("timeout"): post_kwargs["timeout"] = float(params["timeout"]) * 1.5 - response = self.client.post(f"/containers/{self.id}/stop", params=params, **post_kwargs) + response = self.api.post(f"/containers/{self.id}/stop", params=params, **post_kwargs) response.raise_for_status() if response.status_code == requests.codes.no_content: @@ -493,7 +491,7 @@ def top(self, **kwargs) -> Union[Iterator[dict[str, Any]], dict[str, Any]]: "stream": stream, "ps_args": kwargs.get("ps_args"), } - response = self.client.get(f"/containers/{self.id}/top", params=params, stream=stream) + response = self.api.get(f"/containers/{self.id}/top", params=params, stream=stream) response.raise_for_status() if stream: @@ -503,7 +501,7 @@ def top(self, **kwargs) -> Union[Iterator[dict[str, Any]], dict[str, Any]]: def unpause(self) -> None: """Unpause processes in container.""" - response = self.client.post(f"/containers/{self.id}/unpause") + response = self.api.post(f"/containers/{self.id}/unpause") response.raise_for_status() def update(self, **kwargs) -> None: @@ -724,7 +722,7 @@ def update(self, **kwargs) -> None: if kwargs.get("restart_retries"): params["restartRetries"] = kwargs.get("restart_retries") - response = self.client.post( + response = self.api.post( f"/containers/{self.id}/update", params=params, data=json.dumps(data) ) response.raise_for_status() @@ -764,6 +762,6 @@ def wait(self, **kwargs) -> int: # This API endpoint responds with a JSON encoded integer, the exit code of the container. # See: # https://docs.podman.io/en/latest/_static/api.html#tag/containers/operation/ContainerWaitLibpod - response = self.client.post(f"/containers/{self.id}/wait", params=params, timeout=timeout) + response = self.api.post(f"/containers/{self.id}/wait", params=params, timeout=timeout) response.raise_for_status() return response.json() diff --git a/podman/domain/containers_create.py b/podman/domain/containers_create.py index 225bb7aa..7ccdf7f4 100644 --- a/podman/domain/containers_create.py +++ b/podman/domain/containers_create.py @@ -421,7 +421,7 @@ def create( payload = self._render_payload(payload) payload = api.prepare_body(payload) - response = self.client.post( + response = self.api.post( "/containers/create", headers={"content-type": "application/json"}, data=payload, diff --git a/podman/domain/containers_manager.py b/podman/domain/containers_manager.py index ac9b80e6..04770900 100644 --- a/podman/domain/containers_manager.py +++ b/podman/domain/containers_manager.py @@ -3,7 +3,7 @@ import logging import urllib from collections.abc import Mapping -from typing import Any, Union +from typing import Any, Optional, Union from podman import api from podman.domain.containers import Container @@ -24,7 +24,7 @@ def resource(self): return Container def exists(self, key: str) -> bool: - response = self.client.get(f"/containers/{key}/exists") + response = self.api.get(f"/containers/{key}/exists") return response.ok def get(self, key: str, **kwargs) -> Container: @@ -46,7 +46,7 @@ def get(self, key: str, **kwargs) -> Container: compatible = kwargs.get("compatible", False) container_id = urllib.parse.quote_plus(key) - response = self.client.get(f"/containers/{container_id}/json", compatible=compatible) + response = self.api.get(f"/containers/{container_id}/json", compatible=compatible) response.raise_for_status() return self.prepare_model(attrs=response.json()) @@ -105,7 +105,7 @@ def list(self, **kwargs) -> list[Container]: # filters formatted last because some kwargs may need to be mapped into filters params["filters"] = api.prepare_filters(params["filters"]) - response = self.client.get("/containers/json", params=params, compatible=compatible) + response = self.api.get("/containers/json", params=params, compatible=compatible) response.raise_for_status() containers: list[Container] = [self.prepare_model(attrs=i) for i in response.json()] @@ -121,7 +121,7 @@ def list(self, **kwargs) -> list[Container]: return containers - def prune(self, filters: Mapping[str, str] = None) -> dict[str, Any]: + def prune(self, filters: Optional[Mapping[str, str]] = None) -> dict[str, Any]: """Delete stopped containers. Args: @@ -138,7 +138,7 @@ def prune(self, filters: Mapping[str, str] = None) -> dict[str, Any]: APIError: when service reports an error """ params = {"filters": api.prepare_filters(filters)} - response = self.client.post("/containers/prune", params=params) + response = self.api.post("/containers/prune", params=params) response.raise_for_status() results = {"ContainersDeleted": [], "SpaceReclaimed": 0} @@ -173,5 +173,5 @@ def remove(self, container_id: Union[Container, str], **kwargs): # v is used for the compat endpoint while volumes is used for the libpod endpoint params = {"v": kwargs.get("v"), "force": kwargs.get("force"), "volumes": kwargs.get("v")} - response = self.client.delete(f"/containers/{container_id}", params=params) + response = self.api.delete(f"/containers/{container_id}", params=params) response.raise_for_status() diff --git a/podman/domain/containers_run.py b/podman/domain/containers_run.py index ead63ea9..38e0cb50 100644 --- a/podman/domain/containers_run.py +++ b/podman/domain/containers_run.py @@ -108,6 +108,6 @@ def remove_container(container_object: Container) -> None: container.remove() if exit_status != 0: - raise ContainerError(container, exit_status, command, image_id, log_iter) + raise ContainerError(container, exit_status, command or [], image_id, log_iter) return log_iter if kwargs.get("stream", False) or log_iter is None else b"".join(log_iter) # type: ignore[return-value] diff --git a/podman/domain/images.py b/podman/domain/images.py index 733a3ca7..d7dcd822 100644 --- a/podman/domain/images.py +++ b/podman/domain/images.py @@ -49,7 +49,7 @@ def history(self) -> list[dict[str, Any]]: APIError: when service returns an error """ - response = self.client.get(f"/images/{self.id}/history") + response = self.api.get(f"/images/{self.id}/history") response.raise_for_status(not_found=ImageNotFound) return response.json() @@ -105,7 +105,7 @@ def save( raise InvalidArgument(f"'{named}' is not a valid tag for this image") img = urllib.parse.quote(named) - response = self.client.get( + response = self.api.get( f"/images/{img}/get", params={"format": ["docker-archive"]}, stream=True ) response.raise_for_status(not_found=ImageNotFound) @@ -132,7 +132,7 @@ def tag( APIError: when service returns an error """ params = {"repo": repository, "tag": tag} - response = self.client.post(f"/images/{self.id}/tag", params=params) + response = self.api.post(f"/images/{self.id}/tag", params=params) if response.ok: return True diff --git a/podman/domain/images_build.py b/podman/domain/images_build.py index 1850ae70..1333caf9 100644 --- a/podman/domain/images_build.py +++ b/podman/domain/images_build.py @@ -123,7 +123,7 @@ def build(self, **kwargs) -> tuple[Image, Iterator[bytes]]: if kwargs.get("timeout"): post_kwargs["timeout"] = float(kwargs.get("timeout")) - response = self.client.post( # type: ignore[attr-defined] + response = self.api.post( # type: ignore[attr-defined] "/build", params=params, data=body, diff --git a/podman/domain/images_manager.py b/podman/domain/images_manager.py index a2bfb9aa..c0ca05c1 100644 --- a/podman/domain/images_manager.py +++ b/podman/domain/images_manager.py @@ -45,7 +45,7 @@ def resource(self): def exists(self, key: str) -> bool: """Return true when image exists.""" key = urllib.parse.quote_plus(key) - response = self.client.get(f"/images/{key}/exists") + response = self.api.get(f"/images/{key}/exists") return response.ok def list(self, **kwargs) -> builtins.list[Image]: @@ -71,7 +71,7 @@ def list(self, **kwargs) -> builtins.list[Image]: "all": kwargs.get("all"), "filters": api.prepare_filters(filters=filters), } - response = self.client.get("/images/json", params=params) + response = self.api.get("/images/json", params=params) if response.status_code == requests.codes.not_found: return [] response.raise_for_status() @@ -90,7 +90,7 @@ def get(self, name: str) -> Image: # pylint: disable=arguments-differ,arguments APIError: when service returns an error """ name = urllib.parse.quote_plus(name) - response = self.client.get(f"/images/{name}/json") + response = self.api.get(f"/images/{name}/json") response.raise_for_status(not_found=ImageNotFound) return self.prepare_model(response.json()) @@ -152,7 +152,7 @@ def _generator(body: dict) -> Generator[Image, None, None]: yield self.get(item) with Path(file_path).open("rb") if file_path else io.BytesIO(data) as stream: - response = self.client.post( + response = self.api.post( "/images/load", data=stream, headers={"Content-type": "application/x-tar"} ) response.raise_for_status() @@ -191,7 +191,7 @@ def prune( "filters": api.prepare_filters(filters), } - response = self.client.post("/images/prune", params=params) + response = self.api.post("/images/prune", params=params) response.raise_for_status() deleted: builtins.list[dict[str, str]] = [] @@ -269,7 +269,7 @@ def push( name = f'{repository}:{tag}' if tag else repository name = urllib.parse.quote_plus(name) - response = self.client.post( + response = self.api.post( f"/images/{name}/push", params=params, stream=stream, headers=headers ) response.raise_for_status(not_found=ImageNotFound) @@ -395,7 +395,7 @@ def pull( if not params["compatMode"] and not stream: params["quiet"] = True - response = self.client.post("/images/pull", params=params, stream=stream, headers=headers) + response = self.api.post("/images/pull", params=params, stream=stream, headers=headers) response.raise_for_status(not_found=ImageNotFound) if progress_bar: @@ -479,7 +479,7 @@ def remove( if isinstance(image, Image): image = image.id - response = self.client.delete(f"/images/{image}", params={"force": force}) + response = self.api.delete(f"/images/{image}", params={"force": force}) response.raise_for_status(not_found=ImageNotFound) body = response.json() @@ -521,7 +521,7 @@ def search(self, term: str, **kwargs) -> builtins.list[dict[str, Any]]: if "listTags" in kwargs: params["listTags"] = kwargs.get("listTags") - response = self.client.get("/images/search", params=params) + response = self.api.get("/images/search", params=params) response.raise_for_status(not_found=ImageNotFound) return response.json() @@ -548,7 +548,7 @@ def scp( if dest is not None: params["destination"] = dest - response = self.client.post(f"/images/scp/{source}", params=params) + response = self.api.post(f"/images/scp/{source}", params=params) response.raise_for_status() return response.json() diff --git a/podman/domain/manager.py b/podman/domain/manager.py index 47f256a2..5ae9d1b4 100644 --- a/podman/domain/manager.py +++ b/podman/domain/manager.py @@ -47,6 +47,13 @@ def __init__( if attrs is not None: self.attrs.update(attrs) + @property + def api(self) -> APIClient: + """Return the API client, raising if not configured.""" + if self.client is None: + raise AttributeError(f"{self.__class__.__name__} has no API client configured") + return self.client + def __repr__(self): return f"<{self.__class__.__name__}: {self.short_id}>" @@ -116,6 +123,13 @@ def __init__( self.client = client self.podman_client = podman_client + @property + def api(self) -> APIClient: + """Return the API client, raising if not configured.""" + if self.client is None: + raise AttributeError(f"{self.__class__.__name__} has no API client configured") + return self.client + @abstractmethod def exists(self, key: str) -> bool: """Returns True if resource exists. diff --git a/podman/domain/manifests.py b/podman/domain/manifests.py index a5efd053..46457061 100644 --- a/podman/domain/manifests.py +++ b/podman/domain/manifests.py @@ -89,7 +89,7 @@ def add(self, images: list[Union[Image, str]], **kwargs) -> None: data["images"].append(img_item) data = api.prepare_body(data) - response = self.client.put(f"/manifests/{self.quoted_name}", data=data) + response = self.api.put(f"/manifests/{self.quoted_name}", data=data) response.raise_for_status(not_found=ImageNotFound) return self.reload() @@ -126,7 +126,7 @@ def push( } destination_quoted = urllib.parse.quote_plus(destination) - response = self.client.post( + response = self.api.post( f"/manifests/{self.quoted_name}/registry/{destination_quoted}", params=params, headers=headers, @@ -150,7 +150,7 @@ def remove(self, digest: str) -> None: data = {"operation": "remove", "images": [digest]} data = api.prepare_body(data) - response = self.client.put(f"/manifests/{self.quoted_name}", data=data) + response = self.api.put(f"/manifests/{self.quoted_name}", data=data) response.raise_for_status(not_found=ImageNotFound) return self.reload() @@ -199,7 +199,7 @@ def create( params["all"] = all name_quoted = urllib.parse.quote_plus(name) - response = self.client.post(f"/manifests/{name_quoted}", params=params) + response = self.api.post(f"/manifests/{name_quoted}", params=params) response.raise_for_status(not_found=ImageNotFound) body = response.json() @@ -212,7 +212,7 @@ def create( def exists(self, key: str) -> bool: key = urllib.parse.quote_plus(key) - response = self.client.get(f"/manifests/{key}/exists") + response = self.api.get(f"/manifests/{key}/exists") return response.ok def get(self, key: str) -> Manifest: @@ -229,7 +229,7 @@ def get(self, key: str) -> Manifest: APIError: when service reports an error """ quoted_key = urllib.parse.quote_plus(key) - response = self.client.get(f"/manifests/{quoted_key}/json") + response = self.api.get(f"/manifests/{quoted_key}/json") response.raise_for_status() body = response.json() @@ -247,7 +247,7 @@ def remove(self, name: Union[Manifest, str]) -> dict[str, Any]: if isinstance(name, Manifest): name = name.name - response = self.client.delete(f"/manifests/{name}") + response = self.api.delete(f"/manifests/{name}") response.raise_for_status(not_found=ImageNotFound) body = response.json() diff --git a/podman/domain/networks.py b/podman/domain/networks.py index e26d1617..e030251e 100644 --- a/podman/domain/networks.py +++ b/podman/domain/networks.py @@ -112,7 +112,7 @@ def connect(self, container: Union[str, Container], *_, **kwargs) -> None: data = {"Container": container, "EndpointConfig": endpoint_config} data = {k: v for (k, v) in data.items() if not (v is None or len(v) == 0)} # type: ignore[arg-type] - response = self.client.post( + response = self.api.post( f"/networks/{self.name}/connect", data=json.dumps(data), headers={"Content-type": "application/json"}, @@ -136,7 +136,7 @@ def disconnect(self, container: Union[str, Container], **kwargs) -> None: container = container.id data = {"Container": container, "Force": kwargs.get("force")} - response = self.client.post(f"/networks/{self.name}/disconnect", data=json.dumps(data)) + response = self.api.post(f"/networks/{self.name}/disconnect", data=json.dumps(data)) response.raise_for_status() def remove(self, force: Optional[bool] = None, **kwargs) -> None: diff --git a/podman/domain/networks_manager.py b/podman/domain/networks_manager.py index 7137479f..fff842a1 100644 --- a/podman/domain/networks_manager.py +++ b/podman/domain/networks_manager.py @@ -71,7 +71,7 @@ def create(self, name: str, **kwargs) -> Network: with suppress(KeyError): self._prepare_ipam(data, kwargs["ipam"]) - response = self.client.post( + response = self.api.post( "/networks/create", data=http_utils.prepare_body(data), headers={"Content-Type": "application/json"}, @@ -103,7 +103,7 @@ def _prepare_ipam(self, data: dict[str, Any], ipam: dict[str, Any]): data["subnets"].append(subnet) def exists(self, key: str) -> bool: - response = self.client.get(f"/networks/{key}/exists") + response = self.api.get(f"/networks/{key}/exists") return response.ok def get(self, key: str) -> Network: @@ -116,7 +116,7 @@ def get(self, key: str) -> Network: NotFound: when Network does not exist APIError: when error returned by service """ - response = self.client.get(f"/networks/{key}") + response = self.api.get(f"/networks/{key}") response.raise_for_status() return self.prepare_model(attrs=response.json()) @@ -159,7 +159,7 @@ def list(self, **kwargs) -> list[Network]: filters = prepare_filters(filters) params = {"filters": filters} - response = self.client.get("/networks/json", params=params) + response = self.api.get("/networks/json", params=params) response.raise_for_status() return [self.prepare_model(i) for i in response.json()] @@ -178,7 +178,7 @@ def prune( APIError: when service reports error """ params = {"filters": prepare_filters(filters)} - response = self.client.post("/networks/prune", params=params) + response = self.api.post("/networks/prune", params=params) response.raise_for_status() deleted: list[str] = [] @@ -206,5 +206,5 @@ def remove(self, name: Union[Network, str], force: Optional[bool] = None) -> Non if isinstance(name, Network): name = name.name - response = self.client.delete(f"/networks/{name}", params={"force": force}) + response = self.api.delete(f"/networks/{name}", params={"force": force}) response.raise_for_status() diff --git a/podman/domain/pods.py b/podman/domain/pods.py index 4a75ce0d..0cdf2228 100644 --- a/podman/domain/pods.py +++ b/podman/domain/pods.py @@ -37,7 +37,7 @@ def kill(self, signal: Union[str, int, None] = None) -> None: NotFound: when pod not found APIError: when service reports an error """ - response = self.client.post(f"/pods/{self.id}/kill", params={"signal": signal}) + response = self.api.post(f"/pods/{self.id}/kill", params={"signal": signal}) response.raise_for_status() def pause(self) -> None: @@ -47,7 +47,7 @@ def pause(self) -> None: NotFound: when pod not found APIError: when service reports an error """ - response = self.client.post(f"/pods/{self.id}/pause") + response = self.api.post(f"/pods/{self.id}/pause") response.raise_for_status() def remove(self, force: Optional[bool] = None) -> None: @@ -69,7 +69,7 @@ def restart(self) -> None: NotFound: when pod not found APIError: when service reports an error """ - response = self.client.post(f"/pods/{self.id}/restart") + response = self.api.post(f"/pods/{self.id}/restart") response.raise_for_status() def start(self) -> None: @@ -79,7 +79,7 @@ def start(self) -> None: NotFound: when pod not found APIError: when service reports an error """ - response = self.client.post(f"/pods/{self.id}/start") + response = self.api.post(f"/pods/{self.id}/start") response.raise_for_status() def stop(self, timeout: _Timeout = None) -> None: @@ -90,7 +90,7 @@ def stop(self, timeout: _Timeout = None) -> None: APIError: when service reports an error """ params = {"t": timeout} - response = self.client.post(f"/pods/{self.id}/stop", params=params) + response = self.api.post(f"/pods/{self.id}/stop", params=params) response.raise_for_status() def top(self, **kwargs) -> dict[str, Any]: @@ -107,7 +107,7 @@ def top(self, **kwargs) -> dict[str, Any]: "ps_args": kwargs.get("ps_args"), "stream": False, } - response = self.client.get(f"/pods/{self.id}/top", params=params) + response = self.api.get(f"/pods/{self.id}/top", params=params) response.raise_for_status() if len(response.text) == 0: @@ -121,5 +121,5 @@ def unpause(self) -> None: NotFound: when pod not found APIError: when service reports an error """ - response = self.client.post(f"/pods/{self.id}/unpause") + response = self.api.post(f"/pods/{self.id}/unpause") response.raise_for_status() diff --git a/podman/domain/pods_manager.py b/podman/domain/pods_manager.py index 77828606..e150dd2b 100644 --- a/podman/domain/pods_manager.py +++ b/podman/domain/pods_manager.py @@ -33,7 +33,7 @@ def create(self, name: str, **kwargs) -> Pod: data = {} if kwargs is None else kwargs.copy() data["name"] = name - response = self.client.post("/pods/create", data=json.dumps(data)) + response = self.api.post("/pods/create", data=json.dumps(data)) response.raise_for_status() body = response.json() @@ -41,7 +41,7 @@ def create(self, name: str, **kwargs) -> Pod: def exists(self, key: str) -> bool: """Returns True, when pod exists.""" - response = self.client.get(f"/pods/{key}/exists") + response = self.api.get(f"/pods/{key}/exists") return response.ok # pylint is flagging 'pod_id' here vs. 'key' parameter in super.get() @@ -55,7 +55,7 @@ def get(self, pod_id: str) -> Pod: # pylint: disable=arguments-differ,arguments NotFound: when network does not exist APIError: when error returned by service """ - response = self.client.get(f"/pods/{pod_id}/json") + response = self.api.get(f"/pods/{pod_id}/json") response.raise_for_status() return self.prepare_model(attrs=response.json()) @@ -82,7 +82,7 @@ def list(self, **kwargs) -> builtins.list[Pod]: APIError: when an error returned by service """ params = {"filters": api.prepare_filters(kwargs.get("filters"))} - response = self.client.get("/pods/json", params=params) + response = self.api.get("/pods/json", params=params) response.raise_for_status() return [self.prepare_model(attrs=i) for i in response.json()] @@ -97,7 +97,7 @@ def prune(self, filters: Optional[dict[str, str]] = None) -> dict[str, Any]: Raises: APIError: when service reports error """ - response = self.client.post("/pods/prune", params={"filters": api.prepare_filters(filters)}) + response = self.api.post("/pods/prune", params={"filters": api.prepare_filters(filters)}) response.raise_for_status() deleted: builtins.list[str] = [] @@ -128,7 +128,7 @@ def remove(self, pod_id: Union[Pod, str], force: Optional[bool] = None) -> None: if isinstance(pod_id, Pod): pod_id = pod_id.id - response = self.client.delete(f"/pods/{pod_id}", params={"force": force}) + response = self.api.delete(f"/pods/{pod_id}", params={"force": force}) response.raise_for_status() def stats( @@ -159,7 +159,7 @@ def stats( "namesOrIDs": kwargs.get("name"), "stream": stream, } - response = self.client.get("/pods/stats", params=params, stream=stream) + response = self.api.get("/pods/stats", params=params, stream=stream) response.raise_for_status() if stream: diff --git a/podman/domain/quadlets.py b/podman/domain/quadlets.py index c8b8cb04..5c7c62e9 100644 --- a/podman/domain/quadlets.py +++ b/podman/domain/quadlets.py @@ -96,7 +96,7 @@ def exists(self, key: str) -> bool: Returns: True if the quadlet exists, False otherwise. """ - response = self.client.get(f"/quadlets/{key}/exists") + response = self.api.get(f"/quadlets/{key}/exists") if response.status_code == 404: return False response.raise_for_status() @@ -112,7 +112,7 @@ def get(self, name: str) -> Quadlet: NotFound: when quadlet could not be found APIError: when service reports an error """ - response = self.client.get( + response = self.api.get( "/quadlets/json", params={"filters": api.prepare_filters({"name": name})}, ) @@ -137,7 +137,7 @@ def list(self, *_, **kwargs) -> builtins.list[Quadlet]: if filters: params["filters"] = filters - response = self.client.get("/quadlets/json", params=params) + response = self.api.get("/quadlets/json", params=params) if response.status_code == requests.codes.not_found: return [] @@ -160,7 +160,7 @@ def get_contents(self, name: Union[Quadlet, str]) -> str: if isinstance(name, Quadlet): name = name.name - response = self.client.get(f"/quadlets/{name}/file") + response = self.api.get(f"/quadlets/{name}/file") response.raise_for_status() return response.text @@ -180,7 +180,7 @@ def print_contents(self, name: Union[Quadlet, str]) -> None: if isinstance(name, Quadlet): name = name.name - response = self.client.get(f"/quadlets/{name}/file") + response = self.api.get(f"/quadlets/{name}/file") response.raise_for_status() print(response.text.strip()) @@ -219,9 +219,9 @@ def delete( if all: params["all"] = True - response = self.client.delete("/quadlets", params=params) + response = self.api.delete("/quadlets", params=params) else: - response = self.client.delete(f"/quadlets/{name}", params=params) + response = self.api.delete(f"/quadlets/{name}", params=params) response.raise_for_status() return response.json()["Removed"] @@ -299,7 +299,7 @@ def install( tar_path = pathlib.Path(first) if not tar_path.is_file(): raise FileNotFoundError(f"No such file: '{tar_path}'") - response = self.client.post( + response = self.api.post( "/quadlets", params=params, data=tar_path.read_bytes(), @@ -307,7 +307,7 @@ def install( ) else: multipart = self._prepare_install_body(files) - response = self.client.post( + response = self.api.post( "/quadlets", params=params, files=multipart, diff --git a/podman/domain/secrets.py b/podman/domain/secrets.py index 3bc5fa92..57c22ed9 100644 --- a/podman/domain/secrets.py +++ b/podman/domain/secrets.py @@ -60,7 +60,7 @@ def __init__(self, client: APIClient): super().__init__(client) def exists(self, key: str) -> bool: - response = self.client.get(f"/secrets/{key}/json") + response = self.api.get(f"/secrets/{key}/json") return response.ok # pylint is flagging 'secret_id' here vs. 'key' parameter in super.get() @@ -74,7 +74,7 @@ def get(self, secret_id: str) -> Secret: # pylint: disable=arguments-differ,arg NotFound: when Secret does not exist APIError: when error returned by service """ - response = self.client.get(f"/secrets/{secret_id}/json") + response = self.api.get(f"/secrets/{secret_id}/json") response.raise_for_status() return self.prepare_model(attrs=response.json()) @@ -87,7 +87,7 @@ def list(self, **kwargs) -> list[Secret]: Raises: APIError: when error returned by service """ - response = self.client.get("/secrets/json") + response = self.api.get("/secrets/json") response.raise_for_status() return [self.prepare_model(attrs=item) for item in response.json()] @@ -113,7 +113,7 @@ def create( "name": name, "driver": driver, } - response = self.client.post("/secrets/create", params=params, data=data) + response = self.api.post("/secrets/create", params=params, data=data) response.raise_for_status() body = response.json() @@ -139,5 +139,5 @@ def remove( if isinstance(secret_id, Secret): secret_id = secret_id.id - response = self.client.delete(f"/secrets/{secret_id}", params={"all": all}) + response = self.api.delete(f"/secrets/{secret_id}", params={"all": all}) response.raise_for_status() diff --git a/podman/domain/volumes.py b/podman/domain/volumes.py index 9ef565a2..c14dffe8 100644 --- a/podman/domain/volumes.py +++ b/podman/domain/volumes.py @@ -51,7 +51,7 @@ def inspect(self, **kwargs) -> dict: APIError: when service reports an error """ params = {"tlsVerify": kwargs.get("tls_verify", True)} - response = self.client.get(f"/volumes/{self.id}/json", params=params) + response = self.api.get(f"/volumes/{self.id}/json", params=params) response.raise_for_status() return response.json() @@ -84,7 +84,7 @@ def create(self, name: Optional[str] = None, **kwargs) -> Volume: "Name": name, "Options": kwargs.get("driver_opts"), } - response = self.client.post( + response = self.api.post( "/volumes/create", data=api.prepare_body(data), headers={"Content-Type": "application/json"}, @@ -93,7 +93,7 @@ def create(self, name: Optional[str] = None, **kwargs) -> Volume: return self.prepare_model(attrs=response.json()) def exists(self, key: str) -> bool: - response = self.client.get(f"/volumes/{key}/exists") + response = self.api.get(f"/volumes/{key}/exists") return response.ok # pylint is flagging 'volume_id' here vs. 'key' parameter in super.get() @@ -107,7 +107,7 @@ def get(self, volume_id: str) -> Volume: # pylint: disable=arguments-differ,arg NotFound: when volume could not be found APIError: when service reports an error """ - response = self.client.get(f"/volumes/{volume_id}/json") + response = self.api.get(f"/volumes/{volume_id}/json") response.raise_for_status() return self.prepare_model(attrs=response.json()) @@ -122,7 +122,7 @@ def list(self, *_, **kwargs) -> list[Volume]: - name (str): filter by volume's name """ filters = api.prepare_filters(kwargs.get("filters")) - response = self.client.get("/volumes/json", params={"filters": filters}) + response = self.api.get("/volumes/json", params={"filters": filters}) if response.status_code == requests.codes.not_found: return [] @@ -142,7 +142,7 @@ def prune( Raises: APIError: when service reports error """ - response = self.client.post("/volumes/prune") + response = self.api.post("/volumes/prune") data = response.json() response.raise_for_status() @@ -174,7 +174,7 @@ def remove(self, name: Union[Volume, str], force: Optional[bool] = None) -> None """ if isinstance(name, Volume): name = name.name - response = self.client.delete(f"/volumes/{name}", params={"force": force}) + response = self.api.delete(f"/volumes/{name}", params={"force": force}) response.raise_for_status() def export_archive(self, name: Union[Volume, str]) -> bytes: @@ -188,7 +188,7 @@ def export_archive(self, name: Union[Volume, str]) -> bytes: """ if isinstance(name, Volume): name = name.name - response = self.client.get(f"/volumes/{name}/export") + response = self.api.get(f"/volumes/{name}/export") response.raise_for_status() return response._content @@ -211,14 +211,15 @@ def import_archive( if data is None and path is None: raise RuntimeError("Either data or path must be provided !") - elif data is not None and path is not None: + if data is not None and path is not None: raise RuntimeError("Data and path must not be set at the same time !") if data is None: + assert path is not None file = pathlib.Path(path) if not file.exists(): raise RuntimeError(f"Archive {path} does not exist !") data = file.read_bytes() - response = self.client.post(f"/volumes/{name}/import", data=data) + response = self.api.post(f"/volumes/{name}/import", data=data) response.raise_for_status() diff --git a/podman/tests/integration/base.py b/podman/tests/integration/base.py index 3086730f..a733694c 100644 --- a/podman/tests/integration/base.py +++ b/podman/tests/integration/base.py @@ -18,6 +18,7 @@ import os import shutil import uuid +from typing import Optional import fixtures @@ -34,7 +35,7 @@ class IntegrationTest(fixtures.TestWithFixtures): results and logging captured by the unittest module test runner. """ - podman: str = None + podman: Optional[str] = None @classmethod def setUpClass(cls) -> None: diff --git a/podman/tests/integration/utils.py b/podman/tests/integration/utils.py index 7151a7f9..8ead4fe5 100644 --- a/podman/tests/integration/utils.py +++ b/podman/tests/integration/utils.py @@ -41,7 +41,7 @@ def __init__( log_level: str = "WARNING", ) -> None: """create a launcher and build podman command""" - podman_exe: str = podman_path + podman_exe: Optional[str] = podman_path if not podman_exe: podman_exe = shutil.which('podman') if podman_exe is None: diff --git a/podman/tests/unit/test_api_utils.py b/podman/tests/unit/test_api_utils.py index 9c027d67..4cfe95c4 100644 --- a/podman/tests/unit/test_api_utils.py +++ b/podman/tests/unit/test_api_utils.py @@ -122,7 +122,7 @@ def test_prepare_body_all_types(self) -> None: self.assertEqual(actual, json.dumps(payload, sort_keys=True)) def test_prepare_body_none(self) -> None: - payload = { + payload: dict[str, Any] = { "String": "", "Integer": None, "Boolean": False, diff --git a/podman/tests/utils.py b/podman/tests/utils.py index ea11e54f..30549cc7 100644 --- a/podman/tests/utils.py +++ b/podman/tests/utils.py @@ -21,6 +21,7 @@ def freedesktop_os_release() -> dict[str, str]: def podman_version() -> tuple[int, ...]: cmd = ["podman", "info", "--format", "{{.Version.Version}}"] with subprocess.Popen(cmd, stdout=subprocess.PIPE) as proc: + assert proc.stdout is not None version = proc.stdout.read().decode("utf-8").strip() match = re.match(r"(\d+\.\d+\.\d+)", version) if not match: diff --git a/pyproject.toml b/pyproject.toml index 0b58f1a2..fe43628f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,19 +79,19 @@ version = {attr = "podman.version.__version__"} [tool.ruff] line-length = 100 src = ["podman"] - -# This is the section where Black is mostly replaced with Ruff -[tool.ruff.format] exclude = [ ".git", ".history", ".tox", ".venv", + "_build", "build", "dist", "docs", "hack", ] + +[tool.ruff.format] quote-style = "preserve" [tool.ruff.lint] @@ -125,7 +125,6 @@ builtins-ignorelist = ["copyright", "all"] install_types = true non_interactive = true allow_redefinition = true -no_strict_optional = true ignore_missing_imports = true [[tool.mypy.overrides]]