Skip to content

Commit d4e6d44

Browse files
committed
Fix attrs.frozen with enum.Enum subclasses
When creating an Enum subclass of a frozen attrs class, the frozen __setattr__ would prevent Enum from creating members. Now, Enum instances can bypass the frozen check to allow proper member creation. Fixes #1287
1 parent 005e2fb commit d4e6d44

3 files changed

Lines changed: 75 additions & 0 deletions

File tree

changelog.d/1287.change.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed `attrs.frozen` classes not working with `enum.Enum` subclasses.
2+
3+
When creating an Enum subclass of a frozen attrs class, the frozen `__setattr__` would prevent Enum from creating members. Now, Enum instances can bypass the frozen check to allow proper member creation.

src/attr/_make.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,12 @@ def _frozen_setattrs(self, name, value):
570570
BaseException.__setattr__(self, name, value)
571571
return
572572

573+
# Allow Enum subclasses to set attributes during member creation.
574+
# Enum needs to set _name_, _value_, etc. when creating members.
575+
if isinstance(self, enum.Enum):
576+
object.__setattr__(self, name, value)
577+
return
578+
573579
raise FrozenInstanceError
574580

575581

tests/test_functional.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import pickle
1010

1111
from copy import deepcopy
12+
from enum import Enum
1213

1314
import pytest
1415

@@ -816,3 +817,68 @@ def test_invalid_field_name(self):
816817

817818
with pytest.raises(TypeError):
818819
copy.replace(inst, z=42)
820+
821+
822+
class TestEnum:
823+
"""
824+
Tests for Enum + attrs interaction (issue #1287).
825+
"""
826+
827+
def test_define_enum(self):
828+
"""
829+
attrs.define works with Enum subclasses.
830+
831+
Enum members can be created and attrs fields are accessible.
832+
"""
833+
834+
@attr.define
835+
class AttrsDefine:
836+
x: int
837+
y: float
838+
839+
class AttrsDefineEnum(AttrsDefine, Enum):
840+
A = (1, 2.0)
841+
B = (3, 4.0)
842+
843+
assert 1 == AttrsDefineEnum.A.x
844+
assert 2.0 == AttrsDefineEnum.A.y
845+
assert 3 == AttrsDefineEnum.B.x
846+
assert 4.0 == AttrsDefineEnum.B.y
847+
848+
def test_frozen_enum(self):
849+
"""
850+
attrs.frozen works with Enum subclasses.
851+
852+
Enum members can be created even with frozen base classes.
853+
"""
854+
855+
@attr.frozen
856+
class AttrsFrozen:
857+
x: int
858+
y: float
859+
860+
class AttrsFrozenEnum(AttrsFrozen, Enum):
861+
A = (1, 2.0)
862+
B = (3, 4.0)
863+
864+
assert 1 == AttrsFrozenEnum.A.x
865+
assert 2.0 == AttrsFrozenEnum.A.y
866+
assert 3 == AttrsFrozenEnum.B.x
867+
assert 4.0 == AttrsFrozenEnum.B.y
868+
869+
def test_frozen_enum_is_hashable(self):
870+
"""
871+
attrs.frozen Enum subclasses are hashable.
872+
"""
873+
874+
@attr.frozen
875+
class AttrsFrozen:
876+
x: int
877+
878+
class FrozenEnum(AttrsFrozen, Enum):
879+
A = (1,)
880+
B = (2,)
881+
882+
# Should not raise TypeError
883+
hash(FrozenEnum.A)
884+
hash(FrozenEnum.B)

0 commit comments

Comments
 (0)