From 827391b5fd83b1fdab198b3cc95fb0825c29c276 Mon Sep 17 00:00:00 2001 From: Jacob Tomlinson Date: Tue, 2 Jul 2024 10:55:19 +0100 Subject: [PATCH] Start getting mypy --strict passing --- .pre-commit-config.yaml | 2 +- kr8s/__init__.py | 34 +++++++++++++++++------ kr8s/_exceptions.py | 13 ++++++++- kr8s/_objects.py | 8 +++--- kr8s/_testutils.py | 3 +- kr8s/asyncio/__init__.py | 8 ++++-- kr8s/tests/test_api.py | 59 ++++++++++++++++++++-------------------- pyproject.toml | 2 +- 8 files changed, 80 insertions(+), 49 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c844f32b..1f28cb20 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,5 +29,5 @@ repos: rev: 'v1.10.1' hooks: - id: mypy - exclude: "examples" + exclude: "examples|tests|venv|ci|docs|conftest.py" additional_dependencies: [types-pyyaml>=6.0] diff --git a/kr8s/__init__.py b/kr8s/__init__.py index 5eadd34c..26a0d628 100644 --- a/kr8s/__init__.py +++ b/kr8s/__init__.py @@ -3,18 +3,16 @@ from functools import partial, update_wrapper from typing import Optional, Union -import kr8s.objects # noqa - -from ._api import ALL # noqa +from ._api import ALL from ._api import Api as _AsyncApi from ._async_utils import run_sync as _run_sync -from ._async_utils import sync as _sync # noqa +from ._async_utils import sync as _sync from ._exceptions import ( - APITimeoutError, # noqa - ConnectionClosedError, # noqa - ExecError, # noqa - NotFoundError, # noqa - ServerError, # noqa + APITimeoutError, + ConnectionClosedError, + ExecError, + NotFoundError, + ServerError, ) from .asyncio import ( api as _api, @@ -165,3 +163,21 @@ def whoami(): update_wrapper(watch, _watch) api_resources = _run_sync(partial(_api_resources, _asyncio=False)) update_wrapper(api_resources, _api_resources) + +__all__ = [ + "__version__", + "__version_tuple__", + "ALL", + "api", + "api_resources", + "get", + "version", + "watch", + "whoami", + "Api", + "APITimeoutError", + "ConnectionClosedError", + "ExecError", + "NotFoundError", + "ServerError", +] diff --git a/kr8s/_exceptions.py b/kr8s/_exceptions.py index cd74ada0..7996edd0 100644 --- a/kr8s/_exceptions.py +++ b/kr8s/_exceptions.py @@ -1,5 +1,11 @@ # SPDX-FileCopyrightText: Copyright (c) 2023-2024, Kr8s Developers (See LICENSE for list) # SPDX-License-Identifier: BSD 3-Clause License + +from typing import Optional + +import httpx + + class NotFoundError(Exception): """Unable to find the requested resource.""" @@ -27,7 +33,12 @@ class ServerError(Exception): The httpx response object """ - def __init__(self, message, status=None, response=None): + def __init__( + self, + message: str, + status: Optional[str] = None, + response: Optional[httpx.Response] = None, + ) -> None: self.status = status self.response = response super().__init__(message) diff --git a/kr8s/_objects.py b/kr8s/_objects.py index ac5d5016..21722cac 100644 --- a/kr8s/_objects.py +++ b/kr8s/_objects.py @@ -220,7 +220,7 @@ async def get( cls, name, namespace=namespace, **kwargs ) except ServerError as e: - if e.response.status_code == 404: + if e.response and e.response.status_code == 404: continue raise e elif label_selector or field_selector: @@ -297,7 +297,7 @@ async def delete(self, propagation_policy: Optional[str] = None) -> None: ) as resp: self.raw = resp.json() except ServerError as e: - if e.response.status_code == 404: + if e.response and e.response.status_code == 404: raise NotFoundError(f"Object {self.name} does not exist") from e raise e @@ -316,7 +316,7 @@ async def async_refresh(self) -> None: ) as resp: self.raw = resp.json() except ServerError as e: - if e.response.status_code == 404: + if e.response and e.response.status_code == 404: raise NotFoundError(f"Object {self.name} does not exist") from e raise e @@ -344,7 +344,7 @@ async def async_patch(self, patch: Dict, *, subresource=None, type=None) -> None ) as resp: self.raw = resp.json() except ServerError as e: - if e.response.status_code == 404: + if e.response and e.response.status_code == 404: raise NotFoundError(f"Object {self.name} does not exist") from e raise e diff --git a/kr8s/_testutils.py b/kr8s/_testutils.py index 09f9d150..5dfe5a1a 100644 --- a/kr8s/_testutils.py +++ b/kr8s/_testutils.py @@ -2,10 +2,11 @@ # SPDX-License-Identifier: BSD 3-Clause License import contextlib import os +from typing import Generator @contextlib.contextmanager -def set_env(**environ): +def set_env(**environ: str) -> Generator[None, None, None]: """ Temporarily set the process environment variables. diff --git a/kr8s/asyncio/__init__.py b/kr8s/asyncio/__init__.py index d7cfa1c7..46d24ede 100644 --- a/kr8s/asyncio/__init__.py +++ b/kr8s/asyncio/__init__.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: Copyright (c) 2023-2024, Kr8s Developers (See LICENSE for list) # SPDX-License-Identifier: BSD 3-Clause License -from kr8s._api import Api # noqa +from kr8s._api import Api -from ._api import api # noqa -from ._helpers import api_resources, get, version, watch, whoami # noqa +from ._api import api +from ._helpers import api_resources, get, version, watch, whoami + +__all__ = ["api", "api_resources", "get", "version", "watch", "whoami", "Api"] diff --git a/kr8s/tests/test_api.py b/kr8s/tests/test_api.py index f98cc87d..e2d0be48 100644 --- a/kr8s/tests/test_api.py +++ b/kr8s/tests/test_api.py @@ -12,7 +12,7 @@ from kr8s.asyncio.objects import Pod, Table -async def test_factory_bypass(): +async def test_factory_bypass() -> None: with pytest.raises(ValueError, match="kr8s.api()"): _ = kr8s.Api() assert not kr8s.Api._instances @@ -20,7 +20,7 @@ async def test_factory_bypass(): assert kr8s.Api._instances -async def test_api_factory(serviceaccount): +async def test_api_factory(serviceaccount) -> None: k1 = await kr8s.asyncio.api() k2 = await kr8s.asyncio.api() assert k1 is k2 @@ -35,7 +35,7 @@ async def test_api_factory(serviceaccount): assert p.api is not k3 -def test_api_factory_threaded(): +def test_api_factory_threaded() -> None: assert len(kr8s.Api._instances) == 0 q = queue.Queue() @@ -66,7 +66,7 @@ async def create_api(q): assert type(k1) is type(k2) -def test_api_factory_multi_event_loop(): +def test_api_factory_multi_event_loop() -> None: assert len(kr8s.Api._instances) == 0 async def create_api(): @@ -77,7 +77,7 @@ async def create_api(): assert k1 is not k2 -async def test_api_factory_with_kubeconfig(k8s_cluster, serviceaccount): +async def test_api_factory_with_kubeconfig(k8s_cluster, serviceaccount) -> None: k1 = await kr8s.asyncio.api(kubeconfig=k8s_cluster.kubeconfig_path) k2 = await kr8s.asyncio.api(serviceaccount=serviceaccount) k3 = await kr8s.asyncio.api() @@ -96,30 +96,30 @@ async def test_api_factory_with_kubeconfig(k8s_cluster, serviceaccount): assert p3.api is not k2 -def test_version_sync(): +def test_version_sync() -> None: api = kr8s.api() version = api.version() assert "major" in version -async def test_version_sync_in_async(): +async def test_version_sync_in_async() -> None: api = kr8s.api() version = api.version() assert "major" in version -async def test_version(): +async def test_version() -> None: api = await kr8s.asyncio.api() version = await api.version() assert "major" in version -def test_helper_version(): +def test_helper_version() -> None: version = kr8s.version() assert "major" in version -async def test_concurrent_api_creation(): +async def test_concurrent_api_creation() -> None: async def get_api(): api = await kr8s.asyncio.api() await api.version() @@ -129,7 +129,7 @@ async def get_api(): tg.start_soon(get_api) -async def test_both_api_creation_methods_together(): +async def test_both_api_creation_methods_together() -> None: async_api = await kr8s.asyncio.api() api = kr8s.api() @@ -144,7 +144,7 @@ async def test_both_api_creation_methods_together(): assert api.get("ns")[0]._asyncio is False -async def test_bad_api_version(): +async def test_bad_api_version() -> None: api = await kr8s.asyncio.api() with pytest.raises(ValueError): async with api.call_api("GET", version="foo"): @@ -152,14 +152,14 @@ async def test_bad_api_version(): @pytest.mark.parametrize("namespace", [kr8s.ALL, "kube-system"]) -async def test_get_pods(namespace): +async def test_get_pods(namespace) -> None: pods = await kr8s.asyncio.get("pods", namespace=namespace) assert isinstance(pods, list) assert len(pods) > 0 assert isinstance(pods[0], Pod) -async def test_get_pods_as_table(): +async def test_get_pods_as_table() -> None: api = await kr8s.asyncio.api() pods = await api.get("pods", namespace="kube-system", as_object=Table) assert isinstance(pods, Table) @@ -167,7 +167,7 @@ async def test_get_pods_as_table(): assert not await pods.exists() # Cannot exist in the Kubernetes API -async def test_watch_pods(example_pod_spec, ns): +async def test_watch_pods(example_pod_spec, ns) -> None: pod = await Pod(example_pod_spec) await pod.create() while not await pod.ready(): @@ -186,13 +186,13 @@ async def test_watch_pods(example_pod_spec, ns): break -async def test_get_deployments(): +async def test_get_deployments() -> None: api = await kr8s.asyncio.api() deployments = await api.get("deployments") assert isinstance(deployments, list) -async def test_get_class(): +async def test_get_class() -> None: api = await kr8s.asyncio.api() pods = await api.get(Pod, namespace=kr8s.ALL) assert isinstance(pods, list) @@ -200,19 +200,19 @@ async def test_get_class(): assert isinstance(pods[0], Pod) -async def test_api_versions(): +async def test_api_versions() -> None: api = await kr8s.asyncio.api() versions = [version async for version in api.api_versions()] assert "apps/v1" in versions -def test_api_versions_sync(): +def test_api_versions_sync() -> None: api = kr8s.api() versions = [version for version in api.api_versions()] assert "apps/v1" in versions -async def test_api_resources(): +async def test_api_resources() -> None: resources = await kr8s.asyncio.api_resources() names = [r["name"] for r in resources] @@ -235,7 +235,7 @@ async def test_api_resources(): assert "deploy" in deployment["shortNames"] -async def test_ns(ns): +async def test_ns(ns) -> None: api = await kr8s.asyncio.api(namespace=ns) assert ns == api.namespace @@ -243,23 +243,23 @@ async def test_ns(ns): assert api.namespace == "foo" -async def test_async_get_returns_async_objects(): +async def test_async_get_returns_async_objects() -> None: pods = await kr8s.asyncio.get("pods", namespace=kr8s.ALL) assert pods[0]._asyncio is True -def test_sync_get_returns_sync_objects(): +def test_sync_get_returns_sync_objects() -> None: pods = kr8s.get("pods", namespace=kr8s.ALL) assert pods[0]._asyncio is False -def test_sync_api_returns_sync_objects(): +def test_sync_api_returns_sync_objects() -> None: api = kr8s.api() pods = api.get("pods", namespace=kr8s.ALL) assert pods[0]._asyncio is False -async def test_api_names(example_pod_spec, ns): +async def test_api_names(example_pod_spec: dict, ns: str) -> None: pod = await Pod(example_pod_spec) await pod.create() assert pod in await kr8s.asyncio.get("pods", namespace=ns) @@ -275,17 +275,17 @@ async def test_api_names(example_pod_spec, ns): await kr8s.asyncio.get("roles.rbac.authorization.k8s.io/v1", namespace=ns) -async def test_whoami(): +async def test_whoami() -> None: api = await kr8s.asyncio.api() assert await kr8s.asyncio.whoami() == await api.whoami() -async def test_whoami_sync(): +async def test_whoami_sync() -> None: api = kr8s.api() assert kr8s.whoami() == api.whoami() -async def test_api_resources_cache(caplog): +async def test_api_resources_cache(caplog: pytest.LogCaptureFixture) -> None: caplog.set_level("INFO") api = await kr8s.asyncio.api() await api.api_resources() @@ -294,12 +294,13 @@ async def test_api_resources_cache(caplog): assert caplog.text.count('/apis/ "HTTP/1.1 200 OK"') == 1 -async def test_api_timeout(): +async def test_api_timeout() -> None: from httpx import Timeout api = await kr8s.asyncio.api() api.timeout = 10 await api.version() + assert api._session assert api._session.timeout.read == 10 api.timeout = 20 await api.version() diff --git a/pyproject.toml b/pyproject.toml index 1b9883f3..73af058a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,4 +130,4 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" target-version = "py310" [tool.mypy] -exclude = ["examples"] +exclude = ["examples", "tests", "venv", "ci", "docs", "conftest.py"]