From f6fdffc2168763a63f16d6a8ba254b3277da9ed8 Mon Sep 17 00:00:00 2001 From: Jacob Tomlinson Date: Thu, 4 Jul 2024 14:23:13 +0100 Subject: [PATCH] Enable pydocstyle rules in ruff and fix errors (#438) --- kr8s/__init__.py | 111 ++++++++++++++++++------------------ kr8s/_api.py | 63 +++++++------------- kr8s/_async_utils.py | 1 - kr8s/_data_utils.py | 68 +++++++--------------- kr8s/_exceptions.py | 9 +-- kr8s/_objects.py | 2 +- kr8s/_portforward.py | 20 ++++--- kr8s/_testutils.py | 27 ++++++--- kr8s/asyncio/__init__.py | 4 ++ kr8s/asyncio/_api.py | 31 ++++------ kr8s/asyncio/_helpers.py | 53 +++++++---------- kr8s/asyncio/objects.py | 5 ++ kr8s/asyncio/portforward.py | 4 ++ kr8s/objects.py | 5 ++ kr8s/portforward.py | 4 ++ pyproject.toml | 17 +++++- 16 files changed, 199 insertions(+), 225 deletions(-) diff --git a/kr8s/__init__.py b/kr8s/__init__.py index bf6d2771..f8430b43 100644 --- a/kr8s/__init__.py +++ b/kr8s/__init__.py @@ -1,7 +1,13 @@ # SPDX-FileCopyrightText: Copyright (c) 2023-2024, Kr8s Developers (See LICENSE for list) # SPDX-License-Identifier: BSD 3-Clause License +""" +This module contains `kr8s`, a simple, extensible Python client library for Kubernetes. + +At the top level, `kr8s` provides a synchronous API that wraps the asynchronous API provided by `kr8s.asyncio`. +Both APIs are functionally identical with the same objects, method signatures and return values. +""" from functools import partial, update_wrapper -from typing import Optional, Union +from typing import Dict, List, Optional, Union from . import asyncio, objects, portforward from ._api import ALL @@ -47,39 +53,37 @@ class Api(_AsyncApi): __doc__ = _AsyncApi.__doc__ -def get(*args, **kwargs): +def get( + kind: str, + *names: List[str], + namespace: Optional[str] = None, + label_selector: Optional[Union[str, Dict]] = None, + field_selector: Optional[Union[str, Dict]] = None, + as_object: Optional[object] = None, + allow_unknown_type: bool = True, + api=None, + **kwargs, +): """Get a resource by name. - Parameters - ---------- - kind : str - The kind of resource to get - *names : List[str] - The names of the resources to get - namespace : str, optional - The namespace to get the resource from - label_selector : Union[str, Dict], optional - The label selector to filter the resources by - field_selector : Union[str, Dict], optional - The field selector to filter the resources by - as_object : object, optional - The object to populate with the resource data - api : Api, optional - The api to use to get the resource - - Returns - ------- - object + Args: + kind: The kind of resource to get + *names: The names of the resources to get + namespace: The namespace to get the resource from + label_selector: The label selector to filter the resources by + field_selector: The field selector to filter the resources by + as_object: The object to populate with the resource data + allow_unknown_type: Whether to allow unknown types + api: The api to use to get the resource + **kwargs: Additional arguments to pass to the API + + Returns: The populated object - Raises - ------ - ValueError - If the resource is not found - - Examples - -------- + Raises: + ValueError: If the resource is not found + Examples: >>> import kr8s >>> # All of these are equivalent >>> ings = kr8s.get("ing") # Short name @@ -90,7 +94,18 @@ def get(*args, **kwargs): >>> ings = kr8s.get("ingress.v1.networking.k8s.io") # Full with explicit version >>> ings = kr8s.get("ingress.networking.k8s.io/v1") # Full with explicit version alt. """ - return _run_sync(partial(_get, _asyncio=False))(*args, **kwargs) + return _run_sync(_get)( + kind, + *names, + namespace=namespace, + label_selector=label_selector, + field_selector=field_selector, + as_object=as_object, + allow_unknown_type=allow_unknown_type, + api=api, + _asyncio=False, + **kwargs, + ) def api( @@ -104,27 +119,17 @@ def api( If a kr8s object already exists with the same arguments in this thread, it will be returned. - Parameters - ---------- - url : str, optional - The URL of the Kubernetes API server - kubeconfig : str, optional - The path to a kubeconfig file to use - serviceaccount : str, optional - The path of a service account to use - namespace : str, optional - The namespace to use - context : str, optional - The context to use - - Returns - ------- - Api - The API object + Args: + url: The URL of the Kubernetes API server + kubeconfig: The path to a kubeconfig file to use + serviceaccount: The path of a service account to use + namespace: The namespace to use + context: The context to use - Examples - -------- + Returns: + The API object + Examples: >>> import kr8s >>> api = kr8s.api() # Uses the default kubeconfig >>> print(api.version()) # Get the Kubernetes version @@ -144,14 +149,10 @@ def api( def whoami(): """Get the current user's identity. - Returns - ------- - str + Returns: The user's identity - Examples - -------- - + Examples: >>> import kr8s >>> print(kr8s.whoami()) """ diff --git a/kr8s/_api.py b/kr8s/_api.py index acdc53bf..410e5ffd 100644 --- a/kr8s/_api.py +++ b/kr8s/_api.py @@ -247,9 +247,7 @@ async def open_websocket( async def version(self) -> dict: """Get the Kubernetes version information from the API. - Returns - ------- - dict + Returns: The Kubernetes version information. """ @@ -325,23 +323,14 @@ async def lookup_kind(self, kind) -> Tuple[str, bool]: Check whether a resource kind exists on the remote server. - Parameters - ---------- - kind : str - The kind of resource to lookup. - - Returns - ------- - str - The kind of resource. - bool - Whether the resource is namespaced + Args: + kind: The kind of resource to lookup. - Raises - ------ + Returns: + The kind of resource and whether the resource is namespaced - ValueError - If the kind is not found. + Raises: + ValueError: If the kind is not found. """ return await self.async_lookup_kind(kind) @@ -416,31 +405,19 @@ async def get( allow_unknown_type: bool = True, **kwargs, ) -> Union[APIObject, List[APIObject]]: - """ - Get Kubernetes resources. - - Parameters - ---------- - kind : str, type - The kind of resource to get. - *names : List[str], optional - The names of specific resources to get. - namespace : str, optional - The namespace to get the resource from. - label_selector : Union[str, Dict], optional - The label selector to filter the resources by. - field_selector : Union[str, Dict], optional - The field selector to filter the resources by. - as_object : object, optional - The object to return the resources as. - allow_unknown_type: - Automatically create a class for the resource if none exists, default True. - **kwargs - Additional keyword arguments to pass to the API call. - - Returns - ------- - List[object] + """Get Kubernetes resources. + + Args: + kind: The kind of resource to get. + *names: The names of specific resources to get. + namespace: The namespace to get the resource from. + label_selector: The label selector to filter the resources by. + field_selector: The field selector to filter the resources by. + as_object: The object to return the resources as. + allow_unknown_type: Automatically create a class for the resource if none exists, default True. + **kwargs: Additional keyword arguments to pass to the API call. + + Returns: The resources. """ return await self.async_get( diff --git a/kr8s/_async_utils.py b/kr8s/_async_utils.py index e70744d4..dc483f5a 100644 --- a/kr8s/_async_utils.py +++ b/kr8s/_async_utils.py @@ -102,7 +102,6 @@ def run_sync( Returns: Callable: A sync function that executes the coroutine via the :class`Portal`. """ - if inspect.isasyncgenfunction(coro): @wraps(coro) diff --git a/kr8s/_data_utils.py b/kr8s/_data_utils.py index db698fc6..3b88182a 100644 --- a/kr8s/_data_utils.py +++ b/kr8s/_data_utils.py @@ -9,18 +9,12 @@ def list_dict_unpack( ) -> Dict: """Convert a list of dictionaries to a single dictionary. - Parameters - ---------- - input_list : List[Dict] - The list of dictionaries to convert to a single dictionary. - key : str, optional - The key to use for the new dictionary's keys. Defaults to "key". - value : str, optional - The key to use for the new dictionary's values. Defaults to "value". - - Returns - ------- - Dict + Args: + input_list: The list of dictionaries to convert to a single dictionary. + key: The key to use for the new dictionary's keys. Defaults to "key". + value: The key to use for the new dictionary's values. Defaults to "value". + + Returns: A dictionary with the keys and values from the input list. """ return {i[key]: i[value] for i in input_list} @@ -31,18 +25,12 @@ def dict_list_pack( ) -> List[Dict]: """Convert a dictionary to a list of dictionaries. - Parameters - ---------- - input_dict : Dict - The dictionary to convert to a list of dictionaries. - key : str, optional - The key to use for the input dictionary's keys. Defaults to "key". - value : str, optional - The key to use for the input dictionary's values. Defaults to "value". - - Returns - ------- - List[Dict] + Args: + input_dict: The dictionary to convert to a list of dictionaries. + key: The key to use for the input dictionary's keys. Defaults to "key". + value: The key to use for the input dictionary's values. Defaults to "value". + + Returns: A list of dictionaries with the keys and values from the input dictionary. """ return [{key: k, value: v} for k, v in input_dict.items()] @@ -51,16 +39,11 @@ def dict_list_pack( def dot_to_nested_dict(dot_notated_key: str, value: Any) -> Dict: """Convert a dot notated key to a nested dictionary. - Parameters - ---------- - dot_notated_key : str - The dot notated key to convert to a nested dictionary. - value : Any - The value to assign to the innermost key. + Args: + dot_notated_key: The dot notated key to convert to a nested dictionary. + value: The value to assign to the innermost key. - Returns - ------- - Dict + Returns: A nested dictionary with the innermost key being the value of the dot notated key. """ @@ -77,14 +60,10 @@ def dot_to_nested_dict(dot_notated_key: str, value: Any) -> Dict: def dict_to_selector(selector_dict: Dict) -> str: """Convert a dictionary to a Kubernetes selector. - Parameters - ---------- - selector_dict : Dict - The dictionary to convert to a Kubernetes selector. + Args: + selector_dict: The dictionary to convert to a Kubernetes selector. - Returns - ------- - str + Returns: A Kubernetes selector string. """ return ",".join(f"{k}={v}" for k, v in selector_dict.items()) @@ -94,14 +73,11 @@ def xdict(*in_dict, **kwargs): """Dictionary constructor that ignores None values. Args: - in_dict : Dict - A dict to convert. Only one is allowed. - **kwargs - Keyword arguments to be converted to a dict. + in_dict: A dict to convert. Only one is allowed. + **kwargs: Keyword arguments to be converted to a dict. Returns: - Dict - A dict with None values removed. + A dict with None values removed. Raises: ValueError diff --git a/kr8s/_exceptions.py b/kr8s/_exceptions.py index 7996edd0..11e89221 100644 --- a/kr8s/_exceptions.py +++ b/kr8s/_exceptions.py @@ -25,12 +25,9 @@ class ExecError(Exception): class ServerError(Exception): """Error from the Kubernetes API server. - Attributes - ---------- - status : str - The Status object from the Kubernetes API server - response : httpx.Response - The httpx response object + Attributes: + status: The Status object from the Kubernetes API server + response: The httpx response object """ def __init__( diff --git a/kr8s/_objects.py b/kr8s/_objects.py index 79a8d707..ee2f3c01 100644 --- a/kr8s/_objects.py +++ b/kr8s/_objects.py @@ -205,7 +205,6 @@ async def get( **kwargs, ) -> APIObject: """Get a Kubernetes resource by name or via selectors.""" - if api is None: if cls._asyncio: api = await kr8s.asyncio.api() @@ -1684,6 +1683,7 @@ def object_from_spec( Args: spec: A Kubernetes resource spec. + api: An optional API instance to use. allow_unknown_type: Whether to allow unknown resource types. _asyncio: Whether to use asyncio or not. diff --git a/kr8s/_portforward.py b/kr8s/_portforward.py index b39d4ce6..3af06a25 100644 --- a/kr8s/_portforward.py +++ b/kr8s/_portforward.py @@ -255,22 +255,24 @@ async def _ws_to_tcp(self, ws, writer) -> None: writer.write(message[1:]) await writer.drain() - def _is_port_in_use(self, port: int, host: str = "127.0.0.1"): - """ - Check if a given port is in use on a specified host. + def _is_port_in_use(self, port: int, host: str = "127.0.0.1") -> bool: + """Check if a given port is in use on a specified host. + + Args: + port: Port number to check. + host: Host address to check the port on. Default is localhost. - :param port: Port number to check. - :param host: Host address to check the port on. Default is localhost. - :return: True if the port is in use, False otherwise. + Returns: + bool: True if the port is in use, False otherwise. """ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: return s.connect_ex((host, port)) == 0 def _find_available_port(self): - """ - Find a random port that is not in use on any of the given addresses. + """Find a random port that is not in use on any of the given addresses. - :return: An available port number. + Returns: + An available port number. """ while True: port = random.randint(10000, 60000) diff --git a/kr8s/_testutils.py b/kr8s/_testutils.py index 5dfe5a1a..6135b8cf 100644 --- a/kr8s/_testutils.py +++ b/kr8s/_testutils.py @@ -7,18 +7,27 @@ @contextlib.contextmanager def set_env(**environ: str) -> Generator[None, None, None]: - """ - Temporarily set the process environment variables. + """Temporarily sets the process environment variables. + + This context manager allows you to temporarily set the process environment variables + within a specific scope. It saves the current environment variables, updates them with + the provided values, and restores the original environment variables when the scope + is exited. + + Args: + **environ: Keyword arguments representing the environment variables to set. + + Yields: + None - >>> with set_env(PLUGINS_DIR=u'test/plugins'): - ... "PLUGINS_DIR" in os.environ - True + Examples: + >>> with set_env(PLUGINS_DIR='test/plugins'): + ... "PLUGINS_DIR" in os.environ + True - >>> "PLUGINS_DIR" in os.environ - False + >>> "PLUGINS_DIR" in os.environ + False - :type environ: dict[str, unicode] - :param environ: Environment variables to set """ old_environ = dict(os.environ) os.environ.update(environ) diff --git a/kr8s/asyncio/__init__.py b/kr8s/asyncio/__init__.py index f9504969..a40bf9fe 100644 --- a/kr8s/asyncio/__init__.py +++ b/kr8s/asyncio/__init__.py @@ -1,5 +1,9 @@ # SPDX-FileCopyrightText: Copyright (c) 2023-2024, Kr8s Developers (See LICENSE for list) # SPDX-License-Identifier: BSD 3-Clause License +"""The `kr8s` asynchronous API. + +This module provides an asynchronous API for interacting with a Kubernetes cluster. +""" from kr8s._api import Api from . import objects, portforward diff --git a/kr8s/asyncio/_api.py b/kr8s/asyncio/_api.py index 81816913..f52742e2 100644 --- a/kr8s/asyncio/_api.py +++ b/kr8s/asyncio/_api.py @@ -16,36 +16,25 @@ async def api( context: Optional[str] = None, _asyncio: bool = True, ) -> _AsyncApi: - """Create a :class:`kr8s.asyncio.Api` object for interacting with the Kubernetes API. + """Create a `kr8s.asyncio.Api` object for interacting with the Kubernetes API. If a kr8s object already exists with the same arguments in this thread, it will be returned. - Parameters - ---------- - url : str, optional - The URL of the Kubernetes API server - kubeconfig : str, optional - The path to a kubeconfig file to use - serviceaccount : str, optional - The path of a service account to use - namespace : str, optional - The namespace to use - context : str, optional - The context to use + Args: + url: The URL of the Kubernetes API server + kubeconfig: The path to a kubeconfig file to use + serviceaccount: The path of a service account to use + namespace: The namespace to use + context: The context to use - Returns - ------- - Api - The API object - - Examples - -------- + Returns: + kr8s.asyncio.Api: The API object + Examples: >>> import kr8s >>> api = await kr8s.asyncio.api() # Uses the default kubeconfig >>> print(await api.version()) # Get the Kubernetes version """ - from kr8s import Api as _SyncApi if _asyncio: diff --git a/kr8s/asyncio/_helpers.py b/kr8s/asyncio/_helpers.py index 12203dde..28f416ad 100644 --- a/kr8s/asyncio/_helpers.py +++ b/kr8s/asyncio/_helpers.py @@ -21,38 +21,27 @@ async def get( ): """Get a resource by name. - Parameters - ---------- - kind : str - The kind of resource to get - *names : List[str] - The names of the resources to get - namespace : str, optional - The namespace to get the resource from - label_selector : Union[str, Dict], optional - The label selector to filter the resources by - field_selector : Union[str, Dict], optional - The field selector to filter the resources by - as_object : object, optional - The object to populate with the resource data - allow_unknown_type : bool, optional - Automatically create a class for the resource if none exists - api : Api, optional - The api to use to get the resource - - Returns - ------- - object - The populated object - - Raises - ------ - ValueError - If the resource is not found - - Examples - -------- - + This function retrieves a resource from the Kubernetes cluster based on its kind and name(s). + It supports various options for filtering and customization. + + Args: + kind: The kind of resource to get. + *names: The names of the resources to get. + namespace: The namespace to get the resource from. + label_selector: The label selector to filter the resources by. + field_selector: The field selector to filter the resources by. + as_object: The object to populate with the resource data. + allow_unknown_type: Automatically create a class for the resource if none exists. + api: The api to use to get the resource. + **kwargs: Additional keyword arguments to pass to the `httpx` API call. + + Returns: + List[APIObject]: The Kubernetes resource objects. + + Raises: + ValueError: If the resource is not found. + + Examples: >>> import kr8s >>> # All of these are equivalent >>> ings = await kr8s.asyncio.get("ing") # Short name diff --git a/kr8s/asyncio/objects.py b/kr8s/asyncio/objects.py index 03c746e1..9ce1dde1 100644 --- a/kr8s/asyncio/objects.py +++ b/kr8s/asyncio/objects.py @@ -1,5 +1,10 @@ # SPDX-FileCopyrightText: Copyright (c) 2023-2024, Kr8s Developers (See LICENSE for list) # SPDX-License-Identifier: BSD 3-Clause License +"""Objects to represent Kubernetes resources. + +This module provides classes that represent Kubernetes resources. +These classes are used to interact with resources in the Kubernetes API server. +""" from kr8s._objects import ( APIObject, Binding, diff --git a/kr8s/asyncio/portforward.py b/kr8s/asyncio/portforward.py index b16d54a8..1cdd2ade 100644 --- a/kr8s/asyncio/portforward.py +++ b/kr8s/asyncio/portforward.py @@ -1,5 +1,9 @@ # SPDX-FileCopyrightText: Copyright (c) 2023-2024, Kr8s Developers (See LICENSE for list) # SPDX-License-Identifier: BSD 3-Clause License +"""Objects for managing a port forward connection. + +This module provides a class for managing a port forward connection to a Kubernetes Pod or Service. +""" from kr8s._portforward import PortForward __all__ = ["PortForward"] diff --git a/kr8s/objects.py b/kr8s/objects.py index 05e89a34..cc2c5453 100644 --- a/kr8s/objects.py +++ b/kr8s/objects.py @@ -1,5 +1,10 @@ # SPDX-FileCopyrightText: Copyright (c) 2024, Kr8s Developers (See LICENSE for list) # SPDX-License-Identifier: BSD 3-Clause License +"""Objects to represent Kubernetes resources. + +This module provides classes that represent Kubernetes resources. +These classes are used to interact with resources in the Kubernetes API server. +""" from functools import partial from ._async_utils import run_sync, sync diff --git a/kr8s/portforward.py b/kr8s/portforward.py index d9f7fcb2..2173ed8c 100644 --- a/kr8s/portforward.py +++ b/kr8s/portforward.py @@ -1,5 +1,9 @@ # SPDX-FileCopyrightText: Copyright (c) 2024, Kr8s Developers (See LICENSE for list) # SPDX-License-Identifier: BSD 3-Clause License +"""Objects for managing a port forward connection. + +This module provides a class for managing a port forward connection to a Kubernetes Pod or Service. +""" import threading import time diff --git a/pyproject.toml b/pyproject.toml index 73af058a..f1fceba1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,8 +90,11 @@ build-backend = "hatchling.build" [tool.ruff] # Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default. -select = ["E", "F", "I"] -ignore = [] +select = ["D", "E", "F", "I"] +ignore = [ + "D101", # Missing docstring in public class + "D212", # Multi-line docstring summary should start at the first line +] # Allow autofix for all enabled rules (when `--fix`) is provided. fixable = ["I"] @@ -129,5 +132,15 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" # Assume Python 3.10. target-version = "py310" +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.ruff.lint.per-file-ignores] +"kr8s/tests/*" = ["D"] +"conftest.py" = ["D"] +"examples/*" = ["D"] +"docs/*" = ["D"] +"ci/*" = ["D"] + [tool.mypy] exclude = ["examples", "tests", "venv", "ci", "docs", "conftest.py"]