Source code for pystructs.base

"""Base field classes for pystructs.

This module provides the abstract base classes for all field types:
- BaseField: Abstract base with descriptor protocol
- FixedField: Base for fixed-size fields

All field types must inherit from BaseField and implement the
parse/serialize methods.
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, BinaryIO, List, Optional, Protocol

if TYPE_CHECKING:
    from pystructs.struct import Struct


class FieldValidator(Protocol):
    """Protocol for field-level validators."""

    def __call__(self, value: Any, instance: Struct) -> None:
        """Validate a field value.

        Args:
            value: The field value to validate
            instance: The struct instance

        Raises:
            ValidationError: If validation fails
        """
        ...


__all__ = (
    "BaseField",
    "FixedField",
)


[docs] class BaseField(ABC): """Abstract base class for all fields. All field types must inherit from this class and implement: - get_size(instance): Return the byte size of the field - parse(buffer, instance): Parse bytes from buffer and return value - serialize(value, instance): Convert value to bytes Fields also implement the descriptor protocol for attribute access. """ # Set by metaclass to track declaration order _order: int = 0 def __init__( self, default: Any = None, required: bool = True, validators: Optional[List[FieldValidator]] = None, ): """Initialize a field. Args: default: Default value for this field. Can be a value or callable. If callable, it will be called to get the default value. required: If True, field must have a value for serialization. Missing required fields raise SerializationError. validators: List of validator functions for this field. Each validator should accept (value, instance) and raise ValidationError if validation fails. """ self.name: Optional[str] = None self.default = default self.required = required self.validators: List[FieldValidator] = validators or []
[docs] @abstractmethod def get_size(self, instance: Struct) -> int: """Get the byte size of this field. Args: instance: The struct instance (for Ref resolution) Returns: Size in bytes """ pass
[docs] @abstractmethod def parse(self, buffer: BinaryIO, instance: Struct) -> Any: """Parse bytes from buffer. Args: buffer: Binary stream to read from instance: The struct instance being parsed Returns: Parsed value """ pass
[docs] @abstractmethod def serialize(self, value: Any, instance: Struct) -> bytes: """Serialize value to bytes. Args: value: Value to serialize instance: The struct instance being serialized Returns: Serialized bytes """ pass
[docs] def validate(self, value: Any, instance: Struct) -> None: """Run field-level validators. Args: value: Value to validate instance: The struct instance Raises: ValidationError: If validation fails """ for validator in self.validators: validator(value, instance)
# Descriptor protocol def __set_name__(self, owner: type, name: str) -> None: """Called when field is assigned to a class attribute. Args: owner: The class that owns this field name: The attribute name """ self.name = name def __get__(self, instance: Struct | None, owner: type) -> Any: """Get the field value from an instance. Args: instance: The struct instance (or None for class access) owner: The class that owns this field Returns: The field value, or the field itself for class access """ if instance is None: return self return instance._data.get(self.name) def __set__(self, instance: Struct, value: Any) -> None: """Set the field value on an instance. Args: instance: The struct instance value: The value to set """ instance._data[self.name] = value def __repr__(self) -> str: return f"{self.__class__.__name__}(name={self.name!r})"
[docs] class FixedField(BaseField): """Base class for fixed-size fields. Subclasses should set the `size` class attribute to specify the byte size. """ size: int = 0
[docs] def get_size(self, instance: Struct) -> int: """Get the byte size of this field. Returns: The fixed size in bytes """ return self.size