Coverage for dataclasses_struct / field.py: 99%

93 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-30 22:31 +1200

1import abc 

2import ctypes 

3from typing import Any, ClassVar, Generic, Literal, TypeVar, Union 

4 

5T = TypeVar("T") 

6 

7 

8class Field(abc.ABC, Generic[T]): 

9 is_native: bool = True 

10 is_std: bool = True 

11 field_type: Union[type[T], tuple[type[T], ...]] 

12 

13 @abc.abstractmethod 

14 def format(self) -> str: ... 

15 

16 def validate_default(self, val: T) -> None: 

17 pass 

18 

19 def __repr__(self) -> str: 

20 return f"{type(self).__name__}" 

21 

22 

23class BoolField(Field[bool]): 

24 field_type = bool 

25 

26 def format(self) -> str: 

27 return "?" 

28 

29 

30class CharField(Field[bytes]): 

31 field_type = bytes 

32 

33 def format(self) -> str: 

34 return "c" 

35 

36 def validate_default(self, val: bytes) -> None: 

37 if len(val) != 1: 

38 raise ValueError("value must be a single byte") 

39 

40 

41class IntField(Field[int]): 

42 field_type = int 

43 

44 def __init__( 

45 self, 

46 fmt: str, 

47 signed: bool, 

48 size: int, 

49 ): 

50 if signed and fmt.isupper(): 

51 raise ValueError( 

52 "signed integer should have lowercase format string" 

53 ) 

54 

55 self.signed = signed 

56 self.size = size 

57 self._format = fmt 

58 

59 nbits = self.size * 8 

60 if signed: 

61 exp = 1 << (nbits - 1) 

62 self.min_ = -exp 

63 self.max_ = exp - 1 

64 else: 

65 self.min_ = 0 

66 self.max_ = (1 << nbits) - 1 

67 

68 def format(self) -> str: 

69 return self._format 

70 

71 def validate_default(self, val: int) -> None: 

72 if not (self.min_ <= val <= self.max_): 

73 sign = "signed" if self.signed else "unsigned" 

74 n = self.size * 8 

75 raise ValueError(f"value out of range for {n}-bit {sign} integer") 

76 

77 def __repr__(self) -> str: 

78 sign = "signed" if self.signed else "unsigned" 

79 return f"{super().__repr__()}({sign}, {self.size * 8}-bit)" 

80 

81 

82class StdIntField(IntField): 

83 is_native = False 

84 _unsigned_formats: ClassVar = { 

85 1: "B", 

86 2: "H", 

87 4: "I", 

88 8: "Q", 

89 } 

90 

91 def __init__(self, signed: bool, size: Literal[1, 2, 4, 8]): 

92 fmt = self._unsigned_formats[size] 

93 if signed: 

94 fmt = fmt.lower() 

95 super().__init__(fmt, signed, size) 

96 

97 

98class SignedStdIntField(StdIntField): 

99 def __init__(self, size: Literal[1, 2, 4, 8]): 

100 super().__init__(True, size) 

101 

102 

103class UnsignedStdIntField(StdIntField): 

104 def __init__(self, size: Literal[1, 2, 4, 8]): 

105 super().__init__(False, size) 

106 

107 

108class FloatingPointField(Field[float]): 

109 field_type = (int, float) 

110 

111 def __init__(self, format: str): 

112 self._format = format 

113 

114 def format(self) -> str: 

115 return self._format 

116 

117 

118class NativeIntField(IntField): 

119 is_std = False 

120 

121 def __init__(self, fmt: str, ctype_name: str): 

122 size = ctypes.sizeof(getattr(ctypes, f"c_{ctype_name}")) 

123 signed = not ctype_name.startswith("u") 

124 super().__init__(fmt, signed, size) 

125 

126 

127class SizeField(IntField): 

128 is_std = False 

129 

130 def __init__(self, signed: bool): 

131 fmt = "n" if signed else "N" 

132 size = ctypes.sizeof(ctypes.c_ssize_t if signed else ctypes.c_size_t) 

133 super().__init__(fmt, signed, size) 

134 

135 def validate_default(self, val: int) -> None: 

136 if not (self.min_ <= val <= self.max_): 

137 sign = "signed" if self.signed else "unsigned" 

138 raise ValueError(f"value out of range for {sign} size type") 

139 

140 

141class PointerField(IntField): 

142 is_std = False 

143 

144 def __init__(self): 

145 super().__init__("P", False, ctypes.sizeof(ctypes.c_void_p)) 

146 

147 def format(self) -> str: 

148 return "P" 

149 

150 def validate_default(self, val: int) -> None: 

151 if not (self.min_ <= val <= self.max_): 

152 raise ValueError("value out of range for system pointer") 

153 

154 

155builtin_fields: dict[type[Any], Field[Any]] = { 

156 int: NativeIntField("i", "int"), 

157 float: FloatingPointField("d"), 

158 bool: BoolField(), 

159 bytes: CharField(), 

160}