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 theuavcan
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
- 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.
- 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.
- 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
- 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.
- class pydsdl.Container
Bases:
pydsdl._expression._any.Any
- abstract property element_type: Type[pydsdl._expression._any.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])
- property element_type: Type[pydsdl._expression._any.Any]
- 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.
- 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 isA
,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.
- 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
- __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
- class pydsdl.BooleanType(cast_mode: pydsdl._serializable._primitive.PrimitiveType.CastMode)
Bases:
pydsdl._serializable._primitive.PrimitiveType
- __init__(cast_mode: pydsdl._serializable._primitive.PrimitiveType.CastMode)
- 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
- 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
- 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
- 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
- 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
- class pydsdl.VoidType(bit_length: int)
Bases:
pydsdl._serializable._serializable.SerializableType
- MAX_BIT_LENGTH = 64
- property bit_length_set: pydsdl._bit_length_set._bit_length_set.BitLengthSet
- 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 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.
- 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 toalignment_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.
- 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 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.
-
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 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
ofuavcan.node.Heartbeat
.
- property full_namespace: str
The full name without the short name, e.g.,
uavcan.node
foruavcan.node.Heartbeat
.
- property root_namespace: str
The first component of the full name, e.g.,
uavcan
ofuavcan.node.Heartbeat
.
-
property version:
pydsdl.Version
The version numbers of the type, e.g.,
(1, 0)
ofuavcan.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 toalignment_requirement
. That is, sealed types expose their internal structure; for example, a type that contains a single field of typeuint32[2]
would have a single entry in the bit length set:{64}
.
- 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 source_file_path: pathlib.Path
For synthesized types such as service request/response sections, this property is defined as an empty string.
- property has_parent_service: bool
pydsdl.ServiceType
contains two special fields of this type:request
andresponse
. 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.
-
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 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).
-
__init__(name: str, version:
- 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 typedelimiter_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}
whereh
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.
- 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
andresponse
, 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
- class pydsdl.Field(data_type: pydsdl._serializable._serializable.SerializableType, name: str, doc: str = '')
- 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.
Exceptions
- 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.
- 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.
- 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.
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 aValueError
.>>> 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()
butk
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]
- __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, usefixed_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
-
__init__(value: Union[Iterable[int], int,