Forbid Computed var shadowing (#3843)

This commit is contained in:
benedikt-bartscher 2024-09-10 21:09:35 +02:00 committed by GitHub
parent a5c73ad8e5
commit d672c643b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 49 additions and 4 deletions

View File

@ -477,7 +477,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
cls._check_overridden_methods()
# Computed vars should not shadow builtin state props.
cls._check_overriden_basevars()
cls._check_overridden_basevars()
# Reset subclass tracking for this class.
cls.class_subclasses = set()
@ -505,6 +505,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
# Get computed vars.
computed_vars = cls._get_computed_vars()
cls._check_overridden_computed_vars()
new_backend_vars = {
name: value
@ -718,7 +719,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
)
@classmethod
def _check_overriden_basevars(cls):
def _check_overridden_basevars(cls):
"""Check for shadow base vars and raise error if any.
Raises:
@ -730,6 +731,22 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
f"The computed var name `{computed_var_._var_name}` shadows a base var in {cls.__module__}.{cls.__name__}; use a different name instead"
)
@classmethod
def _check_overridden_computed_vars(cls) -> None:
"""Check for shadow computed vars and raise error if any.
Raises:
NameError: When a computed var shadows another.
"""
for name, cv in cls.__dict__.items():
if not isinstance(cv, (ComputedVar, ImmutableComputedVar)):
continue
name = cv._var_name
if name in cls.inherited_vars or name in cls.inherited_backend_vars:
raise NameError(
f"The computed var name `{cv._var_name}` shadows a var in {cls.__module__}.{cls.__name__}; use a different name instead"
)
@classmethod
def get_skip_vars(cls) -> set[str]:
"""Get the vars to skip when serializing.

View File

@ -79,6 +79,11 @@ def ChildState(ParentState, TestObj):
class ChildState(ParentState):
@immutable_computed_var
def var_without_annotation(self):
"""This shadows ParentState.var_without_annotation.
Returns:
TestObj: Test object.
"""
return TestObj
return ChildState
@ -89,6 +94,11 @@ def GrandChildState(ChildState, TestObj):
class GrandChildState(ChildState):
@immutable_computed_var
def var_without_annotation(self):
"""This shadows ChildState.var_without_annotation.
Returns:
TestObj: Test object.
"""
return TestObj
return GrandChildState
@ -738,8 +748,6 @@ def test_var_unsupported_indexing_dicts(var, index):
"fixture",
[
"ParentState",
"ChildState",
"GrandChildState",
"StateWithAnyVar",
],
)
@ -761,6 +769,26 @@ def test_computed_var_without_annotation_error(request, fixture):
)
@pytest.mark.parametrize(
"fixture",
[
"ChildState",
"GrandChildState",
],
)
def test_shadow_computed_var_error(request: pytest.FixtureRequest, fixture: str):
"""Test that a name error is thrown when an attribute of a computed var is
shadowed by another attribute.
Args:
request: Fixture Request.
fixture: The state fixture.
"""
with pytest.raises(NameError):
state = request.getfixturevalue(fixture)
state.var_without_annotation.foo
@pytest.mark.parametrize(
"fixture",
[