Field Type Reference
Field Type Primitives
bydantic comes with a variety of primitive field types that can be used to define bitfields. From these primitives, more complex data structures can be constructed via field type combinators.
uint_field
An unsigned integer field type.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
n
|
int
|
The number of bits used to represent the unsigned integer. |
required |
default
|
int | ellipsis
|
An optional default value to use when constructing the field in a new object. |
...
|
Returns:
Type | Description |
---|---|
Field[int]
|
Field[int]: A field that represents an unsigned integer. |
Example
import bydantic as bd
class Foo(bd.Bitfield):
a: int = bd.uint_field(4)
b: int = bd.uint_field(4, default=0)
foo = Foo(a=1, b=2)
print(foo) # Foo(a=1, b=2)
print(foo.to_bytes()) # b'\x12'
foo2 = Foo.from_bytes_exact(b'\x34')
print(foo2) # Foo(a=3, b=4)
foo3 = Foo(a = 1) # b is set to 0 by default
print(foo3) # Foo(a=1, b=0)
print(foo3.to_bytes()) # b'\x10'
int_field
A signed integer field type.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
n
|
int
|
The number of bits used to represent the signed integer. |
required |
default
|
int | ellipsis
|
An optional default value to use when constructing the field in a new object. |
...
|
Returns:
Type | Description |
---|---|
Field[int]
|
Field[int]: A field that represents a signed integer. |
Example
import bydantic as bd
class Foo(bd.Bitfield):
a: int = bd.int_field(4)
b: int = bd.int_field(4, default=-1)
foo = Foo(a=1, b=-2)
print(foo) # Foo(a=1, b=-2)
print(foo.to_bytes()) # b'\x16'
foo2 = Foo.from_bytes_exact(b'\x34')
print(foo2) # Foo(a=3, b=4)
foo3 = Foo(a = 1) # b is set to -1 by default
print(foo3) # Foo(a=1, b=-1)
print(foo3.to_bytes()) # b'\x13'
bool_field
A boolean field type. (Bit flag)
Parameters:
Name | Type | Description | Default |
---|---|---|---|
default
|
bool | ellipsis
|
An optional default value to use when constructing the field in a new object. |
...
|
Returns:
Type | Description |
---|---|
Field[bool]
|
Field[bool]: A field that represents a boolean. |
Example
import bydantic as bd
class Foo(bd.Bitfield):
a: bool = bd.bool_field()
b: bool = bd.bool_field(default=True)
_pad: int = bd.uint_field(6, default=0) # Pad to a full byte
foo = Foo(a=True, b=False)
print(foo) # Foo(a=True, b=False)
print(foo.to_bytes()) # b'\x80'
foo2 = Foo.from_bytes_exact(b'\x40')
print(foo2) # Foo(a=False, b=True)
foo3 = Foo(a = True) # b is set to True by default
print(foo3) # Foo(a=True, b=True)
print(foo3.to_bytes()) # b'À'
bytes_field
A bytes field type.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
n_bytes
|
int
|
The number of bytes in the field. |
required |
default
|
bytes | ellipsis
|
An optional default value to use when constructing the field in a new object. |
...
|
Returns:
Type | Description |
---|---|
Field[bytes]
|
Field[bytes]: A field that represents a sequence of bytes. |
Example
import bydantic as bd
class Foo(bd.Bitfield):
a: bytes = bd.bytes_field(2)
b: bytes = bd.bytes_field(2, default=b"yz")
foo = Foo(a=b"xy", b=b"uv")
print(foo) # Foo(a=b'xy', b=b'uv')
foo2 = Foo.from_bytes_exact(b'xyuv')
print(foo2) # Foo(a=b'xy', b=b'uv')
foo3 = Foo(a = b"xy") # b is set to b"yz" by default
print(foo3) # Foo(a=b'xy', b=b'yz')
print(foo3.to_bytes()) # b'xyyz'
str_field
A string field type.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
n_bytes
|
int
|
The number of bytes in the field. |
required |
encoding
|
str
|
The encoding to use when converting the bytes to a string. |
'utf-8'
|
default
|
str | ellipsis
|
An optional default value to use when constructing the field in a new object. |
...
|
Returns:
Type | Description |
---|---|
Field[str]
|
Field[str]: A field that represents a string. |
Example
import bydantic as bd
class Foo(bd.Bitfield):
a: str = bd.str_field(2)
b: str = bd.str_field(2, default="yz")
foo = Foo(a="xy", b="uv")
print(foo) # Foo(a='xy', b='uv')
foo2 = Foo.from_bytes_exact(b'xyuv')
print(foo2) # Foo(a='xy', b='uv')
foo3 = Foo(a = "xy") # b is set to "yz" by default
print(foo3) # Foo(a='xy', b='yz')
print(foo3.to_bytes()) # b'xyyz'
uint_enum_field
uint_enum_field(
enum: Type[IntEnumT],
n: int,
*,
default: IntEnumT | ellipsis = ...
) -> Field[IntEnumT]
An unsigned integer enum field type.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
enum
|
Type[IntEnumT]
|
The enum class to use for the field. (Must be a subclass of IntEnum or IntFlag) |
required |
n
|
int
|
The number of bits used to represent the enum. |
required |
default
|
IntEnumT | ellipsis
|
An optional default value to use when constructing the field in a new object
(Must match the enum type passed in the |
...
|
Returns:
Type | Description |
---|---|
Field[IntEnumT]
|
Field[IntEnumT]: A field that represents an unsigned integer enum. |
Example
import bydantic as bd
from enum import IntEnum
class Color(IntEnum):
RED = 1
GREEN = 2
BLUE = 3
PURPLE = 4
class Foo(bd.Bitfield):
a: Color = bd.uint_enum_field(Color, 4)
b: Color = bd.uint_enum_field(Color, 4, default=Color.GREEN)
foo = Foo(a=Color.RED, b=Color.BLUE)
print(foo) # Foo(a=<Color.RED: 1>, b=<Color.BLUE: 3>)
print(foo.to_bytes()) # b'\x13'
foo2 = Foo.from_bytes_exact(b'\x24')
print(foo2) # Foo(a=<Color.GREEN: 2>, b=<Color.PURPLE: 4>)
foo3 = Foo(a = Color.RED) # b is set to Color.GREEN by default
print(foo3) # Foo(a=<Color.RED: 1>, b=<Color.GREEN: 2>)
print(foo3.to_bytes()) # b'\x12'
int_enum_field
int_enum_field(
enum: Type[IntEnumT],
n: int,
*,
default: IntEnumT | ellipsis = ...
) -> Field[IntEnumT]
An signed integer enum field type.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
n
|
int
|
The number of bits used to represent the enum. |
required |
enum
|
Type[IntEnumT]
|
The enum class to use for the field. (Must be a subclass of IntEnum or IntFlag) |
required |
default
|
IntEnumT | ellipsis
|
An optional default value to use when constructing the field in a new object
(Must match the enum type passed in the |
...
|
Returns:
Type | Description |
---|---|
Field[IntEnumT]
|
Field[IntEnumT]: A field that represents an unsigned integer enum. |
Example
import bydantic as bd
from enum import IntEnum
class Color(IntEnum):
RED = -2
GREEN = -1
BLUE = 0
PURPLE = 1
class Foo(bd.Bitfield):
a: Color = bd.int_enum_field(Color, 4)
b: Color = bd.int_enum_field(Color, 4, default=Color.GREEN)
foo = Foo(a=Color.RED, b=Color.BLUE)
print(foo) # Foo(a=<Color.RED: -2>, b=<Color.BLUE: 0>)
print(foo.to_bytes()) # b'\xe0'
foo2 = Foo.from_bytes_exact(b'\xfe')
print(foo2) # Foo(a=<Color.GREEN: -1>, b=<Color.RED: -2>)
none_field
A field type that represents no data.
This field type is most useful when paired with dynamic_field
to create
optional values in a Bitfield.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
default
|
None | ellipsis
|
The optional default value to use when constructing the field in a new object. |
...
|
Returns:
Type | Description |
---|---|
Field[None]
|
Field[None]: A field that represents no data. |
Example
bits_field
A field type that represents a sequence of bits. (A tuple of booleans).
Parameters:
Name | Type | Description | Default |
---|---|---|---|
n
|
int
|
The number of bits in the field. |
required |
default
|
Sequence[bool] | ellipsis
|
An optional default value to use when constructing the field in a new object. |
...
|
Returns:
Type | Description |
---|---|
Field[Tuple[bool, ...]]
|
Field[t.Tuple[bool, ...]]: A field that represents a sequence of bits. |
Example
import bydantic as bd
import typing as t
class Foo(bd.Bitfield):
a: t.Tuple[bool, ...] = bd.bits_field(4)
b: t.Tuple[bool, ...] = bd.bits_field(4, default=(True, False, True, False))
foo = Foo(a=(True, False, True, False), b=(False, True, False, True))
print(foo) # Foo(a=(True, False, True, False), b=(False, True, False, True))
print(foo.to_bytes()) # b'\xaa\xaa'
foo2 = Foo.from_bytes_exact(b'\xaa\xaa')
print(foo2) # Foo(a=(True, False, True, False), b=(False, True, False, True))
foo3 = Foo(a=(True, False, True, False)) # b is set to (True, False, True, False) by default
print(foo3) # Foo(a=(True, False, True, False), b=(True, False, True, False))
print(foo3.to_bytes()) # b'\xaa\xaa'
Literal Field Types
These field types are used to define fields with literal types. They are most often useful for fixed-length headers, padding, etc.
lit_uint_field
A literal unsigned integer field type.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
n
|
int
|
The number of bits used to represent the unsigned integer. |
required |
default
|
LiteralIntT
|
The literal default value to use when constructing the field in a new object. (Required to infer the literal type). |
required |
Returns:
Type | Description |
---|---|
Field[T]
|
Field[LiteralIntT]: A field that represents a literal unsigned integer. |
Example
import bydantic as bd
import typing as t
class Foo(bd.Bitfield):
a: t.Literal[1] = bd.lit_uint_field(4, default=1)
b: t.Literal[2] = bd.lit_uint_field(4, default=2)
foo = Foo()
print(foo) # Foo(a=1, b=2)
print(foo.to_bytes()) # b'\x12'
foo2 = Foo.from_bytes_exact(b'\x12')
print(foo2) # Foo(a=1, b=2)
lit_int_field
A literal signed integer field type.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
n
|
int
|
The number of bits used to represent the signed integer. |
required |
default
|
LiteralIntT
|
The literal default value to use when constructing the field in a new object. (Required to infer the literal type). |
required |
Returns:
Type | Description |
---|---|
Field[T]
|
Field[LiteralIntT]: A field that represents a literal signed integer. |
Example
import bydantic as bd
import typing as t
class Foo(bd.Bitfield):
a: t.Literal[-1] = bd.lit_int_field(4, default=-1)
b: t.Literal[2] = bd.lit_int_field(4, default=2)
foo = Foo()
print(foo) # Foo(a=-1, b=2)
print(foo.to_bytes()) # b'\xf2'
foo2 = Foo.from_bytes_exact(b'\xf2')
print(foo2) # Foo(a=-1, b=2)
lit_bytes_field
A literal bytes field type.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
default
|
LiteralBytesT
|
The literal default value to use when constructing the field in a new object. (Required to infer the literal type). |
required |
Returns:
Type | Description |
---|---|
Field[T]
|
Field[LiteralBytesT]: A field that represents a literal bytes. |
Example
import bydantic as bd
import typing as t
class Foo(bd.Bitfield):
a: t.Literal[b'xy'] = bd.lit_bytes_field(default=b'xy')
b: t.Literal[b'uv'] = bd.lit_bytes_field(default=b'uv')
foo = Foo()
print(foo) # Foo(a=b'xy', b=b'uv')
print(foo.to_bytes()) # b'xyuv'
foo2 = Foo.from_bytes_exact(b'xyuv')
print(foo2) # Foo(a=b'xy', b=b'uv')
# Note that the following shortcut may be alternatively used
# in definitions:
class Shortcut(bd.Bitfield):
a: t.Literal[b'xy'] = b'xy'
b: t.Literal[b'uv'] = b'uv'
lit_str_field
A literal string field type.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
n_bytes
|
int
|
The number of bytes used to represent the string. |
None
|
default
|
LiteralStrT
|
The literal default value to use when constructing the field in a new object. (Required to infer the literal type). |
required |
Returns:
Type | Description |
---|---|
Field[T]
|
Field[LiteralStrT]: A field that represents a literal string. |
Example
import bydantic as bd
import typing as t
class Foo(bd.Bitfield):
a: t.Literal["xy"] = bd.lit_str_field(encoding="utf-8", default="xy")
b: t.Literal["uv"] = bd.lit_str_field(default="uv")
foo = Foo()
print(foo) # Foo(a='xy', b='uv')
print(foo.to_bytes()) # b'xyuv'
foo2 = Foo.from_bytes_exact(b'xyuv')
print(foo2) # Foo(a="xy", b="uv")
# Note that the following shortcut may be alternatively used
# in definitions:
class Shortcut(bd.Bitfield):
a: t.Literal["xy"] = "xy"
b: t.Literal["uv"] = "uv"
Field Type Combinators
These field types can be used build new field types from other field types.
mapped_field
A field type for creating transformations of values.
Transformations are done via the ValueMapper
protocol, an object
defined with deserialize
and serialize
methods. The deserialize
method
is used to transform the value when deserializing the field from
the provided field type, and the serialize
method is used to reverse this transformation
when serializing the field back.
Several built-in value mappers are provided, including
Scale
and
IntScale
,
for scaling values by a given float or int factor, respectively.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
field
|
Field[T]
|
The field to transform. |
required |
vm
|
ValueMapper[T, P]
|
The value mapper to use for the transformation. |
required |
default
|
P | ellipsis
|
An optional default value to use when constructing the field in a new object. |
...
|
Returns:
Type | Description |
---|---|
Field[P]
|
Field[P]: A field that represents the transformed value. |
Example
import bydantic as bd
import typing as t
class Foo(bd.Bitfield):
a: int = bd.uint_field(4)
b: float = bd.map_field(
bd.uint_field(4),
bd.Scale(0.5),
default=0.5
)
foo = Foo(a=1, b=1.5)
print(foo) # Foo(a=1, b=1.5)
print(foo.to_bytes()) # b'\x13'
foo2 = Foo.from_bytes_exact(b'\x13')
print(foo2) # Foo(a=1, b=1.5)
foo3 = Foo(a=1) # b is set to 0.5 by default
print(foo3) # Foo(a=1, b=0.5)
print(foo3.to_bytes()) # b'\x11'
list_field
list_field(
item: Type[T] | Field[T],
n_items: int,
*,
default: List[T] | ellipsis = ...
) -> Field[t.List[T]]
A field type that represents a list of items.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
item
|
Type[T] | Field[T]
|
The type of items in the list. In addition to fields, Bitfield classes can also be returned (provided they don't have dynamic fields). |
required |
n_items
|
int
|
The number of items in the list. |
required |
default
|
List[T] | ellipsis
|
An optional default value to use when constructing the field in a new object. |
...
|
Returns:
Type | Description |
---|---|
Field[List[T]]
|
Field[t.List[T]]: A field that represents a list of items. |
Example
import bydantic as bd
import typing as t
class Foo(bd.Bitfield):
a: int = bd.uint_field(4)
b: t.List[int] = bd.list_field(bd.uint_field(4), 3, default=[1, 2, 3])
foo = Foo(a=1, b=[0, 1, 2])
print(foo) # Foo(a=1, b=[0, 1, 2])
print(foo.to_bytes()) # b'\x10\x12'
foo2 = Foo.from_bytes_exact(b'\x10\x12')
print(foo2) # Foo(a=1, b=[0, 1, 2])
foo3 = Foo(a=1) # b is set to [1, 2, 3] by default
print(foo3) # Foo(a=1, b=[1, 2, 3])
print(foo3.to_bytes()) # b'\x11#'
bitfield_field
bitfield_field(
cls: Type[BitfieldT],
n: int | ellipsis = ...,
*,
default: BitfieldT | ellipsis = ...
) -> Field[BitfieldT]
A field type that represents a Bitfield.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
cls
|
Type[BitfieldT]
|
The Bitfield class to use for the field. |
required |
n
|
int | ellipsis
|
The number of bits in the field. Note: this is optional for bitfields without dynamic fields because it can inferred from the class itself. |
...
|
default
|
BitfieldT | ellipsis
|
An optional default value to use when constructing the field in a new object. |
...
|
Returns:
Type | Description |
---|---|
Field[BitfieldT]
|
Field[BitfieldT]: A field that represents a Bitfield. |
Example
import bydantic as bd
class Foo(bd.Bitfield):
a: int = bd.uint_field(4)
b: int = bd.uint_field(4, default=0)
class Bar(bd.Bitfield):
c: Foo = bd.bitfield_field(Foo, 8)
bar = Bar(c=Foo(a=1, b=2))
print(bar) # Bar(c=Foo(a=1, b=2))
print(bar.to_bytes()) # b'\x12\x02'
bar2 = Bar.from_bytes_exact(b'\x12\x02')
print(bar2) # Bar(c=Foo(a=1, b=2))
bar3 = Bar(c=Foo(a=1)) # c is set to Foo(a=1, b=0) by default
print(bar3) # Bar(c=Foo(a=1, b=0))
print(bar3.to_bytes()) # b'\x10\x00'
# For bitfields without dynamic fields, the size can be inferred:
class Baz(bd.Bitfield):
c: Foo = bd.bitfield_field(Foo)
# Or, more concisely:
class Baz(bd.Bitfield):
c: Foo
baz = Baz(c=Foo(a=1, b=2))
print(baz) # Baz(c=Foo(a=1, b=2))
print(baz.to_bytes()) # b'\x12\x02'
dynamic_field
dynamic_field(
fn: (
Callable[[Any], Type[T] | Field[T]]
| Callable[[Any, int], Type[T] | Field[T]]
),
*,
default: T | ellipsis = ...
) -> Field[T]
A field type that can be decided at runtime, based on the values of already-parsed fields, or the number of bits remaining in the stream.
Note that the discriminator function provided can be either a one-argument or two-argument function. If a one-argument function is provided, it will be called with an intermeditate Bitfield object with the fields that have been parsed so far. If a two-argument function is the first argument will be the intermediate Bitfield object and the second argument will be the number of bits remaining in the stream. The function should use this information to return a valid field type to use for the field.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
fn
|
Callable[[Any], Type[T] | Field[T]] | Callable[[Any, int], Type[T] | Field[T]]
|
A one or two-argument function that returns the field type to use for the field. |
required |
default
|
T | ellipsis
|
An optional default value to use when constructing the field in a new object. |
...
|
Returns:
Type | Description |
---|---|
Field[T]
|
Field[T]: A field that represents a dynamic field. |
Example
import bydantic as bd
import typing as t
class Foo(bd.Bitfield):
a: int = bd.uint_field(8)
b: int | bytes = bd.dynamic_field(
lambda x: bd.uint_field(8) if x.a else bd.bytes_field(n_bytes=1)
)
foo = Foo.from_bytes_exact(b'\x01\x02')
print(foo) # Foo(a=1, b=2)
foo2 = Foo.from_bytes_exact(b'\x00A')
print(foo2) # Foo(a=0, b=b'A')
Value Mappers
bydantic.mapped_field
uses the ValueMapper
protocol for transforming values
when serializing and deserializing and deserializing bitfields. This section
describes the ValueMapper
protocol and the built-in value mappers provided by
bydantic
.
ValueMapper
Bases: Protocol[T, P]
A protocol for transforming values during serialization / deserialization
via map_field
.
Scale
A value mapper that scales a value by a given factor, resulting in a float value.
Attributes:
Name | Type | Description |
---|---|---|
by |
float
|
The factor to scale by. |
n_digits |
int | None
|
The number of digits to round to. If None, no rounding is done. Default is None. |
Example
IntScale
A value mapper that scales a value by a given factor, resulting in an integer value.
Attributes:
Name | Type | Description |
---|---|---|
by |
int
|
The factor to scale by. |