Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent overriding cached attribute as property #107657

Merged
merged 2 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions homeassistant/helpers/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import math
import sys
from timeit import default_timer as timer
from types import FunctionType
from typing import (
TYPE_CHECKING,
Any,
Expand Down Expand Up @@ -381,6 +382,9 @@ def wrap_attr(cls: CachedProperties, property_name: str) -> None:
# Check if an _attr_ class attribute exits and move it to __attr_. We check
# __dict__ here because we don't care about _attr_ class attributes in parents.
if attr_name in cls.__dict__:
attr = getattr(cls, attr_name)
if isinstance(attr, (FunctionType, property)):
raise TypeError(f"Can't override {attr_name} in subclass")
setattr(cls, private_attr_name, getattr(cls, attr_name))
annotations = cls.__annotations__
if attr_name in annotations:
Expand Down
41 changes: 41 additions & 0 deletions tests/helpers/test_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -2406,6 +2406,47 @@ def attribution(self) -> str | None:
assert getattr(ent[1], property) == values[0]


async def test_cached_entity_property_override(hass: HomeAssistant) -> None:
"""Test overriding cached _attr_ raises."""

class EntityWithClassAttribute1(entity.Entity):
"""A derived class which overrides an _attr_ from a parent."""

_attr_attribution: str

class EntityWithClassAttribute2(entity.Entity):
"""A derived class which overrides an _attr_ from a parent."""

_attr_attribution = "blabla"

class EntityWithClassAttribute3(entity.Entity):
"""A derived class which overrides an _attr_ from a parent."""

_attr_attribution: str = "blabla"

class EntityWithClassAttribute4(entity.Entity):
@property
def _attr_not_cached(self):
return "blabla"

class EntityWithClassAttribute5(entity.Entity):
def _attr_not_cached(self):
return "blabla"

with pytest.raises(TypeError):

class EntityWithClassAttribute6(entity.Entity):
@property
def _attr_attribution(self):
return "🤡"

with pytest.raises(TypeError):

class EntityWithClassAttribute7(entity.Entity):
def _attr_attribution(self):
return "🤡"


async def test_entity_report_deprecated_supported_features_values(
caplog: pytest.LogCaptureFixture,
) -> None:
Expand Down