What happened (please include outputs or screenshots):
Starting with 37.0.0, the kubernetes package includes a py.typed marker (added in #2657), which declares it a typed library. A typed library must follow the library interface rules of the typing spec: an imported name is private unless it is re-exported with import X as X or from Y import X as X, or is listed in __all__. kubernetes/utils/__init__.py breaks these rules: it imports its public functions without re-exporting them, so under the spec, create_from_yaml, parse_quantity, and the other functions are private names of kubernetes.utils. Type checkers that follow the spec, such as pyright, report every documented utils.<function> call as an error:
repro.py:3:7 - error: "create_from_yaml" is not exported from module "kubernetes.utils" (reportPrivateImportUsage)
repro.py:4:7 - error: "parse_quantity" is not exported from module "kubernetes.utils" (reportPrivateImportUsage)
We hit this in the dstack CI right after 37.0.0 was released: https://github.com/dstackai/dstack/actions/runs/37585855653/job/112676202973
These are the imports in kubernetes/utils/__init__.py:
|
from .create_from_yaml import (FailToCreateError, create_from_dict, |
|
create_from_yaml, create_from_directory) |
|
from .quantity import parse_quantity |
|
from .duration import parse_duration |
|
from .metrics import (get_nodes_metrics, get_pods_metrics, |
|
get_pods_metrics_in_all_namespaces) |
|
from .retry import (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) |
|
from .keepalive import tcp_keepalive_socket_options |
The official examples use the same pattern, for example:
|
from kubernetes import client, config, utils |
|
|
|
def main(): |
|
config.load_kube_config() |
|
k8s_client = client.ApiClient() |
|
yaml_file = 'examples/yaml_dir/configmap-demo-pod.yml' |
|
utils.create_from_yaml(k8s_client,yaml_file,verbose=True) |
examples/apply_from_directory.py, examples/apply_from_dict.py, and examples/metrics_example.py are affected in the same way, and so is kubernetes.aio.utils, which examples_asyncio/pod_exec.py uses.
What you expected to happen:
kubernetes.utils and kubernetes.aio.utils re-export their public functions as the library interface rules require, so that from kubernetes import utils followed by utils.create_from_yaml(...) type-checks without errors, as it did with 36.0.3.
How to reproduce it (as minimally and precisely as possible):
# repro.py
from kubernetes import client, utils
utils.create_from_yaml(client.ApiClient(), "manifest.yaml")
utils.parse_quantity("1Gi")
pip install kubernetes==37.0.0 pyright
pyright repro.py
With kubernetes==36.0.3, pyright reports no errors for the same file.
Anything else we need to know?:
One possible fix is to add an __all__ list with the imported names to kubernetes/utils/__init__.py and kubernetes/aio/utils/__init__.py. Another is to use redundant aliases, such as from .quantity import parse_quantity as parse_quantity. As a workaround, users can import from the module that defines the function, for example from kubernetes.utils.quantity import parse_quantity.
mypy applies these rules only with --no-implicit-reexport or --strict, which may be why the problem went unnoticed.
Environment:
- Kubernetes version (
kubectl version): N/A (static type checking)
- OS (e.g., MacOS 10.13.6): Ubuntu (GitHub Actions
ubuntu-latest), macOS
- Python version (
python --version): 3.10, 3.12
- Python client version (
pip list | grep kubernetes): 37.0.0
- pyright: 1.1.403
What happened (please include outputs or screenshots):
Starting with 37.0.0, the
kubernetespackage includes apy.typedmarker (added in #2657), which declares it a typed library. A typed library must follow the library interface rules of the typing spec: an imported name is private unless it is re-exported withimport X as Xorfrom Y import X as X, or is listed in__all__.kubernetes/utils/__init__.pybreaks these rules: it imports its public functions without re-exporting them, so under the spec,create_from_yaml,parse_quantity, and the other functions are private names ofkubernetes.utils. Type checkers that follow the spec, such as pyright, report every documentedutils.<function>call as an error:We hit this in the dstack CI right after 37.0.0 was released: https://github.com/dstackai/dstack/actions/runs/37585855653/job/112676202973
These are the imports in
kubernetes/utils/__init__.py:python/kubernetes/utils/__init__.py
Lines 16 to 28 in 322df9a
The official examples use the same pattern, for example:
python/examples/apply_from_single_file.py
Lines 1 to 7 in 322df9a
examples/apply_from_directory.py,examples/apply_from_dict.py, andexamples/metrics_example.pyare affected in the same way, and so iskubernetes.aio.utils, whichexamples_asyncio/pod_exec.pyuses.What you expected to happen:
kubernetes.utilsandkubernetes.aio.utilsre-export their public functions as the library interface rules require, so thatfrom kubernetes import utilsfollowed byutils.create_from_yaml(...)type-checks without errors, as it did with 36.0.3.How to reproduce it (as minimally and precisely as possible):
With
kubernetes==36.0.3, pyright reports no errors for the same file.Anything else we need to know?:
One possible fix is to add an
__all__list with the imported names tokubernetes/utils/__init__.pyandkubernetes/aio/utils/__init__.py. Another is to use redundant aliases, such asfrom .quantity import parse_quantity as parse_quantity. As a workaround, users can import from the module that defines the function, for examplefrom kubernetes.utils.quantity import parse_quantity.mypy applies these rules only with
--no-implicit-reexportor--strict, which may be why the problem went unnoticed.Environment:
kubectl version): N/A (static type checking)ubuntu-latest), macOSpython --version): 3.10, 3.12pip list | grep kubernetes): 37.0.0