"""Integer field types."""
from __future__ import annotations
import struct
from typing import TYPE_CHECKING, BinaryIO, Callable, List
from pystructs.base import FixedField
from pystructs.config import get_config
from pystructs.exceptions import UnexpectedEOF
if TYPE_CHECKING:
from pystructs.struct import Struct
__all__ = (
"IntField",
"Int8",
"UInt8",
"Int16",
"UInt16",
"Int32",
"UInt32",
"Int64",
"UInt64",
)
[docs]
class IntField(FixedField):
"""Base class for integer fields.
Supports configurable byte order (endianness) at field or struct level.
"""
# Subclasses set these
size: int = 0
signed: bool = True
_format_char: str = ""
def __init__(
self,
default: int = 0,
endian: str | None = None,
required: bool = True,
validators: List[Callable] | None = None,
):
"""Initialize an integer field.
Args:
default: Default value (default: 0)
endian: Byte order ('little', 'big', or None for struct default)
required: If True, field must have a value for serialization
validators: List of validator functions
"""
super().__init__(default=default, required=required, validators=validators)
self.endian = endian
def _get_endian(self, instance: Struct | None) -> str:
"""Get the effective endianness for this field.
Priority:
1. Field-level endian
2. Struct-level endian (from Meta)
3. Global default endian
"""
if self.endian:
return self.endian
if instance is not None and hasattr(instance, "_meta") and instance._meta:
return instance._meta.endian
return get_config().default_endian
def _get_format(self, instance: Struct | None) -> str:
"""Get the struct format string for this field."""
endian = self._get_endian(instance)
prefix = "<" if endian == "little" else ">"
return prefix + self._format_char
[docs]
def parse(self, buffer: BinaryIO, instance: Struct) -> int:
"""Parse an integer from the buffer.
Args:
buffer: Binary stream to read from
instance: The struct instance being parsed
Returns:
Parsed integer value
Raises:
UnexpectedEOF: If not enough bytes available
"""
data = buffer.read(self.size)
if len(data) < self.size:
raise UnexpectedEOF(expected=self.size, got=len(data), field=self.name)
fmt = self._get_format(instance)
return struct.unpack(fmt, data)[0]
[docs]
def serialize(self, value: int, instance: Struct) -> bytes:
"""Serialize an integer to bytes.
Args:
value: Integer value to serialize
instance: The struct instance being serialized
Returns:
Serialized bytes
"""
fmt = self._get_format(instance)
return struct.pack(fmt, value)
# Signed integer types
[docs]
class Int8(IntField):
"""8-bit signed integer (-128 to 127)."""
size = 1
signed = True
_format_char = "b"
[docs]
class Int16(IntField):
"""16-bit signed integer (-32768 to 32767)."""
size = 2
signed = True
_format_char = "h"
[docs]
class Int32(IntField):
"""32-bit signed integer."""
size = 4
signed = True
_format_char = "i"
[docs]
class Int64(IntField):
"""64-bit signed integer."""
size = 8
signed = True
_format_char = "q"
# Unsigned integer types
[docs]
class UInt8(IntField):
"""8-bit unsigned integer (0 to 255)."""
size = 1
signed = False
_format_char = "B"
[docs]
class UInt16(IntField):
"""16-bit unsigned integer (0 to 65535)."""
size = 2
signed = False
_format_char = "H"
[docs]
class UInt32(IntField):
"""32-bit unsigned integer."""
size = 4
signed = False
_format_char = "I"
[docs]
class UInt64(IntField):
"""64-bit unsigned integer."""
size = 8
signed = False
_format_char = "Q"