Coverage for dataclasses_struct / types.py: 100%

98 statements  

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

1from typing import Annotated 

2 

3from . import field 

4 

5Char = bytes 

6"""Single char type. Supported in both size modes.""" 

7 

8Bool = Annotated[bool, field.BoolField()] 

9"""Boolean type. Supported in both size modes.""" 

10 

11I8 = Annotated[int, field.SignedStdIntField(1)] 

12"""Fixed-width 8-bit signed integer. Supported with `size="std"`.""" 

13 

14U8 = Annotated[int, field.UnsignedStdIntField(1)] 

15"""Fixed-width 8-bit unsigned integer. Supported with `size="std"`.""" 

16 

17I16 = Annotated[int, field.SignedStdIntField(2)] 

18"""Fixed-width 16-bit signed integer. Supported with `size="std"`.""" 

19 

20U16 = Annotated[int, field.UnsignedStdIntField(2)] 

21"""Fixed-width 16-bit unsigned integer. Supported with `size="std"`.""" 

22 

23I32 = Annotated[int, field.SignedStdIntField(4)] 

24"""Fixed-width 32-bit signed integer. Supported with `size="std"`.""" 

25 

26U32 = Annotated[int, field.UnsignedStdIntField(4)] 

27"""Fixed-width 32-bit unsigned integer. Supported with `size="std"`.""" 

28 

29I64 = Annotated[int, field.SignedStdIntField(8)] 

30"""Fixed-width 64-bit signed integer. Supported with `size="std"`.""" 

31 

32U64 = Annotated[int, field.UnsignedStdIntField(8)] 

33"""Fixed-width 64-bit unsigned integer. Supported with `size="std"`.""" 

34 

35 

36# Native integer types 

37SignedChar = Annotated[int, field.NativeIntField("b", "byte")] 

38"""Equivalent to native C `signed char`. Supported with `size="native"`.""" 

39 

40UnsignedChar = Annotated[int, field.NativeIntField("B", "ubyte")] 

41"""Equivalent to native C `unsigned char`. Supported with `size="native"`.""" 

42 

43Short = Annotated[int, field.NativeIntField("h", "short")] 

44"""Equivalent to native C `short`. Supported with `size="native"`.""" 

45 

46UnsignedShort = Annotated[int, field.NativeIntField("H", "ushort")] 

47"""Equivalent to native C `unsigned short`. Supported with `size="native"`.""" 

48 

49Int = Annotated[int, field.NativeIntField("i", "int")] 

50"""Equivalent to native C `int`. Supported with `size="native"`.""" 

51 

52UnsignedInt = Annotated[int, field.NativeIntField("I", "uint")] 

53"""Equivalent to native C `unsigned int`. Supported with `size="native"`.""" 

54 

55Long = Annotated[int, field.NativeIntField("l", "long")] 

56"""Equivalent to native C `long`. Supported with `size="native"`.""" 

57 

58UnsignedLong = Annotated[int, field.NativeIntField("L", "ulong")] 

59"""Equivalent to native C `unsigned long`. Supported with `size="native"`.""" 

60 

61LongLong = Annotated[int, field.NativeIntField("q", "longlong")] 

62"""Equivalent to native C `long long`. Supported with `size="native"`.""" 

63 

64UnsignedLongLong = Annotated[int, field.NativeIntField("Q", "ulonglong")] 

65"""Equivalent to native C `unsigned long long`. Supported with 

66`size="native"`.""" 

67 

68 

69# Native size types 

70UnsignedSize = Annotated[int, field.SizeField(signed=False)] 

71"""Equivalent to native C `size_t`. Supported with `size="native"`.""" 

72 

73SignedSize = Annotated[int, field.SizeField(signed=True)] 

74"""Equivalent to native C `ssize_t` (a POSIX extension type). Supported with 

75`size="native"`.""" 

76 

77# Native pointer types 

78Pointer = Annotated[int, field.PointerField()] 

79"""Equivalent to native C `void *` pointer. Supported with `size="native"`.""" 

80 

81# Floating point types 

82F16 = Annotated[float, field.FloatingPointField("e")] 

83"""Half-precision floating point number. Supported in both size modes. 

84 

85Some compilers provide support for half precision floats on certain platforms 

86(e.g. [GCC](https://gcc.gnu.org/onlinedocs/gcc/Half-Precision.html), 

87[Clang](https://clang.llvm.org/docs/LanguageExtensions.html#half-precision-floating-point)). 

88It is also available as 

89[`std::float16_t`](https://en.cppreference.com/w/cpp/types/floating-point.html) 

90in C++23. 

91""" 

92 

93F32 = Annotated[float, field.FloatingPointField("f")] 

94"""Single-precision floating point number, equivalent to `float` in C. 

95Supported in both size modes.""" 

96 

97F64 = Annotated[float, field.FloatingPointField("d")] 

98"""Double-precision floating point number, equivalent to `double` in C. 

99Supported in both size modes.""" 

100 

101 

102class LengthPrefixed(field.Field[bytes]): 

103 """ 

104 Length-prefixed byte array, also known as a 'Pascal string'. 

105 

106 Packed to a fixed-length array of bytes, where the first byte is the length 

107 of the data. Data shorter than the maximum size is padded with zero bytes. 

108 

109 Must be used to annotate a `bytes` field with `typing.Annotated`: 

110 

111 ```python 

112 import dataclasses_struct as dcs 

113 

114 @dcs.dataclass_struct() 

115 class Example: 

116 fixed_length: Annotated[bytes, dcs.LengthPrefixed(10)] 

117 ``` 

118 

119 Args: 

120 size: The maximum size of the string including the length byte. Must be 

121 between 2 and 256 inclusive. The maximum array length that can be 

122 stored without truncation is `size - 1`. 

123 

124 Raises: 

125 ValueError: If `size` is outside the valid range. 

126 """ 

127 

128 field_type = bytes 

129 

130 def __init__(self, size: int): 

131 if not (isinstance(size, int) and 2 <= size <= 256): 

132 raise ValueError("size must be an int between 2 and 256") 

133 self.size = size 

134 

135 def format(self) -> str: 

136 return f"{self.size}p" 

137 

138 def __repr__(self) -> str: 

139 return f"{type(self).__name__}({self.size})" 

140 

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

142 if len(val) > self.size - 1: 

143 msg = f"bytes cannot be longer than {self.size - 1} bytes" 

144 raise ValueError(msg) 

145 

146 

147class CString(field.Field[bytes]): 

148 """ 

149 Null-terminated byte array, commonly known as a 'C string'. 

150 

151 Packed to a fixed-length array of bytes of length `size` that is guaranteed 

152 to be null-terminated. Note that `size` includes the null byte, so arrays 

153 longer than `size - 1` are truncated. 

154 

155 When unpacking, the trailing null byte and any subsequent bytes are 

156 discarded. 

157 

158 Must be used to annotate a `bytes` field with `typing.Annotated`. For 

159 example, 

160 

161 ```python 

162 import dataclasses_struct as dcs 

163 

164 @dcs.dataclass_struct() 

165 class Example: 

166 cstr: Annotated[bytes, dcs.CString(5)] 

167 

168 >>> example = Example(b"123") 

169 >>> packed = example.pack() 

170 >>> packed 

171 b'123\x00\x00' 

172 >>> unpacked = Example.unpack(packed) 

173 >>> unpacked 

174 Example(cstr=b'123') 

175 ``` 

176 

177 Note that there is additional overhead when unpacking as the bytes array is 

178 searched for the null terminator. If this is a concern (e.g. for very large 

179 arrays), it may be better to just use a regular fixed-length bytes array 

180 with a single trailing pad byte to ensure the array is always 

181 null-terminated. E.g. the following 

182 

183 ```python 

184 class Test: 

185 array: Annotated[bytes, LENGTH, dcs.PadAfter(1)]` 

186 ``` 

187 

188 is equivalent to the following array declaration in C: 

189 

190 ```c 

191 struct Test { 

192 char array[LENGTH + 1]; 

193 }; 

194 ``` 

195 

196 Args: 

197 size: The total size of the byte array, including the trailing null 

198 byte. 

199 

200 Raises: 

201 ValueError: If `size` is not a positive, non-zero integer. 

202 """ 

203 

204 field_type = bytes 

205 

206 def __init__(self, size: int): 

207 if not isinstance(size, int) or size <= 0: 

208 raise ValueError("C string length must be positive non-zero int") 

209 

210 self.size = size 

211 

212 def __repr__(self) -> str: 

213 return f"{type(self).__name__}({self.size})" 

214 

215 def format(self) -> str: 

216 return f"{self.size - 1}sx" 

217 

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

219 n = len(val) 

220 if n >= self.size: 

221 msg = ( 

222 f"C string cannot be longer than {self.size - 1} bytes" 

223 " (size includes the null terminator)" 

224 ) 

225 raise ValueError(msg) 

226 

227 

228class _Padding: 

229 before: bool 

230 

231 def __init__(self, size: int): 

232 if not isinstance(size, int) or size < 0: 

233 raise ValueError("padding size must be non-negative int") 

234 self.size = size 

235 

236 def __repr__(self) -> str: 

237 return f"{type(self).__name__}({self.size})" 

238 

239 

240class PadBefore(_Padding): 

241 """Add zero-bytes padding before the field. 

242 

243 Should be used with `typing.Annotated`. 

244 

245 ```python 

246 from typing import Annotated 

247 import dataclasses_struct as dcs 

248 

249 @dcs.dataclass_struct() 

250 class Padded: 

251 x: Annotated[int, dcs.PadBefore(5)] 

252 ``` 

253 

254 Args: 

255 size: The number of padding bytes to add before the field. 

256 """ 

257 

258 before = True 

259 

260 def __init__(self, size: int): 

261 super().__init__(size) 

262 

263 

264class PadAfter(_Padding): 

265 """Add zero-bytes padding after the field. 

266 

267 Should be used with `typing.Annotated`. 

268 

269 ```python 

270 from typing import Annotated 

271 import dataclasses_struct as dcs 

272 

273 @dcs.dataclass_struct() 

274 class Padded: 

275 x: Annotated[int, dcs.PadAfter(5)] 

276 ``` 

277 

278 Args: 

279 size: The number of padding bytes to add after the field. 

280 """ 

281 

282 before = False 

283 

284 def __init__(self, size: int): 

285 super().__init__(size)