diff --git a/CHANGELOG.md b/CHANGELOG.md index 774b50b76d..266e93a511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# v37.0.1 + +Kubernetes API Version: v1.37.1 + +- Fixed type checkers such as pyright reporting documented functions and classes in `kubernetes.config`, `kubernetes.stream`, `kubernetes.utils`, `kubernetes.watch` and their `kubernetes.aio` counterparts, for example `config.load_kube_config` and `utils.create_from_yaml`, as not exported since v37.0.0. Wildcard imports such as `from kubernetes.config import *` no longer import unrelated names like `exists` and `expanduser`. + +- Fixed importing API submodules such as `core_v1_api` from the deprecated `kubernetes.client.apis` package, which raised `ImportError` since v37.0.0. + # v37.0.0 Kubernetes API Version: v1.37.1 diff --git a/README.md b/README.md index b033ce58ea..26106dfad4 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ supported versions of Kubernetes clusters. - [client 34.y.z](https://pypi.org/project/kubernetes/34.1.0/): Kubernetes 1.33 or below (+-), Kubernetes 1.34 (✓), Kubernetes 1.35 or above (+-) - [client 35.y.z](https://pypi.org/project/kubernetes/35.0.0/): Kubernetes 1.34 or below (+-), Kubernetes 1.35 (✓), Kubernetes 1.36 or above (+-) - [client 36.y.z](https://pypi.org/project/kubernetes/36.0.3/): Kubernetes 1.35 or below (+-), Kubernetes 1.36 (✓), Kubernetes 1.37 or above (+-) -- [client 37.y.z](https://pypi.org/project/kubernetes/37.0.0/): Kubernetes 1.36 or below (+-), Kubernetes 1.37 (✓), Kubernetes 1.38 or above (+-) +- [client 37.y.z](https://pypi.org/project/kubernetes/37.0.1/): Kubernetes 1.36 or below (+-), Kubernetes 1.37 (✓), Kubernetes 1.38 or above (+-) > See [here](#homogenizing-the-kubernetes-python-client-versions) for an explanation of why there is no v13-v16 release. diff --git a/kubernetes/README.md b/kubernetes/README.md index 552183fde6..9dd265b58d 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -4,7 +4,7 @@ No description provided (generated by Openapi Generator https://github.com/opena This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: - API version: release-1.37 -- Package version: 37.0.0 +- Package version: 37.0.1 - Generator version: 7.25.0 - Build package: org.openapitools.codegen.languages.PythonClientCodegen diff --git a/kubernetes/__init__.py b/kubernetes/__init__.py index ddc387d6e5..0ac0625346 100644 --- a/kubernetes/__init__.py +++ b/kubernetes/__init__.py @@ -14,7 +14,7 @@ __project__ = 'kubernetes' # The version is auto-updated. Please do not edit. -__version__ = "37.0.0" +__version__ = "37.0.1" from . import client from . import config diff --git a/kubernetes/aio/README.md b/kubernetes/aio/README.md index 48f1950543..2106e67f24 100644 --- a/kubernetes/aio/README.md +++ b/kubernetes/aio/README.md @@ -4,7 +4,7 @@ No description provided (generated by Openapi Generator https://github.com/opena This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: - API version: release-1.37 -- Package version: 37.0.0 +- Package version: 37.0.1 - Generator version: 7.25.0 - Build package: org.openapitools.codegen.languages.PythonClientCodegen diff --git a/kubernetes/aio/__init__.py b/kubernetes/aio/__init__.py index ef0fb41d89..35d685a78e 100644 --- a/kubernetes/aio/__init__.py +++ b/kubernetes/aio/__init__.py @@ -14,7 +14,7 @@ __project__ = "kubernetes_aio" # The version is auto-updated. Please do not edit. -__version__ = "37.0.0" +__version__ = "37.0.1" import kubernetes.aio.client as client import kubernetes.aio.config as config diff --git a/kubernetes/aio/client/__init__.py b/kubernetes/aio/client/__init__.py index e89dac048a..c60b21d880 100644 --- a/kubernetes/aio/client/__init__.py +++ b/kubernetes/aio/client/__init__.py @@ -14,7 +14,7 @@ """ # noqa: E501 -__version__ = "37.0.0" +__version__ = "37.0.1" # Define package exports __all__ = [ diff --git a/kubernetes/aio/client/api_client.py b/kubernetes/aio/client/api_client.py index fa21c2894f..5d0cafb9e4 100644 --- a/kubernetes/aio/client/api_client.py +++ b/kubernetes/aio/client/api_client.py @@ -122,7 +122,7 @@ def __init__( self.default_headers[header_name] = header_value self.cookie = cookie # Set default User-Agent. - self.user_agent = 'OpenAPI-Generator/37.0.0/python' + self.user_agent = 'OpenAPI-Generator/37.0.1/python' self.client_side_validation = configuration.client_side_validation async def __aenter__(self): diff --git a/kubernetes/aio/client/configuration.py b/kubernetes/aio/client/configuration.py index f496785918..cf052c2dfc 100644 --- a/kubernetes/aio/client/configuration.py +++ b/kubernetes/aio/client/configuration.py @@ -612,7 +612,7 @@ def to_debug_report(self) -> str: "OS: {env}\n"\ "Python Version: {pyversion}\n"\ "Version of the API: release-1.37\n"\ - "SDK Package Version: 37.0.0".\ + "SDK Package Version: 37.0.1".\ format(env=sys.platform, pyversion=sys.version) def get_host_settings(self) -> List[HostSetting]: diff --git a/kubernetes/aio/config/__init__.py b/kubernetes/aio/config/__init__.py index 10a2740018..14a50502de 100644 --- a/kubernetes/aio/config/__init__.py +++ b/kubernetes/aio/config/__init__.py @@ -22,6 +22,19 @@ new_client_from_config_dict, refresh_token, ) +__all__ = [ + "ConfigException", + "load_incluster_config", + "KUBE_CONFIG_DEFAULT_LOCATION", + "list_kube_config_contexts", + "load_kube_config", + "load_kube_config_from_dict", + "new_client_from_config", + "new_client_from_config_dict", + "refresh_token", + "load_config", +] + async def load_config(**kwargs): """ diff --git a/kubernetes/aio/stream/__init__.py b/kubernetes/aio/stream/__init__.py index 8a75e3742d..0a84d5c764 100644 --- a/kubernetes/aio/stream/__init__.py +++ b/kubernetes/aio/stream/__init__.py @@ -13,3 +13,7 @@ # limitations under the License. from .ws_client import WsApiClient + +__all__ = [ + "WsApiClient", +] diff --git a/kubernetes/aio/utils/__init__.py b/kubernetes/aio/utils/__init__.py index ebb210019d..2b6eb544ab 100644 --- a/kubernetes/aio/utils/__init__.py +++ b/kubernetes/aio/utils/__init__.py @@ -24,3 +24,23 @@ is_conflict, is_retry_after_response, is_too_many_requests, retry_after_backoff, retry_after_max_retries, retry_after_seconds) + +__all__ = [ + "FailToCreateError", + "create_from_dict", + "create_from_yaml", + "create_from_yaml_single_item", + "Backoff", + "DEFAULT_BACKOFF", + "DEFAULT_RETRY", + "DEFAULT_RETRY_AFTER_BACKOFF", + "on_error", + "on_retry_after_error", + "retry_on_conflict", + "is_conflict", + "is_retry_after_response", + "is_too_many_requests", + "retry_after_backoff", + "retry_after_max_retries", + "retry_after_seconds", +] diff --git a/kubernetes/aio/watch/__init__.py b/kubernetes/aio/watch/__init__.py index ca9ac06987..4918febc47 100644 --- a/kubernetes/aio/watch/__init__.py +++ b/kubernetes/aio/watch/__init__.py @@ -13,3 +13,7 @@ # limitations under the License. from .watch import Watch + +__all__ = [ + "Watch", +] diff --git a/kubernetes/client/__init__.py b/kubernetes/client/__init__.py index 728ea051db..8a9f8c2f04 100644 --- a/kubernetes/client/__init__.py +++ b/kubernetes/client/__init__.py @@ -14,7 +14,7 @@ """ # noqa: E501 -__version__ = "37.0.0" +__version__ = "37.0.1" # Define package exports __all__ = [ diff --git a/kubernetes/client/api_client.py b/kubernetes/client/api_client.py index 2e60e8580d..7f71c4b32e 100644 --- a/kubernetes/client/api_client.py +++ b/kubernetes/client/api_client.py @@ -129,7 +129,7 @@ def __init__( self.pool_threads = pool_threads self._pool_lock = Lock() # Set default User-Agent. - self.user_agent = 'OpenAPI-Generator/37.0.0/python' + self.user_agent = 'OpenAPI-Generator/37.0.1/python' self.client_side_validation = configuration.client_side_validation def close(self): diff --git a/kubernetes/client/apis/__init__.py b/kubernetes/client/apis/__init__.py index ca4b321de2..97cbd1d4a0 100644 --- a/kubernetes/client/apis/__init__.py +++ b/kubernetes/client/apis/__init__.py @@ -1,4 +1,6 @@ from __future__ import absolute_import +from importlib import import_module as _import_module +from types import ModuleType as _ModuleType import warnings # flake8: noqa @@ -11,3 +13,18 @@ "The package kubernetes.client.apis is renamed and deprecated, use kubernetes.client.api instead (please note that the trailing s was removed).", DeprecationWarning ) + + +def __getattr__(name: str) -> _ModuleType: + # kubernetes.client.api imports its submodules lazily, so the star import + # above no longer brings in module names such as core_v1_api. + if name.startswith('_') or not name.isidentifier(): + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + module_name = f"kubernetes.client.api.{name}" + try: + return _import_module(module_name) + except ModuleNotFoundError as e: + if e.name != module_name: + raise + raise AttributeError( + f"module {__name__!r} has no attribute {name!r}") from None diff --git a/kubernetes/client/configuration.py b/kubernetes/client/configuration.py index 28df8ba25a..ba2d428622 100644 --- a/kubernetes/client/configuration.py +++ b/kubernetes/client/configuration.py @@ -613,7 +613,7 @@ def to_debug_report(self) -> str: "OS: {env}\n"\ "Python Version: {pyversion}\n"\ "Version of the API: release-1.37\n"\ - "SDK Package Version: 37.0.0".\ + "SDK Package Version: 37.0.1".\ format(env=sys.platform, pyversion=sys.version) def get_host_settings(self) -> List[HostSetting]: diff --git a/kubernetes/config/__init__.py b/kubernetes/config/__init__.py index 3f49ce0e91..85f4338c7f 100644 --- a/kubernetes/config/__init__.py +++ b/kubernetes/config/__init__.py @@ -20,6 +20,18 @@ list_kube_config_contexts, load_kube_config, load_kube_config_from_dict, new_client_from_config, new_client_from_config_dict) +__all__ = [ + "ConfigException", + "load_incluster_config", + "KUBE_CONFIG_DEFAULT_LOCATION", + "list_kube_config_contexts", + "load_kube_config", + "load_kube_config_from_dict", + "new_client_from_config", + "new_client_from_config_dict", + "load_config", +] + def load_config(**kwargs): """ diff --git a/kubernetes/stream/__init__.py b/kubernetes/stream/__init__.py index cd34652811..df4c731171 100644 --- a/kubernetes/stream/__init__.py +++ b/kubernetes/stream/__init__.py @@ -13,3 +13,8 @@ # limitations under the License. from .stream import stream, portforward + +__all__ = [ + "stream", + "portforward", +] diff --git a/kubernetes/test/test_apis_alias.py b/kubernetes/test/test_apis_alias.py new file mode 100644 index 0000000000..cfb1441ff7 --- /dev/null +++ b/kubernetes/test/test_apis_alias.py @@ -0,0 +1,81 @@ +# Copyright 2026 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import importlib +import pkgutil +import unittest +from unittest import mock +import warnings + +import kubernetes.client.api + + +def _import_alias(): + # The alias installs its own 'default' filter for its DeprecationWarning, + # so record the warning instead of trying to filter it out. + with warnings.catch_warnings(record=True): + return importlib.import_module('kubernetes.client.apis') + + +class TestApisAlias(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.apis = _import_alias() + + def test_from_import_submodule(self): + from kubernetes.client.apis import core_v1_api + + self.assertIs( + core_v1_api, + importlib.import_module('kubernetes.client.api.core_v1_api')) + + def test_every_api_submodule_is_reachable(self): + names = [m.name for m in pkgutil.iter_modules( + kubernetes.client.api.__path__)] + self.assertIn('core_v1_api', names) + for name in names: + with self.subTest(name=name): + self.assertIs( + getattr(self.apis, name), + importlib.import_module('kubernetes.client.api.' + name)) + + def test_from_import_class(self): + from kubernetes.client.apis import CoreV1Api + + self.assertIs(CoreV1Api, kubernetes.client.api.CoreV1Api) + + def test_unknown_name(self): + with self.assertRaises(ImportError): + from kubernetes.client.apis import no_such_api # noqa: F401 + self.assertFalse(hasattr(self.apis, 'no_such_api')) + + def test_non_submodule_names_are_not_imported(self): + with mock.patch('kubernetes.client.apis._import_module') as m: + for name in ('__wrapped__', '_no_such_api', '.core_v1_api'): + with self.subTest(name=name): + self.assertFalse(hasattr(self.apis, name)) + m.assert_not_called() + + def test_missing_dependency_propagates(self): + error = ModuleNotFoundError("No module named 'missing_dep'", + name='missing_dep') + with mock.patch('kubernetes.client.apis._import_module', + side_effect=error): + with self.assertRaises(ModuleNotFoundError) as cm: + self.apis.core_v1_api + self.assertIs(cm.exception, error) + + +if __name__ == '__main__': + unittest.main() diff --git a/kubernetes/utils/__init__.py b/kubernetes/utils/__init__.py index 4b8c07f312..b691d9de92 100644 --- a/kubernetes/utils/__init__.py +++ b/kubernetes/utils/__init__.py @@ -26,3 +26,29 @@ retry_after_max_retries, retry_on_conflict, retry_after_seconds) from .keepalive import tcp_keepalive_socket_options + +__all__ = [ + "FailToCreateError", + "create_from_dict", + "create_from_yaml", + "create_from_directory", + "parse_quantity", + "parse_duration", + "get_nodes_metrics", + "get_pods_metrics", + "get_pods_metrics_in_all_namespaces", + "Backoff", + "DEFAULT_BACKOFF", + "DEFAULT_RETRY", + "DEFAULT_RETRY_AFTER_BACKOFF", + "is_conflict", + "is_retry_after_response", + "is_too_many_requests", + "on_error", + "on_retry_after_error", + "retry_after_backoff", + "retry_after_max_retries", + "retry_on_conflict", + "retry_after_seconds", + "tcp_keepalive_socket_options", +] diff --git a/kubernetes/watch/__init__.py b/kubernetes/watch/__init__.py index ca9ac06987..4918febc47 100644 --- a/kubernetes/watch/__init__.py +++ b/kubernetes/watch/__init__.py @@ -13,3 +13,7 @@ # limitations under the License. from .watch import Watch + +__all__ = [ + "Watch", +] diff --git a/scripts/constants.py b/scripts/constants.py index 587c2415fd..a0eb82e1d0 100644 --- a/scripts/constants.py +++ b/scripts/constants.py @@ -18,7 +18,7 @@ KUBERNETES_BRANCH = "release-1.37" # client version for packaging and releasing. -CLIENT_VERSION = "37.0.0" +CLIENT_VERSION = "37.0.1" # Name of the release package PACKAGE_NAME = "kubernetes" diff --git a/setup-asyncio.py b/setup-asyncio.py index bb661c5c94..04b5cf828f 100644 --- a/setup-asyncio.py +++ b/setup-asyncio.py @@ -16,7 +16,7 @@ # Do not edit these constants. They will be updated automatically # by scripts/update-client-asyncio.sh. -CLIENT_VERSION = "37.0.0" +CLIENT_VERSION = "37.0.1" PACKAGE_NAME = "kubernetes.aio" DEVELOPMENT_STATUS = "5 - Production/Stable" diff --git a/setup-release.py b/setup-release.py index e467d7f7e7..5d1217f897 100644 --- a/setup-release.py +++ b/setup-release.py @@ -16,7 +16,7 @@ # Do not edit these constants. They will be updated automatically # by scripts/update-client.sh. -CLIENT_VERSION = "37.0.0" +CLIENT_VERSION = "37.0.1" PACKAGE_NAME = "kubernetes" DEVELOPMENT_STATUS = "5 - Production/Stable" diff --git a/setup.py b/setup.py index 27bb0ca119..abc4f74101 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ # Do not edit these constants. They will be updated automatically # by scripts/update-client.sh. -CLIENT_VERSION = "37.0.0" +CLIENT_VERSION = "37.0.1" PACKAGE_NAME = "kubernetes" DEVELOPMENT_STATUS = "5 - Production/Stable"