PyDSDL usage

The entirety of the library API is exposed at the top level as pydsdl.*. There are no usable submodules.

You can find a practical usage example in the Nunavut code generation library that uses PyDSDL as the frontend.

The main function

pydsdl.read_namespace(root_namespace_directory: Union[pathlib.Path, str], lookup_directories: Union[None, pathlib.Path, str, Iterable[Union[pathlib.Path, str]]] = None, print_output_handler: Optional[Callable[[pathlib.Path, int, str], None]] = None, allow_unregulated_fixed_port_id: bool = False, allow_root_namespace_name_collision: bool = True) List[pydsdl._serializable._composite.CompositeType]

This function is the main entry point of the library. It reads all DSDL definitions from the specified root namespace directory and produces the annotated AST.

Parameters
  • root_namespace_directory – The path of the root namespace directory that will be read. For example, dsdl/uavcan to read the uavcan namespace.

  • lookup_directories – List of other namespace directories containing data type definitions that are referred to from the target root namespace. For example, if you are reading a vendor-specific namespace, the list of lookup directories should always include a path to the standard root namespace uavcan, otherwise the types defined in the vendor-specific namespace won’t be able to use data types from the standard namespace.

  • print_output_handler – If provided, this callable will be invoked when a @print directive is encountered or when the frontend needs to emit a diagnostic; the arguments are: path, line number (1-based), text. If not provided, no output will be produced except for the standard Python logging subsystem (but @print expressions will be evaluated anyway, and a failed evaluation will be a fatal error).

  • allow_unregulated_fixed_port_id – Do not reject unregulated fixed port identifiers. As demanded by the specification, the frontend rejects unregulated fixed port ID by default. This is a dangerous feature that must not be used unless you understand the risks. Please read https://opencyphal.org/guide.

  • allow_root_namespace_name_collision – Allow using the source root namespace name in the look up dirs or the same root namespace name multiple times in the lookup dirs. This will enable defining a namespace partially and let other entities define new messages or new sub-namespaces in the same root namespace.

Returns

A list of pydsdl.CompositeType sorted lexicographically by full data type name, then by major version (newest version first), then by minor version (newest version first). The ordering guarantee allows the caller to always find the newest version simply by picking the first matching occurrence.

Raises

pydsdl.FrontendError, MemoryError, SystemError, OSError if directories do not exist or inaccessible, ValueError/TypeError if the arguments are invalid.

Type model

Inheritance diagram of pydsdl._expression._primitive.Primitive, pydsdl._expression._primitive.Boolean, pydsdl._expression._primitive.Rational, pydsdl._expression._primitive.String, pydsdl._expression._container.Container, pydsdl._expression._container.Set, pydsdl._serializable._serializable.SerializableType, pydsdl._serializable._primitive.PrimitiveType, pydsdl._serializable._primitive.BooleanType, pydsdl._serializable._primitive.ArithmeticType, pydsdl._serializable._primitive.IntegerType, pydsdl._serializable._primitive.SignedIntegerType, pydsdl._serializable._primitive.UnsignedIntegerType, pydsdl._serializable._primitive.FloatType, pydsdl._serializable._void.VoidType, pydsdl._serializable._array.ArrayType, pydsdl._serializable._array.FixedLengthArrayType, pydsdl._serializable._array.VariableLengthArrayType, pydsdl._serializable._composite.CompositeType, pydsdl._serializable._composite.UnionType, pydsdl._serializable._composite.StructureType, pydsdl._serializable._composite.DelimitedType, pydsdl._serializable._composite.ServiceType, pydsdl._serializable._attribute.Attribute, pydsdl._serializable._attribute.Field, pydsdl._serializable._attribute.PaddingField, pydsdl._serializable._attribute.Constant

class pydsdl.Any

Bases: abc.ABC

This abstract class represents an arbitrary intrinsic DSDL expression value. Both serializable types and expression types derive from this common ancestor.

Per the DSDL data model, a serializable type is also a value. Serializable types have the suffix Type because their instances represent not DSDL values but DSDL types.

Instances of this type can be pickled.

TYPE_NAME = None

The DSDL-name of the data type implemented by the class, as defined in Specification.

abstract __hash__() int
abstract __eq__(other: object) bool
abstract __str__() str

Returns a DSDL spec-compatible textual representation of the contained value suitable for printing.

__repr__() str
class pydsdl.Primitive

Bases: pydsdl._expression._any.Any

abstract property native_value: Any

Yields an appropriate Python-native representation of the contained value, like fractions.Fraction, str, etc. Specializations define covariant return types.

class pydsdl.Boolean(value: bool = False)

Bases: pydsdl._expression._primitive.Primitive

TYPE_NAME = 'bool'

The DSDL-name of the data type implemented by the class, as defined in Specification.

__init__(value: bool = False)
property native_value: bool
__hash__() int
__eq__(other: object) bool
__str__() str
__bool__() bool
class pydsdl.Rational(value: Union[int, fractions.Fraction])

Bases: pydsdl._expression._primitive.Primitive

TYPE_NAME = 'rational'

The DSDL-name of the data type implemented by the class, as defined in Specification.

__init__(value: Union[int, fractions.Fraction])
property native_value: fractions.Fraction
as_native_integer() int

Returns the inferior as a native integer, unless it cannot be represented as such without the loss of precision; i.e., if denominator != 1, in which case an invalid operand exception is thrown.

is_integer() bool

Whether the demonimator equals one.

__hash__() int
__eq__(other: object) bool
__str__() str
class pydsdl.String(value: str)

Bases: pydsdl._expression._primitive.Primitive

TYPE_NAME = 'string'

The DSDL-name of the data type implemented by the class, as defined in Specification.

__init__(value: str)
property native_value: str
__hash__() int
__eq__(other: object) bool
__str__() str
class pydsdl.Container

Bases: pydsdl._expression._any.Any

abstract property element_type: Type[pydsdl._expression._any.Any]
abstract __iter__() Iterator[Any]
class pydsdl.Set(elements: Iterable[pydsdl._expression._any.Any])

Bases: pydsdl._expression._container.Container

TYPE_NAME = 'set'

The DSDL-name of the data type implemented by the class, as defined in Specification.

__init__(elements: Iterable[pydsdl._expression._any.Any])
__iter__() Iterator[Any]
property element_type: Type[pydsdl._expression._any.Any]
__hash__() int
__eq__(other: object) bool
__str__() str
class pydsdl.SerializableType

Bases: pydsdl._expression._any.Any

Instances are immutable. Invoking __str__() on a data type returns its uniform normalized definition, e.g., uavcan.node.Heartbeat.1.0[<=36], truncated float16[<=36].

TYPE_NAME = 'metaserializable'

The DSDL-name of the data type implemented by the class, as defined in Specification.

BITS_PER_BYTE = 8

This is dictated by the Cyphal Specification.

__init__() None
abstract property bit_length_set: pydsdl._bit_length_set._bit_length_set.BitLengthSet

A set of all possible bit length values of the serialized representations of this type. Refer to the specification for the background. The returned set is guaranteed to be non-empty. See pydsdl.BitLengthSet.

abstract property alignment_requirement: int

Serialized representations of this type are required/guaranteed to be aligned such that their offset from the beginning of the containing serialized representation, in bits, is a multiple of this value, in bits. Alignment of a type whose alignment requirement is X bits is facilitated by injecting [0, X) zero padding bits before the serialized representation of the type.

For any element L of the bit length set of a type whose alignment requirement is A, L % A = 0. I.e., the length of a serialized representation of the type is always a multiple of its alignment requirement.

This value is always a non-negative integer power of two. The alignment of one is a degenerate case denoting no alignment.

abstract __str__() str
__hash__() int
__eq__(other: object) bool
class pydsdl.PrimitiveType(bit_length: int, cast_mode: pydsdl._serializable._primitive.PrimitiveType.CastMode)

Bases: pydsdl._serializable._serializable.SerializableType

MAX_BIT_LENGTH = 64
BITS_IN_BYTE = 8
class CastMode(value)

Bases: enum.Enum

An enumeration.

SATURATED = 0
TRUNCATED = 1
__init__(bit_length: int, cast_mode: pydsdl._serializable._primitive.PrimitiveType.CastMode)
property bit_length_set: pydsdl._bit_length_set._bit_length_set.BitLengthSet
property bit_length: int

This is a shortcut for next(iter(x.bit_length_set)), because the bit length set of a primitive type always contains exactly one element (i.e., primitive types are fixed-length).

property standard_bit_length: bool

The term “standard length” here means that values of such bit length are commonly used in modern computer microarchitectures, such as uint8, float64, int32, and so on. Booleans are excluded. More precisely, a primitive is said to be “standard length” when the following hold:

bit_length >= 8
2**ceil(log2(bit_length)) == bit_length.
property cast_mode: pydsdl._serializable._primitive.PrimitiveType.CastMode
property alignment_requirement: int
abstract __str__() str
__repr__() str
class pydsdl.BooleanType(cast_mode: pydsdl._serializable._primitive.PrimitiveType.CastMode)

Bases: pydsdl._serializable._primitive.PrimitiveType

__init__(cast_mode: pydsdl._serializable._primitive.PrimitiveType.CastMode)
__str__() str
class pydsdl.ArithmeticType(bit_length: int, cast_mode: pydsdl._serializable._primitive.PrimitiveType.CastMode)

Bases: pydsdl._serializable._primitive.PrimitiveType

__init__(bit_length: int, cast_mode: pydsdl._serializable._primitive.PrimitiveType.CastMode)
abstract property inclusive_value_range: pydsdl.ValueRange
abstract __str__() str
class pydsdl.IntegerType(bit_length: int, cast_mode: pydsdl._serializable._primitive.PrimitiveType.CastMode)

Bases: pydsdl._serializable._primitive.ArithmeticType

__init__(bit_length: int, cast_mode: pydsdl._serializable._primitive.PrimitiveType.CastMode)
abstract property inclusive_value_range: pydsdl.ValueRange
abstract __str__() str
class pydsdl.SignedIntegerType(bit_length: int, cast_mode: pydsdl._serializable._primitive.PrimitiveType.CastMode)

Bases: pydsdl._serializable._primitive.IntegerType

__init__(bit_length: int, cast_mode: pydsdl._serializable._primitive.PrimitiveType.CastMode)
property inclusive_value_range: pydsdl.ValueRange
__str__() str
class pydsdl.UnsignedIntegerType(bit_length: int, cast_mode: pydsdl._serializable._primitive.PrimitiveType.CastMode)

Bases: pydsdl._serializable._primitive.IntegerType

__init__(bit_length: int, cast_mode: pydsdl._serializable._primitive.PrimitiveType.CastMode)
property inclusive_value_range: pydsdl.ValueRange
__str__() str
class pydsdl.FloatType(bit_length: int, cast_mode: pydsdl._serializable._primitive.PrimitiveType.CastMode)

Bases: pydsdl._serializable._primitive.ArithmeticType

__init__(bit_length: int, cast_mode: pydsdl._serializable._primitive.PrimitiveType.CastMode)
property inclusive_value_range: pydsdl.ValueRange
__str__() str
class pydsdl.VoidType(bit_length: int)

Bases: pydsdl._serializable._serializable.SerializableType

MAX_BIT_LENGTH = 64
__init__(bit_length: int)
property bit_length_set: pydsdl._bit_length_set._bit_length_set.BitLengthSet
property bit_length: int

This is a shortcut for next(iter(x.bit_length_set)), because the bit length set of a void type always contains exactly one element (i.e., void types are fixed-length).

property alignment_requirement: int
__str__() str
__repr__() str
class pydsdl.ArrayType(element_type: pydsdl._serializable._serializable.SerializableType, capacity: int)

Bases: pydsdl._serializable._serializable.SerializableType

__init__(element_type: pydsdl._serializable._serializable.SerializableType, capacity: int)
property element_type: pydsdl._serializable._serializable.SerializableType
property capacity: int

The (maximum) number of elements in the (variable-length) array.

property string_like: bool

True if the array might contain a text string, in which case it is termed to be “string-like”. A string-like array is a variable-length array of uint8. See https://github.com/OpenCyphal/specification/issues/51.

property alignment_requirement: int

The alignment requirement of an array equals that of its element type. The length of the serialized representation of any type is a multiple of its alignment requirement; therefore, every element is always placed such that its alignment requirement is satisfied.

abstract __str__() str
class pydsdl.FixedLengthArrayType(element_type: pydsdl._serializable._serializable.SerializableType, capacity: int)

Bases: pydsdl._serializable._array.ArrayType

__init__(element_type: pydsdl._serializable._serializable.SerializableType, capacity: int)
property bit_length_set: pydsdl._bit_length_set._bit_length_set.BitLengthSet
enumerate_elements_with_offsets(base_offset: pydsdl._bit_length_set._bit_length_set.BitLengthSet = BitLengthSet({0})) Iterator[Tuple[int, pydsdl._bit_length_set._bit_length_set.BitLengthSet]]

This is a convenience method for code generation. Its behavior mimics that of pydsdl.StructureType.iterate_fields_with_offsets(), except that we iterate over indexes instead of fields.

Parameters

base_offset – The base offset to add to each element. If not supplied, assumed to be {0}. The base offset will be implicitly padded out to alignment_requirement.

Returns

For an N-element array, an iterator over N elements, where each element is a tuple of the index of the array element (zero-based) and its offset as a bit length set.

__str__() str
__repr__() str
class pydsdl.VariableLengthArrayType(element_type: pydsdl._serializable._serializable.SerializableType, capacity: int)

Bases: pydsdl._serializable._array.ArrayType

__init__(element_type: pydsdl._serializable._serializable.SerializableType, capacity: int)
property bit_length_set: pydsdl._bit_length_set._bit_length_set.BitLengthSet
property string_like: bool

See the base class.

property length_field_type: pydsdl._serializable._primitive.UnsignedIntegerType

The unsigned integer type of the implicit array length field. Note that the set of valid length values is a subset of that of the returned type.

__str__() str
__repr__() str
class pydsdl.CompositeType(name: str, version: pydsdl.Version, attributes: Iterable[pydsdl._serializable._attribute.Attribute], deprecated: bool, fixed_port_id: Optional[int], source_file_path: pathlib.Path, has_parent_service: bool, doc: str = '')

Bases: pydsdl._serializable._serializable.SerializableType

This is the most interesting type in the library because it represents an actual DSDL definition upon its interpretation. This is an abstract class with several specializations.

MAX_NAME_LENGTH = 255
MAX_VERSION_NUMBER = 255
NAME_COMPONENT_SEPARATOR = '.'
__init__(name: str, version: pydsdl.Version, attributes: Iterable[pydsdl._serializable._attribute.Attribute], deprecated: bool, fixed_port_id: Optional[int], source_file_path: pathlib.Path, has_parent_service: bool, doc: str = '')
property full_name: str

The full name, e.g., uavcan.node.Heartbeat.

property name_components: List[str]

Components of the full name as a list, e.g., ['uavcan', 'node', 'Heartbeat'].

property short_name: str

The last component of the full name, e.g., Heartbeat of uavcan.node.Heartbeat.

property doc: str

The DSDL header comment provided for this data type without the leading #.

property full_namespace: str

The full name without the short name, e.g., uavcan.node for uavcan.node.Heartbeat.

property root_namespace: str

The first component of the full name, e.g., uavcan of uavcan.node.Heartbeat.

property version: pydsdl.Version

The version numbers of the type, e.g., (1, 0) of uavcan.node.Heartbeat.1.0.

property extent: int

The amount of memory, in bits, that needs to be allocated in order to store a serialized representation of this type or any of its minor versions under the same major version. This value is always at least as large as the sum of maximum bit lengths of all fields padded to one byte. If the type is sealed, its extent equals bit_length_set.max.

property bit_length_set: pydsdl._bit_length_set._bit_length_set.BitLengthSet

The bit length set of a composite is always aligned at alignment_requirement. For a sealed type this is the true bit length set computed by aggregating the fields and padding the result to alignment_requirement. That is, sealed types expose their internal structure; for example, a type that contains a single field of type uint32[2] would have a single entry in the bit length set: {64}.

property deprecated: bool

Whether the definition is marked @deprecated.

property attributes: List[pydsdl._serializable._attribute.Attribute]
property fields: List[pydsdl._serializable._attribute.Field]
property fields_except_padding: List[pydsdl._serializable._attribute.Field]
property constants: List[pydsdl._serializable._attribute.Constant]
property inner_type: pydsdl._serializable._composite.CompositeType

If the concrete type is a decorator over another Composite (such as DelimitedType), this property provides access to the decorated instance. Otherwise, returns the current instance reference unchanged. This is intended for use in scenarios where the decoration is irrelevant and the user needs to know the concrete type of the decorated instance.

property fixed_port_id: Optional[int]
property has_fixed_port_id: bool
property source_file_path: pathlib.Path

For synthesized types such as service request/response sections, this property is defined as an empty string.

property alignment_requirement: int
property has_parent_service: bool

pydsdl.ServiceType contains two special fields of this type: request and response. This property is True if this type is a service request/response type. The version and deprecation status are shared with that of the parent service. The name of the parent service equals the full namespace name of this type. For example: ns.Service.Request.2.3 –> ns.Service.2.3.

abstract iterate_fields_with_offsets(base_offset: pydsdl._bit_length_set._bit_length_set.BitLengthSet = BitLengthSet({0})) Iterator[Tuple[pydsdl._serializable._attribute.Field, pydsdl._bit_length_set._bit_length_set.BitLengthSet]]

Iterates over every field (not attribute – constants are excluded) of the data type, yielding it together with its offset, where the offset is represented as pydsdl.BitLengthSet. The offset of each field is added to the base offset, which may be specified by the caller; if not specified, the base offset is assumed to be {0}.

The objective of this method is to allow code generators to easily implement fully unrolled serialization and deserialization routines, where “unrolled” means that upon encountering another (nested) composite type, the serialization routine would not delegate its serialization to the serialization routine of the encountered type, but instead would serialize it in-place, as if the field of that type was replaced with its own fields in-place. The lack of delegation has very important performance implications: when the serialization routine does not delegate serialization of the nested types, it can perform infinitely deep field alignment analysis, thus being able to reliably statically determine whether each field of the type, including nested types at arbitrarily deep levels of nesting, is aligned relative to the origin of the serialized representation of the outermost type. As a result, the code generator will be able to avoid unnecessary reliance on slow bit-level copy routines replacing them instead with much faster byte-level copy (like memcpy()) or even plain memory aliasing.

When invoked on a tagged union type, the method yields the same offset for every field (since that’s how tagged unions are serialized), where the offset equals the bit length of the implicit union tag (plus the base offset, of course, if provided).

Please refer to the usage examples to see how this feature can be used.

Parameters

base_offset – Assume the specified base offset; assume zero offset if the parameter is not provided. The base offset will be implicitly padded out to alignment_requirement.

Returns

A generator of (Field, BitLengthSet).

__getitem__(attribute_name: str) pydsdl._serializable._attribute.Attribute

Allows the caller to retrieve an attribute by name. Padding fields are not accessible via this interface because they don’t have names. Raises KeyError if there is no such attribute.

__str__() str

Returns a string like uavcan.node.Heartbeat.1.0.

__repr__() str
class pydsdl.UnionType(name: str, version: pydsdl.Version, attributes: Iterable[pydsdl._serializable._attribute.Attribute], deprecated: bool, fixed_port_id: Optional[int], source_file_path: pathlib.Path, has_parent_service: bool, doc: str = '')

Bases: pydsdl._serializable._composite.CompositeType

A message type that is marked @union.

MIN_NUMBER_OF_VARIANTS = 2
__init__(name: str, version: pydsdl.Version, attributes: Iterable[pydsdl._serializable._attribute.Attribute], deprecated: bool, fixed_port_id: Optional[int], source_file_path: pathlib.Path, has_parent_service: bool, doc: str = '')
property bit_length_set: pydsdl._bit_length_set._bit_length_set.BitLengthSet
property number_of_variants: int
property tag_field_type: pydsdl._serializable._primitive.UnsignedIntegerType

The unsigned integer type of the implicit union tag field. Note that the set of valid tag values is a subset of that of the returned type.

iterate_fields_with_offsets(base_offset: pydsdl._bit_length_set._bit_length_set.BitLengthSet = BitLengthSet({0})) Iterator[Tuple[pydsdl._serializable._attribute.Field, pydsdl._bit_length_set._bit_length_set.BitLengthSet]]

See the base class.

static aggregate_bit_length_sets(field_types: Sequence[pydsdl._serializable._serializable.SerializableType]) pydsdl._bit_length_set._bit_length_set.BitLengthSet

Computes the bit length set for a tagged union type given the type of each of its variants. The final padding is not applied.

Unions are easy to handle because when serialized, a union is essentially just a single field prefixed with a fixed-length integer tag. So we just build a full set of combinations and then add the tag length to each element.

Observe that unions are not defined for less than 2 elements; however, this function tries to be generic by properly handling those cases as well, even though they are not permitted by the specification. For zero fields, the function yields {0}; for one field, the function yields the BLS of the field itself.

class pydsdl.StructureType(name: str, version: pydsdl.Version, attributes: Iterable[pydsdl._serializable._attribute.Attribute], deprecated: bool, fixed_port_id: Optional[int], source_file_path: pathlib.Path, has_parent_service: bool, doc: str = '')

Bases: pydsdl._serializable._composite.CompositeType

A message type that is NOT marked @union.

__init__(name: str, version: pydsdl.Version, attributes: Iterable[pydsdl._serializable._attribute.Attribute], deprecated: bool, fixed_port_id: Optional[int], source_file_path: pathlib.Path, has_parent_service: bool, doc: str = '')
iterate_fields_with_offsets(base_offset: pydsdl._bit_length_set._bit_length_set.BitLengthSet = BitLengthSet({0})) Iterator[Tuple[pydsdl._serializable._attribute.Field, pydsdl._bit_length_set._bit_length_set.BitLengthSet]]

See the base class.

property bit_length_set: pydsdl._bit_length_set._bit_length_set.BitLengthSet
static aggregate_bit_length_sets(field_types: Sequence[pydsdl._serializable._serializable.SerializableType]) pydsdl._bit_length_set._bit_length_set.BitLengthSet

Computes the bit length set for a structure type given the type of each of its fields. The final padding is not applied (but inter-field padding obviously is).

class pydsdl.DelimitedType(inner: pydsdl._serializable._composite.CompositeType, extent: int)

Bases: pydsdl._serializable._composite.CompositeType

Composites that are not sealed are wrapped into this container. It is a decorator over a composite type instance that injects the extent, bit length set, and field iteration logic that is specific to delimited (appendable, non-sealed) types.

Most of the attributes are copied from the wrapped type (e.g., name, fixed port-ID, attributes, etc.), except for those that relate to the bit layout.

Non-sealed composites are serialized into delimited opaque containers like uint8[<=(extent + 7) // 8], where the implicit length prefix is of type delimiter_header_type. Their bit length set is also computed as if it was an array as declared above, in order to prevent the containing definitions from making assumptions about the offsets of the following fields that might not survive the evolution of the type (e.g., version 1 may be 64 bits long, version 2 might be 56 bits long, then version 3 could grow to 96 bits, unpredictable).

__init__(inner: pydsdl._serializable._composite.CompositeType, extent: int)
property inner_type: pydsdl._serializable._composite.CompositeType

The appendable type that is serialized inside this delimited container. Its bit length set, extent, and other layout-specific entities are computed as if it was a sealed type.

property extent: int

The extent of a delimited type is specified explicitly via @extent EXPRESSION, where the expression shall yield an integer multiple of 8.

Optional optimization hint: if the objective is to allocate buffer memory for constructing a new serialized representation locally, then it may be beneficial to use the extent of the inner type rather than this one because it may be smaller. This is not safe for deserialization, of course.

property bit_length_set: pydsdl._bit_length_set._bit_length_set.BitLengthSet

For a non-sealed type, not many guarantees about the bit length set can be provided, because the type may be mutated in the next minor revision. Therefore, a synthetic bit length set is constructed that is merely a list of all possible bit lengths plus the delimiter header. For example, a type that contains a single field of type uint32[2] would have the bit length set of {h, h+8, h+16, ..., h+56, h+64} where h is the length of the delimiter header.

property delimiter_header_type: pydsdl._serializable._primitive.UnsignedIntegerType

The type of the integer prefix field that encodes the size of the serialized representation [in bytes] of the inner_type.

iterate_fields_with_offsets(base_offset: pydsdl._bit_length_set._bit_length_set.BitLengthSet = BitLengthSet({0})) Iterator[Tuple[pydsdl._serializable._attribute.Field, pydsdl._bit_length_set._bit_length_set.BitLengthSet]]

Delegates the call to the inner type, but with the base offset increased by the size of the delimiter header.

__repr__() str
class pydsdl.ServiceType(request: pydsdl._serializable._composite.CompositeType, response: pydsdl._serializable._composite.CompositeType, fixed_port_id: Optional[int])

Bases: pydsdl._serializable._composite.CompositeType

A service (not message) type. Unlike message types, it can’t be serialized directly.

There are exactly two pseudo-fields: request and response, which contain the request and the response structure of the service type, respectively.

__init__(request: pydsdl._serializable._composite.CompositeType, response: pydsdl._serializable._composite.CompositeType, fixed_port_id: Optional[int])
property bit_length_set: pydsdl._bit_length_set._bit_length_set.BitLengthSet
property request_type: pydsdl._serializable._composite.CompositeType
property response_type: pydsdl._serializable._composite.CompositeType
iterate_fields_with_offsets(base_offset: pydsdl._bit_length_set._bit_length_set.BitLengthSet = BitLengthSet({0})) Iterator[Tuple[pydsdl._serializable._attribute.Field, pydsdl._bit_length_set._bit_length_set.BitLengthSet]]

Always raises a TypeError.

class pydsdl.Attribute(data_type: pydsdl._serializable._serializable.SerializableType, name: str, doc: str = '')

Bases: pydsdl._expression._any.Any

__init__(data_type: pydsdl._serializable._serializable.SerializableType, name: str, doc: str = '')
property data_type: pydsdl._serializable._serializable.SerializableType
property name: str

For padding fields this is an empty string.

property doc: str

Docs for this attribute without the leading #.

__hash__() int
__eq__(other: object) bool
__str__() str

Returns the normalized DSDL representation of the attribute.

__repr__() str
class pydsdl.Field(data_type: pydsdl._serializable._serializable.SerializableType, name: str, doc: str = '')

Bases: pydsdl._serializable._attribute.Attribute

class pydsdl.PaddingField(data_type: pydsdl._serializable._void.VoidType, doc: str = '')

Bases: pydsdl._serializable._attribute.Field

__init__(data_type: pydsdl._serializable._void.VoidType, doc: str = '')
class pydsdl.Constant(data_type: pydsdl._serializable._serializable.SerializableType, name: str, value: pydsdl._expression._any.Any, doc: str = '')

Bases: pydsdl._serializable._attribute.Attribute

__init__(data_type: pydsdl._serializable._serializable.SerializableType, name: str, value: pydsdl._expression._any.Any, doc: str = '')
property value: pydsdl._expression._any.Any

The result of evaluating the constant initialization expression. The value is guaranteed to be compliant with the constant’s own type – it is checked at the evaluation time. The compliance rules are defined in the Specification.

__hash__() int
__eq__(other: object) bool

Constants are equal if their type, name, and value are equal.

__str__() str

Returns the normalized DSDL representation of the constant and its value.

__repr__() str

Exceptions

Inheritance diagram of pydsdl._error.InternalError, pydsdl._error.InvalidDefinitionError, pydsdl._expression._any.InvalidOperandError, pydsdl._expression._any.UndefinedOperatorError, pydsdl._expression._any.UndefinedAttributeError, pydsdl._serializable._serializable.TypeParameterError, pydsdl._serializable._primitive.InvalidBitLengthError, pydsdl._serializable._primitive.InvalidCastModeError, pydsdl._serializable._array.InvalidNumberOfElementsError, pydsdl._serializable._name.InvalidNameError, pydsdl._serializable._attribute.InvalidConstantValueError, pydsdl._serializable._attribute.InvalidTypeError, pydsdl._serializable._composite.InvalidVersionError, pydsdl._serializable._composite.AttributeNameCollisionError, pydsdl._serializable._composite.InvalidExtentError, pydsdl._serializable._composite.InvalidFixedPortIDError, pydsdl._serializable._composite.MalformedUnionError, pydsdl._serializable._composite.DeprecatedDependencyError, pydsdl._parser.DSDLSyntaxError, pydsdl._dsdl_definition.FileNameFormatError, pydsdl._data_schema_builder.BitLengthAnalysisError, pydsdl._data_type_builder.AssertionCheckFailureError, pydsdl._data_type_builder.UndefinedDataTypeError, pydsdl._data_type_builder.UndefinedIdentifierError, pydsdl._data_type_builder.InvalidDirectiveError, pydsdl._data_type_builder.UnregulatedFixedPortIDError, pydsdl._data_type_builder.MissingSerializationModeError, pydsdl._namespace.RootNamespaceNameCollisionError, pydsdl._namespace.DataTypeCollisionError, pydsdl._namespace.DataTypeNameCollisionError, pydsdl._namespace.NestedRootNamespaceError, pydsdl._namespace.FixedPortIDCollisionError, pydsdl._namespace.VersionsOfDifferentKindError, pydsdl._namespace.MinorVersionFixedPortIDError, pydsdl._namespace.ExtentConsistencyError, pydsdl._namespace.SealingConsistencyError

exception pydsdl.FrontendError(text: str, path: Optional[pathlib.Path] = None, line: Optional[int] = None)

Bases: Exception

This is the root exception type for all custom exceptions defined in the library. This type itself is not expected to be particularly useful to the library user; please refer to the direct descendants instead.

__init__(text: str, path: Optional[pathlib.Path] = None, line: Optional[int] = None)
set_error_location_if_unknown(path: Optional[pathlib.Path] = None, line: Optional[int] = None) None

Entries that are already known will be left unchanged. This is useful when propagating exceptions through recursive instances, e.g., when processing nested definitions.

property path: Optional[pathlib.Path]

Source file path where the error has occurred, if known.

property line: Optional[int]

Source file line number (first line numbered 1) where the error has occurred, if known. The path is always known if the line number is set.

property text: str
__str__() str

Nicely formats an error string in the typical error format [file:[line:]]description. Example:

uavcan/internet/udp/500.HandleIncomingPacket.1.0.dsdl:33: Error such and such
__repr__() str
exception pydsdl.InvalidDefinitionError(text: str, path: Optional[pathlib.Path] = None, line: Optional[int] = None)

Bases: pydsdl._error.FrontendError

This exception type is used to point out mistakes and errors in DSDL definitions. This type is inherited by a dozen of specialized exception types; however, the class hierarchy beneath this type may be unstable and should not be relied upon by the application directly.

exception pydsdl.InternalError(text: Optional[str] = None, path: Optional[pathlib.Path] = None, line: Optional[int] = None, culprit: Optional[Exception] = None)

Bases: pydsdl._error.FrontendError

This exception is used to report internal errors in the front end itself that prevented it from processing the definitions. Every occurrence should be reported to the developers.

__init__(text: Optional[str] = None, path: Optional[pathlib.Path] = None, line: Optional[int] = None, culprit: Optional[Exception] = None)

Ancillary members

class pydsdl.BitLengthSet(value: Union[Iterable[int], int, pydsdl.Operator, pydsdl._bit_length_set._bit_length_set.BitLengthSet])

Bases: object

This type represents the Bit Length Set as defined in the Specification. It is used for representing bit offsets of fields and bit lengths of serialized representations.

Most of the methods are evaluated analytically in nearly constant time rather than numerically. This is critical for complex layouts where numerical methods break due to combinatorial explosion and/or memory limits (see this discussed in https://github.com/OpenCyphal/pydsdl/issues/23). There are several methods that trigger numerical expansion of the solution; due to the aforementioned combinatorial difficulties, they may be effectively incomputable in reasonable time, so production systems should not rely on them.

Instances are guaranteed to be immutable.

>>> b = 16 + BitLengthSet(8).repeat_range(256)
>>> b
BitLengthSet(concat({16},repeat(<=256,{8})))
>>> b = 32 + b.repeat_range(65536)
>>> b
BitLengthSet(concat({32},repeat(<=65536,concat({16},repeat(<=256,{8})))))
>>> b.min, b.max
(32, 135266336)
>>> sorted(b % 16)
[0, 8]
>>> sorted(b % 32)
[0, 8, 16, 24]
__init__(value: Union[Iterable[int], int, pydsdl.Operator, pydsdl._bit_length_set._bit_length_set.BitLengthSet])

Accepts any iterable that yields integers (like another bit length set) or a single integer.

is_aligned_at(bit_length: int) bool

Shorthand for set(self % bit_length) == {0}.

>>> BitLengthSet(64).is_aligned_at(32)
True
>>> BitLengthSet(48).is_aligned_at(32)
False
>>> BitLengthSet(48).is_aligned_at(16)
True
>>> BitLengthSet(0).is_aligned_at(1234567)
True
is_aligned_at_byte() bool

A shorthand for is_aligned_at() using the standard byte size as prescribed by the Specification.

>>> BitLengthSet(32).is_aligned_at_byte()
True
>>> BitLengthSet(33).is_aligned_at_byte()
False
property min: int

The smallest element in the set derived analytically.

>>> BitLengthSet.concatenate([{1, 2, 3}, {4, 5, 6}, {7, 8, 9}]).min
12
property max: int

The largest element in the set derived analytically.

>>> BitLengthSet.concatenate([{1, 2, 3}, {4, 5, 6}, {7, 8, 9}]).pad_to_alignment(8).max
24
property fixed_length: bool

Shorthand for self.min == self.max.

>>> BitLengthSet(8).repeat(1).fixed_length
True
>>> BitLengthSet(8).repeat_range(1).fixed_length
False
__mod__(divisor: int) Iterable[int]

Elementwise modulus derived analytically.

>>> sorted(BitLengthSet([0]) % 12345)
[0]
>>> sorted(BitLengthSet([8, 12, 16]) % 8)
[0, 4]
pad_to_alignment(bit_length: int) pydsdl._bit_length_set._bit_length_set.BitLengthSet

Transform the bit length set expression such that the set becomes aligned at the specified alignment goal. After this transformation is applied, elements may become up to bit_length-1 bits larger. The argument shall be a positive integer, otherwise it’s a ValueError.

>>> from random import randint
>>> alignment = randint(1, 64)
>>> BitLengthSet(randint(1, 1000) for _ in range(100)).pad_to_alignment(alignment).is_aligned_at(alignment)
True
repeat(k: int) pydsdl._bit_length_set._bit_length_set.BitLengthSet

Construct a new bit length set expression that repeats the current one the specified number of times. This reflects the arrangement of fixed-length DSDL array elements. This is a special case of concatenate().

>>> sorted(BitLengthSet(1).repeat(0))
[0]
>>> sorted(BitLengthSet(1).repeat(1))
[1]
>>> sorted(BitLengthSet({1, 2, 3}).repeat(1))
[1, 2, 3]
>>> sorted(BitLengthSet({1, 2, 3}).repeat(2))
[2, 3, 4, 5, 6]
repeat_range(k_max: int) pydsdl._bit_length_set._bit_length_set.BitLengthSet

This is like repeat() but k spans the range [0, k_max].

>>> sorted(BitLengthSet({1, 2, 3}).repeat_range(2))
[0, 1, 2, 3, 4, 5, 6]
static concatenate(sets: Iterable[Union[pydsdl._bit_length_set._bit_length_set.BitLengthSet, Iterable[int], int]]) pydsdl._bit_length_set._bit_length_set.BitLengthSet

Construct a new bit length set expression that concatenates multiple bit length sets one after another. This reflects the data fields arrangement in a DSDL structure type.

>>> sorted(BitLengthSet.concatenate([1, 2, 10]))
[13]
>>> sorted(BitLengthSet.concatenate([{1, 2}, {4, 5}]))
[5, 6, 7]
>>> sorted(BitLengthSet.concatenate([{1, 2, 3}, {4, 5, 6}]))
[5, 6, 7, 8, 9]
>>> sorted(BitLengthSet.concatenate([{1, 2, 3}, {4, 5, 6}, {7, 8, 9}]))
[12, 13, 14, 15, 16, 17, 18]
static unite(sets: Iterable[Union[pydsdl._bit_length_set._bit_length_set.BitLengthSet, Iterable[int], int]]) pydsdl._bit_length_set._bit_length_set.BitLengthSet

Construct a new bit length set expression that is a union of multiple bit length sets. This reflects the data fields arrangement in a DSDL discriminated union.

>>> sorted(BitLengthSet.unite([1, 2, 10]))
[1, 2, 10]
>>> sorted(BitLengthSet.unite([{1, 2}, {2, 3}]))
[1, 2, 3]
__add__(other: Union[pydsdl._bit_length_set._bit_length_set.BitLengthSet, Iterable[int], int]) pydsdl._bit_length_set._bit_length_set.BitLengthSet

A shorthand for concatenate([self, other]). One can easily see that if the argument is a set of one value (or a scalar), this method will result in the addition of said scalar to every element of the original set.

>>> sorted(BitLengthSet(0) + BitLengthSet(0))
[0]
>>> sorted(BitLengthSet(4) + BitLengthSet(3))
[7]
>>> sorted(BitLengthSet({4, 91}) + 3)
[7, 94]
>>> sorted(BitLengthSet({4, 91}) + {5, 7})
[9, 11, 96, 98]
__radd__(other: Union[pydsdl._bit_length_set._bit_length_set.BitLengthSet, Iterable[int], int]) pydsdl._bit_length_set._bit_length_set.BitLengthSet

See __add__().

>>> sorted({1, 2, 3} + BitLengthSet({4, 5, 6}))
[5, 6, 7, 8, 9]
>>> sorted(1 + BitLengthSet({2, 5, 7}))
[3, 6, 8]
__or__(other: Union[pydsdl._bit_length_set._bit_length_set.BitLengthSet, Iterable[int], int]) pydsdl._bit_length_set._bit_length_set.BitLengthSet

A shorthand for unite([self, other]).

>>> a = BitLengthSet(0)
>>> a = a | BitLengthSet({1, 2, 3})
>>> sorted(a)
[0, 1, 2, 3]
>>> a = a | {3, 4, 5}
>>> sorted(a)
[0, 1, 2, 3, 4, 5]
>>> sorted(a | 6)
[0, 1, 2, 3, 4, 5, 6]
__ror__(other: Union[pydsdl._bit_length_set._bit_length_set.BitLengthSet, Iterable[int], int]) pydsdl._bit_length_set._bit_length_set.BitLengthSet

See __or__().

>>> sorted({1, 2, 3} | BitLengthSet({4, 5, 6}))
[1, 2, 3, 4, 5, 6]
>>> sorted(1 | BitLengthSet({2, 5, 7}))
[1, 2, 5, 7]
__iter__() Iterator[int]

Attention

This method triggers slow numerical expansion.

You might be tempted to use min(foo) or max(foo) for detecting length bounds. This may be effectively incomputable for data types with complex layout. Instead, use min and max.

__len__() int

Attention

This method triggers slow numerical expansion.

You might be tempted to use something like len(foo) == 1 for detecting fixed-length sets. This may be effectively incomputable for data types with complex layout. Instead, use fixed_length.

>>> len(BitLengthSet(0))
1
>>> len(BitLengthSet([1, 2, 3]))
3
__eq__(other: Any) bool

Currently, this method performs an approximate comparison that may yield a false-positive for some operands. This is done to avoid performing the costly numerical expansion of the operands. The implementation may be changed to perform exact comparison in the future if the underlying solver is updated accordingly.

>>> BitLengthSet([1, 2, 4]) == {1, 2, 4}
True
>>> BitLengthSet([1, 2, 4]) == {1, 3, 4}
False
>>> BitLengthSet([123]) == BitLengthSet(123)
True
__hash__() int

Hash is computed in constant time (numerical expansion is not performed).

>>> hash(BitLengthSet({1, 4})) != hash(BitLengthSet({1, 3}))
True
__bool__() bool

This method is overridden to avoid accidental invocation of __len__() in boolean expressions because it triggers numerical expansion.

Returns

Always True.

__str__() str
__repr__() str