Skip to content

Commit 9ebddb1

Browse files
fix: surface broken import errors when loading components
1 parent 4ec17cf commit 9ebddb1

File tree

3 files changed

+41
-2
lines changed

3 files changed

+41
-2
lines changed

src/django_unicorn/components/unicorn_view.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,8 +1017,15 @@ def _get_component_class(module_name: str, class_name: str) -> type[Component]:
10171017

10181018
return component
10191019
except ModuleNotFoundError as e:
1020-
logger.debug(e)
1021-
pass
1020+
# Only silently skip when the module we're looking for simply doesn't
1021+
# exist. If a *different* module is missing it means the component file
1022+
# was found and started executing but contains a broken import - that
1023+
# error should be surfaced rather than swallowed.
1024+
if e.name is None or module_name == e.name or module_name.startswith(e.name + "."):
1025+
logger.debug(e)
1026+
else:
1027+
message = f"The component module '{module_name}' could not be loaded: {e}"
1028+
raise ComponentModuleLoadError(message, locations=locations) from e
10221029
except AttributeError as e:
10231030
logger.debug(e)
10241031
attribute_exception = e
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from nonexistent_package_xyz_abc import SomeClass # noqa: F401 # type: ignore[import]
2+
3+
from django_unicorn.components import UnicornView
4+
5+
6+
class FakeComponentWithBrokenImport(UnicornView):
7+
template_name = "templates/test_component.html"

tests/views/message/test_message.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,31 @@ def test_message_component_class_with_attribute_error(client):
156156
)
157157

158158
assert e.value.__cause__
159+
# The original AttributeError should be included in the message so the
160+
# developer can see what actually went wrong, not just that loading failed.
161+
assert "not_a_valid_attribute" in e.exconly()
162+
163+
164+
def test_message_component_module_with_broken_import(client):
165+
"""A component that exists but contains a broken import should surface the
166+
underlying ModuleNotFoundError rather than masking it with a generic
167+
"component module could not be loaded" message (issue #380)."""
168+
data = {
169+
"data": {},
170+
"meta": "DVVk97cx",
171+
"id": str(uuid4()),
172+
"epoch": time.time(),
173+
}
174+
175+
with pytest.raises(ComponentModuleLoadError) as e:
176+
post_json(
177+
client,
178+
data,
179+
url="/message/tests.views.fake_components_with_broken_import.FakeComponentWithBrokenImport",
180+
)
181+
182+
assert e.value.__cause__
183+
assert "nonexistent_package_xyz_abc" in e.exconly()
159184

160185

161186
def test_message_component_with_dash(client):

0 commit comments

Comments
 (0)