Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions kubernetes/client/apis/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
81 changes: 81 additions & 0 deletions kubernetes/test/test_apis_alias.py
Original file line number Diff line number Diff line change
@@ -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()
Loading