dissect.cstruct 5.0.dev7__py3-none-any.whl → 5.0.dev9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import functools
4
+ import warnings
4
5
  from io import BytesIO
5
6
  from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar, TypeVar
6
7
 
7
8
  from dissect.cstruct.exception import ArraySizeError
8
9
  from dissect.cstruct.expression import Expression
10
+ from dissect.cstruct.util import classproperty, normalize_endianness
9
11
 
10
12
  if TYPE_CHECKING:
11
13
  from collections.abc import Callable
@@ -21,17 +23,17 @@ EOF = -0xE0F # Negative counts are illegal anyway, so abuse that for our EOF se
21
23
  class MetaType(type):
22
24
  """Base metaclass for cstruct type classes."""
23
25
 
24
- cs: cstruct
26
+ __cs__: cstruct
25
27
  """The cstruct instance this type class belongs to."""
26
- size: int | None
28
+ __size__: int | None
27
29
  """The size of the type in bytes. Can be ``None`` for dynamic sized types."""
28
- dynamic: bool
30
+ __dynamic__: bool
29
31
  """Whether or not the type is dynamically sized."""
30
- alignment: int | None
32
+ __alignment__: int | None
31
33
  """The alignment of the type in bytes. A value of ``None`` will be treated as 1-byte aligned."""
32
34
 
33
35
  # This must be the actual type, but since Array is a subclass of BaseType, we correct this at the bottom of the file
34
- ArrayType: type[BaseArray] = "Array"
36
+ __ArrayType__: type[BaseArray] = "Array"
35
37
  """The array type for this type class."""
36
38
 
37
39
  def __call__(cls, *args, **kwargs) -> Self: # type: ignore
@@ -41,10 +43,12 @@ class MetaType(type):
41
43
  stream = args[0]
42
44
 
43
45
  if _is_readable_type(stream):
44
- endian = normalize_endianness(endian) if (endian := kwargs.get("endian")) is not None else cls.cs.endian
46
+ endian = (
47
+ normalize_endianness(endian) if (endian := kwargs.get("endian")) is not None else cls.__cs__.endian
48
+ )
45
49
  return cls._read(stream, endian=endian)
46
50
 
47
- if issubclass(cls, bytes) and isinstance(stream, bytes) and len(stream) == cls.size:
51
+ if issubclass(cls, bytes) and isinstance(stream, bytes) and len(stream) == cls.__size__:
48
52
  # Shortcut for char/bytes type
49
53
  return type.__call__(cls, *args, **kwargs)
50
54
 
@@ -55,7 +59,7 @@ class MetaType(type):
55
59
 
56
60
  def __getitem__(cls, num_entries: int | Expression | None) -> type[BaseArray]:
57
61
  """Create a new array with the given number of entries."""
58
- return cls.cs._make_array(cls, num_entries)
62
+ return cls.__cs__._make_array(cls, num_entries)
59
63
 
60
64
  def __bool__(cls) -> bool:
61
65
  """Type class is always truthy."""
@@ -63,10 +67,10 @@ class MetaType(type):
63
67
 
64
68
  def __len__(cls) -> int:
65
69
  """Return the byte size of the type."""
66
- if cls.size is None:
70
+ if cls.__size__ is None:
67
71
  raise TypeError("Dynamic size")
68
72
 
69
- return cls.size
73
+ return cls.__size__
70
74
 
71
75
  def __default__(cls) -> Self: # type: ignore
72
76
  """Return the default value of this type."""
@@ -83,7 +87,7 @@ class MetaType(type):
83
87
  Returns:
84
88
  The parsed value of this type.
85
89
  """
86
- endian = normalize_endianness(endian) if endian is not None else cls.cs.endian
90
+ endian = normalize_endianness(endian) if endian is not None else cls.__cs__.endian
87
91
  return cls._read(BytesIO(data), endian=endian)
88
92
 
89
93
  def read(cls, obj: BinaryIO | bytes | memoryview | bytearray, *, endian: AllowedEndianness | None = None) -> Self: # type: ignore
@@ -103,7 +107,7 @@ class MetaType(type):
103
107
  if not _is_readable_type(obj):
104
108
  raise TypeError("Invalid object type")
105
109
 
106
- endian = normalize_endianness(endian) if endian is not None else cls.cs.endian
110
+ endian = normalize_endianness(endian) if endian is not None else cls.__cs__.endian
107
111
  return cls._read(obj, endian=endian)
108
112
 
109
113
  def write(cls, stream: BinaryIO, value: Any, *, endian: AllowedEndianness | None = None) -> int:
@@ -118,7 +122,7 @@ class MetaType(type):
118
122
  Returns:
119
123
  The amount of bytes written.
120
124
  """
121
- endian = normalize_endianness(endian) if endian is not None else cls.cs.endian
125
+ endian = normalize_endianness(endian) if endian is not None else cls.__cs__.endian
122
126
  return cls._write(stream, value, endian=endian)
123
127
 
124
128
  def dumps(cls, value: Any, *, endian: AllowedEndianness | None = None) -> bytes:
@@ -132,7 +136,7 @@ class MetaType(type):
132
136
  Returns:
133
137
  The raw bytes of this type.
134
138
  """
135
- endian = normalize_endianness(endian) if endian is not None else cls.cs.endian
139
+ endian = normalize_endianness(endian) if endian is not None else cls.__cs__.endian
136
140
 
137
141
  out = BytesIO()
138
142
  cls._write(out, value, endian=endian)
@@ -210,6 +214,56 @@ class MetaType(type):
210
214
  """
211
215
  return cls._write_array(stream, [*array, cls.__default__()], endian=endian)
212
216
 
217
+ @property
218
+ def cs(cls) -> cstruct:
219
+ """Access the cstruct object for this type."""
220
+ warnings.warn(
221
+ "The 'cs' property is deprecated, use '__cs__' instead.",
222
+ DeprecationWarning,
223
+ stacklevel=2,
224
+ )
225
+ return cls.__cs__
226
+
227
+ @property
228
+ def size(cls) -> int | None:
229
+ """Access the size of this type."""
230
+ warnings.warn(
231
+ "The 'size' property is deprecated, use '__size__', len(cls) or sizeof(cls) instead.",
232
+ DeprecationWarning,
233
+ stacklevel=2,
234
+ )
235
+ return cls.__size__
236
+
237
+ @property
238
+ def dynamic(cls) -> bool:
239
+ """Access whether this type is dynamically sized."""
240
+ warnings.warn(
241
+ "The 'dynamic' property is deprecated, use '__dynamic__' instead.",
242
+ DeprecationWarning,
243
+ stacklevel=2,
244
+ )
245
+ return cls.__dynamic__
246
+
247
+ @property
248
+ def alignment(cls) -> int | None:
249
+ """Access the alignment of this type."""
250
+ warnings.warn(
251
+ "The 'alignment' property is deprecated, use '__alignment__' instead.",
252
+ DeprecationWarning,
253
+ stacklevel=2,
254
+ )
255
+ return cls.__alignment__
256
+
257
+ @property
258
+ def ArrayType(cls) -> type[BaseArray]:
259
+ """Access the array type of this type."""
260
+ warnings.warn(
261
+ "The 'ArrayType' property is deprecated, use '__ArrayType__' instead.",
262
+ DeprecationWarning,
263
+ stacklevel=2,
264
+ )
265
+ return cls.__ArrayType__
266
+
213
267
 
214
268
  class _overload:
215
269
  """Descriptor to use on the ``write`` and ``dumps`` methods on cstruct types.
@@ -240,10 +294,10 @@ class BaseType(metaclass=MetaType):
240
294
 
241
295
  def __len__(self) -> int:
242
296
  """Return the byte size of the type."""
243
- if self.__class__.size is None:
297
+ if self.__class__.__size__ is None:
244
298
  raise TypeError("Dynamic size")
245
299
 
246
- return self.__class__.size
300
+ return self.__class__.__size__
247
301
 
248
302
 
249
303
  T = TypeVar("T", bound=BaseType)
@@ -260,46 +314,76 @@ class BaseArray(BaseType):
260
314
  x[expr] -> expr -> dynamic length.
261
315
  """
262
316
 
263
- type: ClassVar[type[BaseType]]
264
- num_entries: ClassVar[int | Expression | None]
265
- null_terminated: ClassVar[bool]
317
+ __type__: ClassVar[type[BaseType]]
318
+ __num_entries__: ClassVar[int | Expression | None]
319
+ __null_terminated__: ClassVar[bool]
266
320
 
267
321
  @classmethod
268
322
  def __default__(cls) -> BaseType:
269
323
  return type.__call__(
270
- cls, [cls.type.__default__()] * (cls.num_entries if isinstance(cls.num_entries, int) else 0)
324
+ cls, [cls.__type__.__default__()] * (cls.__num_entries__ if isinstance(cls.__num_entries__, int) else 0)
271
325
  )
272
326
 
273
327
  @classmethod
274
328
  def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> list[BaseType]:
275
- if cls.null_terminated:
276
- return cls.type._read_0(stream, context=context, endian=endian)
329
+ if cls.__null_terminated__:
330
+ return cls.__type__._read_0(stream, context=context, endian=endian)
277
331
 
278
- if isinstance(cls.num_entries, int):
279
- num = max(0, cls.num_entries)
280
- elif cls.num_entries is None:
332
+ if isinstance(cls.__num_entries__, int):
333
+ num = max(0, cls.__num_entries__)
334
+ elif cls.__num_entries__ is None:
281
335
  num = EOF
282
- elif isinstance(cls.num_entries, Expression):
336
+ elif isinstance(cls.__num_entries__, Expression):
283
337
  try:
284
- num = max(0, cls.num_entries.evaluate(cls.cs, context))
338
+ num = max(0, cls.__num_entries__.evaluate(cls.__cs__, context))
285
339
  except Exception:
286
- if cls.num_entries.expression != "EOF":
340
+ if cls.__num_entries__.expression != "EOF":
287
341
  raise
288
342
  num = EOF
289
343
 
290
- return cls.type._read_array(stream, num, context=context, endian=endian)
344
+ return cls.__type__._read_array(stream, num, context=context, endian=endian)
291
345
 
292
346
  @classmethod
293
347
  def _write(cls, stream: BinaryIO, data: list[Any], *, endian: Endianness) -> int:
294
- if cls.null_terminated:
295
- return cls.type._write_0(stream, data, endian=endian)
348
+ if cls.__null_terminated__:
349
+ return cls.__type__._write_0(stream, data, endian=endian)
296
350
 
297
- if not cls.dynamic and (remaining := cls.num_entries - (actual_size := len(data))):
351
+ if not cls.__dynamic__ and (remaining := cls.__num_entries__ - (actual_size := len(data))):
298
352
  if remaining < 0:
299
- raise ArraySizeError(f"Expected static array size {cls.num_entries}, got {actual_size} instead.")
300
- data = data + [cls.type.__default__()] * remaining
301
-
302
- return cls.type._write_array(stream, data, endian=endian)
353
+ raise ArraySizeError(f"Expected static array size {cls.__num_entries__}, got {actual_size} instead.")
354
+ data = data + [cls.__type__.__default__()] * remaining
355
+
356
+ return cls.__type__._write_array(stream, data, endian=endian)
357
+
358
+ @classproperty
359
+ def type(cls) -> type[BaseType]:
360
+ """Access the array element type."""
361
+ warnings.warn(
362
+ "The 'type' property is deprecated, use '__type__' instead.",
363
+ DeprecationWarning,
364
+ stacklevel=2,
365
+ )
366
+ return cls.__type__
367
+
368
+ @classproperty
369
+ def num_entries(cls) -> int | Expression | None:
370
+ """Access the number of entries for this array type."""
371
+ warnings.warn(
372
+ "The 'num_entries' property is deprecated, use '__num_entries__' instead.",
373
+ DeprecationWarning,
374
+ stacklevel=2,
375
+ )
376
+ return cls.__num_entries__
377
+
378
+ @classproperty
379
+ def null_terminated(cls) -> bool:
380
+ """Access whether this array type is null-terminated."""
381
+ warnings.warn(
382
+ "The 'null_terminated' property is deprecated, use '__null_terminated__' instead.",
383
+ DeprecationWarning,
384
+ stacklevel=2,
385
+ )
386
+ return cls.__null_terminated__
303
387
 
304
388
 
305
389
  class Array(list[T], BaseArray):
@@ -328,27 +412,5 @@ def _is_eof(stream: BinaryIO) -> bool:
328
412
  return False
329
413
 
330
414
 
331
- ENDIANNESS_MAP: dict[AllowedEndianness, Endianness] = {
332
- "<": "<",
333
- ">": ">",
334
- "!": "!",
335
- "@": "@",
336
- "=": "=",
337
- "network": "!",
338
- "little": "<",
339
- "big": ">",
340
- }
341
-
342
-
343
- def normalize_endianness(endian: AllowedEndianness) -> Endianness:
344
- """Normalize an endianness string to one of the standard format characters."""
345
- try:
346
- return ENDIANNESS_MAP[endian]
347
- except KeyError:
348
- raise ValueError(
349
- f"Invalid endianness: {endian!r}, expected one of {', '.join(ENDIANNESS_MAP.keys())}"
350
- ) from None
351
-
352
-
353
415
  # As mentioned in the BaseType class, we correctly set the type here
354
- MetaType.ArrayType = Array
416
+ MetaType.__ArrayType__ = Array
@@ -16,7 +16,7 @@ class CharArray(bytes, BaseArray):
16
16
 
17
17
  @classmethod
18
18
  def __default__(cls) -> Self:
19
- return type.__call__(cls, b"\x00" * (0 if cls.dynamic or cls.null_terminated else cls.num_entries))
19
+ return type.__call__(cls, b"\x00" * (0 if cls.__dynamic__ or cls.__null_terminated__ else cls.__num_entries__))
20
20
 
21
21
  @classmethod
22
22
  def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
@@ -29,12 +29,12 @@ class CharArray(bytes, BaseArray):
29
29
  elif isinstance(data, str):
30
30
  data = data.encode("latin-1")
31
31
 
32
- if cls.null_terminated:
32
+ if cls.__null_terminated__:
33
33
  data += b"\x00"
34
34
 
35
- if not cls.dynamic and (remaining := cls.num_entries - (actual_size := len(data))):
35
+ if not cls.__dynamic__ and (remaining := cls.__num_entries__ - (actual_size := len(data))):
36
36
  if remaining < 0:
37
- raise ArraySizeError(f"Expected static array size {cls.num_entries}, got {actual_size} instead.")
37
+ raise ArraySizeError(f"Expected static array size {cls.__num_entries__}, got {actual_size} instead.")
38
38
  data += b"\x00" * remaining
39
39
 
40
40
  return stream.write(data)
@@ -43,7 +43,7 @@ class CharArray(bytes, BaseArray):
43
43
  class Char(bytes, BaseType):
44
44
  """Character type for reading and writing bytes."""
45
45
 
46
- ArrayType = CharArray
46
+ __ArrayType__ = CharArray
47
47
 
48
48
  @classmethod
49
49
  def __default__(cls) -> Self:
@@ -21,7 +21,7 @@ _S = TypeVar("_S")
21
21
 
22
22
 
23
23
  class EnumMetaType(EnumMeta, MetaType):
24
- type: type[BaseType]
24
+ __type__: type[BaseType]
25
25
 
26
26
  @overload
27
27
  def __call__(cls, value: cstruct, name: str, type_: type[BaseType], *args, **kwargs) -> type[Enum]: ...
@@ -39,11 +39,11 @@ class EnumMetaType(EnumMeta, MetaType):
39
39
  ) -> Enum | type[Enum]:
40
40
  if name is None:
41
41
  if value is None:
42
- value = cls.type.__default__()
42
+ value = cls.__type__.__default__()
43
43
 
44
44
  if not isinstance(value, int):
45
45
  # value is a parsable value
46
- value = cls.type(value)
46
+ value = cls.__type__(value)
47
47
 
48
48
  return super().__call__(value)
49
49
 
@@ -54,11 +54,11 @@ class EnumMetaType(EnumMeta, MetaType):
54
54
  raise TypeError("Enum can only be created from int type")
55
55
 
56
56
  enum_cls = super().__call__(name, *args, **kwargs)
57
- enum_cls.cs = cs
58
- enum_cls.type = type_
59
- enum_cls.size = type_.size
60
- enum_cls.dynamic = type_.dynamic
61
- enum_cls.alignment = type_.alignment
57
+ enum_cls.__cs__ = cs
58
+ enum_cls.__type__ = type_
59
+ enum_cls.__size__ = type_.__size__
60
+ enum_cls.__dynamic__ = type_.__dynamic__
61
+ enum_cls.__alignment__ = type_.__alignment__
62
62
 
63
63
  _fix_alias_members(enum_cls)
64
64
 
@@ -85,26 +85,26 @@ class EnumMetaType(EnumMeta, MetaType):
85
85
  return value in cls._value2member_map_
86
86
 
87
87
  def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
88
- return cls(cls.type._read(stream, context=context, endian=endian))
88
+ return cls(cls.__type__._read(stream, context=context, endian=endian))
89
89
 
90
90
  def _read_array(
91
91
  cls, stream: BinaryIO, count: int, *, context: dict[str, Any] | None = None, endian: Endianness
92
92
  ) -> list[Self]:
93
- return list(map(cls, cls.type._read_array(stream, count, context=context, endian=endian)))
93
+ return list(map(cls, cls.__type__._read_array(stream, count, context=context, endian=endian)))
94
94
 
95
95
  def _read_0(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> list[Self]:
96
- return list(map(cls, cls.type._read_0(stream, context=context, endian=endian)))
96
+ return list(map(cls, cls.__type__._read_0(stream, context=context, endian=endian)))
97
97
 
98
98
  def _write(cls, stream: BinaryIO, data: Enum, *, endian: Endianness) -> int:
99
- return cls.type._write(stream, data.value, endian=endian)
99
+ return cls.__type__._write(stream, data.value, endian=endian)
100
100
 
101
101
  def _write_array(cls, stream: BinaryIO, array: list[BaseType | int], *, endian: Endianness) -> int:
102
102
  data = [entry.value if isinstance(entry, _Enum) else entry for entry in array]
103
- return cls.type._write_array(stream, data, endian=endian)
103
+ return cls.__type__._write_array(stream, data, endian=endian)
104
104
 
105
105
  def _write_0(cls, stream: BinaryIO, array: list[BaseType | int], *, endian: Endianness) -> int:
106
106
  data = [entry.value if isinstance(entry, _Enum) else entry for entry in array]
107
- return cls._write_array(stream, [*data, cls.type.__default__()], endian=endian)
107
+ return cls._write_array(stream, [*data, cls.__type__.__default__()], endian=endian)
108
108
 
109
109
  def cdef(cls) -> str:
110
110
  """Render this enum (or flag) back to its C-style definition.
@@ -113,7 +113,7 @@ class EnumMetaType(EnumMeta, MetaType):
113
113
  The C-style definition as a string.
114
114
  """
115
115
  keyword = "flag" if issubclass(cls, IntFlag) else "enum"
116
- underlying = cls.type.__name__
116
+ underlying = cls.__type__.__name__
117
117
  title = f"{keyword} {cls.__name__} : {underlying}" if cls.__name__ else f"{keyword} : {underlying}"
118
118
 
119
119
  lines = [f"{title} {{"]
@@ -18,10 +18,10 @@ class Int(int, BaseType):
18
18
 
19
19
  @classmethod
20
20
  def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
21
- data = stream.read(cls.size)
21
+ data = stream.read(cls.__size__)
22
22
 
23
- if len(data) != cls.size:
24
- raise EOFError(f"Read {len(data)} bytes, but expected {cls.size}")
23
+ if len(data) != cls.__size__:
24
+ raise EOFError(f"Read {len(data)} bytes, but expected {cls.__size__}")
25
25
 
26
26
  return cls.from_bytes(data, ENDIANNESS_TO_BYTEORDER_MAP[endian], signed=cls.signed)
27
27
 
@@ -39,4 +39,4 @@ class Int(int, BaseType):
39
39
 
40
40
  @classmethod
41
41
  def _write(cls, stream: BinaryIO, data: int, *, endian: Endianness) -> int:
42
- return stream.write(data.to_bytes(cls.size, ENDIANNESS_TO_BYTEORDER_MAP[endian], signed=cls.signed))
42
+ return stream.write(data.to_bytes(cls.__size__, ENDIANNESS_TO_BYTEORDER_MAP[endian], signed=cls.signed))
@@ -1,10 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
- from functools import lru_cache
4
- from struct import Struct
3
+ import warnings
5
4
  from typing import TYPE_CHECKING, Any, BinaryIO, Generic, TypeVar
6
5
 
7
6
  from dissect.cstruct.types.base import EOF, BaseType
7
+ from dissect.cstruct.util import _struct, classproperty
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from typing_extensions import Self
@@ -12,27 +12,22 @@ if TYPE_CHECKING:
12
12
  from dissect.cstruct.cstruct import Endianness
13
13
 
14
14
 
15
- @lru_cache(1024)
16
- def _struct(endian: str, packchar: str) -> Struct:
17
- return Struct(f"{endian}{packchar}")
18
-
19
-
20
15
  T = TypeVar("T", int, float)
21
16
 
22
17
 
23
18
  class Packed(BaseType, Generic[T]):
24
19
  """Packed type for Python struct (un)packing."""
25
20
 
26
- packchar: str
21
+ __fmt__: str
27
22
 
28
23
  @classmethod
29
24
  def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
30
- data = stream.read(cls.size)
25
+ data = stream.read(cls.__size__)
31
26
 
32
- if len(data) != cls.size:
33
- raise EOFError(f"Read {len(data)} bytes, but expected {cls.size}")
27
+ if len(data) != cls.__size__:
28
+ raise EOFError(f"Read {len(data)} bytes, but expected {cls.__size__}")
34
29
 
35
- return cls.__new__(cls, _struct(endian, cls.packchar).unpack(data)[0])
30
+ return cls.__new__(cls, _struct(endian, cls.__fmt__).unpack(data)[0])
36
31
 
37
32
  @classmethod
38
33
  def _read_array(
@@ -41,12 +36,12 @@ class Packed(BaseType, Generic[T]):
41
36
  if count == EOF:
42
37
  data = stream.read()
43
38
  length = len(data)
44
- count = length // cls.size
39
+ count = length // cls.__size__
45
40
  else:
46
- length = cls.size * count
41
+ length = cls.__size__ * count
47
42
  data = stream.read(length)
48
43
 
49
- fmt = _struct(endian, f"{count}{cls.packchar}")
44
+ fmt = _struct(endian, f"{count}{cls.__fmt__}")
50
45
 
51
46
  if len(data) != length:
52
47
  raise EOFError(f"Read {len(data)} bytes, but expected {length}")
@@ -57,12 +52,12 @@ class Packed(BaseType, Generic[T]):
57
52
  def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None, *, endian: Endianness) -> Self:
58
53
  result = []
59
54
 
60
- fmt = _struct(endian, cls.packchar)
55
+ fmt = _struct(endian, cls.__fmt__)
61
56
  while True:
62
- data = stream.read(cls.size)
57
+ data = stream.read(cls.__size__)
63
58
 
64
- if len(data) != cls.size:
65
- raise EOFError(f"Read {len(data)} bytes, but expected {cls.size}")
59
+ if len(data) != cls.__size__:
60
+ raise EOFError(f"Read {len(data)} bytes, but expected {cls.__size__}")
66
61
 
67
62
  if (value := fmt.unpack(data)[0]) == 0:
68
63
  break
@@ -73,8 +68,18 @@ class Packed(BaseType, Generic[T]):
73
68
 
74
69
  @classmethod
75
70
  def _write(cls, stream: BinaryIO, data: Packed[T], *, endian: Endianness) -> int:
76
- return stream.write(_struct(endian, cls.packchar).pack(data))
71
+ return stream.write(_struct(endian, cls.__fmt__).pack(data))
77
72
 
78
73
  @classmethod
79
74
  def _write_array(cls, stream: BinaryIO, data: list[Packed[T]], *, endian: Endianness) -> int:
80
- return stream.write(_struct(endian, f"{len(data)}{cls.packchar}").pack(*data))
75
+ return stream.write(_struct(endian, f"{len(data)}{cls.__fmt__}").pack(*data))
76
+
77
+ @classproperty
78
+ def packchar(cls) -> str:
79
+ """Access the struct packing character of packed integer/float types."""
80
+ warnings.warn(
81
+ "The 'packchar' property is deprecated, use '__fmt__' instead.",
82
+ DeprecationWarning,
83
+ stacklevel=2,
84
+ )
85
+ return cls.__fmt__
@@ -21,23 +21,23 @@ T = TypeVar("T", bound=BaseType)
21
21
  class Pointer(int, BaseType, Generic[T]):
22
22
  """Pointer to some other type."""
23
23
 
24
- type: type[T]
25
- _stream: BinaryIO | None
26
- _context: dict[str, Any] | None
27
- _endian: Endianness
24
+ __type__: type[T]
25
+ __stream: BinaryIO | None
26
+ __context: dict[str, Any] | None
27
+ __endian: Endianness
28
28
 
29
29
  def __new__(
30
30
  cls, value: int, stream: BinaryIO | None, *, context: dict[str, Any] | None = None, endian: Endianness
31
31
  ) -> Self:
32
32
  obj = super().__new__(cls, value)
33
- obj._stream = stream
34
- obj._context = context
35
- obj._endian = endian
33
+ obj.__stream = stream
34
+ obj.__context = context
35
+ obj.__endian = endian
36
36
  obj.dereference = cache(obj.dereference)
37
37
  return obj
38
38
 
39
39
  def __repr__(self) -> str:
40
- return f"<{self.type.__name__}* @ {self:#x}>"
40
+ return f"<{self.__type__.__name__}* @ {self:#x}>"
41
41
 
42
42
  def __str__(self) -> str:
43
43
  return str(self.dereference())
@@ -51,9 +51,9 @@ class Pointer(int, BaseType, Generic[T]):
51
51
  return type.__call__(
52
52
  self.__class__,
53
53
  op(self, other),
54
- self._stream,
55
- context=self._context,
56
- endian=self._endian,
54
+ self.__stream,
55
+ context=self.__context,
56
+ endian=self.__endian,
57
57
  )
58
58
 
59
59
  return method
@@ -74,17 +74,17 @@ class Pointer(int, BaseType, Generic[T]):
74
74
  def __default__(cls) -> Self:
75
75
  return cls.__new__(
76
76
  cls,
77
- cls.cs.pointer.__default__(),
77
+ cls.__cs__.pointer.__default__(),
78
78
  None,
79
79
  context=None,
80
- endian=cls.cs.endian,
80
+ endian=cls.__cs__.endian,
81
81
  )
82
82
 
83
83
  @classmethod
84
84
  def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
85
85
  return cls.__new__(
86
86
  cls,
87
- cls.cs.pointer._read(stream, context=context, endian=endian),
87
+ cls.__cs__.pointer._read(stream, context=context, endian=endian),
88
88
  stream,
89
89
  context=context,
90
90
  endian=endian,
@@ -92,7 +92,7 @@ class Pointer(int, BaseType, Generic[T]):
92
92
 
93
93
  @classmethod
94
94
  def _write(cls, stream: BinaryIO, data: int, *, endian: Endianness) -> int:
95
- return cls.cs.pointer._write(stream, data, endian=endian)
95
+ return cls.__cs__.pointer._write(stream, data, endian=endian)
96
96
 
97
97
  def dereference(self, *, endian: AllowedEndianness | None = None) -> T | None:
98
98
  """Dereference the pointer and read the value it points to.
@@ -101,22 +101,22 @@ class Pointer(int, BaseType, Generic[T]):
101
101
  endian: Optional endianness to use when reading the value.
102
102
  If not provided, the endianness used when reading the pointer itself will be used.
103
103
  """
104
- if self == 0 or self._stream is None:
104
+ if self == 0 or self.__stream is None:
105
105
  raise NullPointerDereference
106
106
 
107
- endian = normalize_endianness(endian) if endian is not None else self._endian
108
- if issubclass(self.type, Void):
107
+ endian = normalize_endianness(endian) if endian is not None else self.__endian
108
+ if issubclass(self.__type__, Void):
109
109
  return None
110
110
 
111
- position = self._stream.tell()
112
- self._stream.seek(self)
111
+ position = self.__stream.tell()
112
+ self.__stream.seek(self)
113
113
 
114
- if issubclass(self.type, Char):
114
+ if issubclass(self.__type__, Char):
115
115
  # This makes the assumption that a char pointer is a null-terminated string
116
- value = self.type._read_0(self._stream, context=self._context, endian=endian)
116
+ value = self.__type__._read_0(self.__stream, context=self.__context, endian=endian)
117
117
  else:
118
- value = self.type._read(self._stream, context=self._context, endian=endian)
118
+ value = self.__type__._read(self.__stream, context=self.__context, endian=endian)
119
119
 
120
120
  # Restore the stream position after reading the value
121
- self._stream.seek(position)
121
+ self.__stream.seek(position)
122
122
  return value