From 876c1e026172abccfdb6b8277085d871cf3a19c7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Jul 2026 18:32:00 +0000 Subject: [PATCH] Declare the operator's kubernetes dependency and fail with guidance pyisolate/operator/run_operator() imports the third-party `kubernetes` client, but it was never declared as a dependency or extra, so on a clean install the call raised a bare ImportError with no hint about the fix. - Add a `pyisolate[operator]` optional-dependency group with `kubernetes`. - Wrap the import so a missing client raises a RuntimeError that points at `pip install pyisolate[operator]` instead of a bare ImportError. New test asserts the actionable error on hosts without the kubernetes client (skipped where it is installed). Existing operator tests inject a fake kubernetes module and are unaffected. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01PbbJc7Ntj159D9LNGevwC2 --- pyisolate/operator/__init__.py | 16 ++++++++++++++-- pyproject.toml | 1 + tests/test_operator.py | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/pyisolate/operator/__init__.py b/pyisolate/operator/__init__.py index 5dcd775..0b00287 100644 --- a/pyisolate/operator/__init__.py +++ b/pyisolate/operator/__init__.py @@ -1,4 +1,5 @@ """Kubernetes operator for PyIsolate sandboxes.""" + from __future__ import annotations import logging @@ -15,8 +16,19 @@ def run_operator(namespace: str = "default") -> None: - """Start the operator watch loop.""" - from kubernetes import client, config, watch # type: ignore + """Start the operator watch loop. + + Requires the optional ``kubernetes`` client, which is not a core dependency. + Install it with ``pip install pyisolate[operator]``. + """ + try: + from kubernetes import client, config, watch # type: ignore + except ImportError as exc: + raise RuntimeError( + "the PyIsolate Kubernetes operator requires the 'kubernetes' " + "package, which is not installed. Install it with " + "`pip install pyisolate[operator]`." + ) from exc config.load_incluster_config() api = client.CustomObjectsApi() diff --git a/pyproject.toml b/pyproject.toml index 80f4dcd..17dc55a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,7 @@ pyisolate-doctor = "pyisolate.doctor:main" [project.optional-dependencies] pqcrypto = ["pqcrypto"] +operator = ["kubernetes"] release = [ "build", "twine", diff --git a/tests/test_operator.py b/tests/test_operator.py index 573349d..cf01965 100644 --- a/tests/test_operator.py +++ b/tests/test_operator.py @@ -3,6 +3,8 @@ import types from pathlib import Path +import pytest + def load_operator(): pkg = types.ModuleType("pyisolate") @@ -34,6 +36,18 @@ def test_operator_module(): assert hasattr(op, "scale_sandboxes") +def test_run_operator_without_kubernetes_gives_actionable_error(): + try: + import kubernetes # noqa: F401 + + pytest.skip("kubernetes is installed; missing-dependency path not exercised") + except ImportError: + pass + op = load_operator() + with pytest.raises(RuntimeError, match=r"pyisolate\[operator\]"): + op.run_operator() + + def test_operator_handles_relisted_added_and_modified(monkeypatch): op = load_operator() events = [