Skip to content

Commit

Permalink
Migrate to Nunavut v2 (#318)
Browse files Browse the repository at this point in the history
Closes #277 

Co-authored-by: Erik Rainey <[email protected]>
Co-authored-by: Pavel Kirienko <[email protected]>
  • Loading branch information
3 people authored Jan 8, 2024
1 parent a6a899c commit d02f84e
Show file tree
Hide file tree
Showing 47 changed files with 352 additions and 3,550 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ coverage.xml
.*compiled
*.cache
*.db
nunavut_support.py

.scannerwork

Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
Changelog
=========

v1.17
-----
- Move to Nunavut Version 2
See (`#318 <https://github.com/OpenCyphal/pycyphal/pull/318>`_) for details on the internal changes.

v1.16
-----

Expand Down
15 changes: 7 additions & 8 deletions docs/pages/architecture.rst
Original file line number Diff line number Diff line change
Expand Up @@ -230,26 +230,25 @@ The auto-generated classes have a high-level application-facing API and built-in
serialization and deserialization routines.

By default, pycyphal installs an import hook, which automatically compiles DSDLs on import (if not yet compiled).
Import hook is triggered when all other import handlers fail (local folder or ``PYTHONPATH``). Import hook then
checks for a root namespace matching imported module name inside one of the paths in ``CYPHAL_PATH`` environment
variable. If found, DSDL root namespace is compiled into output directory given by ``PYCYPHAL_PATH`` environment
variable, or if not provided, into `~/.pycyphal` (or OS equivalent). Default import hook can be disabled by setting
``PYCYPHAL_NO_IMPORT_HOOK=True`` environment variable.
Import hook is triggered when all other import handlers fail (local folder or ``PYTHONPATH``). The import hook then
checks for a root namespace matching imported module name inside one of the paths in the ``CYPHAL_PATH`` environment
variable. If found, DSDL root namespace is compiled into output directory given by the ``PYCYPHAL_PATH`` environment
variable, or if not provided, into ``~/.pycyphal`` (or OS equivalent).
The default import hook can be disabled by setting the ``PYCYPHAL_NO_IMPORT_HOOK`` environment variable to 1.

The main API entries are:

- :func:`pycyphal.dsdl.compile` --- transcompiles a DSDL namespace into a Python package.

- :func:`pycyphal.dsdl.serialize` and :func:`pycyphal.dsdl.deserialize` --- serialize and deserialize
an instance of an autogenerated class.

- :class:`pycyphal.dsdl.CompositeObject` and :class:`pycyphal.dsdl.ServiceObject` --- base classes for
Python classes generated from DSDL type definitions; message types and service types, respectively.
These functions are wrappers of the Nunavut generated support functions in ``nunavut_support.py``.

- :func:`pycyphal.dsdl.to_builtin` and :func:`pycyphal.dsdl.update_from_builtin` --- used to convert
a DSDL object instance to/from a simplified representation using only built-in types such as :class:`dict`,
:class:`list`, :class:`int`, :class:`float`, :class:`str`, and so on. These can be used as an intermediate
representation for conversion to/from JSON, YAML, and other commonly used serialization formats.
These functions are wrappers of the Nunavut generated support functions in ``nunavut_support.py``.


Presentation layer
Expand Down
2 changes: 1 addition & 1 deletion pycyphal/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.16.1"
__version__ = "1.17.0"
7 changes: 4 additions & 3 deletions pycyphal/application/_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import logging
import uavcan.node
import pycyphal
import nunavut_support
from pycyphal.presentation import Presentation, ServiceRequestMetadata, Publisher, Subscriber, Server, Client
from . import heartbeat_publisher
from . import register
Expand Down Expand Up @@ -202,7 +203,7 @@ def _resolve_port(self, dtype: Any, kind: str, name_or_id: str | int) -> int:
return self._resolve_named_port(dtype, kind, name_or_id)
if isinstance(name_or_id, str):
assert not name_or_id
res = pycyphal.dsdl.get_fixed_port_id(dtype)
res = nunavut_support.get_fixed_port_id(dtype)
if res is not None:
return res
raise TypeError(f"Type {dtype} has no fixed port-ID, and no port name is given")
Expand All @@ -227,7 +228,7 @@ def _resolve_named_port(self, dtype: Any, kind: str, name: str, *, default: int
)
)
# Expose the type information to other network participants as prescribed by the Specification.
model = pycyphal.dsdl.get_model(dtype)
model = nunavut_support.get_model(dtype)
self.registry[self._get_port_type_register_name(kind, name)] = lambda: register.Value(
string=register.String(str(model))
)
Expand All @@ -236,7 +237,7 @@ def _resolve_named_port(self, dtype: Any, kind: str, name: str, *, default: int

# Default to the fixed port-ID if the register value is invalid.
_logger.debug("%r: %r = %r not in [0, %d], assume undefined", self, id_register_name, port_id, mask)
fpid = pycyphal.dsdl.get_fixed_port_id(dtype)
fpid = nunavut_support.get_fixed_port_id(dtype)
if fpid is not None:
return fpid

Expand Down
4 changes: 3 additions & 1 deletion pycyphal/application/_port_list_publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
from uavcan.node.port import ServiceIDList_0 as ServiceIDList
from uavcan.node.port import SubjectID_1 as SubjectID

import nunavut_support


@dataclasses.dataclass(frozen=True)
class _State:
Expand Down Expand Up @@ -130,7 +132,7 @@ def _make_port_list(state: _State, packet_capture_mode: bool) -> List:


def _make_subject_id_list(ports: Set[int]) -> SubjectIDList:
sparse_list_type = pycyphal.dsdl.get_model(SubjectIDList)["sparse_list"].data_type
sparse_list_type = nunavut_support.get_model(SubjectIDList)["sparse_list"].data_type
assert isinstance(sparse_list_type, pydsdl.ArrayType)

if len(ports) <= sparse_list_type.capacity:
Expand Down
6 changes: 4 additions & 2 deletions pycyphal/application/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import uavcan.file
import uavcan.primitive

import nunavut_support

# import X as Y is not an accepted form; see https://github.com/python/mypy/issues/11706
Path = uavcan.file.Path_2
Error = uavcan.file.Error_1
Expand Down Expand Up @@ -52,7 +54,7 @@ def __init__(
self._roots = [pathlib.Path(x).resolve() for x in roots]

# noinspection PyUnresolvedReferences
self._data_transfer_capacity = int(pycyphal.dsdl.get_model(Unstructured)["value"].data_type.capacity)
self._data_transfer_capacity = int(nunavut_support.get_model(Unstructured)["value"].data_type.capacity)

s_ls = node.get_server(List)
s_if = node.get_server(GetInfo)
Expand Down Expand Up @@ -293,7 +295,7 @@ def __init__(
self._clients: typing.Dict[typing.Type[object], pycyphal.presentation.Client[object]] = {}

# noinspection PyUnresolvedReferences
self._data_transfer_capacity = int(pycyphal.dsdl.get_model(Unstructured)["value"].data_type.capacity)
self._data_transfer_capacity = int(nunavut_support.get_model(Unstructured)["value"].data_type.capacity)

@property
def data_transfer_capacity(self) -> int:
Expand Down
3 changes: 2 additions & 1 deletion pycyphal/application/heartbeat_publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from uavcan.node import Heartbeat_1 as Heartbeat
import pycyphal
import pycyphal.application
import nunavut_support


class Health(enum.IntEnum):
Expand Down Expand Up @@ -44,7 +45,7 @@ class Mode(enum.IntEnum):


VENDOR_SPECIFIC_STATUS_CODE_MASK = (
2 ** pycyphal.dsdl.get_model(Heartbeat)["vendor_specific_status_code"].data_type.bit_length_set.max - 1
2 ** nunavut_support.get_model(Heartbeat)["vendor_specific_status_code"].data_type.bit_length_set.max - 1
)


Expand Down
7 changes: 4 additions & 3 deletions pycyphal/application/plug_and_play.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@
from uavcan.pnp import NodeIDAllocationData_2 as NodeIDAllocationData_2
import pycyphal
import pycyphal.application
import nunavut_support

# import X as Y is not an accepted form; see https://github.com/python/mypy/issues/11706
ID = uavcan.node.ID_1

_PSEUDO_UNIQUE_ID_MASK = (
2 ** pycyphal.dsdl.get_model(NodeIDAllocationData_1)["unique_id_hash"].data_type.bit_length_set.max - 1
2 ** nunavut_support.get_model(NodeIDAllocationData_1)["unique_id_hash"].data_type.bit_length_set.max - 1
)

_NODE_ID_MASK = 2 ** pycyphal.dsdl.get_model(ID)["value"].data_type.bit_length_set.max - 1
_NODE_ID_MASK = 2 ** nunavut_support.get_model(ID)["value"].data_type.bit_length_set.max - 1

_UNIQUE_ID_SIZE_BYTES = pycyphal.application.NodeInfo().unique_id.size

Expand Down Expand Up @@ -65,7 +66,7 @@ class Allocatee:

DEFAULT_PRIORITY = pycyphal.transport.Priority.SLOW

_MTU_THRESHOLD = pycyphal.dsdl.get_model(NodeIDAllocationData_2).bit_length_set.max // 8
_MTU_THRESHOLD = nunavut_support.get_model(NodeIDAllocationData_2).bit_length_set.max // 8

def __init__(
self,
Expand Down
2 changes: 1 addition & 1 deletion pycyphal/application/register/_value.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import Union, Iterable, List, Any, Optional, no_type_check
from numpy.typing import NDArray
import pycyphal
from pycyphal.dsdl import get_attribute
from nunavut_support import get_attribute
from .backend import Value as Value
from . import String, Unstructured, Bit
from . import Integer8, Integer16, Integer32, Integer64
Expand Down
6 changes: 3 additions & 3 deletions pycyphal/application/register/backend/static.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from pathlib import Path
import logging
import sqlite3
import pycyphal
import nunavut_support
from . import Entry, BackendError, Backend, Value


Expand Down Expand Up @@ -107,7 +107,7 @@ def __getitem__(self, key: str) -> Entry:
raise KeyError(key)
mutable, value = res
assert isinstance(value, bytes)
obj = pycyphal.dsdl.deserialize(Value, [memoryview(value)])
obj = nunavut_support.deserialize(Value, [memoryview(value)])
if obj is None: # pragma: no cover
_logger.warning("%r: Value of %r is not a valid serialization of %s: %r", self, key, Value, value)
raise KeyError(key)
Expand Down Expand Up @@ -135,7 +135,7 @@ def __setitem__(self, key: str, value: Union[Entry, Value]) -> None:
self._execute(
r"insert or replace into register (name, value, mutable) values (?, ?, ?)",
key,
b"".join(pycyphal.dsdl.serialize(e.value)),
b"".join(nunavut_support.serialize(e.value)),
e.mutable,
commit=True,
)
Expand Down
32 changes: 14 additions & 18 deletions pycyphal/dsdl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,22 @@
from ._compiler import compile_all as compile_all
from ._compiler import GeneratedPackageInfo as GeneratedPackageInfo

from ._composite_object import serialize as serialize
from ._composite_object import deserialize as deserialize

from ._composite_object import get_fixed_port_id as get_fixed_port_id
from ._composite_object import get_model as get_model
from ._composite_object import get_class as get_class
from ._composite_object import get_extent_bytes as get_extent_bytes

from ._composite_object import get_attribute as get_attribute
from ._composite_object import set_attribute as set_attribute

from ._composite_object import is_serializable as is_serializable
from ._composite_object import is_message_type as is_message_type
from ._composite_object import is_service_type as is_service_type

from ._builtin_form import to_builtin as to_builtin
from ._builtin_form import update_from_builtin as update_from_builtin

from ._import_hook import install_import_hook as install_import_hook

from ._support_wrappers import serialize as serialize
from ._support_wrappers import deserialize as deserialize
from ._support_wrappers import get_model as get_model
from ._support_wrappers import get_class as get_class
from ._support_wrappers import get_extent_bytes as get_extent_bytes
from ._support_wrappers import get_fixed_port_id as get_fixed_port_id
from ._support_wrappers import get_attribute as get_attribute
from ._support_wrappers import set_attribute as set_attribute
from ._support_wrappers import is_serializable as is_serializable
from ._support_wrappers import is_message_type as is_message_type
from ._support_wrappers import is_service_type as is_service_type
from ._support_wrappers import to_builtin as to_builtin
from ._support_wrappers import update_from_builtin as update_from_builtin


def generate_package(*args, **kwargs): # type: ignore # pragma: no cover
"""Deprecated alias of :func:`compile`."""
Expand Down
Loading

0 comments on commit d02f84e

Please sign in to comment.