fix(suod): defer optional suod import + actionable error (closes #640)#676
Merged
Merged
Conversation
…hao062#640) `pyod/models/suod.py` had a `try/except ImportError` that printed a friendly hint when the optional `suod` package was missing — and then, on the very next line, unconditionally executed `from suod.models.base import SUOD as SUOD_model`, which raised a bare ImportError. Net result: users who imported `pyod.models.suod` without `suod` installed got a crash, not the helpful message. This change moves the real import inside the try/except, captures the original ImportError, and raises an actionable message ("pip install suod ...") only when the user constructs `SUOD()`. The module-load path is now side-effect-free (no print, no crash), so `pyod.models.suod` can be safely introspected without the optional dep. Regression test (`TestSUODOptionalImport::test_constructor_raises_actionable_error_when_suod_missing`) monkey-patches the module-level sentinel and confirms the new error message is raised at constructor time. AI disclosure: drafted with assistance from Claude (Anthropic). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
yzhao062
added a commit
that referenced
this pull request
May 12, 2026
Cleanup follow-up to #676. The new test block landed with CRLF line endings (likely a contributor-side editor / git config artifact) and was the only section flagged by `git diff --cached --check` as trailing whitespace. Normalizes only the new block to LF; the rest of the file keeps its existing CRLF until a broader normalization pass. No behavior change.
3 tasks
yzhao062
added a commit
that referenced
this pull request
May 13, 2026
Bump version to 3.5.1 and add CHANGES.txt entry summarizing the patch release: the jbbqqf bundle (#673/#674/#675/#676/#677), the tuanaiseo GAAL torch-optional fix (#660) with our follow-up across mo_gaal/so_gaal/so_gaal_new, and the NSF POSE Phase II funding acknowledgment. Issues closed: #502 #546 #635 #638 #640. No new public API and no breaking changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
pyod/models/suod.pyhad atry/except ImportErrorthat printed afriendly hint when the optional
suodpackage was missing — and then,on the very next line, unconditionally ran
from suod.models.base import SUOD as SUOD_model, which raised a bareImportError. Users importing
pyod.models.suodwithoutsuodinstalled therefore got a crash, not the friendly hint. This PR makes
the module load side-effect-free and raises an actionable error
(
pip install suod ...) only when the user constructsSUOD().Fixes #640 — Possible import error in suod.py
Context
The reporter installed pyod via pip, tried
from pyod.models.suod import SUOD, and got an ImportError. The bug is in lines 12-16 of master:The
printis harmless but the next line is not guarded — so the"helpful" branch of the try/except never gets a chance to take effect.
Changes
pyod/models/suod.pyfrom suod.models.base import SUOD as SUOD_modelinside the try/except.
_SUOD_IMPORT_ERRORso theconstructor can include it in its error message (debuggability).
SUOD.__init__, ifSUOD_model is None, raise a clearImportError naming the install command and including the original
cause.
pyod/test/test_suod.pyTestSUODOptionalImport::test_constructor_raises_actionable_error_when_suod_missingwhich monkey-patches the module-level sentinels and confirms the
new error message format. (Doesn't actually uninstall
suod— thatwould break the rest of the suite.)
Reproduce BEFORE/AFTER yourself (copy-paste)
What I ran locally
pytest pyod/test/test_suod.py -q-> 17 passed (16 prior + 1 newregression test) using the project's test environment with
suodinstalled.
master(verified viagit stash):AttributeError: module 'pyod.models.suod' has no attribute '_SUOD_IMPORT_ERROR'— i.e. the sentinel didn't exist because themodule crashed during the unconditional import on master without
suod.Edge cases tested
suodinstalledSUOD()TestSUODsetUpsuodmissingimport pyod.models.suodTestSUODOptionalImport(monkey-patch path)suodmissingSUOD()ImportErrorcontainingpip install suodTestSUODOptionalImportRisk / blast radius
suodinstalled is unchanged.pyod.models.suodnow imports cleanly even whensuodis absent;any code that did
import pyod.models.suodto introspect (withoutintending to fit a model) will no longer be broken by an absent
optional dep.
optional code paths now see a more verbose message; behaviour-wise,
it is still an ImportError so
try/except ImportErrorcontinues towork.
Release note
PR template checklist (per
PULL_REQUEST_TEMPLATE.md)TestSUODOptionalImport).pytest pyod/test/test_suod.py -q-> 17 passed.PR drafted with assistance from Claude Code (Anthropic). The change was
reviewed manually against pyod's source. The reproducer block above is
the one I used during development; reviewers can paste it verbatim.