From b392be61a852a518fffbe4df9136af5c068da5ac Mon Sep 17 00:00:00 2001 From: Joongi Kim Date: Wed, 12 Jun 2024 13:59:52 +0900 Subject: [PATCH] Fix exec/tty related type annotations and type checks --- aiodocker/containers.py | 34 ++++++++++++++++++++++------------ aiodocker/execs.py | 8 +++++--- tests/conftest.py | 29 ++++++++++++++++++++++++----- tests/test_execs.py | 5 +++-- 4 files changed, 54 insertions(+), 22 deletions(-) diff --git a/aiodocker/containers.py b/aiodocker/containers.py index a4558a2e..b9b914f9 100644 --- a/aiodocker/containers.py +++ b/aiodocker/containers.py @@ -8,6 +8,8 @@ from multidict import MultiDict from yarl import URL +from aiodocker.types import JSONObject + from .exceptions import DockerContainerError, DockerError from .execs import Exec from .jsonstream import json_stream_list, json_stream_stream @@ -21,13 +23,17 @@ class DockerContainers: def __init__(self, docker): self.docker = docker - async def list(self, **kwargs): + async def list(self, **kwargs) -> Sequence[DockerContainer]: data = await self.docker._query_json( "containers/json", method="GET", params=kwargs ) return [DockerContainer(self.docker, **x) for x in data] - async def create_or_replace(self, name, config): + async def create_or_replace( + self, + name: str, + config: JSONObject, + ) -> DockerContainer: container = None try: @@ -46,25 +52,29 @@ async def create_or_replace(self, name, config): return container - async def create(self, config, *, name=None): + async def create( + self, + config: JSONObject, + *, + name: Optional[str] = None, + ) -> DockerContainer: url = "containers/create" - - config = json.dumps(config, sort_keys=True).encode("utf-8") + encoded_config = json.dumps(config, sort_keys=True).encode("utf-8") kwargs = {} if name: kwargs["name"] = name data = await self.docker._query_json( - url, method="POST", data=config, params=kwargs + url, method="POST", data=encoded_config, params=kwargs ) return DockerContainer(self.docker, id=data["Id"]) async def run( self, - config, + config: JSONObject, *, auth: Optional[Union[Mapping, str, bytes]] = None, name: Optional[str] = None, - ): + ) -> DockerContainer: """ Create and start a container. @@ -93,22 +103,22 @@ async def run( return container - async def get(self, container, **kwargs): + async def get(self, container_id: str, **kwargs) -> DockerContainer: data = await self.docker._query_json( - f"containers/{container}/json", + f"containers/{container_id}/json", method="GET", params=kwargs, ) return DockerContainer(self.docker, **data) - def container(self, container_id, **kwargs): + def container(self, container_id: str, **kwargs) -> DockerContainer: data = {"id": container_id} data.update(kwargs) return DockerContainer(self.docker, **data) def exec(self, exec_id: str) -> Exec: """Return Exec instance for already created exec object.""" - return Exec(self.docker, exec_id, None) + return Exec(self.docker, exec_id) class DockerContainer: diff --git a/aiodocker/execs.py b/aiodocker/execs.py index f3f37a79..859c3709 100644 --- a/aiodocker/execs.py +++ b/aiodocker/execs.py @@ -32,7 +32,7 @@ class Exec: - def __init__(self, docker: "Docker", id: str, tty: Optional[bool]) -> None: + def __init__(self, docker: "Docker", id: str, tty: Optional[bool] = None) -> None: self.docker = docker self._id = id self._tty = tty @@ -96,8 +96,10 @@ def start(self, *, timeout=None, detach=False): from stdout or 2 if from stderr. """ if detach: - assert self._tty is not None - return self._start_detached(timeout, self._tty) + return self._start_detached( + timeout, + self._tty if self._tty is not None else False, + ) else: async def setup() -> Tuple[URL, bytes, bool]: diff --git a/tests/conftest.py b/tests/conftest.py index 1264e696..9a862f8e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,11 +5,14 @@ import sys import traceback import uuid +from collections.abc import AsyncIterator, Awaitable, Callable from typing import Any, Dict import pytest from packaging.version import parse as parse_version +from typing_extensions import TypeAlias +from aiodocker.containers import DockerContainer from aiodocker.docker import Docker from aiodocker.exceptions import DockerError @@ -128,24 +131,40 @@ async def swarm(docker): assert await docker.swarm.leave(force=True) -@pytest.fixture -async def make_container(docker): - container = None +AsyncContainerFactory: TypeAlias = Callable[ + [Dict[str, Any], str], Awaitable[DockerContainer] +] + - async def _spawn(config: Dict[str, Any], name=None): +@pytest.fixture +async def make_container( + docker: Docker, +) -> AsyncIterator[AsyncContainerFactory]: + container: DockerContainer | None = None + + async def _spawn( + config: Dict[str, Any], + name: str, + ) -> DockerContainer: nonlocal container container = await docker.containers.create_or_replace(config=config, name=name) + assert container is not None await container.start() return container yield _spawn if container is not None: + assert isinstance(container, DockerContainer) await container.delete(force=True) @pytest.fixture -async def shell_container(docker, make_container, image_name): +async def shell_container( + docker: Docker, + make_container, + image_name: str, +) -> AsyncContainerFactory: config = { "Cmd": ["python"], "Image": image_name, diff --git a/tests/test_execs.py b/tests/test_execs.py index ce167da9..c3e0afa6 100644 --- a/tests/test_execs.py +++ b/tests/test_execs.py @@ -151,9 +151,10 @@ async def test_exec_restore_tty_attached(docker, shell_container): ) exec2 = docker.containers.exec(exec1.id) - async with exec2.start() as stream: + stream = exec2.start() + assert isinstance(stream, Stream) + async with stream: assert exec2._tty - stream @pytest.mark.asyncio