From e00c4c2d285519a7915928c6bbb539be322cdfa3 Mon Sep 17 00:00:00 2001 From: C1-BA-B1-F3 Date: Fri, 26 Jun 2026 04:38:31 +0800 Subject: [PATCH 1/2] fix: Add type validation to _fetch_class_library_tuple to prevent crashes from signature mismatches Fixes #6969 When community pipelines inherit from StableDiffusionPipeline but don't update their __init__ signature to include new parameters like 'image_encoder', positional arguments can be misaligned. This causes non-module values (like booleans from 'requires_safety_checker') to be passed where modules are expected, resulting in cryptic AttributeError: 'bool' object has no attribute '__module__'. This fix adds validation in _fetch_class_library_tuple to check for __module__ attribute and raise a clear TypeError with actionable guidance when invalid types are passed. --- .../pipelines/pipeline_loading_utils.py | 10 +++ test_fix_6969.py | 76 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 test_fix_6969.py diff --git a/src/diffusers/pipelines/pipeline_loading_utils.py b/src/diffusers/pipelines/pipeline_loading_utils.py index d695f5e7284d..3080cae3fc45 100644 --- a/src/diffusers/pipelines/pipeline_loading_utils.py +++ b/src/diffusers/pipelines/pipeline_loading_utils.py @@ -958,6 +958,16 @@ def _fetch_class_library_tuple(module): # register the config from the original module, not the dynamo compiled one not_compiled_module = _unwrap_model(module) + + # Validate that the module is a valid class instance or type + if not hasattr(not_compiled_module, "__module__"): + raise TypeError( + f"Expected a module or class instance for pipeline component, but received {type(not_compiled_module).__name__}: {not_compiled_module!r}. " + "This is likely due to a signature mismatch in a pipeline's __init__ method. " + "If you are using a custom pipeline, please ensure it matches the signature of the base pipeline class. " + "For example, if inheriting from StableDiffusionPipeline, ensure your __init__ accepts all parameters " + "including 'image_encoder' (added in recent versions)." + ) library = not_compiled_module.__module__.split(".")[0] # check if the module is a pipeline module diff --git a/test_fix_6969.py b/test_fix_6969.py new file mode 100644 index 000000000000..5be4c05cf679 --- /dev/null +++ b/test_fix_6969.py @@ -0,0 +1,76 @@ +"""Test to verify fix for issue #6969: Expanded init fields cause incompatibilities.""" +import sys +import os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) + +from diffusers.pipelines.pipeline_loading_utils import _fetch_class_library_tuple + + +def test_valid_module(): + """Test that valid modules still work.""" + from transformers import CLIPTextModel + library, class_name = _fetch_class_library_tuple(CLIPTextModel) + assert library == "transformers" + assert class_name == "CLIPTextModel" + print("✓ test_valid_module passed") + + +def test_bool_raises_type_error(): + """Test that passing a bool (like requires_safety_checker) raises TypeError.""" + try: + _fetch_class_library_tuple(True) + print("✗ test_bool_raises_type_error FAILED - no exception raised") + return False + except TypeError as e: + error_msg = str(e) + assert "bool" in error_msg, f"Error message should mention 'bool', got: {error_msg}" + assert "signature mismatch" in error_msg.lower(), f"Error should mention signature mismatch, got: {error_msg}" + print(f"✓ test_bool_raises_type_error passed - got expected error: {error_msg[:100]}...") + return True + + +def test_int_raises_type_error(): + """Test that passing an int raises TypeError.""" + try: + _fetch_class_library_tuple(42) + print("✗ test_int_raises_type_error FAILED - no exception raised") + return False + except TypeError as e: + error_msg = str(e) + assert "int" in error_msg, f"Error message should mention 'int', got: {error_msg}" + print(f"✓ test_int_raises_type_error passed") + return True + + +def test_string_raises_type_error(): + """Test that passing a string raises TypeError.""" + try: + _fetch_class_library_tuple("not_a_module") + print("✗ test_string_raises_type_error FAILED - no exception raised") + return False + except TypeError as e: + error_msg = str(e) + assert "str" in error_msg, f"Error message should mention 'str', got: {error_msg}" + print(f"✓ test_string_raises_type_error passed") + return True + + +def test_none_handled_by_register_modules(): + """Test that None is handled correctly (it should be caught before _fetch_class_library_tuple).""" + # None should be handled by register_modules before reaching _fetch_class_library_tuple + # This test just documents the expected behavior + print("✓ test_none_handled_by_register_modules passed - None is handled by register_modules") + + +if __name__ == "__main__": + print("Testing fix for issue #6969...") + print() + + test_valid_module() + test_bool_raises_type_error() + test_int_raises_type_error() + test_string_raises_type_error() + test_none_handled_by_register_modules() + + print() + print("All tests passed!") From 82dc604e3731b44c249beb4f2f3712c80096a990 Mon Sep 17 00:00:00 2001 From: C1-BA-B1-F3 Date: Fri, 26 Jun 2026 04:41:06 +0800 Subject: [PATCH 2/2] chore: Remove test file from PR --- test_fix_6969.py | 76 ------------------------------------------------ 1 file changed, 76 deletions(-) delete mode 100644 test_fix_6969.py diff --git a/test_fix_6969.py b/test_fix_6969.py deleted file mode 100644 index 5be4c05cf679..000000000000 --- a/test_fix_6969.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Test to verify fix for issue #6969: Expanded init fields cause incompatibilities.""" -import sys -import os -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) - -from diffusers.pipelines.pipeline_loading_utils import _fetch_class_library_tuple - - -def test_valid_module(): - """Test that valid modules still work.""" - from transformers import CLIPTextModel - library, class_name = _fetch_class_library_tuple(CLIPTextModel) - assert library == "transformers" - assert class_name == "CLIPTextModel" - print("✓ test_valid_module passed") - - -def test_bool_raises_type_error(): - """Test that passing a bool (like requires_safety_checker) raises TypeError.""" - try: - _fetch_class_library_tuple(True) - print("✗ test_bool_raises_type_error FAILED - no exception raised") - return False - except TypeError as e: - error_msg = str(e) - assert "bool" in error_msg, f"Error message should mention 'bool', got: {error_msg}" - assert "signature mismatch" in error_msg.lower(), f"Error should mention signature mismatch, got: {error_msg}" - print(f"✓ test_bool_raises_type_error passed - got expected error: {error_msg[:100]}...") - return True - - -def test_int_raises_type_error(): - """Test that passing an int raises TypeError.""" - try: - _fetch_class_library_tuple(42) - print("✗ test_int_raises_type_error FAILED - no exception raised") - return False - except TypeError as e: - error_msg = str(e) - assert "int" in error_msg, f"Error message should mention 'int', got: {error_msg}" - print(f"✓ test_int_raises_type_error passed") - return True - - -def test_string_raises_type_error(): - """Test that passing a string raises TypeError.""" - try: - _fetch_class_library_tuple("not_a_module") - print("✗ test_string_raises_type_error FAILED - no exception raised") - return False - except TypeError as e: - error_msg = str(e) - assert "str" in error_msg, f"Error message should mention 'str', got: {error_msg}" - print(f"✓ test_string_raises_type_error passed") - return True - - -def test_none_handled_by_register_modules(): - """Test that None is handled correctly (it should be caught before _fetch_class_library_tuple).""" - # None should be handled by register_modules before reaching _fetch_class_library_tuple - # This test just documents the expected behavior - print("✓ test_none_handled_by_register_modules passed - None is handled by register_modules") - - -if __name__ == "__main__": - print("Testing fix for issue #6969...") - print() - - test_valid_module() - test_bool_raises_type_error() - test_int_raises_type_error() - test_string_raises_type_error() - test_none_handled_by_register_modules() - - print() - print("All tests passed!")