mirror of
https://github.com/home-assistant/core.git
synced 2025-04-22 16:27:56 +00:00
Add guard to HomeAssistantError __str__
method to prevent a recursive loop (#113913)
* Add guard to HomeAssistantError `__str__` method to prevent a recursive loop * Use repr of class instance instead * Apply suggestion to explain __str__ method is missing --------- Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
a6d98c1857
commit
4da701a8e9
@ -64,6 +64,15 @@ class HomeAssistantError(Exception):
|
||||
return self._message
|
||||
|
||||
if not self.generate_message:
|
||||
# Initialize self._message to the string repr of the class
|
||||
# to prevent a recursive loop.
|
||||
self._message = (
|
||||
f"Parent class {self.__class__.__name__} is missing __str__ method"
|
||||
)
|
||||
# If the there is an other super class involved,
|
||||
# we want to call its __str__ method.
|
||||
# If the super().__str__ method is missing in the base_class
|
||||
# the call will be recursive and we return our initialized default.
|
||||
self._message = super().__str__()
|
||||
return self._message
|
||||
|
||||
|
@ -119,3 +119,159 @@ async def test_home_assistant_error(
|
||||
assert str(exc.value) == message
|
||||
# Get string of exception again from the cache
|
||||
assert str(exc.value) == message
|
||||
|
||||
|
||||
async def test_home_assistant_error_subclass(hass: HomeAssistant) -> None:
|
||||
"""Test __str__ method on an HomeAssistantError subclass."""
|
||||
|
||||
class _SubExceptionDefault(HomeAssistantError):
|
||||
"""Sub class, default with generated message."""
|
||||
|
||||
class _SubExceptionConstructor(HomeAssistantError):
|
||||
"""Sub class with constructor, no generated message."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
custom_arg: str,
|
||||
translation_domain: str | None = None,
|
||||
translation_key: str | None = None,
|
||||
translation_placeholders: dict[str, str] | None = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
self,
|
||||
translation_domain=translation_domain,
|
||||
translation_key=translation_key,
|
||||
translation_placeholders=translation_placeholders,
|
||||
)
|
||||
self.custom_arg = custom_arg
|
||||
|
||||
class _SubExceptionConstructorGenerate(HomeAssistantError):
|
||||
"""Sub class with constructor, with generated message."""
|
||||
|
||||
generate_message: bool = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
custom_arg: str,
|
||||
translation_domain: str | None = None,
|
||||
translation_key: str | None = None,
|
||||
translation_placeholders: dict[str, str] | None = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
self,
|
||||
translation_domain=translation_domain,
|
||||
translation_key=translation_key,
|
||||
translation_placeholders=translation_placeholders,
|
||||
)
|
||||
self.custom_arg = custom_arg
|
||||
|
||||
class _SubExceptionGenerate(HomeAssistantError):
|
||||
"""Sub class, no generated message."""
|
||||
|
||||
generate_message: bool = True
|
||||
|
||||
class _SubClassWithExceptionGroup(HomeAssistantError, BaseExceptionGroup):
|
||||
"""Sub class with exception group, no generated message."""
|
||||
|
||||
class _SubClassWithExceptionGroupGenerate(HomeAssistantError, BaseExceptionGroup):
|
||||
"""Sub class with exception group and generated message."""
|
||||
|
||||
generate_message: bool = True
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.translation.async_get_cached_translations",
|
||||
return_value={"component.test.exceptions.bla.message": "{bla} from cache"},
|
||||
):
|
||||
# A subclass without a constructor generates a message by default
|
||||
with pytest.raises(HomeAssistantError) as exc:
|
||||
raise _SubExceptionDefault(
|
||||
translation_domain="test",
|
||||
translation_key="bla",
|
||||
translation_placeholders={"bla": "Bla"},
|
||||
)
|
||||
assert str(exc.value) == "Bla from cache"
|
||||
|
||||
# A subclass with a constructor that does not parse `args` to the super class
|
||||
with pytest.raises(HomeAssistantError) as exc:
|
||||
raise _SubExceptionConstructor(
|
||||
"custom arg",
|
||||
translation_domain="test",
|
||||
translation_key="bla",
|
||||
translation_placeholders={"bla": "Bla"},
|
||||
)
|
||||
assert (
|
||||
str(exc.value)
|
||||
== "Parent class _SubExceptionConstructor is missing __str__ method"
|
||||
)
|
||||
with pytest.raises(HomeAssistantError) as exc:
|
||||
raise _SubExceptionConstructor(
|
||||
"custom arg",
|
||||
)
|
||||
assert (
|
||||
str(exc.value)
|
||||
== "Parent class _SubExceptionConstructor is missing __str__ method"
|
||||
)
|
||||
|
||||
# A subclass with a constructor that generates the message
|
||||
with pytest.raises(HomeAssistantError) as exc:
|
||||
raise _SubExceptionConstructorGenerate(
|
||||
"custom arg",
|
||||
translation_domain="test",
|
||||
translation_key="bla",
|
||||
translation_placeholders={"bla": "Bla"},
|
||||
)
|
||||
assert str(exc.value) == "Bla from cache"
|
||||
|
||||
# A subclass without overridden constructors and passed args
|
||||
# defaults to the passed args
|
||||
with pytest.raises(HomeAssistantError) as exc:
|
||||
raise _SubExceptionDefault(
|
||||
ValueError("wrong value"),
|
||||
translation_domain="test",
|
||||
translation_key="bla",
|
||||
translation_placeholders={"bla": "Bla"},
|
||||
)
|
||||
assert str(exc.value) == "wrong value"
|
||||
|
||||
# A subclass without overridden constructors and passed args
|
||||
# and generate_message = True, generates a message
|
||||
with pytest.raises(HomeAssistantError) as exc:
|
||||
raise _SubExceptionGenerate(
|
||||
ValueError("wrong value"),
|
||||
translation_domain="test",
|
||||
translation_key="bla",
|
||||
translation_placeholders={"bla": "Bla"},
|
||||
)
|
||||
assert str(exc.value) == "Bla from cache"
|
||||
|
||||
# A subclass with and ExceptionGroup subclass requires a message to be passed.
|
||||
# As we pass args, we will not generate the message.
|
||||
# The __str__ constructor defaults to that of the super class.
|
||||
with pytest.raises(HomeAssistantError) as exc:
|
||||
raise _SubClassWithExceptionGroup(
|
||||
"group message",
|
||||
[ValueError("wrong value"), TypeError("wrong type")],
|
||||
translation_domain="test",
|
||||
translation_key="bla",
|
||||
translation_placeholders={"bla": "Bla"},
|
||||
)
|
||||
assert str(exc.value) == "group message (2 sub-exceptions)"
|
||||
with pytest.raises(HomeAssistantError) as exc:
|
||||
raise _SubClassWithExceptionGroup(
|
||||
"group message",
|
||||
[ValueError("wrong value"), TypeError("wrong type")],
|
||||
)
|
||||
assert str(exc.value) == "group message (2 sub-exceptions)"
|
||||
|
||||
# A subclass with and ExceptionGroup subclass requires a message to be passed.
|
||||
# The `generate_message` flag is set.`
|
||||
# The __str__ constructor will return the generated message.
|
||||
with pytest.raises(HomeAssistantError) as exc:
|
||||
raise _SubClassWithExceptionGroupGenerate(
|
||||
"group message",
|
||||
[ValueError("wrong value"), TypeError("wrong type")],
|
||||
translation_domain="test",
|
||||
translation_key="bla",
|
||||
translation_placeholders={"bla": "Bla"},
|
||||
)
|
||||
assert str(exc.value) == "Bla from cache"
|
||||
|
Loading…
x
Reference in New Issue
Block a user