Skip to content

Python: Test actionable optional connector dependency errors - #14329

Open
mikemikimike wants to merge 3 commits into
microsoft:mainfrom
mikemikimike:issue-14328
Open

Python: Test actionable optional connector dependency errors#14329
mikemikimike wants to merge 3 commits into
microsoft:mainfrom
mikemikimike:issue-14328

Conversation

@mikemikimike

@mikemikimike mikemikimike commented Aug 24, 2026

Copy link
Copy Markdown

Summary

  • make missing Hugging Face imports report the hugging_face Semantic Kernel extra
  • make the existing ONNX missing-dependency error report the onnx extra
  • add isolated, parameterized subprocess coverage that blocks each upstream package even when extras are installed locally

Fixes #14328

Implementation and compatibility

The test covers the existing optional-dependency paths for transformers and onnxruntime_genai. Hugging Face now catches import failures before its service module leaks a generic ModuleNotFoundError; ONNX keeps its existing lazy import behavior and only improves the message. No dependency ranges, public APIs, successful-import behavior, provider calls, or credentials are changed.

Validation

Passed:

  • python -m pytest tests/unit/connectors/test_optional_dependencies.py tests/unit/connectors/ai/onnx/test_onnx_prompt_execution_settings.py -q — 9 passed
  • python -m ruff check semantic_kernel/connectors/ai/hugging_face/__init__.py semantic_kernel/connectors/ai/hugging_face/hf_prompt_execution_settings.py semantic_kernel/connectors/ai/hugging_face/services/hf_text_completion.py tests/unit/connectors/test_optional_dependencies.py
  • python -m ruff check --ignore RUF070 semantic_kernel/connectors/ai/onnx/services/onnx_gen_ai_completion_base.py
  • python -m ruff format --check on all four changed files
  • python -m compileall -q on all four changed files
  • git diff --check upstream/main...HEAD

Not fully run / environment limitations:

  • The full ONNX service unit directory requires the onnxruntime_genai optional package; without it, 21 tests fail at their existing mock/import setup and 10 pass.
  • Unfiltered Ruff on the ONNX base file reports the pre-existing RUF070 assignment at line 92, outside this diff.
  • Targeted mypy traverses the connector package and reports 35 pre-existing errors in semantic_kernel/connectors/mcp.py with the locally installed MCP version; --follow-imports=skip still reports three pre-existing override errors in hf_text_completion.py.
  • The full repository test suite was not run because this change is limited to optional-import error paths.

Evidence self-check (round 1)

  1. Issue meaningful and reproducible — Yes. The issue is open with no competing PR. Before the fix, the parameterized assertions failed because messages were transformers is not installed. and onnxruntime-genai is not installed. without install extras. The revised subprocess test also exposed the Hugging Face package-level generic ModuleNotFoundError path.
  2. Requirements satisfied and solved — Yes. Fresh subprocesses install a meta-path blocker for each package, then import/construct the connector and assert both the normalized package name and semantic-kernel[hugging_face] / semantic-kernel[onnx]. Both cases pass after the patch.
  3. Additional problems assessed — Yes. Successful imports, defaults, public types, dependency ranges, provider behavior, data formats, security boundaries, and performance-sensitive paths are unchanged. Only optional-import failure handling and messages change.
  4. Atomicity, consistency, isolation, and races assessed — Yes. These synchronous import/constructor guards execute before network, file, cache, queue, database, or shared-state work. The tests run each blocked import in a separate process, preventing module-cache leakage and requiring no teardown beyond process exit.
  5. Tests sufficient — Yes. Coverage maps directly to the two representative acceptance paths, proves isolation from locally installed extras, checks package and extra names, and fails if either actionable message is removed. Success-path behavior remains covered by existing connector tests; provider/network tests are not relevant to this failure-only change.

Evidence self-check (round 2)

  1. Issue meaningful and reproducible — Yes. Re-read Add regression coverage for missing optional connector dependencies #14328 after the final push: it remains open, has one implementation comment, and this PR is the only open PR returned for issue 14328. The original red run produced two assertion failures because neither message named its extra.
  2. Requirements satisfied and solved — Yes. At final head a6a75a766a3775d909c16148f5363d99e96ef33a, python -m pytest tests/unit/connectors/test_optional_dependencies.py tests/unit/connectors/ai/onnx/test_onnx_prompt_execution_settings.py -q reports 9 passed. PR files show only the two representative connector paths, their import guard, and the parameterized regression test.
  3. Additional problems assessed — Yes. Final review narrowed the new Hugging Face catches from ImportError to ModuleNotFoundError, so unrelated import-time failures are not rewritten. Public APIs, dependency ranges, successful imports, provider calls, data, security, and performance behavior remain unchanged.
  4. Atomicity, consistency, isolation, and races assessed — Yes. The final source still performs only synchronous import checks before external work. Each missing-package case executes in a fresh child process, so sys.meta_path and module cache state cannot leak between cases; process exit owns cleanup.
  5. Tests sufficient — Yes. Final verification repeated the two isolated missing-dependency cases plus seven adjacent ONNX settings tests, Ruff (with only the documented pre-existing RUF070 exclusion for the unchanged ONNX yield), Ruff formatting, compileall, and git diff --check. Local HEAD, fork ref, and PR head all match a6a75a766a3775d909c16148f5363d99e96ef33a.

Copilot AI lite review requested due to automatic review settings August 24, 2026 16:22
@mikemikimike
mikemikimike requested a review from a team as a code owner August 24, 2026 16:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves Python connector optional-dependency failures by making the resulting ImportError messages actionable (naming the missing upstream package and the correct semantic-kernel[...] extra), and adds subprocess-isolated regression tests that remain valid even when extras are installed locally.

Changes:

  • Add a parameterized subprocess test that blocks specific upstream packages via a sys.meta_path finder and asserts the error message includes both the missing package and the corresponding install extra.
  • Improve the ONNX connector’s missing-dependency error message to reference semantic-kernel[onnx].
  • Add proactive Hugging Face import guarding and improve the Hugging Face prompt settings missing-dependency message to reference semantic-kernel[hugging_face].

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
python/tests/unit/connectors/test_optional_dependencies.py Adds subprocess-isolated regression coverage for actionable optional-dependency errors.
python/semantic_kernel/connectors/ai/onnx/services/onnx_gen_ai_completion_base.py Updates the ONNX missing-dependency message to include the onnx extra install hint.
python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_completion.py Wraps Hugging Face imports to raise an actionable error referencing the hugging_face extra.
python/semantic_kernel/connectors/ai/hugging_face/hf_prompt_execution_settings.py Makes the missing-transformers path raise an actionable error referencing the hugging_face extra.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +35 to +56
script = """
import importlib
import importlib.abc
import sys

class MissingDependencyFinder(importlib.abc.MetaPathFinder):
def find_spec(self, fullname, path, target=None):
if fullname == sys.argv[3] or fullname.startswith(f"{sys.argv[3]}."):
raise ModuleNotFoundError(f"No module named '{sys.argv[3]}'", name=sys.argv[3])
return None

sys.meta_path.insert(0, MissingDependencyFinder())
try:
module = importlib.import_module(sys.argv[1])
connector = getattr(module, sys.argv[2])() if sys.argv[5] else getattr(module, sys.argv[2])("unused")
if sys.argv[5]:
getattr(connector, sys.argv[5])()
except ImportError as exc:
print(exc)
else:
raise AssertionError("Expected an ImportError for the blocked optional dependency")
"""

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified by running the subprocess test: the contents of the triple-quoted string start at column 0, so Python does not receive leading indentation. No code change is needed for this point.

Comment on lines +12 to +16
try:
imported = importlib.import_module("transformers")
ready = hasattr(imported, "GenerationConfig")
except ImportError:
ready = False

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a6a75a7 by catching only ModuleNotFoundError around the transformers probe. Other import-time ImportErrors are no longer converted into the optional-dependency fallback.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAF Automated Review — Iteration 1

Result: Findings reported
Scope: full PR (1 commit(s)): 60e3b814d9c0
Model: claude-opus-4.8

Overview

This PR makes optional-dependency ImportError messages actionable by naming the pip extra (semantic-kernel[hugging_face] / semantic-kernel[onnx]) and adds a subprocess test that blocks upstream packages with a meta-path finder. The production changes are fail-closed and low-risk: the ONNX and Hugging Face guards raise before any degraded path executes, ModuleNotFoundError remains an ImportError subclass so existing catch sites are unaffected, and no APIs, dependency ranges, or successful-import behavior change. The one residual risk is in the new test itself: the Hugging Face parametrization asserts on a package name that is not guaranteed to surface (importing the submodule eagerly imports the completion module, whose import torch runs before transformers), so the case fails when torch is absent and never exercises the get_generation_config message it names — both reproduced during review.

Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
1 verified finding remained after source verification (1 medium) across 1 file. Details are attached to the affected lines below.

Affected areas: python/tests/unit/connectors/test_optional_dependencies.py

text=True,
)

assert package_name.replace("_", "-") in result.stdout

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the Hugging Face parametrization this assertion is environment-dependent and does not cover the path it names. Importing ...hugging_face.hf_prompt_execution_settings first executes the package __init__.py, which imports hf_text_completion, whose top-level import torch runs before from transformers import .... The meta-path finder blocks only transformers (argv[3]), so when torch is not installed the raised message is "torch is not installed..." and this assertion ("transformers" in stdout) fails — reproduced as 1 failed, 1 passed in an environment without the hugging_face extra. It passes in CI only because unit-all-except-dapr runs uv sync --all-extras, so torch is present and transformers surfaces first. A side effect is that the ImportError is raised at import time from hf_text_completion, so get_generation_config() (the method_name this row targets) is never invoked and its new message goes untested. Make the case hermetic: block both torch and transformers in the finder, or assert only on the extra token (f"semantic-kernel[{extra_name}]", already checked on the next line) rather than a specific package name that is not guaranteed to surface first.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 984c0d3. The Hugging Face case now loads hf_prompt_execution_settings.py directly, bypassing the package init imports, blocks transformers, and invokes get_generation_config(). I reproduced the old environment-dependent behavior by forcing torch to be missing (the old assertion failed with the torch message), then verified the updated focused test passes for both connectors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add regression coverage for missing optional connector dependencies

2 participants