diff --git a/docs/news.rst b/docs/news.rst index a5cc6c723..35d81af6f 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -25,6 +25,16 @@ Backward-incompatible changes (:issue:`6585`, :issue:`7731`) +Bug fixes +~~~~~~~~~ + +- Disabling a component whose import path cannot be imported, e.g. a + component removed from a later Scrapy version, no longer raises + :exc:`ModuleNotFoundError` while resolving :ref:`component priority + dictionaries `. This bug was introduced in + Scrapy 2.15.0. + (:issue:`7820`) + .. _release-2.17.0: Scrapy 2.17.0 (2026-07-07) diff --git a/scrapy/settings/__init__.py b/scrapy/settings/__init__.py index 5c5edf101..0ab66fc62 100644 --- a/scrapy/settings/__init__.py +++ b/scrapy/settings/__init__.py @@ -366,7 +366,7 @@ class BaseSettings(MutableMapping[str, Any]): def normalize_key(key: Any) -> Any: try: loaded_key = load_object(key) - except (NameError, TypeError, ValueError): + except (ImportError, NameError, TypeError, ValueError): loaded_key = key else: import_path = global_object_name(loaded_key) diff --git a/tests/test_settings/__init__.py b/tests/test_settings/__init__.py index 97c095ae0..ff3b64c14 100644 --- a/tests/test_settings/__init__.py +++ b/tests/test_settings/__init__.py @@ -428,6 +428,9 @@ class TestBaseSettings: pytest.param(1, TypeError, id="type-error"), pytest.param("foo", ValueError, id="value-error"), pytest.param("csv.gz", NameError, id="name-error"), + pytest.param( + "nonexistent.module.Component", ImportError, id="import-error" + ), ], ) def test_get_component_priority_dict_with_base_handles_load_object_exceptions( @@ -446,6 +449,20 @@ class TestBaseSettings: assert isinstance(value, BaseSettings) assert dict(value) == {key: 1} + def test_get_component_priority_dict_with_base_disable_unimportable_key(self): + """Disabling a component whose module cannot be imported, e.g. one + removed from a later Scrapy version, must not break settings + resolution.""" + settings = BaseSettings( + { + "FOO_BASE": BaseSettings({"csv.excel": 1}), + "FOO": BaseSettings({"nonexistent.module.Component": None}), + } + ) + value = settings.get_component_priority_dict_with_base("FOO") + + assert dict(value) == {"csv.excel": 1} + def test_get_component_priority_dict_with_base_override_none_by_type(self): settings = BaseSettings() setting_names = set()