Models and fields

class bytespec.ProtoModel(**kwargs: Any)[source]

The base model class for binary serialization.

Declare fields using annotations and create an instance with keyword arguments. encode() writes a message; decode() reconstructs an instance of the same class. Use field() to configure individual fields.

Parameters:

**kwargs – Field values. Omitted fields receive a default, the result of a factory, or None for optional fields; all other fields are required.

Note

Annotations define the binary format, but explicitly supplied values are not fully type-checked by the constructor. Fields are mutable. The schema is validated when a subclass is declared. If a subclass declares its own fields, the parent’s serializable fields are not merged with them.

The base class for declarative models. The constructor accepts only keyword arguments for declared fields. First example: Your first model. Working with files and buffers: Files and multiple messages.

encode(include_constructor: bool = True) bytes[source]

Write the current model state as a single binary message.

Parameters:

include_constructor – Write Constructor elements from __header__. False omits them entirely, retaining the other elements.

Returns:

The __header__ elements in the specified order, followed by the fields. Optional fields whose value is None are not written.

Raises:

EncodeError – A required value is missing, or a field value cannot be written in the selected representation.

Note

Implementation errors in a custom codec are not wrapped automatically. __validate__ is not called again.

Returns the model header and payload as bytes.

classmethod decode(buffer: bytes) Self[source]

Reconstruct a model from a buffer containing exactly one message.

Parameters:

buffer – A complete binary message matching the model class.

Returns:

A new instance of the class with the field values read from the buffer.

Raises:

DecodeError – Incomplete/invalid data, an incorrect constructor, or extra bytes after the end of the message.

Note

For multiple messages in a single buffer, use decode_from. Unknown trailing data within the declared payload is skipped.

Reads exactly one message; bytes after its end are forbidden.

classmethod decode_from(buffer: bytes, offset: int, expect_constructor: bool = True) tuple[Self, int][source]

Read a single message from the specified position in a buffer.

Parameters:
  • buffer – A buffer containing the complete message.

  • offset – The nonnegative absolute position where the message starts.

  • expect_constructor – Read and validate Constructor elements. False omits them entirely; the corresponding bytes must not be present in the input.

Returns:

A pair containing the new instance and the absolute position after the message. The position can be passed to the next call to read the following message.

Raises:
  • DecodeError – Incomplete or invalid data, an incorrect constructor, or a field extending beyond the declared payload bounds.

  • ValueError – Negative offset.

Note

Trailing data within the declared payload and unknown flag bits are skipped without being preserved. Bytes after the message are left to the caller. Network fragments are not accumulated between calls. Without PayloadLength, the end is determined by the known fields, with no separate body boundary. Creating the instance runs its own __validate__; validator exceptions propagate without wrapping.

Reads a message from a nonnegative offset. Returns the instance and the absolute end position of the message in the supplied buffer.

classmethod configure_codecs() dict[type, CodecFactory]

Define additional codec selection rules for field types.

Override this classmethod in the model. Rules apply at class declaration, extending inherited rules and replacing matching keys.

Returns:

A mapping from Python types to codec factories. A factory receives the annotation and FieldInfo and returns a ready-to-use codec instance. There are no additional rules by default.

Returns a mapping from types to codec factories. Called when creating a subclass to extend or replace inherited rules. Example: Codecs: custom formats and types.

__validate__() None[source]

Validate relationships between field values that have already been assigned.

Override this method in a concrete model and raise an exception if an invariant is violated. The return value is ignored. The method is called automatically at the end of __init__, including during decode, only if it is defined on the concrete class itself. To run parent validation, call super().__validate__() from the child method.

Attribute assignments and encode do not call the method again. User exceptions propagate without wrapping.

Called after fields are assigned only when the method is defined on the concrete class itself. Mutation and encode do not call it again. Examples and error rules: Validating model values.

__constructor__: int = 1

The number for the Constructor element; does not select a class automatically.

__byte_order__: bytespec.ByteOrder = ByteOrder.BIG

The byte order of fixed-width numbers, prefixes, and the header.

__header__

A tuple of framing elements. Defaults to (Constructor(2), Flags(8), PayloadLength(4)). An empty tuple removes framing. Elements: Header elements; guide: Headers and framing.

Settings are specified at class declaration. The internal compiled schema should not be edited manually.

bytespec.field(index: int | None = None, *, flag: int | None = None, default: Any = MISSING, default_factory: Callable[[], Any] | _MissingType = MISSING, prefix_length: Literal[1, 2, 4, 8] | Annotated[int, VarIntSpec(signed=False)] | None = None, encoding: str = 'utf-8', codec: ICodec[Any] | None = None) Any[source]

Configure a serializable model field.

Parameters:
  • index – The field position, starting at zero. If omitted, the smallest available index is selected in field declaration order.

  • flag – The presence bit number (0–63). Required for T | None; must be unique and fit within the model’s flags size.

  • default – The value used when the constructor argument is omitted. Explicit None differs from having no default and is only valid for optional fields.

  • default_factory – A function with no arguments that creates a value when the field is omitted. Cannot be specified together with default.

  • prefix_length – The length prefix size: 1, 2, 4, or 8 bytes, or VarUInt. None keeps the codec’s default setting. For a list, configures the list itself, not its items.

  • encoding – The encoding of text values. Defaults to UTF-8.

  • codec – A ready-to-use codec instance that replaces automatic selection. Other field settings do not reconfigure this instance.

Returns:

A field description for use in the body of a model class.

Note

Setting compatibility is checked at model declaration. Unsupported or conflicting settings raise SchemaError.

Describes a field; without index, the next available index is selected. flag is the presence bit number for an optional field. The default and factory apply when the argument is omitted and are mutually exclusive. prefix_length and encoding configure the automatically selected codec; codec explicitly supplies a ready-to-use instance. See Fields and default values and Optional fields.

class bytespec.ByteOrder(*values)[source]

The byte order of the model’s fixed-width numbers and prefixes.

BIG means big-endian (>), and LITTLE means little-endian (<). This setting does not affect varints, bytes contents, or the UUID representation.

The byte order of fixed-width numbers and prefixes.

BIG = '>'
LITTLE = '<'

Field declaration rules

The schema is built when a subclass is declared. An annotated attribute becomes a field if it has no assigned value or uses field(). Assigning an ordinary value causes the attribute to be skipped during schema construction. Unannotated attributes are not serialized. A bare ClassVar annotation is not automatically excluded; use ordinary assignment for class settings.

Indices are unique nonnegative integers from 0 to N - 1, where N is the field count. If an index is omitted, the smallest index still available is selected while walking the declaration. Explicit indices declared later are not reserved in advance, so mixing the two approaches can produce a duplicate. Declaration order is usually sufficient; when setting indices explicitly, it is simpler to specify all of them. Field indices and names are not written to the bytes.

Defaults and instance creation

An omitted value is resolved in this order: default, a call to default_factory, then None for optional fields. Otherwise, a TypeError is raised for the missing required field. Unknown keyword arguments are also rejected. default and default_factory are mutually exclusive; declaring both raises SchemaError.

The default is checked at class creation: None is only allowed for optional fields; other values must match the type. The factory is called without arguments at instance creation; an incompatible return type raises TypeError. Lists are checked as containers without checking every item. This check does not validate numeric ranges or value sizes.

Explicit arguments are assigned as supplied, without full runtime validation or type conversion. The object is mutable; encode() reads its current state. Models of the same concrete class are compared by their serializable fields; repr shows the class name and those fields. Objects of different classes are not equal even if their fields match. There is no automatic conversion to a dictionary. After all fields have been assigned, the class’s own __validate__ is called; the full lifecycle is described in Validating model values.

Annotations and inheritance

Annotations are resolved immediately; the names of the types used must be available. There is no separate step for resolving references to classes that have not yet been declared. It is simpler to define models at module scope, with nested model classes defined before the classes that contain them.

A subclass with its own fields builds its schema from its own annotations: the parent’s serializable fields are not merged with them. A subclass without new fields retains inherited fields but receives its own schema with the current framing settings. The codecs, defaults, and factories for those fields are retained; they are not resolved again. A standalone class without fields receives an empty schema and also supports encode/decode. Custom header and multiple inheritance rules: Model inheritance. For composition, use Lists and nested models.