pycyphal.dsdl package

Module contents

This module is used for automatic generation of Python classes from DSDL type definitions and also for various manipulations on them. Auto-generated classes have a high-level application-facing API and built-in auto-generated serialization and deserialization routines.

The serialization code heavily relies on NumPy and the data alignment analysis implemented in PyDSDL. Some of the technical details are covered in the following posts:

The main entity of this module is the function compile().

pycyphal.dsdl.compile(root_namespace_directory: None | str | Path = None, lookup_directories: list[str | Path] | None = None, output_directory: None | str | Path = None, allow_unregulated_fixed_port_id: bool = False) GeneratedPackageInfo | None[source]

This function runs the Nunavut transpiler converting a specified DSDL root namespace into a Python package. In the generated package, nested DSDL namespaces are represented as Python subpackages, DSDL types as Python classes, type version numbers as class name suffixes separated via underscores (like Type_1_0), constants as class attributes, fields as properties. For a more detailed information on how to use generated types refer to the Nunavut documentation.

Generated packages do not automatically import their nested subpackages. For example, if the application needs to use uavcan.node.Heartbeat.1.0, it has to import uavcan.node explicitly; doing just import uavcan is not sufficient.

If the source definition contains identifiers, type names, namespace components, or other entities whose names are listed in nunavut.lang.py.PYTHON_RESERVED_IDENTIFIERS, the compiler applies substitution by suffixing such entities with an underscore _. A small subset of applications may require access to a generated entity without knowing in advance whether its name is a reserved identifier or not (i.e., whether it’s prefixed or not). To simplify usage, the generated nunavut_support module provides functions get_attribute and set_attribute that provide access to the generated class/object attributes using their original names before substitution. Likewise, the get_model function can find a generated type even if any of its name components are prefixed; e.g., a DSDL type str.Type.1.0 would be imported as str_.Type_1_0.

Tip

Production applications should compile their DSDL namespaces as part of the package build process. This can be done by overriding the build_py command in setup.py and invoking this function from there.

Tip

Configure your IDE to index the compilation output directory as a source directory to enable code completion. For PyCharm: right click the directory –> “Mark Directory as” –> “Sources Root”.

Parameters:
  • root_namespace_directory – The source DSDL root namespace directory path. The last component of the path is the name of the root namespace. For example, to generate package for the root namespace uavcan, the path would be like foo/bar/uavcan. If set to None, only the nunavut_support module will be generated.

  • lookup_directories – An iterable of DSDL root namespace directory paths where to search for referred DSDL definitions. The format of each path is the same as for the previous parameter; i.e., the last component of each path is a DSDL root namespace name. If you are generating code for a vendor-specific DSDL root namespace, make sure to provide at least the path to the standard uavcan namespace directory here.

  • output_directory – The generated Python package directory will be placed into this directory. If not specified or None, the current working directory is used. For example, if this argument equals foo/bar, and the DSDL root namespace name is uavcan, the top-level __init__.py of the generated package will end up in foo/bar/uavcan/__init__.py. The directory tree will be created automatically if it does not exist (like mkdir -p). If the destination exists, it will be silently written over. Applications that compile DSDL lazily are recommended to shard the output directory by the library version number to avoid compatibility issues with code generated by older versions of the library. Don’t forget to add the output directory to PYTHONPATH.

  • allow_unregulated_fixed_port_id – If True, the compiler will not reject unregulated data types with fixed port-ID. If you are not sure what it means, do not use it, and read the Cyphal specification first.

Returns:

An instance of GeneratedPackageInfo describing the generated package, unless the root namespace is empty, in which case it’s None.

Raises:

OSError if required operations on the file system could not be performed; pydsdl.InvalidDefinitionError if the source DSDL definitions are invalid; pydsdl.InternalError if there is a bug in the DSDL processing front-end; ValueError if any of the arguments are otherwise invalid.

The following table is an excerpt from the Cyphal specification. Observe that unregulated fixed port identifiers are prohibited by default, but it can be overridden.

Scope

Regulated

Unregulated

Public

Standard and contributed (e.g., vendor-specific) definitions. Fixed port identifiers are allowed; they are called “regulated port-IDs”.

Definitions distributed separately from the Cyphal specification. Fixed port identifiers are not allowed.

Private

Nonexistent category.

Definitions that are not available to anyone except their authors. Fixed port identifiers are permitted (although not recommended); they are called “unregulated fixed port-IDs”.

pycyphal.dsdl.compile_all(root_namespace_directories: Iterable[Path | str], output_directory: None | str | Path = None, *, allow_unregulated_fixed_port_id: bool = False) list[GeneratedPackageInfo][source]

This is a simple convenience wrapper over compile() that addresses a very common use case where the application needs to compile multiple inter-dependent namespaces.

Parameters:
  • root_namespace_directoriescompile() will be invoked once for each directory in the list, using all of them as look-up dirs for each other. They may be ordered arbitrarily. Directories that contain no DSDL definitions are ignored.

  • output_directory – See compile().

  • allow_unregulated_fixed_port_id – See compile().

Returns:

A list of of GeneratedPackageInfo, one per non-empty root namespace directory.

>>> import sys
>>> import pathlib
>>> import importlib
>>> import pycyphal
>>> compiled_dsdl_dir = pathlib.Path(".lazy_compiled", pycyphal.__version__)
>>> compiled_dsdl_dir.mkdir(parents=True, exist_ok=True)
>>> sys.path.insert(0, str(compiled_dsdl_dir))
>>> try:
...     import sirius_cyber_corp
...     import uavcan.si.sample.volumetric_flow_rate
... except (ImportError, AttributeError):
...     _ = pycyphal.dsdl.compile_all(
...         [
...             DEMO_DIR / "custom_data_types/sirius_cyber_corp",
...             DEMO_DIR / "public_regulated_data_types/uavcan",
...             DEMO_DIR / "public_regulated_data_types/reg/",
...         ],
...         output_directory=compiled_dsdl_dir,
...     )
...     importlib.invalidate_caches()
...     import sirius_cyber_corp
...     import uavcan.si.sample.volumetric_flow_rate
class pycyphal.dsdl.GeneratedPackageInfo(path: 'pathlib.Path', models: 'Sequence[pydsdl.CompositeType]', name: 'str')[source]

Bases: object

path: Path

Path to the directory that contains the top-level __init__.py.

models: Sequence[CompositeType]

List of PyDSDL objects describing the source DSDL definitions. This can be used for arbitrarily complex introspection and reflection.

__delattr__(name)[source]
__eq__(other)[source]
__hash__()[source]
__init__(path: Path, models: Sequence[CompositeType], name: str) None[source]
__match_args__ = ('path', 'models', 'name')
__repr__()[source]
__setattr__(name, value)[source]
name: str

The name of the generated package, which is the same as the name of the DSDL root namespace unless the name had to be stropped. See nunavut.lang.py.PYTHON_RESERVED_IDENTIFIERS.

pycyphal.dsdl.install_import_hook(lookup_directories: Iterable[Path | str] | None = None, output_directory: None | str | Path = None, allow_unregulated_fixed_port_id: bool | None = None) None[source]

Installs python import hook, which automatically compiles any DSDL if package is not found.

A default import hook is automatically installed when pycyphal is imported. To opt out, set environment variable PYCYPHAL_NO_IMPORT_HOOK=True before importing pycyphal.

Parameters:
  • lookup_directories – List of directories where to look for DSDL sources. If not provided, it is sourced from CYPHAL_PATH environment variable.

  • output_directory – Directory to output compiled DSDL packages. If not provided, PYCYPHAL_PATH environment variable is used. If that is not available either, a default ~/.pycyphal (or other OS equivalent) directory is used.

  • allow_unregulated_fixed_port_id – If True, the compiler will not reject unregulated data types with fixed port-ID. If not provided, it will be sourced from CYPHAL_ALLOW_UNREGULATED_FIXED_PORT_ID variable or default to False.

pycyphal.dsdl.serialize(obj: Any) Iterable[memoryview][source]

A wrapper over nunavut_support.serialize. The nunavut_support module will be generated automatically if it is not importable.

pycyphal.dsdl.deserialize(dtype: Type[pycyphal.dsdl.T], fragmented_serialized_representation: Sequence[memoryview]) pycyphal.dsdl.T | None[source]

A wrapper over nunavut_support.deserialize. The nunavut_support module will be generated automatically if it is not importable.

pycyphal.dsdl.get_model(class_or_instance: Any) CompositeType[source]

A wrapper over nunavut_support.get_model. The nunavut_support module will be generated automatically if it is not importable.

pycyphal.dsdl.get_class(model: CompositeType) type[source]

A wrapper over nunavut_support.get_class. The nunavut_support module will be generated automatically if it is not importable.

pycyphal.dsdl.get_extent_bytes(class_or_instance: Any) int[source]

A wrapper over nunavut_support.get_extent_bytes. The nunavut_support module will be generated automatically if it is not importable.

pycyphal.dsdl.get_fixed_port_id(class_or_instance: Any) int | None[source]

A wrapper over nunavut_support.get_fixed_port_id. The nunavut_support module will be generated automatically if it is not importable.

pycyphal.dsdl.get_attribute(obj: Any, name: str) Any[source]

A wrapper over nunavut_support.get_attribute. The nunavut_support module will be generated automatically if it is not importable.

pycyphal.dsdl.set_attribute(obj: Any, name: str, value: Any) None[source]

A wrapper over nunavut_support.set_attribute. The nunavut_support module will be generated automatically if it is not importable.

pycyphal.dsdl.is_serializable(dtype: Any) bool[source]

A wrapper over nunavut_support.is_serializable. The nunavut_support module will be generated automatically if it is not importable.

pycyphal.dsdl.is_message_type(dtype: Any) bool[source]

A wrapper over nunavut_support.is_message_type. The nunavut_support module will be generated automatically if it is not importable.

pycyphal.dsdl.is_service_type(dtype: Any) bool[source]

A wrapper over nunavut_support.is_service_type. The nunavut_support module will be generated automatically if it is not importable.

pycyphal.dsdl.to_builtin(obj: object) Dict[str, Any][source]

A wrapper over nunavut_support.to_builtin. The nunavut_support module will be generated automatically if it is not importable.

pycyphal.dsdl.update_from_builtin(destination: pycyphal.dsdl.T, source: Any) pycyphal.dsdl.T[source]

A wrapper over nunavut_support.update_from_builtin. The nunavut_support module will be generated automatically if it is not importable.

pycyphal.dsdl.generate_package(*args, **kwargs)[source]

Deprecated alias of compile().