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 functions
- pydsdl.read_namespace(root_namespace_directory: Path | str, lookup_directories: None | Path | str | Iterable[Path | str] = None, print_output_handler: Callable[[Path, int, str], None] | None = None, allow_unregulated_fixed_port_id: bool = False, allow_root_namespace_name_collision: bool = True, *, strict: bool = False) list[pydsdl._serializable._composite.CompositeType]
This function is a main entry point for 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/uavcanto read theuavcannamespace.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
@printdirective 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@printexpressions 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.
strict – Reject features that are not [yet] part of the Cyphal Specification.
- Returns:
A list of
pydsdl.CompositeTypefound under the root_namespace_directory and 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.Error,MemoryError,SystemError,OSErrorif directories do not exist or inaccessible,ValueError/TypeErrorif the arguments are invalid.
- pydsdl.read_files(dsdl_files: None | Path | str | Iterable[Path | str], root_namespace_directories_or_names: None | Path | str | Iterable[Path | str], lookup_directories: None | Path | str | Iterable[Path | str] = None, print_output_handler: Callable[[Path, int, str], None] | None = None, allow_unregulated_fixed_port_id: bool = False, *, strict: bool = False) tuple[list[pydsdl._serializable._composite.CompositeType], list[pydsdl._serializable._composite.CompositeType]]
This function is a main entry point for the library. It reads all DSDL definitions from the specified
dsdl_filesand produces the annotated AST for these types and the transitive closure of the types they depend on.- Parameters:
dsdl_files (Any) – A list of paths to dsdl files to parse.
root_namespace_directories_or_names –
This can be a set of names of root namespaces or paths to root namespaces. All
dsdl_filesprovided must be under one of these roots. For example, given:dsdl_files = [ Path("workspace/project/types/animals/felines/Tabby.1.0.dsdl"), Path("workspace/project/types/animals/canines/Boxer.1.0.dsdl"), Path("workspace/project/types/plants/trees/DouglasFir.1.0.dsdl") ]
then this argument must be one of:
root_namespace_directories_or_names = ["animals", "plants"] # or root_namespace_directories_or_names = [ Path("workspace/project/types/animals"), Path("workspace/project/types/plants") ] # or root_namespace_directories_or_names = [ Path("animals"), Path("workspace/project/types/plants") ] # etc
Any target path not found on the filesystem must be located under a root namespace directory. For example, given a set of relative target files like this:
dsdl_files = [ Path("animals/felines/Tabby.1.0.dsdl"), Path("animals/canines/Boxer.1.0.dsdl"), Path("plants/trees/DouglasFir.1.0.dsdl") ]
then this argument must include paths under which the target files can be found. For example:
root_namespace_directories_or_names = [ Path("workspace/project/types/animals"), Path("workspace/project/types/plants") ]
Note that relative paths shown here would only resolve correctly if the current working directory is set to the root of the workspace (this function does not search system paths for the target files). If the target files are located in a different folder the paths must be absolute. For example:
root_namespace_directories_or_names = [ Path("/path/to/workspace/project/types/animals"), Path("/path/to/workspace/project/types/plants") ]
When reading vendor-specific types, 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.lookup_directories – List of namespace directories containing data type definitions that are referred to from the target dsdl files. Callers should prefer combining the lookup directories with the root namespace directories unless certain paths should be excluded when looking for target types. That is, only
root_namespace_directories_or_namesis used to find the target types but both this list androot_namespace_directories_or_namesare used to find the dependent types.print_output_handler – If provided, this callable will be invoked when a
@printdirective 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@printexpressions 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.
strict – Reject features that are not [yet] part of the Cyphal Specification.
- Returns:
A Tuple of lists of
pydsdl.CompositeType. The first index in the Tuple are the types parsed from thedsdl_filesargument. The second index are types that the targetdsdl_filesutilizes. A note for using these values to describe build dependencies: eachpydsdl.CompositeTypehas two fields that provide links back to the filesystem where the dsdl files were located when parsing the type;source_file_pathandsource_file_path_to_root.- Raises:
pydsdl.Error,MemoryError,SystemError,OSErrorif directories do not exist or inaccessible,ValueError/TypeErrorif the arguments are invalid.
- pydsdl.serialize(schema: CompositeType, obj: dict[str, Any], *, with_delimiter_header: bool = False) bytes
Serialize a Python object to bytes according to the given schema.
- Parameters:
schema – The composite type schema defining the structure.
obj – The Python object to serialize as a dict keyed by field name.
with_delimiter_header – If True, prepend a delimiter header to the output.
- Returns:
The serialized bytes.
- Raises:
SerDesError – If serialization fails.
TypeError – If schema is a ServiceType.
ValueError – If with_delimiter_header=True on a non-delimited type.
Usage demo
demo/DemoMessage.1.0.dsdl# A simple demo type. bool flag uint32 counter float64 temperature float32[4] numeric_data utf8[<=256] text_data # UTF-8 encoded text data byte[<=64] binary_data # Raw binary data @extent 1024 * 8
demo/demo_serdes.py#!/usr/bin/env python3 """ Self-contained demo of PyDSDL serialization. """ import pydsdl from pathlib import Path SCRIPT_DIR = Path(__file__).parent DSDL_FILE = SCRIPT_DIR / "DemoMessage.1.0.dsdl" def main() -> None: print("Loading DSDL type from:", DSDL_FILE.name) types, _ = pydsdl.read_files( dsdl_files=DSDL_FILE, root_namespace_directories_or_names=SCRIPT_DIR, lookup_directories=[], ) schema = types[0] print(f"✓ Loaded type: {schema.full_name} v{schema.version.major}.{schema.version.minor}") print(" Fields:", [f"{f.data_type} {f.name}" for f in schema.fields_except_padding]) print("Creating example object:") obj = { "flag": True, "counter": 42, "temperature": 23.5, "numeric_data": [1.0, 2.0, 3, 4], "text_data": "Hello, SerDes!", "binary_data": b"\x00\x01\x02\x03", } for key, value in obj.items(): value_repr = repr(value) if len(value_repr) > 50: value_repr = value_repr[:47] + "..." print(f" {key:15} = {value_repr} ({type(value).__name__})") print("Serializing object to bytes") serialized_data = pydsdl.serialize(schema, obj) print(f"✓ Serialized to {len(serialized_data)} bytes:") print(f" {serialized_data.hex()}") print("Deserializing bytes back to object") deserialized = pydsdl.deserialize(schema, serialized_data) print("✓ Deserialized successfully:") for key, value in deserialized.items(): value_repr = repr(value) if len(value_repr) > 50: value_repr = value_repr[:47] + "..." print(f" {key:15} = {value_repr} ({type(value).__name__})") print("Verifying roundtrip equality") assert obj == deserialized, "Roundtrip failed! Objects don't match." print("✓ Roundtrip verification passed: Original == Deserialized") if __name__ == "__main__": main()
- pydsdl.deserialize(schema: CompositeType, data: bytes | bytearray | memoryview, *, with_delimiter_header: bool = False) dict[str, Any]
The counterpart of
pydsdl.serialize().- Parameters:
schema – The composite type schema defining the structure.
data – The bytes to deserialize.
with_delimiter_header – If True, expect and parse a delimiter header from the input.
- Returns:
The deserialized Python object as a dict keyed by field name.
- Raises:
SerDesError – If deserialization fails.
TypeError – If schema is a ServiceType.
ValueError – If with_delimiter_header=True on a non-delimited type.
Type model
- class pydsdl.Any
Bases:
ABCThis 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
Typebecause their instances represent not DSDL values but DSDL types.Instances of this type can be pickled.
- TYPE_NAME: str = None
The DSDL-name of the data type implemented by the class, as defined in Specification.
- class pydsdl.Primitive
Bases:
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:
Primitive
- class pydsdl.Rational(value: int | Fraction)
Bases:
Primitive- TYPE_NAME: str = 'rational'
The DSDL-name of the data type implemented by the class, as defined in Specification.
- class pydsdl.String(value: str)
Bases:
Primitive
- class pydsdl.Set(elements: Iterable[Any])
Bases:
Container
- class pydsdl.SerializableType
Bases:
AnyInstances 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: str = '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: 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
Lof 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: CastMode)
Bases:
SerializableType- MAX_BIT_LENGTH = 64
- BITS_IN_BYTE = 8
- class CastMode(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)
Bases:
Enum- SATURATED = 0
- TRUNCATED = 1
- property 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.
- class pydsdl.ArithmeticType(bit_length: int, cast_mode: CastMode)
Bases:
PrimitiveType-
abstract property inclusive_value_range:
pydsdl.ValueRange
-
abstract property inclusive_value_range:
- class pydsdl.IntegerType(bit_length: int, cast_mode: CastMode)
Bases:
ArithmeticType-
abstract property inclusive_value_range:
pydsdl.ValueRange
-
abstract property inclusive_value_range:
- class pydsdl.SignedIntegerType(bit_length: int, cast_mode: CastMode)
Bases:
IntegerType-
property inclusive_value_range:
pydsdl.ValueRange
-
property inclusive_value_range:
- class pydsdl.UnsignedIntegerType(bit_length: int, cast_mode: CastMode)
Bases:
IntegerType-
property inclusive_value_range:
pydsdl.ValueRange
-
property inclusive_value_range:
- class pydsdl.ByteType
Bases:
UnsignedIntegerTypeThis type is used as the array element type for byte strings.
- class pydsdl.UTF8Type
Bases:
UnsignedIntegerTypeThis type is used as the array element type for UTF-8 strings.
- class pydsdl.FloatType(bit_length: int, cast_mode: CastMode)
Bases:
ArithmeticType-
property inclusive_value_range:
pydsdl.ValueRange
-
property inclusive_value_range:
- class pydsdl.VoidType(bit_length: int)
Bases:
SerializableType- MAX_BIT_LENGTH = 64
- property bit_length_set: BitLengthSet
- class pydsdl.ArrayType(element_type: SerializableType, capacity: int)
Bases:
SerializableType- __init__(element_type: SerializableType, capacity: int)
- property element_type: SerializableType
- property string_like: bool
This property is deprecated and will be removed in a future release. Replace with an explicit check for
isinstance(array.element_type, UTF8Type).
- class pydsdl.FixedLengthArrayType(element_type: SerializableType, capacity: int)
Bases:
ArrayType- __init__(element_type: SerializableType, capacity: int)
- property bit_length_set: BitLengthSet
- enumerate_elements_with_offsets(base_offset: BitLengthSet = BitLengthSet({0})) Iterator[Tuple[int, 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: SerializableType, capacity: int)
Bases:
ArrayType- __init__(element_type: SerializableType, capacity: int)
- property bit_length_set: BitLengthSet
- property length_field_type: 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[Attribute], deprecated: bool, fixed_port_id: int | None, source_file_path: Path, has_parent_service: bool, doc: str = '') Bases:
SerializableTypeThis 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[Attribute], deprecated: bool, fixed_port_id: int | None, source_file_path: 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 namespace_components: List[str]
Components of the namspace as a list, e.g.,
['uavcan', 'node'].
- property short_name: str
The last component of the full name, e.g.,
Heartbeatofuavcan.node.Heartbeat.
- property full_namespace: str
The full name without the short name, e.g.,
uavcan.nodeforuavcan.node.Heartbeat.
- property root_namespace: str
The first component of the full name, e.g.,
uavcanofuavcan.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: 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 inner_type: 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: Path
The path to the dsdl file from which this type was read. For synthesized types such as service request/response sections, this property is the path to the service type since request and response types are defined within the service type’s dsdl file.
- property source_file_path_to_root: Path
The path to the folder that is the root namespace folder for the source_file_path this type was read from. The source_file_path will always be relative to the source_file_path_to_root but not all types that share the same root_namespace will have the same path to their root folder since types may be contributed to a root namespace from several different file trees. For example:
` path0 = "workspace_0/project_a/types/animal/feline/Tabby.1.0.dsdl" path1 = "workspace_1/project_b/types/animal/canine/Boxer.1.0.dsdl" `In these examples path0 and path1 will produce composite types with animal as the root namespace but both with have different source_file_path_to_root paths.
- property has_parent_service: bool
pydsdl.ServiceTypecontains two special fields of this type:requestandresponse. 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: BitLengthSet = BitLengthSet({0})) Iterator[Tuple[Field, 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).
-
class pydsdl.UnionType(name: str, version:
pydsdl.Version, attributes: Iterable[Attribute], deprecated: bool, fixed_port_id: int | None, source_file_path: Path, has_parent_service: bool, doc: str = '') Bases:
CompositeTypeA message type that is marked
@union.- MIN_NUMBER_OF_VARIANTS = 2
-
__init__(name: str, version:
pydsdl.Version, attributes: Iterable[Attribute], deprecated: bool, fixed_port_id: int | None, source_file_path: Path, has_parent_service: bool, doc: str = '')
- property bit_length_set: BitLengthSet
- property tag_field_type: 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: BitLengthSet = BitLengthSet({0})) Iterator[Tuple[Field, BitLengthSet]]
See the base class.
- static aggregate_bit_length_sets(field_types: Sequence[SerializableType]) 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[Attribute], deprecated: bool, fixed_port_id: int | None, source_file_path: Path, has_parent_service: bool, doc: str = '') Bases:
CompositeTypeA message type that is NOT marked
@union.-
__init__(name: str, version:
pydsdl.Version, attributes: Iterable[Attribute], deprecated: bool, fixed_port_id: int | None, source_file_path: Path, has_parent_service: bool, doc: str = '')
- iterate_fields_with_offsets(base_offset: BitLengthSet = BitLengthSet({0})) Iterator[Tuple[Field, BitLengthSet]]
See the base class.
- property bit_length_set: BitLengthSet
- static aggregate_bit_length_sets(field_types: Sequence[SerializableType]) 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: CompositeType, extent: int)
Bases:
CompositeTypeComposites 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: CompositeType, extent: int)
- property inner_type: 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: 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}wherehis the length of the delimiter header.
- property delimiter_header_type: 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: BitLengthSet = BitLengthSet({0})) Iterator[Tuple[Field, 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: CompositeType, response: CompositeType, fixed_port_id: int | None)
Bases:
CompositeTypeA service (not message) type. Unlike message types, it can’t be serialized directly.
There are exactly two pseudo-fields:
requestandresponse, which contain the request and the response structure of the service type, respectively.- __init__(request: CompositeType, response: CompositeType, fixed_port_id: int | None)
- property bit_length_set: BitLengthSet
- property request_type: CompositeType
- property response_type: CompositeType
- iterate_fields_with_offsets(base_offset: BitLengthSet = BitLengthSet({0})) Iterator[Tuple[Field, BitLengthSet]]
Always raises a
TypeError.
- class pydsdl.Attribute(data_type: SerializableType, name: str, doc: str = '')
Bases:
Any- __init__(data_type: SerializableType, name: str, doc: str = '')
- property data_type: SerializableType
- class pydsdl.Field(data_type: SerializableType, name: str, doc: str = '')
Bases:
Attribute
- class pydsdl.Constant(data_type: SerializableType, name: str, value: Any, doc: str = '')
Bases:
Attribute- __init__(data_type: SerializableType, name: str, value: Any, doc: str = '')
Exceptions
- exception pydsdl.Error(text: str, path: Path | None = None, line: int | None = None)
Bases:
ExceptionThis 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: Path | None = None, line: int | None = 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 line: int | None
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.
Note
FrontendError is retained as a backward-compatibility alias for Error.
- exception pydsdl.InvalidDefinitionError(text: str, path: Path | None = None, line: int | None = None)
Bases:
ErrorThis 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.SerDesError(text: str, path: Path | None = None, line: int | None = None)
Bases:
ErrorRoot exception for serialization/deserialization errors. This is raised when serialization or deserialization operations fail.
- exception pydsdl.InternalError(text: str | None = None, path: Path | None = None, line: int | None = None, culprit: Exception | None = None)
Bases:
ErrorThis 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: Iterable[int] | int |
pydsdl.Operator| BitLengthSet) Bases:
objectThis 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: Iterable[int] | int |
pydsdl.Operator| 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) 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-1bits 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) 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) BitLengthSet
This is like
repeat()butkspans the range[0, k_max].>>> sorted(BitLengthSet({1, 2, 3}).repeat_range(2)) [0, 1, 2, 3, 4, 5, 6]
- static concatenate(sets: Iterable[BitLengthSet | Iterable[int] | int]) 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[BitLengthSet | Iterable[int] | int]) 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: BitLengthSet | Iterable[int] | int) 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: BitLengthSet | Iterable[int] | int) 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: BitLengthSet | Iterable[int] | int) 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: BitLengthSet | Iterable[int] | int) 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) == 1for 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: Iterable[int] | int |