rostran/core/parameters.py (240 lines of code) (raw):

import re from typing import Union, List, Iterable from openpyxl.cell.cell import Cell from .exceptions import InvalidTemplateParameter, InvalidTemplateParameters from .metadata import MetaData, MetaItem from .utils import get_and_validate_cell, sorted_data class Parameter: TYPES = (STRING, NUMBER, LIST, MAP, BOOLEAN) = ( "String", "Number", "CommaDelimitedList", "Json", "Boolean", ) PROPERTIES = ( TYPE, LABEL, DESCRIPTION, CONSTRAINT_DESCRIPTION, ASSOCIATION_PROPERTY, ASSOCIATION_PROPERTY_METADATA, DEFAULT, ALLOWED_VALUES, ALLOWED_PATTERN, MIN_LENGTH, MAX_LENGTH, MIN_VALUE, MAX_VALUE, NO_ECHO, CONFIRM, TEXT_AREA, ) = ( "Type", "Label", "Description", "ConstraintDescription", "AssociationProperty", "AssociationPropertyMetadata", "Default", "AllowedValues", "AllowedPattern", "MinLength", "MaxLength", "MinValue", "MaxValue", "NoEcho", "Confirm", "TextArea", ) NULL = ... def __init__( self, name: str, type: str, default=None, association_property: str = None, association_property_metadata: dict = None, description: str = None, constraint_description: str = None, allowed_values: list = None, min_length: int = None, max_length: int = None, allowed_pattern: str = None, min_value: Union[int, float] = None, max_value: Union[int, float] = None, label=None, no_echo: bool = None, confirm: bool = None, text_area: bool = None, orig_data: dict = None, ): self.name = name self.type = type self.default = default self.association_property = association_property self.association_property_metadata = association_property_metadata self.description = description self.constraint_description = constraint_description self.allowed_values = allowed_values self.allowed_pattern = allowed_pattern self.min_length = min_length self.max_length = max_length self.no_echo = no_echo self.min_value = min_value self.max_value = max_value self.label = label self.confirm = confirm self.text_area = text_area self.orig_data = orig_data @classmethod def initialize(cls, name: str, value: dict): if not isinstance(value, dict): raise InvalidTemplateParameter( name=name, reason=f"The type of value ({value}) should be dict", ) if cls.DEFAULT in value: default = cls.NULL if value[cls.DEFAULT] is None else value[cls.DEFAULT] else: default = None param = cls( name, type=value.get(cls.TYPE), default=default, association_property=value.get(cls.ASSOCIATION_PROPERTY), association_property_metadata=value.get(cls.ASSOCIATION_PROPERTY_METADATA), description=value.get(cls.DESCRIPTION), constraint_description=value.get(cls.CONSTRAINT_DESCRIPTION), allowed_values=value.get(cls.ALLOWED_VALUES), min_length=value.get(cls.MIN_LENGTH), max_length=value.get(cls.MAX_LENGTH), allowed_pattern=value.get(cls.ALLOWED_PATTERN), min_value=value.get(cls.MIN_VALUE), max_value=value.get(cls.MAX_VALUE), label=value.get(cls.LABEL), no_echo=value.get(cls.NO_ECHO), confirm=value.get(cls.CONFIRM), text_area=value.get(cls.TEXT_AREA), orig_data=value, ) param.validate() return param @classmethod def initialize_from_excel(cls, header_cell: Cell, data_cell: Cell): param_name = get_and_validate_cell(header_cell, InvalidTemplateParameter) result = re.findall(r"(\S+)\((\S+)\)", param_name) if result: param_name, param_type = result[0] if param_type not in cls.TYPES: raise InvalidTemplateParameter( name=param_name, reason=f"Parameter type {param_type} of {header_cell} is not supported. Allowed types: {cls.TYPES}", ) else: param_name, param_type = param_name, cls.STRING param = cls(name=param_name, type=param_type, default=data_cell.value) param.validate() return param def validate(self): if not isinstance(self.name, str): raise InvalidTemplateParameter( name=self.name, reason=f"The type should be str", ) if self.type not in self.TYPES: raise InvalidTemplateParameter( name=self.name, reason=f"Parameter type {self.type} is not supported. Allowed types: {self.TYPES}", ) if self.association_property is not None and not isinstance( self.association_property, str ): raise InvalidTemplateParameter( name=self.name, reason=f"The type of {self.ASSOCIATION_PROPERTY} should be str", ) if self.association_property_metadata is not None and not isinstance( self.association_property_metadata, dict ): raise InvalidTemplateParameter( name=self.name, reason=f"The type of {self.ASSOCIATION_PROPERTY_METADATA} should be dict", ) def as_dict(self, format=False): if not format and self.orig_data: data = {k: v for k, v in self.orig_data.items() if v is not None} else: data = {self.TYPE: self.type} if self.label is not None: data.update({self.LABEL: self.label}) if self.description is not None: data.update({self.DESCRIPTION: self.description}) if self.constraint_description is not None: data.update({self.CONSTRAINT_DESCRIPTION: self.constraint_description}) if self.association_property is not None: data.update({self.ASSOCIATION_PROPERTY: self.association_property}) if self.association_property_metadata is not None: data.update( { self.ASSOCIATION_PROPERTY_METADATA: self.association_property_metadata } ) if self.default is not None: default = None if self.default is self.NULL else self.default data.update({self.DEFAULT: default}) if self.allowed_values is not None: data.update({self.ALLOWED_VALUES: self.allowed_values}) if self.allowed_pattern is not None: data.update({self.ALLOWED_PATTERN: self.allowed_pattern}) if self.min_length is not None: data.update({self.MIN_LENGTH: self.min_length}) if self.max_length is not None: data.update({self.MAX_LENGTH: self.max_length}) if self.min_value is not None: data.update({self.MIN_VALUE: self.min_value}) if self.max_value is not None: data.update({self.MAX_VALUE: self.max_value}) if self.no_echo is not None: data.update({self.NO_ECHO: self.no_echo}) if self.confirm is not None: data.update({self.CONFIRM: self.confirm}) if self.text_area is not None: data.update({self.TEXT_AREA: self.text_area}) return {self.name: data} class Parameters(dict): @classmethod def initialize(cls, data: dict): if not isinstance(data, dict): raise InvalidTemplateParameters( reason=f"The type of data ({data}) should be dict" ) params = cls() for name, value in data.items(): param = Parameter.initialize(name, value) params.add(param) return params def add(self, param: Parameter): self[param.name] = param def as_dict(self, format=False, metadata: MetaData = None) -> dict: data = {} keys = self.keys() if format and metadata: ros_interface = metadata.get(MetaData.ROS_INTERFACE) if ros_interface: param_groups = ros_interface.value.get(MetaItem.PARAMETER_GROUPS) score = 0 key_scores = {} if param_groups: for param_group in param_groups: parameters = param_group[MetaItem.PARAMETERS] for parameter in parameters: if isinstance(parameter, str): key_scores[parameter] = score score += 1 if key_scores: keys = sorted_data(keys, scores=key_scores) for key in keys: param: Parameter = self[key] data.update(param.as_dict(format)) return data