Наследование моделей

Чтобы несколько сообщений использовали одинаковое framing, вынесите настройки в базовый класс, а поля объявите в конкретных моделях:

from bytespec import Constructor, Flags, PayloadLength, ProtoModel, SchemaError
from bytespec.types import UInt8

class Compact(ProtoModel):
    __header__ = (Constructor(1), PayloadLength(1))

class Reading(Compact):
    __constructor__ = 7
    value: UInt8

reading = Reading(value=42)
assert Reading.decode(reading.encode()) == reading
print(reading.encode().hex(" "))
07 01 2a

Наследуются __header__, __constructor__, __byte_order__ и правила configure_codecs(). Framing-настройки можно переопределить и в подклассе без собственных полей: каждый подкласс получает заново собранную схему.

Несколько базовых классов

При отсутствии явного __header__ учитываются custom headers прямых баз ProtoModel. Стандартный header не конкурирует с пользовательским:

class DefaultBase(ProtoModel):
    pass

class Mixed(DefaultBase, Compact):
    value: UInt8

assert Mixed.__header__ is Compact.__header__
assert Mixed.decode(Mixed(value=1).encode()) == Mixed(value=1)

Diamond inheritance одного header тоже допустим:

class Left(Compact):
    pass

class Right(Compact):
    pass

class Diamond(Left, Right):
    value: UInt8

assert Diamond.__header__ is Compact.__header__
assert Diamond.decode(Diamond(value=2).encode()) == Diamond(value=2)

Два разных custom headers требуют явного выбора:

class Other(ProtoModel):
    __header__ = (PayloadLength(2),)

try:
    class Conflict(Compact, Other):
        value: UInt8
except SchemaError as error:
    print(error)

class Resolved(Compact, Other):
    __header__ = Compact.__header__
    value: UInt8

assert Resolved.decode(Resolved(value=3).encode()) == Resolved(value=3)
Conflict: conflicting inherited headers; define __header__ explicitly

Сравнивается идентичность tuple, а не эквивалентность элементов. Два независимо созданных (Constructor(1), PayloadLength(1)) конфликтуют даже при одинаковом wire format. Чтобы переиспользовать один header, наследуйте его или присвойте существующий tuple. Пустой tuple () тоже считается custom header. Специальное объединение применяется только к header; остальные настройки следуют обычному порядку поиска Python.

Поля не объединяются

Подкласс с собственными полями строит схему только из них:

class Parent(ProtoModel):
    __header__ = ()
    first: UInt8

class Child(Parent):
    second: UInt8

child = Child(second=2)
print(child.encode().hex(" "))
02

first в схему Child не попадает. Для структуры «родительские данные плюс новые данные» используйте Списки и вложенные модели или явно объявите весь набор полей дочерней модели.

Изменить framing, сохранив поля

Подкласс без собственных сериализуемых полей сохраняет унаследованные поля, их codecs, defaults и factories. Для него создаётся отдельная схема с его __constructor__, __header__ и __byte_order__. Повторять поля ради смены framing не нужно:

from bytespec import ByteOrder
from bytespec.types import UInt16

class ReadingBase(ProtoModel):
    __constructor__ = 2
    __header__ = (Constructor(1), PayloadLength(1))
    value: UInt16

class LittleReading(ReadingBase):
    __constructor__ = 3
    __header__ = (PayloadLength(1), Constructor(2))
    __byte_order__ = ByteOrder.LITTLE

reading = LittleReading(value=0x1234)
assert LittleReading.decode(reading.encode()) == reading
print(reading.encode().hex(" "))
print(ReadingBase(value=0x1234).encode().hex(" "))
02 03 00 34 12
02 02 12 34

В первой строке длина идёт перед двухбайтовым constructor, а число записано little-endian. Формат родителя не изменился. Если в унаследованных полях есть optional, новый header тоже должен содержать Flags достаточной ширины: иначе объявление подкласса завершается SchemaError.

При multiple inheritance без собственных полей используется первый доступный набор полей по MRO, без объединения схем базовых классов. Правило выбора custom header при этом остаётся описанным выше.

Автоматический вызов __validate__ имеет отдельное правило: метод родителя сам не вызывается для дочернего экземпляра. Как явно переиспользовать проверку — Проверка значений модели.