init version

This commit is contained in:
Xuwznln
2025-09-02 16:39:44 +08:00
commit 94f0c112e5
41 changed files with 6004 additions and 0 deletions

View File

View File

@@ -0,0 +1,54 @@
from __future__ import annotations
from typing import Any, Dict, TypedDict
ENVELOPE_VERSION: str = "1"
class Properties(TypedDict, total=False):
ros_msg_cls_path: str
ros_msg_cls_namespace: str
json_schema: Dict[str, Any]
class FormatMetadata(TypedDict, total=False):
"""Additional metadata for source format, optional.
Examples: field statistics, original type descriptions, field type mappings, etc.
"""
current_format: str
source_cls_name: str
source_cls_module: str
properties: Properties
class MessageEnvelope(TypedDict, total=True):
"""Unified message envelope format.
- version: Protocol version
- format: Source format (MessageType.value)
- type_info: Type information (applicable for ROS2, Pydantic, etc.)
- content: Normalized message content (dictionary)
- metadata: Additional metadata
"""
version: str
format: str
content: Dict[str, Any]
metadata: FormatMetadata
def create_envelope(
*,
format_name: str,
content: Dict[str, Any],
metadata: FormatMetadata,
) -> MessageEnvelope:
env: MessageEnvelope = {
"version": ENVELOPE_VERSION,
"format": format_name,
"content": content,
"metadata": metadata,
}
return env

View File

@@ -0,0 +1,406 @@
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional, cast
from msgcenterpy.core.type_converter import StandardType
from msgcenterpy.core.type_info import (
ConstraintType,
Consts,
TypeInfo,
TypeInfoPostProcessor,
)
from msgcenterpy.utils.decorator import experimental
TEST_MODE = True
class FieldAccessor:
"""
字段访问器,支持类型转换和约束验证的统一字段访问接口
只需要getitem和setitem外部必须通过字典的方式来赋值
"""
@property
def parent_msg_center(self) -> Optional["FieldAccessor"]:
return self._parent
@property
def full_path_from_root(self) -> str:
if self._parent is None:
return self._field_name or "unknown"
else:
parent_path = self._parent.full_path_from_root
return f"{parent_path}.{self._field_name or 'unknown'}"
@property
def root_accessor_msg_center(self) -> "FieldAccessor":
"""获取根访问器"""
current = self
while current._parent is not None:
current = current._parent
return current
@property
def value(self) -> Any:
return self._data
@value.setter
def value(self, data: Any) -> None:
if self._parent is not None and self._field_name is not None:
self._parent[self._field_name] = data
@property
def type_info(self) -> Optional[TypeInfo]:
if self._type_info is not None:
return self._type_info
# 如果是根accessor或者没有字段名无法获取TypeInfo
if self._parent is None or self._field_name is None:
return None
# 调用类型信息提供者获取类型信息,调用是耗时的
if self._type_info_provider is None:
return None
type_info = self._type_info_provider.get_field_type_info(self._field_name, self._data, self._parent)
# 对TypeInfo进行后处理添加默认约束
if type_info:
TypeInfoPostProcessor.post_process_type_info(type_info)
self._type_info = type_info
return type_info
"""标记方便排除getitem/setitem不要删除"""
_data: Any = None
_type_info_provider: "TypeInfoProvider" = None # type: ignore[assignment]
_parent: Optional["FieldAccessor"] = None
_field_name: str = None # type: ignore[assignment]
_cache: Dict[str, "FieldAccessor"] = None # type: ignore[assignment]
_type_info: Optional[TypeInfo] = None
def __init__(
self,
data: Any,
type_info_provider: "TypeInfoProvider",
parent: Optional["FieldAccessor"],
field_name: str,
):
"""
初始化字段访问器
Args:
data: 要访问的数据对象
type_info_provider: 类型信息提供者
parent: 父字段访问器,用于嵌套访问
field_name: 当前访问器对应的字段名(用于构建路径)
"""
self._data = data
self._type_info_provider = type_info_provider
self._parent = parent
self._field_name = field_name
self._cache: Dict[str, "FieldAccessor"] = {} # 缓存FieldAccessor而不是TypeInfo
self._type_info: Optional[TypeInfo] = None # 当前accessor的TypeInfo
def get_sub_type_info(self, field_name: str) -> Optional[TypeInfo]:
"""获取字段的类型信息通过获取字段的accessor"""
field_accessor = self[field_name]
return field_accessor.type_info
def __getitem__(self, field_name: str) -> "FieldAccessor":
"""获取字段访问器,支持嵌套访问"""
# 检查缓存中是否有对应的 accessor
if self._cache is None:
self._cache = {}
if field_name in self._cache:
cached_accessor = self._cache[field_name]
# 更新 accessor 的数据源,以防数据已更改
if TEST_MODE:
raw_value = self._get_raw_value(field_name)
if cached_accessor.value != raw_value:
raise ValueError(
f"Cached accessor value mismatch for field '{field_name}': expected {raw_value}, got {cached_accessor.value}"
)
return cached_accessor
# 获取原始值并创建新的 accessor
raw_value = self._get_raw_value(field_name)
if self._type_info_provider is None:
raise RuntimeError("TypeInfoProvider not initialized")
accessor = FieldAccessorFactory.create_accessor(
data=raw_value,
type_info_provider=self._type_info_provider,
parent=self,
field_name=field_name,
)
self._cache[field_name] = accessor
return accessor
def __setitem__(self, field_name: str, value: Any) -> None:
"""设置字段值,支持类型转换和验证"""
# 获取类型信息
if field_name in self._get_field_names():
type_info = self.get_sub_type_info(field_name)
if type_info is not None:
# 进行类型转换
converted_value = type_info.convert_value(value) # 这步自带validate
value = converted_value
# 对子field设置value依然会上溯走set_raw_value确保一致性
# 设置值
sub_accessor = self[field_name]
self._set_raw_value(field_name, value)
sub_accessor._data = self._get_raw_value(field_name) # 有可能内部还有赋值的处理
# 清除相关缓存
self.clear_cache(field_name)
def __contains__(self, field_name: str) -> bool:
return self._has_field(field_name)
def __getattr__(self, field_name: str) -> "FieldAccessor | Any":
"""支持通过属性访问字段,用于嵌套访问如 accessor.pose.position.x"""
for cls in self.__class__.__mro__:
if field_name in cls.__dict__:
return cast(Any, super().__getattribute__(field_name))
return self[field_name]
def __setattr__(self, field_name: str, value: Any) -> None:
"""支持通过属性设置字段值,用于嵌套赋值如 accessor.pose.position.x = 1.0"""
for cls in self.__class__.__mro__:
if field_name in cls.__dict__:
return super().__setattr__(field_name, value)
self[field_name] = value
return None
def clear_cache(self, field_name: Optional[str] = None) -> None:
"""失效字段相关的缓存"""
if self._cache is not None and field_name is not None and field_name in self._cache:
self._cache[field_name].clear_type_info()
def clear_type_info(self) -> None:
"""清空所有缓存"""
if self._type_info is not None:
self._type_info._outdated = True
self._type_info = None
def get_nested_field_accessor(self, path: str, separator: str = ".") -> "FieldAccessor":
parts = path.split(separator)
current = self
for part in parts:
current = self[part]
return current
def set_nested_value(self, path: str, value: Any, separator: str = ".") -> None:
current = self.get_nested_field_accessor(path, separator)
current.value = value
def _get_raw_value(self, field_name: str) -> Any:
"""获取原始字段值(子类实现)"""
if hasattr(self._data, "__getitem__"):
return self._data[field_name]
elif hasattr(self._data, field_name):
return getattr(self._data, field_name)
else:
raise KeyError(f"Field {field_name} not found")
def _set_raw_value(self, field_name: str, value: Any) -> None:
"""设置原始字段值(子类实现)"""
if hasattr(self._data, "__setitem__"):
self._data[field_name] = value
elif hasattr(self._data, field_name):
setattr(self._data, field_name, value)
else:
raise KeyError(f"Field {field_name} not found")
def _has_field(self, field_name: str) -> bool:
"""检查字段是否存在(子类实现)"""
if hasattr(self._data, "__contains__"):
return field_name in self._data
else:
return field_name in self._get_field_names()
def _get_field_names(self) -> list[str]:
"""获取所有字段名(子类实现)"""
if callable(getattr(self._data, "keys", None)):
# noinspection PyCallingNonCallable
return list(self._data.keys())
elif hasattr(self._data, "__dict__"):
return list(self._data.__dict__.keys())
elif hasattr(self._data, "__slots__"):
# noinspection PyTypeChecker
return list(self._data.__slots__)
else:
# 回退方案使用dir()但过滤掉特殊方法
return [name for name in dir(self._data) if not name.startswith("_")]
def get_json_schema(self) -> Dict[str, Any]:
"""原有的递归生成 JSON Schema 逻辑"""
# 获取当前访问器的类型信息
current_type_info = self.type_info
# 如果当前层级有类型信息使用它生成基本schema
if current_type_info is not None:
schema = current_type_info.to_json_schema_property()
else:
# 如果没有类型信息创建基本的object schema
schema = {"type": "object", "additionalProperties": True}
# 如果这是一个对象类型,需要递归处理其字段
if schema.get("type") == "object":
properties = {}
required_fields = []
# 获取所有字段名
field_names = self._get_field_names()
for field_name in field_names:
try:
# 获取字段的访问器
field_accessor = self[field_name]
field_type_info = field_accessor.type_info
if field_type_info is not None:
# 根据字段类型决定如何生成schema
if field_type_info.standard_type == StandardType.OBJECT:
# 对于嵌套对象,递归调用
field_schema = field_accessor.get_json_schema()
else:
# 对于基本类型直接使用type_info转换
field_schema = field_type_info.to_json_schema_property()
properties[field_name] = field_schema
# 检查是否为必需字段
if field_type_info.has_constraint(ConstraintType.REQUIRED):
required_fields.append(field_name)
except Exception as e:
# 如果字段处理失败,记录警告但继续处理其他字段
print(f"Warning: Failed to generate schema for field '{field_name}': {e}")
continue
# 更新schema中的properties
if properties:
schema["properties"] = properties
# 设置必需字段
if required_fields:
schema["required"] = required_fields
# 如果没有字段信息,允许额外属性
if not properties:
schema["additionalProperties"] = True
else:
schema["additionalProperties"] = False
return schema
@experimental("Feature under development")
def update_from_dict(self, source_data: Dict[str, Any]) -> None:
"""递归更新嵌套字典
Args:
source_data: 源数据字典
"""
for key, new_value in source_data.items():
field_exists = self._has_field(key)
could_add = self._could_allow_new_field(key, new_value)
if field_exists:
current_field_accessor = self[key]
current_type_info = current_field_accessor.type_info
# 当前key: Object交给子dict去迭代
if (
current_type_info
and current_type_info.standard_type == StandardType.OBJECT
and isinstance(new_value, dict)
):
current_field_accessor.update_from_dict(new_value)
# 当前key: Array每个值要交给子array去迭代
elif (
current_type_info
and hasattr(current_type_info.standard_type, "IS_ARRAY")
and current_type_info.standard_type.IS_ARRAY
and isinstance(new_value, list)
):
# 存在情况Array嵌套这里后续支持逐个赋值可能需要利用iter进行赋值
self[key] = new_value
else:
# 不限制类型 或 python类型包含
if could_add or (current_type_info and issubclass(type(new_value), current_type_info.python_type)):
self[key] = new_value
else:
raise ValueError(f"{key}")
elif could_add:
self[key] = new_value
def _could_allow_new_field(self, field_name: str, field_value: Any) -> bool:
"""检查是否应该允许添加新字段
通过检查当前type_info中的TYPE_KEEP约束来判断
- 如果有TYPE_KEEP且为True说明类型结构固定不允许添加新字段
- 如果没有TYPE_KEEP约束或为False则允许添加新字段
Args:
field_name: 字段名
field_value: 字段值
Returns:
是否允许添加该字段
"""
parent_type_info = self.type_info
if parent_type_info is None:
return True # DEBUGGER NEEDED
type_keep_constraint = parent_type_info.get_constraint(ConstraintType.TYPE_KEEP)
if type_keep_constraint is not None and type_keep_constraint.value:
return False
return True
class TypeInfoProvider(ABC):
"""Require All Message Instances Extends This get_field_typ_info"""
@abstractmethod
def get_field_type_info(
self, field_name: str, field_value: Any, field_accessor: "FieldAccessor"
) -> Optional[TypeInfo]:
"""获取指定字段的类型信息
Args:
field_name: 字段名,简单字段名如 'field'
field_value: 字段的当前值用于动态类型推断不能为None
field_accessor: 字段访问器提供额外的上下文信息不能为None
Returns:
字段的TypeInfo如果字段不存在则返回None
"""
pass
class ROS2FieldAccessor(FieldAccessor):
def _get_raw_value(self, field_name: str) -> Any:
return getattr(self._data, field_name)
def _set_raw_value(self, field_name: str, value: Any) -> None:
return setattr(self._data, field_name, value)
def _has_field(self, field_name: str) -> bool:
return hasattr(self._data, field_name)
def _get_field_names(self) -> list[str]:
if hasattr(self._data, "_fields_and_field_types"):
# noinspection PyProtectedMember
fields_and_types: Dict[str, str] = cast(Dict[str, str], self._data._fields_and_field_types)
return list(fields_and_types.keys())
else:
return []
class FieldAccessorFactory:
@staticmethod
def create_accessor(
data: Any,
type_info_provider: TypeInfoProvider,
parent: Optional[FieldAccessor] = None,
field_name: str = Consts.ACCESSOR_ROOT_NODE,
) -> FieldAccessor:
if hasattr(data, "_fields_and_field_types"):
return ROS2FieldAccessor(data, type_info_provider, parent, field_name)
else:
return FieldAccessor(data, type_info_provider, parent, field_name)

View File

@@ -0,0 +1,69 @@
from typing import Any, Dict, Optional, Type
from msgcenterpy.core.envelope import MessageEnvelope, Properties
from msgcenterpy.core.message_instance import MessageInstance
from msgcenterpy.core.types import MessageType
class MessageCenter:
"""Message Center singleton class that manages all message types and instances"""
_instance: Optional["MessageCenter"] = None
@classmethod
def get_instance(cls) -> "MessageCenter":
"""Get MessageCenter singleton instance"""
if cls._instance is None:
cls._instance = cls()
return cls._instance
def __init__(self) -> None:
"""Private constructor, use get_instance() to get singleton"""
self._type_registry: Dict[MessageType, Type[MessageInstance]] = {}
self._register_builtin_types()
def _register_builtin_types(self) -> None:
"""Register built-in message types with lazy import to avoid circular dependencies"""
try:
from msgcenterpy.instances.ros2_instance import ROS2MessageInstance
self._type_registry[MessageType.ROS2] = ROS2MessageInstance
except ImportError:
pass
try:
from msgcenterpy.instances.json_schema_instance import (
JSONSchemaMessageInstance,
)
self._type_registry[MessageType.JSON_SCHEMA] = JSONSchemaMessageInstance
except ImportError:
pass
def get_instance_class(self, message_type: MessageType) -> Type[MessageInstance]:
"""Get instance class for the specified message type"""
instance_class = self._type_registry.get(message_type)
if not instance_class:
raise ValueError(f"Unsupported message type: {message_type}")
return instance_class
def convert(
self,
source: MessageInstance,
target_type: MessageType,
override_properties: Dict[str, Any],
**kwargs: Any,
) -> MessageInstance:
"""Convert message types"""
target_class = self.get_instance_class(target_type)
dict_data: MessageEnvelope = source.export_to_envelope()
if "properties" not in dict_data["metadata"]:
dict_data["metadata"]["properties"] = Properties()
dict_data["metadata"]["properties"].update(override_properties) # type: ignore[typeddict-item]
target_instance = target_class.import_from_envelope(dict_data)
return target_instance
# Module-level convenience function using singleton
def get_message_center() -> MessageCenter:
"""Get message center singleton"""
return MessageCenter.get_instance()

View File

@@ -0,0 +1,193 @@
import uuid
from abc import ABC, abstractmethod
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any, Dict, Generic, Optional, Type, TypeVar, cast
from msgcenterpy.core.envelope import FormatMetadata, MessageEnvelope, Properties
from msgcenterpy.core.field_accessor import (
FieldAccessor,
FieldAccessorFactory,
TypeInfoProvider,
)
from msgcenterpy.core.types import MessageType
if TYPE_CHECKING:
# 仅用于类型检查的导入,避免运行时循环依赖
from msgcenterpy.instances.json_schema_instance import JSONSchemaMessageInstance
from msgcenterpy.instances.ros2_instance import ROS2MessageInstance
T = TypeVar("T")
class MessageInstance(TypeInfoProvider, ABC, Generic[T]):
"""统一消息实例基类"""
_init_ok: bool = False
# 字段访问器相关方法
@property
def fields(self) -> FieldAccessor:
if self._field_accessor is None:
raise RuntimeError("FieldAccessor not initialized")
return self._field_accessor
def __setattr__(self, field_name: str, value: Any) -> None:
if not self._init_ok:
return super().__setattr__(field_name, value)
for cls in self.__class__.__mro__:
if field_name in cls.__dict__:
return super().__setattr__(field_name, value)
self.fields[field_name] = value
return None
def __getattr__(self, field_name: str) -> Any:
if not self._init_ok:
return super().__getattribute__(field_name)
for cls in self.__class__.__mro__:
if field_name in cls.__dict__:
return super().__getattribute__(field_name)
return self.fields[field_name]
def __getitem__(self, field_name: str) -> Any:
"""支持通过下标访问字段"""
return self.fields[field_name]
def __setitem__(self, field_name: str, value: Any) -> None:
"""支持通过下标设置字段"""
self.fields[field_name] = value
def __contains__(self, field_name: str) -> bool:
"""支持in操作符检查字段是否存在"""
return field_name in self.fields
def __init__(
self,
inner_data: T,
message_type: MessageType,
metadata: Optional[FormatMetadata] = None,
):
# 初始化标记和基础属性
self._field_accessor: Optional[FieldAccessor] = None
self._instance_id: str = str(uuid.uuid4())
self.inner_data: T = inner_data # 原始类型数据
self.message_type: MessageType = message_type
self._metadata: FormatMetadata = metadata or FormatMetadata()
self._created_at = datetime.now(timezone.utc)
self._collect_public_properties_to_metadata()
self._field_accessor = FieldAccessorFactory.create_accessor(self.inner_data, self)
self._init_ok = True
def _collect_public_properties_to_metadata(self) -> None:
"""将所有非下划线开头的 @property 的当前值放入 metadata.properties 中。
仅收集只读属性,忽略访问抛出异常的属性。
"""
properties_bucket = self._metadata.setdefault("properties", Properties())
for cls in self.__class__.__mro__:
for attribute_name, attribute_value in cls.__dict__.items():
if attribute_name.startswith("_"):
continue
if isinstance(attribute_value, property):
try:
# 避免重复收集已存在的属性
if attribute_name not in properties_bucket:
properties_bucket[attribute_name] = getattr(self, attribute_name) # type: ignore[literal-required]
except (AttributeError, TypeError, RuntimeError):
# Skip attributes that can't be accessed or have incompatible types
# This includes attributes that require initialization to complete (like 'fields')
pass
def to(self, target_type: MessageType, **kwargs: Any) -> "MessageInstance[Any]":
"""直接转换到目标类型"""
if target_type == MessageType.ROS2:
return cast("MessageInstance[Any]", self.to_ros2(**kwargs))
elif target_type == MessageType.DICT:
return cast("MessageInstance[Any]", self.to_dict(**kwargs))
elif target_type == MessageType.JSON:
return cast("MessageInstance[Any]", self.to_json(**kwargs))
elif target_type == MessageType.JSON_SCHEMA:
return cast("MessageInstance[Any]", self.to_json_schema(**kwargs))
elif target_type == MessageType.YAML:
return cast("MessageInstance[Any]", self.to_yaml(**kwargs))
elif target_type == MessageType.PYDANTIC:
return cast("MessageInstance[Any]", self.to_pydantic(**kwargs))
elif target_type == MessageType.DATACLASS:
return cast("MessageInstance[Any]", self.to_dataclass(**kwargs))
else:
raise ValueError(f"Unsupported target message type: {target_type}")
@classmethod
@abstractmethod
def import_from_envelope(cls, data: MessageEnvelope, **kwargs: Any) -> "MessageInstance[Any]":
"""从统一信封字典创建实例(仅接受 data 一个参数)。"""
# metadata会被重置
pass
@abstractmethod
def export_to_envelope(self, **kwargs: Any) -> MessageEnvelope:
"""导出为字典格式"""
pass
@abstractmethod
def get_python_dict(self) -> Dict[str, Any]:
"""获取当前所有的字段名和对应的python可读值"""
pass
@abstractmethod
def set_python_dict(self, value: Dict[str, Any], **kwargs: Any) -> bool:
"""设置所有字段的值"""
pass
def get_json_schema(self) -> Dict[str, Any]:
"""生成当前消息实例的JSON Schema委托给FieldAccessor递归处理"""
# 直接调用FieldAccessor的get_json_schema方法
schema = self.fields.get_json_schema()
# 添加schema元信息对于JSONSchemaMessageInstance如果已有title则保持否则添加默认title
from msgcenterpy.instances.json_schema_instance import JSONSchemaMessageInstance
if isinstance(self, JSONSchemaMessageInstance):
# 对于JSON Schema实例如果schema中没有title则添加一个
if "title" not in schema:
schema["title"] = f"{self.__class__.__name__} Schema" # type: ignore
if "description" not in schema:
schema["description"] = f"JSON Schema generated from {self.message_type.value} message instance" # type: ignore
else:
# 对于其他类型的实例总是添加schema元信息
schema["title"] = f"{self.__class__.__name__} Schema" # type: ignore
schema["description"] = f"JSON Schema generated from {self.message_type.value} message instance" # type: ignore
return schema
def __repr__(self) -> str:
return f"{self.__class__.__name__}(type={self.message_type.value}, id={self._instance_id[:8]})"
# 便捷转换方法使用MessageCenter单例
def to_ros2(self, type_hint: str | Type[Any], **kwargs: Any) -> "ROS2MessageInstance":
"""转换到ROS2实例。传入必备的类型提示"""
override_properties = {}
from msgcenterpy.core.message_center import get_message_center
ros2_message_instance = cast(
ROS2MessageInstance,
get_message_center().get_instance_class(MessageType.ROS2),
)
ros_type = ros2_message_instance.obtain_ros_cls_from_str(type_hint)
override_properties["ros_msg_cls_path"] = ROS2MessageInstance.get_ros_msg_cls_path(ros_type)
override_properties["ros_msg_cls_namespace"] = ROS2MessageInstance.get_ros_msg_cls_namespace(ros_type)
return cast(
ROS2MessageInstance,
get_message_center().convert(self, MessageType.ROS2, override_properties, **kwargs),
)
def to_json_schema(self, **kwargs: Any) -> "JSONSchemaMessageInstance":
"""转换到JSON Schema实例"""
override_properties = {"json_schema": self.get_json_schema()}
from msgcenterpy.core.message_center import get_message_center
from msgcenterpy.instances.json_schema_instance import JSONSchemaMessageInstance
return cast(
JSONSchemaMessageInstance,
get_message_center().convert(self, MessageType.JSON_SCHEMA, override_properties, **kwargs),
)

View File

@@ -0,0 +1,411 @@
from datetime import datetime
from decimal import Decimal
from enum import Enum
from typing import Any, Dict, Type, Union, get_args, get_origin
class StandardType(Enum):
"""标准化的数据类型,用于不同数据源之间的转换
增强版本,提供更细粒度的类型保留以更好地保存原始类型信息
"""
# 基础类型
STRING = "string" # 字符串类型
WSTRING = "wstring" # 宽字符串类型
CHAR = "char" # 字符类型
WCHAR = "wchar" # 宽字符类型
# 整数类型(细分以保留精度信息)
INT8 = "int8" # 8位有符号整数
UINT8 = "uint8" # 8位无符号整数
INT16 = "int16" # 16位有符号整数
UINT16 = "uint16" # 16位无符号整数
INT32 = "int32" # 32位有符号整数
UINT32 = "uint32" # 32位无符号整数
INT64 = "int64" # 64位有符号整数
UINT64 = "uint64" # 64位无符号整数
INTEGER = "integer" # 通用整数类型(向后兼容)
BYTE = "byte" # 字节类型
OCTET = "octet" # 八位字节类型
# 浮点类型(细分以保留精度信息)
FLOAT32 = "float32" # 32位浮点数
FLOAT64 = "float64" # 64位浮点数双精度
DOUBLE = "double" # 双精度浮点数
FLOAT = "float" # 通用浮点类型(向后兼容)
# 布尔类型
BOOLEAN = "boolean" # 布尔类型
BOOL = "bool" # 布尔类型ROS2风格
# 空值类型
NULL = "null" # 空值类型
# 容器类型
ARRAY = "array" # 数组/序列类型
BOUNDED_ARRAY = "bounded_array" # 有界数组类型
UNBOUNDED_ARRAY = "unbounded_array" # 无界数组类型
SEQUENCE = "sequence" # 序列类型
BOUNDED_SEQUENCE = "bounded_sequence" # 有界序列类型
UNBOUNDED_SEQUENCE = "unbounded_sequence" # 无界序列类型
OBJECT = "object" # 对象/映射类型
# 扩展类型
DATETIME = "datetime" # 日期时间类型
TIME = "time" # 时间类型
DURATION = "duration" # 持续时间类型
BYTES = "bytes" # 字节数据类型
DECIMAL = "decimal" # 精确小数类型
# 特殊类型
UNKNOWN = "unknown" # 未知类型
ANY = "any" # 任意类型
@property
def IS_ARRAY(self) -> bool:
"""判断该类型是否为数组/序列类型"""
array_like = {
StandardType.ARRAY,
StandardType.BOUNDED_ARRAY,
StandardType.UNBOUNDED_ARRAY,
StandardType.SEQUENCE,
StandardType.BOUNDED_SEQUENCE,
StandardType.UNBOUNDED_SEQUENCE,
}
return self in array_like
class TypeConverter:
"""类型转换器,负责不同数据源类型之间的转换和标准化"""
# Python基础类型到标准类型的映射
PYTHON_TO_STANDARD = {
str: StandardType.STRING,
int: StandardType.INTEGER, # 保持向后兼容的通用整数
float: StandardType.FLOAT, # 保持向后兼容的通用浮点
bool: StandardType.BOOLEAN,
type(None): StandardType.NULL,
list: StandardType.ARRAY,
tuple: StandardType.ARRAY,
dict: StandardType.OBJECT,
datetime: StandardType.DATETIME,
bytes: StandardType.BYTES,
bytearray: StandardType.BYTES,
Decimal: StandardType.DECIMAL,
}
# 标准类型到Python类型的映射
STANDARD_TO_PYTHON = {
# 字符串类型
StandardType.STRING: str,
StandardType.WSTRING: str,
StandardType.CHAR: str,
StandardType.WCHAR: str,
# 整数类型都映射到intPython会自动处理范围
StandardType.INT8: int,
StandardType.UINT8: int,
StandardType.INT16: int,
StandardType.UINT16: int,
StandardType.INT32: int,
StandardType.UINT32: int,
StandardType.INT64: int,
StandardType.UINT64: int,
StandardType.INTEGER: int,
StandardType.BYTE: int,
StandardType.OCTET: int,
# 浮点类型
StandardType.FLOAT32: float,
StandardType.FLOAT64: float,
StandardType.DOUBLE: float,
StandardType.FLOAT: float,
# 布尔类型
StandardType.BOOLEAN: bool,
StandardType.BOOL: bool,
# 空值类型
StandardType.NULL: type(None),
# 容器类型
StandardType.ARRAY: list,
StandardType.BOUNDED_ARRAY: list,
StandardType.UNBOUNDED_ARRAY: list,
StandardType.SEQUENCE: list,
StandardType.BOUNDED_SEQUENCE: list,
StandardType.UNBOUNDED_SEQUENCE: list,
StandardType.OBJECT: dict,
# 扩展类型
StandardType.DATETIME: datetime,
StandardType.TIME: datetime,
StandardType.DURATION: float, # 持续时间用秒表示
StandardType.BYTES: bytes,
StandardType.DECIMAL: Decimal,
# 特殊类型
StandardType.UNKNOWN: object,
StandardType.ANY: object,
}
# ROS2类型到标准类型的映射保留原始类型精度
ROS2_TO_STANDARD = {
# 字符串类型(保留具体类型)
"string": StandardType.STRING,
"wstring": StandardType.WSTRING,
"char": StandardType.CHAR,
"wchar": StandardType.WCHAR,
# 整数类型(保留精度信息)
"int8": StandardType.INT8,
"uint8": StandardType.UINT8,
"int16": StandardType.INT16,
"short": StandardType.INT16, # to check
"uint16": StandardType.UINT16,
"unsigned short": StandardType.UINT16, # to check
"int32": StandardType.INT32,
"uint32": StandardType.UINT32,
"int64": StandardType.INT64,
"long": StandardType.INT64, # to check
"long long": StandardType.INT64, # to check
"uint64": StandardType.UINT64,
"unsigned long": StandardType.UINT64, # to check
"unsigned long long": StandardType.UINT64, # to check
"byte": StandardType.BYTE,
"octet": StandardType.OCTET,
# 浮点类型(保留精度信息)
"float32": StandardType.FLOAT32,
"float64": StandardType.FLOAT64,
"double": StandardType.DOUBLE,
"long double": StandardType.DOUBLE,
"float": StandardType.FLOAT32, # 默认为32位
# 布尔类型
"bool": StandardType.BOOL,
"boolean": StandardType.BOOLEAN,
# 时间和持续时间(更精确的类型映射)
"time": StandardType.TIME,
"duration": StandardType.DURATION,
# 向后兼容的通用映射(当需要时可以回退到这些)
"generic_int": StandardType.INTEGER,
"generic_float": StandardType.FLOAT,
"generic_bool": StandardType.BOOLEAN,
"generic_string": StandardType.STRING,
}
# JSON Schema类型到标准类型的映射
JSON_SCHEMA_TO_STANDARD = {
"string": StandardType.STRING,
"integer": StandardType.INTEGER,
"number": StandardType.FLOAT,
"boolean": StandardType.BOOLEAN,
"null": StandardType.NULL,
"array": StandardType.ARRAY,
"object": StandardType.OBJECT,
}
# 标准类型到Python类型的映射
STANDARD_TO_JSON_SCHEMA = {
# 字符串类型
StandardType.STRING: "string",
StandardType.WSTRING: "string",
StandardType.CHAR: "string",
StandardType.WCHAR: "string",
# 整数类型都映射到intPython会自动处理范围
StandardType.INT8: "integer",
StandardType.UINT8: "integer",
StandardType.INT16: "integer",
StandardType.UINT16: "integer",
StandardType.INT32: "integer",
StandardType.UINT32: "integer",
StandardType.INT64: "integer",
StandardType.UINT64: "integer",
StandardType.INTEGER: "integer",
StandardType.BYTE: "integer",
StandardType.OCTET: "integer",
# 浮点类型
StandardType.FLOAT32: "number",
StandardType.FLOAT64: "number",
StandardType.DOUBLE: "number",
StandardType.FLOAT: "number",
# 布尔类型
StandardType.BOOLEAN: "boolean",
StandardType.BOOL: "boolean",
# 空值类型
StandardType.NULL: "null",
# 容器类型
StandardType.ARRAY: "array",
StandardType.BOUNDED_ARRAY: "array",
StandardType.UNBOUNDED_ARRAY: "array",
StandardType.SEQUENCE: "array",
StandardType.BOUNDED_SEQUENCE: "array",
StandardType.UNBOUNDED_SEQUENCE: "array",
StandardType.OBJECT: "object",
# 扩展类型
StandardType.DATETIME: "string", # 在JSON Schema中日期时间通常表示为字符串
StandardType.TIME: "string",
StandardType.DURATION: "number",
StandardType.BYTES: "string", # 字节数据在JSON Schema中通常表示为base64字符串
StandardType.DECIMAL: "number",
# 特殊类型
StandardType.UNKNOWN: "string",
StandardType.ANY: "string",
}
# Array typecode到标准类型的映射更精确的类型保留
ARRAY_TYPECODE_TO_STANDARD = {
"b": StandardType.INT8, # signed char
"B": StandardType.UINT8, # unsigned char
"h": StandardType.INT16, # signed short
"H": StandardType.UINT16, # unsigned short
"i": StandardType.INT32, # signed int
"I": StandardType.UINT32, # unsigned int
"l": StandardType.INT64, # signed long
"L": StandardType.UINT64, # unsigned long
"f": StandardType.FLOAT32, # float
"d": StandardType.FLOAT64, # double
}
# Array typecode到Python类型的映射
ARRAY_TYPECODE_TO_PYTHON = {
"b": int, # signed char
"B": int, # unsigned char
"h": int, # signed short
"H": int, # unsigned short
"i": int, # signed int
"I": int, # unsigned int
"l": int, # signed long
"L": int, # unsigned long
"f": float, # float
"d": float, # double
}
"""Python Type"""
@classmethod
def python_type_to_standard(cls, python_type: Type) -> StandardType:
"""将Python类型转换为标准类型"""
# 处理泛型类型
origin = get_origin(python_type)
if origin is not None:
if origin in (list, tuple):
return StandardType.ARRAY
elif origin is dict:
return StandardType.OBJECT
elif origin in (Union, type(Union[int, None])):
# 处理Optional类型和Union类型
args = get_args(python_type)
non_none_types = [arg for arg in args if arg != type(None)]
if len(non_none_types) == 1:
return cls.python_type_to_standard(non_none_types[0])
return StandardType.ANY
# 处理基础类型
return cls.PYTHON_TO_STANDARD.get(python_type, StandardType.UNKNOWN)
@classmethod
def standard_to_python_type(cls, standard_type: StandardType) -> Type:
"""将标准类型转换为Python类型"""
return cls.STANDARD_TO_PYTHON.get(standard_type, object)
"""ROS2"""
@classmethod
def ros2_type_str_to_standard(cls, ros2_type_str: str) -> StandardType:
"""将ROS2类型字符串转换为标准类型"""
if "[" in ros2_type_str and "]" in ros2_type_str:
return StandardType.ARRAY
base_type = ros2_type_str.split("/")[-1].lower()
return cls.ROS2_TO_STANDARD.get(base_type, StandardType.UNKNOWN)
@classmethod
def rosidl_definition_to_standard(cls, definition_type: Any) -> StandardType:
from rosidl_parser.definition import ( # type: ignore
Array,
BasicType,
BoundedSequence,
BoundedString,
BoundedWString,
NamedType,
NamespacedType,
UnboundedSequence,
UnboundedString,
UnboundedWString,
)
# 基础类型转换(保留原始类型精度)
if isinstance(definition_type, BasicType):
type_name = definition_type.typename.lower()
return cls.ros2_type_str_to_standard(type_name)
# 字符串类型(区分普通字符串和宽字符串)
elif isinstance(definition_type, (UnboundedString, BoundedString)):
return StandardType.STRING
elif isinstance(definition_type, (UnboundedWString, BoundedWString)):
return StandardType.WSTRING
# 数组和序列类型(更精确的类型区分)
elif isinstance(definition_type, Array):
return StandardType.BOUNDED_ARRAY
elif isinstance(definition_type, UnboundedSequence):
return StandardType.UNBOUNDED_SEQUENCE
elif isinstance(definition_type, BoundedSequence):
return StandardType.BOUNDED_SEQUENCE
# 命名类型和命名空间类型统一为OBJECT
elif isinstance(definition_type, (NamedType, NamespacedType)):
return StandardType.OBJECT
# 未知类型
else:
return StandardType.UNKNOWN
@classmethod
def array_typecode_to_standard(cls, typecode: str) -> StandardType:
"""将array.array的typecode转换为标准类型"""
return cls.ARRAY_TYPECODE_TO_STANDARD.get(typecode, StandardType.UNKNOWN)
"""JSON Schema"""
@classmethod
def json_schema_type_to_standard(cls, json_type: str) -> StandardType:
"""将JSON Schema类型转换为标准类型"""
return cls.JSON_SCHEMA_TO_STANDARD.get(json_type, StandardType.UNKNOWN)
@classmethod
def standard_type_to_json_schema_type(cls, standard_type: StandardType) -> str:
"""将StandardType转换为JSON Schema类型字符串"""
return cls.STANDARD_TO_JSON_SCHEMA.get(standard_type, "string")
"""值转换"""
@classmethod
def convert_to_python_value_with_standard_type(cls, value: Any, target_standard_type: StandardType) -> Any:
"""将值转换为指定的标准类型对应的Python值"""
if value is None:
return None if target_standard_type == StandardType.NULL else None
target_python_type = cls.standard_to_python_type(target_standard_type)
if target_python_type != object and type(value) == target_python_type:
# object交由target_standard_type为OBJECT的分支处理同样返回原值
return value
if target_standard_type == StandardType.ARRAY:
if isinstance(value, (list, tuple)):
return list(value)
elif hasattr(value, "typecode"): # array.array
return list(value)
elif isinstance(value, str):
return list(value) # 字符串转为字符数组
else:
return [value] # 单个值包装为数组
elif target_standard_type == StandardType.OBJECT:
return value
elif target_standard_type == StandardType.DATETIME:
if isinstance(value, datetime):
return value
elif isinstance(value, (int, float)):
return datetime.fromtimestamp(value)
elif isinstance(value, str):
return datetime.fromisoformat(value.replace("Z", "+00:00"))
else:
return datetime.now()
elif target_standard_type == StandardType.BYTES:
if isinstance(value, bytes):
return value
elif isinstance(value, str):
return value.encode("utf-8")
elif isinstance(value, (list, tuple)):
return bytes(value)
else:
return str(value).encode("utf-8")
else:
# 基础类型转换
return target_python_type(value)

View File

@@ -0,0 +1,400 @@
import copy
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, List, Optional, Type
from msgcenterpy.core.type_converter import StandardType, TypeConverter
class Consts:
ELEMENT_TYPE_INFO_SYMBOL = "ELEMENT_TYPE_INFO"
ACCESSOR_ROOT_NODE = "MSG_CENTER_ROOT"
class ConstraintType(Enum):
"""Constraint type enumeration"""
MIN_VALUE = "min_value"
MAX_VALUE = "max_value"
MIN_LENGTH = "min_length"
MAX_LENGTH = "max_length"
MIN_ITEMS = "min_items"
MAX_ITEMS = "max_items"
PATTERN = "pattern"
ENUM_VALUES = "enum_values"
MULTIPLE_OF = "multiple_of"
TYPE_KEEP = "type_keep"
EXCLUSIVE_MIN = "exclusive_min"
EXCLUSIVE_MAX = "exclusive_max"
UNIQUE_ITEMS = "unique_items"
DEFAULT_VALUE = "default_value"
REQUIRED = "required"
FORMAT = "format"
@dataclass
class TypeConstraint:
"""Type constraint definition"""
type: ConstraintType
value: Any
description: Optional[str] = None
def to_json_schema_property(self) -> Dict[str, Any]:
"""Convert to JSON Schema property"""
mapping = {
ConstraintType.MIN_VALUE: "minimum",
ConstraintType.MAX_VALUE: "maximum",
ConstraintType.MIN_LENGTH: "minLength",
ConstraintType.MAX_LENGTH: "maxLength",
ConstraintType.MIN_ITEMS: "minItems",
ConstraintType.MAX_ITEMS: "maxItems",
ConstraintType.PATTERN: "pattern",
ConstraintType.ENUM_VALUES: "enum",
ConstraintType.MULTIPLE_OF: "multipleOf",
ConstraintType.EXCLUSIVE_MIN: "exclusiveMinimum",
ConstraintType.EXCLUSIVE_MAX: "exclusiveMaximum",
ConstraintType.UNIQUE_ITEMS: "uniqueItems",
ConstraintType.DEFAULT_VALUE: "default",
ConstraintType.FORMAT: "format",
}
property_name = mapping.get(self.type)
if property_name:
result = {property_name: self.value}
if self.description:
result["description"] = self.description
return result
return {}
@dataclass
class TypeInfo:
"""Detailed type information including standard type, Python type and constraints"""
# Basic type information
field_name: str
field_path: str
standard_type: StandardType
python_type: Type
original_type: Any # Original type (e.g., ROS2 type instance)
_outdated: bool = False
@property
def outdated(self) -> bool:
return self._outdated
# Value information
current_value: Any = None
default_value: Any = None
# Constraints
constraints: List[TypeConstraint] = field(default_factory=list)
# Array/sequence related information
is_array: bool = False
array_size: Optional[int] = None # Fixed size array
_element_type_info: Optional["TypeInfo"] = None # Array element type
@property
def element_type_info(self) -> Optional["TypeInfo"]:
return self._element_type_info
@element_type_info.setter
def element_type_info(self, value: Optional["TypeInfo"]) -> None:
if self.outdated:
raise ValueError("Should not change an outdated type")
if value is not None:
value.field_name = Consts.ELEMENT_TYPE_INFO_SYMBOL
value.field_path = Consts.ELEMENT_TYPE_INFO_SYMBOL
self._element_type_info = value
# Object related information
is_object: bool = False
object_fields: Dict[str, "TypeInfo"] = field(default_factory=dict)
# Additional metadata
metadata: Dict[str, Any] = field(default_factory=dict)
@property
def python_value_from_standard_type(self) -> Any:
return TypeConverter.convert_to_python_value_with_standard_type(self.current_value, self.standard_type)
def add_constraint(
self,
constraint_type: ConstraintType,
value: Any,
description: Optional[str] = None,
) -> None:
"""Add constraint"""
constraint = TypeConstraint(constraint_type, value, description)
# Avoid duplicate constraints of the same type
self.constraints = [c for c in self.constraints if c.type != constraint_type]
self.constraints.append(constraint)
def get_constraint(self, constraint_type: ConstraintType) -> Optional[TypeConstraint]:
"""Get constraint of specified type"""
for constraint in self.constraints:
if constraint.type == constraint_type:
return constraint
return None
def has_constraint(self, constraint_type: ConstraintType) -> bool:
"""Check if constraint of specified type exists"""
return self.get_constraint(constraint_type) is not None
def get_constraint_value(self, constraint_type: ConstraintType) -> Any:
"""Get value of specified constraint"""
constraint = self.get_constraint(constraint_type)
return constraint.value if constraint else None
def validate_value(self, value: Any) -> bool:
"""Validate value according to constraints"""
try:
if self.get_constraint(ConstraintType.TYPE_KEEP):
# ROS includes TYPE_KEEP
if type(self.current_value) != type(value):
return False
# Basic type check
if not self._validate_basic_type(value):
return False
# Numeric constraint check
if not self._validate_numeric_constraints(value):
return False
# String constraint check
if not self._validate_string_constraints(value):
return False
# Array constraint check
if not self._validate_array_constraints(value):
return False
return True
except Exception:
return False
def _validate_basic_type(self, value: Any) -> bool:
"""Validate basic type"""
if value is None:
return not self.has_constraint(ConstraintType.REQUIRED)
return True
def _validate_numeric_constraints(self, value: Any) -> bool:
"""Validate numeric constraints"""
if not isinstance(value, (int, float)):
return True
min_val = self.get_constraint_value(ConstraintType.MIN_VALUE)
if min_val is not None and value < min_val:
return False
max_val = self.get_constraint_value(ConstraintType.MAX_VALUE)
if max_val is not None and value > max_val:
return False
exclusive_min = self.get_constraint_value(ConstraintType.EXCLUSIVE_MIN)
if exclusive_min is not None and value <= exclusive_min:
return False
exclusive_max = self.get_constraint_value(ConstraintType.EXCLUSIVE_MAX)
if exclusive_max is not None and value >= exclusive_max:
return False
multiple_of = self.get_constraint_value(ConstraintType.MULTIPLE_OF)
if multiple_of is not None and value % multiple_of != 0:
return False
return True
def _validate_string_constraints(self, value: Any) -> bool:
"""Validate string constraints"""
if not isinstance(value, str):
return True
min_len = self.get_constraint_value(ConstraintType.MIN_LENGTH)
if min_len is not None and len(value) < min_len:
return False
max_len = self.get_constraint_value(ConstraintType.MAX_LENGTH)
if max_len is not None and len(value) > max_len:
return False
pattern = self.get_constraint_value(ConstraintType.PATTERN)
if pattern is not None:
import re
if not re.match(pattern, value):
return False
enum_values = self.get_constraint_value(ConstraintType.ENUM_VALUES)
if enum_values is not None and value not in enum_values:
return False
return True
def _validate_array_constraints(self, value: Any) -> bool:
"""Validate array constraints"""
if not isinstance(value, (list, tuple)):
return True
min_items = self.get_constraint_value(ConstraintType.MIN_ITEMS)
if min_items is not None and len(value) < min_items:
return False
max_items = self.get_constraint_value(ConstraintType.MAX_ITEMS)
if max_items is not None and len(value) > max_items:
return False
if self.array_size is not None and len(value) != self.array_size:
return False
unique_items = self.get_constraint_value(ConstraintType.UNIQUE_ITEMS)
if unique_items and len(set(value)) != len(value):
return False
return True
def to_json_schema_property(self, include_constraints: bool = True) -> Dict[str, Any]:
"""Convert to JSON Schema property definition"""
from msgcenterpy.core.type_converter import TypeConverter
# Basic properties
property_schema: Dict[str, Any] = {"type": TypeConverter.standard_type_to_json_schema_type(self.standard_type)}
# Add constraints
if include_constraints:
for constraint in self.constraints:
constraint_props = constraint.to_json_schema_property()
property_schema.update(constraint_props)
# Special handling for array types
if self.is_array and self.element_type_info:
property_schema["items"] = self.element_type_info.to_json_schema_property(include_constraints)
# Special handling for object types
if self.is_object and self.object_fields:
properties = {}
for field_name, field_info in self.object_fields.items():
properties[field_name] = field_info.to_json_schema_property(include_constraints)
property_schema["properties"] = properties
# Add description
if self.original_type:
property_schema["description"] = f"Field of type {self.original_type}"
return property_schema
def convert_value(self, value: Any, target_standard_type: Optional[StandardType] = None) -> Any:
"""Convert value to current type or specified target type"""
target_type = target_standard_type or self.standard_type
converted_value = TypeConverter.convert_to_python_value_with_standard_type(value, target_type)
# Validate converted value
if target_standard_type is None and not self.validate_value(converted_value):
# Format constraint information
constraints_info = []
for c in self.constraints:
constraint_desc = f"{c.type.value}: {c.value}"
if c.description:
constraint_desc += f" ({c.description})"
constraints_info.append(constraint_desc)
constraints_str = ", ".join(constraints_info) if constraints_info else "No constraints"
raise ValueError(
f"Value {value} does not meet constraints for field {self.field_name}. "
f"Constraints: [{constraints_str}]"
)
return converted_value
def get_value_info(self) -> Dict[str, Any]:
"""Get detailed information about current value"""
return {
"field_name": self.field_name,
"current_value": self.current_value,
"standard_type": self.standard_type.value,
"python_type": self.python_type.__name__,
"original_type": self.original_type,
"is_valid": self.validate_value(self.current_value),
"constraints": [
{"type": c.type.value, "value": c.value, "description": c.description} for c in self.constraints
],
"is_array": self.is_array,
"array_size": self.array_size,
"is_object": self.is_object,
"metadata": self.metadata,
}
def clone(self) -> "TypeInfo":
"""Create deep copy of TypeInfo"""
return copy.deepcopy(self)
def __repr__(self) -> str:
constraints_str = f", {len(self.constraints)} constraints" if self.constraints else ""
return f"TypeInfo({self.field_name}: {self.standard_type.value}{constraints_str})"
class TypeInfoPostProcessor:
"""TypeInfo post-processor that adds default constraints to TypeInfo"""
@staticmethod
def add_basic_type_constraints(type_info: TypeInfo) -> None:
"""Add range constraints for basic types"""
if not type_info.standard_type:
return
standard_type = type_info.standard_type
# Integer type range constraints
if standard_type == StandardType.INT8:
type_info.add_constraint(ConstraintType.MIN_VALUE, -128)
type_info.add_constraint(ConstraintType.MAX_VALUE, 127)
elif standard_type in (
StandardType.UINT8,
StandardType.BYTE,
StandardType.OCTET,
):
type_info.add_constraint(ConstraintType.MIN_VALUE, 0)
type_info.add_constraint(ConstraintType.MAX_VALUE, 255)
elif standard_type == StandardType.INT16:
type_info.add_constraint(ConstraintType.MIN_VALUE, -32768)
type_info.add_constraint(ConstraintType.MAX_VALUE, 32767)
elif standard_type == StandardType.UINT16:
type_info.add_constraint(ConstraintType.MIN_VALUE, 0)
type_info.add_constraint(ConstraintType.MAX_VALUE, 65535)
elif standard_type == StandardType.INT32:
type_info.add_constraint(ConstraintType.MIN_VALUE, -2147483648)
type_info.add_constraint(ConstraintType.MAX_VALUE, 2147483647)
elif standard_type == StandardType.UINT32:
type_info.add_constraint(ConstraintType.MIN_VALUE, 0)
type_info.add_constraint(ConstraintType.MAX_VALUE, 4294967295)
elif standard_type == StandardType.INT64:
type_info.add_constraint(ConstraintType.MIN_VALUE, -9223372036854775808)
type_info.add_constraint(ConstraintType.MAX_VALUE, 9223372036854775807)
elif standard_type == StandardType.UINT64:
type_info.add_constraint(ConstraintType.MIN_VALUE, 0)
type_info.add_constraint(ConstraintType.MAX_VALUE, 18446744073709551615)
# Floating point type range constraints
elif standard_type in (StandardType.FLOAT, StandardType.FLOAT32):
type_info.add_constraint(ConstraintType.MIN_VALUE, -3.4028235e38)
type_info.add_constraint(ConstraintType.MAX_VALUE, 3.4028235e38)
elif standard_type in (StandardType.DOUBLE, StandardType.FLOAT64):
type_info.add_constraint(ConstraintType.MIN_VALUE, -1.7976931348623157e308)
type_info.add_constraint(ConstraintType.MAX_VALUE, 1.7976931348623157e308)
@staticmethod
def add_default_constraints(type_info: TypeInfo) -> None:
"""Add default constraints"""
field_value = type_info.current_value
# Add constraints for array types
if isinstance(field_value, (list, tuple)):
type_info.is_array = True
# 不再添加冗余的 MIN_ITEMS: 0
@staticmethod
def post_process_type_info(type_info: TypeInfo) -> None:
"""Post-process TypeInfo, adding various default constraints"""
TypeInfoPostProcessor.add_basic_type_constraints(type_info)
TypeInfoPostProcessor.add_default_constraints(type_info)

25
msgcenterpy/core/types.py Normal file
View File

@@ -0,0 +1,25 @@
from enum import Enum
class MessageType(Enum):
"""Supported message types"""
ROS2 = "ros2"
PYDANTIC = "pydantic"
DATACLASS = "dataclass"
JSON = "json"
JSON_SCHEMA = "json_schema"
DICT = "dict"
YAML = "yaml"
class ConversionError(Exception):
"""Conversion error exception"""
pass
class ValidationError(Exception):
"""Validation error exception"""
pass