From 9bf3c4ad2de1a1bb2168429c6e84c42ed18728e9 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Wed, 20 Mar 2024 17:22:06 -0400 Subject: [PATCH 1/4] Add types to defintions Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/definition.py | 191 +++++++++++++++------- 1 file changed, 129 insertions(+), 62 deletions(-) diff --git a/rosidl_parser/rosidl_parser/definition.py b/rosidl_parser/rosidl_parser/definition.py index 689b71384..94b7aa4a1 100644 --- a/rosidl_parser/rosidl_parser/definition.py +++ b/rosidl_parser/rosidl_parser/definition.py @@ -13,8 +13,16 @@ # limitations under the License. import pathlib +from typing import Dict from typing import Iterable +from typing import List +from typing import Literal +from typing import Optional +from typing import Set from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import Union # Basic types as defined by the IDL specification @@ -85,6 +93,40 @@ OCTET_TYPE, ) +if TYPE_CHECKING: + SignedNonexplicitIntegerTypeValues = Union[Literal['short'], Literal['long'], + Literal['long long']] + UnsignedNonexplicitIntegerTypeValues = Union[Literal['unsigned short'], + Literal['unsigned long'], + Literal['unsigned long long']] + + NonexplicitIntegerTypeValues = Union[SignedNonexplicitIntegerTypeValues, + UnsignedNonexplicitIntegerTypeValues] + + FloatingPointTypeValues = Union[Literal['float'], Literal['double'], + Literal['long double']] + CharacterTypeValues = Union[Literal['char'], Literal['wchar']] + BooleanValue = Literal['boolean'] + OctetValue = Literal['octet'] + + SignedExplicitIntegerTypeValues = Union[Literal['int8'], Literal['int16'], + Literal['int32'], Literal['int64']] + UnsignedExplicitIntegerTypeValues = Union[Literal['uint8'], Literal['uint16'], + Literal['uint32'], Literal['uint64']] + + ExplicitIntegerTypeValues = Union[SignedExplicitIntegerTypeValues, + UnsignedExplicitIntegerTypeValues] + + SignedIntegerTypeValues = Union[SignedNonexplicitIntegerTypeValues, + SignedExplicitIntegerTypeValues] + UnsignedIntegerTypeValues = Union[UnsignedNonexplicitIntegerTypeValues, + UnsignedExplicitIntegerTypeValues] + IntegerTypeValues = Union[SignedIntegerTypeValues, UnsignedIntegerTypeValues] + + BasicTypeValues = Union[IntegerTypeValues, FloatingPointTypeValues, + CharacterTypeValues, BooleanValue, + OctetValue] + EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME = 'structure_needs_at_least_one_member' CONSTANT_MODULE_SUFFIX = '_Constants' @@ -107,8 +149,8 @@ class AbstractType: __slots__ = () - def __eq__(self, other): - return type(self) == type(other) + def __eq__(self, other: object) -> bool: + return isinstance(other, type(self)) class AbstractNestableType(AbstractType): @@ -137,7 +179,7 @@ class BasicType(AbstractNestableType): __slots__ = ('typename', ) - def __init__(self, typename: str): + def __init__(self, typename: 'BasicTypeValues') -> None: """ Create a BasicType. @@ -147,7 +189,9 @@ def __init__(self, typename: str): assert typename in BASIC_TYPES self.typename = typename - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, BasicType): + return False return super().__eq__(other) and self.typename == other.typename @@ -156,7 +200,7 @@ class NamedType(AbstractNestableType): __slots__ = ('name') - def __init__(self, name: str): + def __init__(self, name: str) -> None: """ Create a NamedType. @@ -165,7 +209,9 @@ def __init__(self, name: str): super().__init__() self.name = name - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, NamedType): + return False return super().__eq__(other) and self.name == other.name @@ -174,7 +220,7 @@ class NamespacedType(AbstractNestableType): __slots__ = ('namespaces', 'name') - def __init__(self, namespaces: Iterable[str], name: str): + def __init__(self, namespaces: Iterable[str], name: str) -> None: """ Create a NamespacedType. @@ -189,7 +235,9 @@ def __init__(self, namespaces: Iterable[str], name: str): def namespaced_name(self) -> Tuple[str, ...]: return (*self.namespaces, self.name) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, NamespacedType): + return False return super().__eq__(other) and \ self.namespaces == other.namespaces and self.name == other.name @@ -199,7 +247,7 @@ class AbstractGenericString(AbstractNestableType): __slots__ = () - def has_maximum_size(self): + def has_maximum_size(self) -> bool: raise NotImplementedError('Only implemented in subclasses') @@ -214,7 +262,7 @@ class BoundedString(AbstractString): __slots__ = ('maximum_size', ) - def __init__(self, maximum_size: int): + def __init__(self, maximum_size: int) -> None: """ Create a BoundedString. @@ -224,10 +272,12 @@ def __init__(self, maximum_size: int): assert maximum_size >= 0 self.maximum_size = maximum_size - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[True]': return True - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, BoundedString): + return False return super().__eq__(other) and \ self.maximum_size == other.maximum_size @@ -237,7 +287,7 @@ class UnboundedString(AbstractString): __slots__ = () - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[False]': return False @@ -252,7 +302,7 @@ class BoundedWString(AbstractWString): __slots__ = ('maximum_size', ) - def __init__(self, maximum_size: int): + def __init__(self, maximum_size: int) -> None: """ Create a BoundedWString. @@ -265,10 +315,12 @@ def __init__(self, maximum_size: int): # assert maximum_size > 0 self.maximum_size = maximum_size - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[True]': return True - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, BoundedWString): + return False return super().__eq__(other) and \ self.maximum_size == other.maximum_size @@ -278,7 +330,7 @@ class UnboundedWString(AbstractWString): __slots__ = () - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[False]': return False @@ -294,7 +346,7 @@ class AbstractNestedType(AbstractType): __slots__ = ('value_type', ) - def __init__(self, value_type: AbstractNestableType): + def __init__(self, value_type: AbstractNestableType) -> None: """ Create an AbstractNestedType. @@ -304,10 +356,12 @@ def __init__(self, value_type: AbstractNestableType): assert isinstance(value_type, AbstractNestableType) self.value_type = value_type - def has_maximum_size(self): + def has_maximum_size(self) -> bool: raise NotImplementedError('Only implemented in subclasses') - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, AbstractNestedType): + return False return super().__eq__(other) and self.value_type == other.value_type @@ -316,7 +370,7 @@ class Array(AbstractNestedType): __slots__ = ('size') - def __init__(self, value_type: AbstractNestableType, size: int): + def __init__(self, value_type: AbstractNestableType, size: int) -> None: """ Create an Array. @@ -328,19 +382,21 @@ def __init__(self, value_type: AbstractNestableType, size: int): assert size > 0 self.size = size - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[True]': return True - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, Array): + return False return super().__eq__(other) and self.size == other.size class AbstractSequence(AbstractNestedType): """The abstract base class of sequence types.""" - __slots__ = set() + __slots__: Set[str] = set() - def __init__(self, value_type: AbstractNestableType): + def __init__(self, value_type: AbstractNestableType) -> None: super().__init__(value_type) @@ -349,7 +405,7 @@ class BoundedSequence(AbstractSequence): __slots__ = ('maximum_size', ) - def __init__(self, value_type: AbstractNestableType, maximum_size: int): + def __init__(self, value_type: AbstractNestableType, maximum_size: int) -> None: """ Create a BoundedSequence. @@ -360,10 +416,12 @@ def __init__(self, value_type: AbstractNestableType, maximum_size: int): assert maximum_size > 0 self.maximum_size = maximum_size - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[True]': return True - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, BoundedSequence): + return False return super().__eq__(other) and \ self.maximum_size == other.maximum_size @@ -373,7 +431,7 @@ class UnboundedSequence(AbstractSequence): __slots__ = () - def __init__(self, value_type: AbstractNestableType): + def __init__(self, value_type: AbstractNestableType) -> None: """ Create an UnboundedSequence. @@ -381,16 +439,19 @@ def __init__(self, value_type: AbstractNestableType): """ super().__init__(value_type) - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[False]': return False +ValueType = Union[str, int, float, bool, Dict[str, Union[str, int, float, bool]], None] + + class Annotation: """An annotation identified by a name with an arbitrary value.""" __slots__ = ('name', 'value') - def __init__(self, name: str, value): + def __init__(self, name: str, value: ValueType) -> None: """ Create an Annotation. @@ -409,14 +470,14 @@ class Annotatable: __slots__ = ('annotations', ) - def __init__(self): - self.annotations = [] + def __init__(self) -> None: + self.annotations: List[Annotation] = [] - def get_annotation_value(self, name): + def get_annotation_value(self, name: str) -> ValueType: """ Get the unique value of an annotation of a specific type. - :param str name: the name of the annotation type + :param name: the name of the annotation type :returns: the annotation value :raises: ValueError if there is no or multiple annotations with the given name @@ -428,16 +489,16 @@ def get_annotation_value(self, name): raise ValueError(f"Multiple '{name}' annotations") return values[0] - def get_annotation_values(self, name): + def get_annotation_values(self, name: str) -> List[ValueType]: """ Get the values of annotations of a specific type. - :param str name: the name of the annotation type + :param name: the name of the annotation type :returns: a list of annotation values """ return [a.value for a in self.annotations if a.name == name] - def get_comment_lines(self): + def get_comment_lines(self) -> List[str]: """ Get the comment lines of the annotatable. @@ -445,28 +506,29 @@ def get_comment_lines(self): """ comments = [ x['text'] for x in self.get_annotation_values('verbatim') if + isinstance(x, Dict) and 'language' in x and 'text' in x and x['language'] == 'comment' ] - lines = [] + lines: List[str] = [] for comment in comments: - lines.extend(comment.splitlines()) + lines.extend(str(comment).splitlines()) return lines - def has_annotation(self, name): + def has_annotation(self, name: str) -> bool: """ Check if there is exactly one annotation of a specific type. - :param str name: the name of the annotation type + :param name: the name of the annotation type :returns: True if there is exactly one annotation, False otherwise """ values = self.get_annotation_values(name) return len(values) == 1 - def has_annotations(self, name): + def has_annotations(self, name: str) -> bool: """ Check if there are any annotations of a specific type. - :param str name: the name of the annotation type + :param name: the name of the annotation type :returns: True if there are any annotations, False otherwise """ annotations = self.get_annotation_values(name) @@ -496,7 +558,8 @@ class Structure(Annotatable): __slots__ = ('namespaced_type', 'members') - def __init__(self, namespaced_type: NamespacedType, members=None): + def __init__(self, namespaced_type: NamespacedType, + members: Optional[List['Member']] = None) -> None: """ Create a Structure. @@ -514,11 +577,11 @@ class Include: __slots__ = ('locator', ) - def __init__(self, locator): + def __init__(self, locator: str) -> None: """ Create an Include. - :param str locator: a URI identifying the included file + :param locator: a URI identifying the included file """ self.locator = locator @@ -528,7 +591,8 @@ class Constant(Annotatable): __slots__ = ('name', 'type', 'value') - def __init__(self, name: str, type_: AbstractType, value): + def __init__(self, name: str, type_: AbstractType, + value: Union[str, int, float, bool]) -> None: """ Create a Constant. @@ -548,7 +612,7 @@ class Message: __slots__ = ('structure', 'constants') - def __init__(self, structure: Structure): + def __init__(self, structure: Structure) -> None: """ Create a Message. @@ -557,7 +621,7 @@ def __init__(self, structure: Structure): super().__init__() assert isinstance(structure, Structure) self.structure = structure - self.constants = [] + self.constants: List[Constant] = [] class Service: @@ -568,7 +632,7 @@ class Service: def __init__( self, namespaced_type: NamespacedType, request: Message, response: Message - ): + ) -> None: """ Create a Service. @@ -622,7 +686,7 @@ class Action: def __init__( self, namespaced_type: NamespacedType, goal: Message, result: Message, feedback: Message - ): + ) -> None: """ Create an Action. @@ -732,18 +796,18 @@ class IdlLocator: __slots__ = ('basepath', 'relative_path') - def __init__(self, basepath, relative_path): + def __init__(self, basepath: str, relative_path: str) -> None: """ Create an IdlLocator. - :param str basepath: the basepath of file - :param str relative_path: the path relative to the basepath of the file + :param basepath: the basepath of file + :param relative_path: the path relative to the basepath of the file """ super().__init__() self.basepath = pathlib.Path(basepath) self.relative_path = pathlib.Path(relative_path) - def get_absolute_path(self): + def get_absolute_path(self) -> pathlib.Path: return self.basepath / self.relative_path @@ -752,11 +816,14 @@ class IdlContent: __slots__ = ('elements', ) - def __init__(self): + def __init__(self) -> None: super().__init__() - self.elements = [] + self.elements: List[Union[Include, Message, Service, Action]] = [] - def get_elements_of_type(self, type_): + def get_elements_of_type( + self, + type_: Type[Union[Include, Message, Service, Action]] + ) -> List[Union[Include, Message, Service, Action]]: return [e for e in self.elements if isinstance(e, type_)] @@ -765,12 +832,12 @@ class IdlFile: __slots__ = ('locator', 'content') - def __init__(self, locator, content): + def __init__(self, locator: IdlLocator, content: IdlContent) -> None: """ Create an IdlFile. - :param IdlLocator locator: the locator of the IDL file - :param IdlContent content: the content of the IDL file + :param locator: the locator of the IDL file + :param content: the content of the IDL file """ super().__init__() assert isinstance(locator, IdlLocator) From 4eecaccdaa56746546bab7248d76898fd5722a07 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Wed, 20 Mar 2024 17:29:48 -0400 Subject: [PATCH 2/4] Move Literal import Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/definition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rosidl_parser/rosidl_parser/definition.py b/rosidl_parser/rosidl_parser/definition.py index 94b7aa4a1..5bf49c414 100644 --- a/rosidl_parser/rosidl_parser/definition.py +++ b/rosidl_parser/rosidl_parser/definition.py @@ -16,7 +16,6 @@ from typing import Dict from typing import Iterable from typing import List -from typing import Literal from typing import Optional from typing import Set from typing import Tuple @@ -94,6 +93,7 @@ ) if TYPE_CHECKING: + from typing import Literal SignedNonexplicitIntegerTypeValues = Union[Literal['short'], Literal['long'], Literal['long long']] UnsignedNonexplicitIntegerTypeValues = Union[Literal['unsigned short'], From 0df8f0fc00d449664c088da8a6d96902cbc20cfd Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Wed, 20 Mar 2024 17:48:44 -0400 Subject: [PATCH 3/4] Cleaner Literals Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/definition.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/rosidl_parser/rosidl_parser/definition.py b/rosidl_parser/rosidl_parser/definition.py index 5bf49c414..1894a7165 100644 --- a/rosidl_parser/rosidl_parser/definition.py +++ b/rosidl_parser/rosidl_parser/definition.py @@ -94,25 +94,20 @@ if TYPE_CHECKING: from typing import Literal - SignedNonexplicitIntegerTypeValues = Union[Literal['short'], Literal['long'], - Literal['long long']] - UnsignedNonexplicitIntegerTypeValues = Union[Literal['unsigned short'], - Literal['unsigned long'], - Literal['unsigned long long']] + SignedNonexplicitIntegerTypeValues = Literal['short', 'long', 'long long'] + UnsignedNonexplicitIntegerTypeValues = Literal['unsigned short', 'unsigned long', + 'unsigned long long'] NonexplicitIntegerTypeValues = Union[SignedNonexplicitIntegerTypeValues, UnsignedNonexplicitIntegerTypeValues] - FloatingPointTypeValues = Union[Literal['float'], Literal['double'], - Literal['long double']] - CharacterTypeValues = Union[Literal['char'], Literal['wchar']] + FloatingPointTypeValues = Literal['float', 'double', 'long double'] + CharacterTypeValues = Literal['char', 'wchar'] BooleanValue = Literal['boolean'] OctetValue = Literal['octet'] - SignedExplicitIntegerTypeValues = Union[Literal['int8'], Literal['int16'], - Literal['int32'], Literal['int64']] - UnsignedExplicitIntegerTypeValues = Union[Literal['uint8'], Literal['uint16'], - Literal['uint32'], Literal['uint64']] + SignedExplicitIntegerTypeValues = Literal['int8', 'int16', 'int32' 'int64'] + UnsignedExplicitIntegerTypeValues = Literal['uint8', 'uint16', 'uint32', 'uint64'] ExplicitIntegerTypeValues = Union[SignedExplicitIntegerTypeValues, UnsignedExplicitIntegerTypeValues] From e818297c4bd41386dbeca8572598e3eb8aab42e4 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Wed, 20 Mar 2024 20:13:20 -0400 Subject: [PATCH 4/4] Add py.typed Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/py.typed | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rosidl_parser/rosidl_parser/py.typed diff --git a/rosidl_parser/rosidl_parser/py.typed b/rosidl_parser/rosidl_parser/py.typed new file mode 100644 index 000000000..e69de29bb