Skip to content

Commit 72402da

Browse files
committed
🎉 Finally fix integer type quirks!
1 parent 87dd436 commit 72402da

File tree

4 files changed

+96
-51
lines changed

4 files changed

+96
-51
lines changed

tests/test_types_basic.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@ class TE(t.enum_flag_uint16):
3838
assert TE(0x8012).serialize() == data
3939

4040

41+
def test_abstract_ints():
42+
assert issubclass(t.uint8_t, t.uint_t)
43+
assert not issubclass(t.uint8_t, t.int_t)
44+
assert t.int_t._signed is True
45+
assert t.uint_t._signed is False
46+
47+
with pytest.raises(TypeError):
48+
t.int_t(0)
49+
50+
with pytest.raises(TypeError):
51+
t.FixedIntType(0)
52+
53+
4154
def test_int_too_short():
4255
with pytest.raises(ValueError):
4356
t.uint8_t.deserialize(b"")
@@ -159,3 +172,11 @@ class TestList(t.FixedList, length=3, item_type=t.uint16_t):
159172
assert r[0] == 0x1234
160173
assert r[1] == 0xAA55
161174
assert r[2] == 0xAB89
175+
176+
177+
def test_enum_instance_types():
178+
class TestEnum(t.enum_uint8):
179+
Member = 0x00
180+
181+
assert TestEnum._member_type_ is t.uint8_t
182+
assert type(TestEnum.Member.value) is t.uint8_t

tests/test_types_named.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,21 @@ def test_addr_mode_address():
9595

9696

9797
def test_missing_status_enum():
98-
assert 0x33 not in list(t.Status)
99-
assert isinstance(t.Status(0x33), t.Status)
100-
assert t.Status(0x33).value == 0x33
98+
class TestEnum(t.MissingEnumMixin, t.enum_uint8):
99+
Member = 0x00
101100

102-
# Status values that don't fit can't be created
101+
assert 0xFF not in list(TestEnum)
102+
assert isinstance(TestEnum(0xFF), TestEnum)
103+
assert TestEnum(0xFF).value == 0xFF
104+
assert type(TestEnum(0xFF).value) is t.uint8_t
105+
106+
# Missing members that don't fit can't be created
107+
with pytest.raises(ValueError):
108+
TestEnum(0xFF + 1)
109+
110+
# Missing members that aren't integers can't be created
103111
with pytest.raises(ValueError):
104-
t.Status(0xFF + 1)
112+
TestEnum("0xFF")
105113

106114

107115
def test_zdo_nullable_node_descriptor():

zigpy_znp/types/basic.py

Lines changed: 59 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,32 @@ def serialize_list(objects) -> Bytes:
2424
return Bytes(b"".join([o.serialize() for o in objects]))
2525

2626

27-
class int_t(int):
28-
_signed = True
27+
class FixedIntType(int):
28+
_signed = None
2929
_size = None
3030

31+
def _concrete_new(cls, value=0):
32+
instance = super().__new__(cls, value)
33+
instance.serialize()
34+
35+
return instance
36+
3137
def __new__(cls, value):
32-
instance = int.__new__(cls, value)
38+
raise TypeError(f"Instances of abstract type {cls} cannot be created")
3339

34-
if instance._signed is not None and instance._size is not None:
35-
# It's a concrete int_t type, check to make sure it's valid
36-
instance.serialize()
40+
def __init_subclass__(cls, signed=None, size=None, **kwargs) -> None:
41+
if signed is not None:
42+
cls._signed = signed
3743

38-
return instance
44+
if size is not None:
45+
cls._size = size
46+
47+
# XXX: The enum module uses the first class with `__new__` in its `__dict__`
48+
# as the member type. We have to give each subclass its own `__new__`.
49+
if signed is not None or size is not None:
50+
cls.__new__ = cls._concrete_new
51+
52+
super().__init_subclass__(**kwargs)
3953

4054
def serialize(self) -> bytes:
4155
try:
@@ -54,72 +68,76 @@ def deserialize(cls, data: bytes) -> typing.Tuple["int_t", bytes]:
5468
return r, data
5569

5670

57-
class int8s(int_t):
58-
_size = 1
71+
class uint_t(FixedIntType, signed=False):
72+
pass
5973

6074

61-
class int16s(int_t):
62-
_size = 2
75+
class int_t(FixedIntType, signed=True):
76+
pass
77+
78+
79+
class int8s(int_t, size=1):
80+
pass
6381

6482

65-
class int24s(int_t):
66-
_size = 3
83+
class int16s(int_t, size=2):
84+
pass
6785

6886

69-
class int32s(int_t):
70-
_size = 4
87+
class int24s(int_t, size=3):
88+
pass
7189

7290

73-
class int40s(int_t):
74-
_size = 5
91+
class int32s(int_t, size=4):
92+
pass
7593

7694

77-
class int48s(int_t):
78-
_size = 6
95+
class int40s(int_t, size=5):
96+
pass
7997

8098

81-
class int56s(int_t):
82-
_size = 7
99+
class int48s(int_t, size=6):
100+
pass
83101

84102

85-
class int64s(int_t):
86-
_size = 8
103+
class int56s(int_t, size=7):
104+
pass
87105

88106

89-
class uint_t(int_t):
90-
_signed = False
107+
class int64s(int_t, size=8):
108+
pass
91109

92110

93-
class uint8_t(uint_t):
94-
_size = 1
111+
class uint8_t(uint_t, size=1):
112+
pass
95113

96114

97-
class uint16_t(uint_t):
98-
_size = 2
115+
class uint16_t(uint_t, size=2):
116+
pass
99117

100118

101-
class uint24_t(uint_t):
102-
_size = 3
119+
class uint24_t(uint_t, size=3):
120+
pass
103121

104122

105-
class uint32_t(uint_t):
106-
_size = 4
123+
class uint32_t(uint_t, size=4):
124+
pass
107125

108126

109-
class uint40_t(uint_t):
110-
_size = 5
127+
class uint40_t(uint_t, size=5):
128+
pass
111129

112130

113-
class uint48_t(uint_t):
114-
_size = 6
131+
class uint48_t(uint_t, size=6):
132+
pass
115133

116134

117-
class uint56_t(uint_t):
118-
_size = 7
135+
class uint56_t(uint_t, size=7):
136+
pass
119137

120138

121-
class uint64_t(uint_t):
122-
_size = 8
139+
class uint64_t(uint_t, size=8):
140+
pass
123141

124142

125143
class ShortBytes(Bytes):

zigpy_znp/types/named.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,12 @@ class Schema:
149149
class MissingEnumMixin:
150150
@classmethod
151151
def _missing_(cls, value):
152-
if not isinstance(value, int) or value < 0 or value > 0xFF:
153-
# `return None` works with Python 3.7.7, breaks with 3.7.1
152+
if not isinstance(value, int):
154153
raise ValueError(f"{value} is not a valid {cls.__name__}")
155154

156-
# XXX: infer type from enum
157-
new_member = basic.uint8_t.__new__(cls, value)
155+
new_member = cls._member_type_.__new__(cls, value)
158156
new_member._name_ = f"unknown_0x{value:02X}"
159-
new_member._value_ = value
157+
new_member._value_ = cls._member_type_(value)
160158

161159
if sys.version_info >= (3, 8):
162160
# Show the warning in the calling code, not in this function

0 commit comments

Comments
 (0)