plexus-msgs-python 1.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plexus/msgs/__init__.py +6 -0
- plexus/msgs/__main__.py +40 -0
- plexus/msgs/control/plexus_msgs_control_control_command_pb2.py +39 -0
- plexus/msgs/control/plexus_msgs_control_control_command_pb2.pyi +33 -0
- plexus/msgs/control/plexus_msgs_control_dbw_command_pb2.py +41 -0
- plexus/msgs/control/plexus_msgs_control_dbw_command_pb2.pyi +42 -0
- plexus/msgs/descriptor.pb +0 -0
- plexus/msgs/localization/plexus_msgs_localization_odometry_pb2.py +38 -0
- plexus/msgs/localization/plexus_msgs_localization_odometry_pb2.pyi +15 -0
- plexus/msgs/localization/plexus_msgs_localization_positioning_pb2.py +39 -0
- plexus/msgs/localization/plexus_msgs_localization_positioning_pb2.pyi +18 -0
- plexus/msgs/mapping/plexus_msgs_mapping_map_region_pb2.py +54 -0
- plexus/msgs/mapping/plexus_msgs_mapping_map_region_pb2.pyi +104 -0
- plexus/msgs/perception/plexus_msgs_perception_lane_detection_pb2.py +53 -0
- plexus/msgs/perception/plexus_msgs_perception_lane_detection_pb2.pyi +97 -0
- plexus/msgs/perception/plexus_msgs_perception_obstacle_detection_pb2.py +51 -0
- plexus/msgs/perception/plexus_msgs_perception_obstacle_detection_pb2.pyi +82 -0
- plexus/msgs/planning/plexus_msgs_planning_driving_trajectory_pb2.py +40 -0
- plexus/msgs/planning/plexus_msgs_planning_driving_trajectory_pb2.pyi +18 -0
- plexus/msgs/planning/plexus_msgs_planning_obstacle_threat_pb2.py +41 -0
- plexus/msgs/planning/plexus_msgs_planning_obstacle_threat_pb2.pyi +61 -0
- plexus/msgs/plexus_common_carto_pb2.py +40 -0
- plexus/msgs/plexus_common_carto_pb2.pyi +26 -0
- plexus/msgs/plexus_common_entity_pb2.py +47 -0
- plexus/msgs/plexus_common_entity_pb2.pyi +96 -0
- plexus/msgs/plexus_common_geom_pb2.py +64 -0
- plexus/msgs/plexus_common_geom_pb2.pyi +120 -0
- plexus/msgs/plexus_common_message_pb2.py +40 -0
- plexus/msgs/plexus_common_message_pb2.pyi +29 -0
- plexus/msgs/plexus_common_pb2.py +63 -0
- plexus/msgs/plexus_common_pb2.pyi +171 -0
- plexus/msgs/plexus_pb2.py +38 -0
- plexus/msgs/plexus_pb2.pyi +27 -0
- plexus/msgs/routing/plexus_msgs_routing_navigation_info_pb2.py +52 -0
- plexus/msgs/routing/plexus_msgs_routing_navigation_info_pb2.pyi +117 -0
- plexus/msgs/testing/plexus_msgs_testing_dummy_pb2.py +141 -0
- plexus/msgs/testing/plexus_msgs_testing_dummy_pb2.pyi +276 -0
- plexus/msgs/utils.py +530 -0
- plexus/msgs/vehicle/plexus_msgs_vehicle_can_messages_pb2.py +39 -0
- plexus/msgs/vehicle/plexus_msgs_vehicle_can_messages_pb2.pyi +20 -0
- plexus_msgs_python-1.0.4.dist-info/METADATA +20 -0
- plexus_msgs_python-1.0.4.dist-info/RECORD +45 -0
- plexus_msgs_python-1.0.4.dist-info/WHEEL +5 -0
- plexus_msgs_python-1.0.4.dist-info/entry_points.txt +2 -0
- plexus_msgs_python-1.0.4.dist-info/top_level.txt +1 -0
plexus/msgs/utils.py
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import copy
|
|
3
|
+
import importlib
|
|
4
|
+
import importlib.resources
|
|
5
|
+
import pkgutil
|
|
6
|
+
import textwrap
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from types import ModuleType
|
|
9
|
+
from typing import Any, TypeVar
|
|
10
|
+
|
|
11
|
+
import jinja2
|
|
12
|
+
from google.protobuf import descriptor_pool, message_factory
|
|
13
|
+
from google.protobuf.descriptor_pb2 import (
|
|
14
|
+
DescriptorProto as MessageDesc,
|
|
15
|
+
FieldDescriptorProto as FieldDesc,
|
|
16
|
+
FileDescriptorProto as FileDesc,
|
|
17
|
+
FileDescriptorSet as FileDescSet,
|
|
18
|
+
)
|
|
19
|
+
from google.protobuf.message import Message
|
|
20
|
+
from iker.common.utils.funcutils import memorized, singleton
|
|
21
|
+
|
|
22
|
+
from plexus.msgs.plexus_common_message_pb2 import (
|
|
23
|
+
Message as PlexusMessageProto,
|
|
24
|
+
MessageHeader as PlexusMessageHeaderProto,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"import_plexus_msgs",
|
|
29
|
+
"get_protobuf_class",
|
|
30
|
+
"wrap_plexus_message",
|
|
31
|
+
"unwrap_plexus_message",
|
|
32
|
+
"import_submodules",
|
|
33
|
+
"traverse_descriptor_file",
|
|
34
|
+
"plexus_msgs_omitted_fields",
|
|
35
|
+
"plexus_msgs_composite_fields",
|
|
36
|
+
"generate_plexus_msg_tailor_python_code",
|
|
37
|
+
"generate_plexus_msg_tailor_java_code",
|
|
38
|
+
"generate_plexus_msg_tailor_cpp_code",
|
|
39
|
+
"tailor_plexus_msg",
|
|
40
|
+
"tailor_plexus_msg_2",
|
|
41
|
+
"copy_plexus_message",
|
|
42
|
+
"copy_plexus_message_2",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
MessageT = TypeVar("MessageT", bound=Message)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@singleton
|
|
49
|
+
def import_plexus_msgs():
|
|
50
|
+
"""Imports all modules in the ``plexus.msgs`` package"""
|
|
51
|
+
import plexus.msgs
|
|
52
|
+
return import_submodules(plexus.msgs)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@singleton
|
|
56
|
+
def plexus_msgs_descriptor_path() -> str:
|
|
57
|
+
"""Returns the path to the plexus messages descriptor file"""
|
|
58
|
+
import plexus.msgs
|
|
59
|
+
return str(importlib.resources.files(plexus.msgs).joinpath("descriptor.pb"))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@memorized
|
|
63
|
+
def get_protobuf_class(name: str) -> Callable[..., MessageT]:
|
|
64
|
+
"""Returns the protobuf class for a given name"""
|
|
65
|
+
import_plexus_msgs()
|
|
66
|
+
|
|
67
|
+
pool = descriptor_pool.Default()
|
|
68
|
+
desc = pool.FindMessageTypeByName(name)
|
|
69
|
+
return message_factory.GetMessageClass(desc)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def wrap_plexus_message(header: PlexusMessageHeaderProto, payload: MessageT) -> bytes:
|
|
73
|
+
"""Wraps a plexus message into a protobuf encoded string"""
|
|
74
|
+
if header.payload_type != payload.DESCRIPTOR.full_name:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"header payload type '{header.payload_type}' does not match payload type '{payload.DESCRIPTOR.full_name}'"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
plexus_message_pb = PlexusMessageProto()
|
|
80
|
+
plexus_message_pb.header.CopyFrom(header)
|
|
81
|
+
plexus_message_pb.payload = base64.b64encode(payload.SerializeToString())
|
|
82
|
+
|
|
83
|
+
return plexus_message_pb.SerializeToString()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def unwrap_plexus_message(data: bytes) -> tuple[PlexusMessageHeaderProto, MessageT]:
|
|
87
|
+
"""Unwraps a plexus message from a protobuf encoded string"""
|
|
88
|
+
plexus_message_pb = PlexusMessageProto()
|
|
89
|
+
plexus_message_pb.ParseFromString(data)
|
|
90
|
+
|
|
91
|
+
payload_class = get_protobuf_class(plexus_message_pb.header.payload_type)
|
|
92
|
+
payload_pb = payload_class()
|
|
93
|
+
payload_pb.ParseFromString(base64.b64decode(plexus_message_pb.payload))
|
|
94
|
+
|
|
95
|
+
return plexus_message_pb.header, payload_pb
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def import_submodules(package: str | ModuleType) -> dict[str, ModuleType]:
|
|
99
|
+
"""Imports all submodules of a package, recursively"""
|
|
100
|
+
if isinstance(package, str):
|
|
101
|
+
return import_submodules(importlib.import_module(package))
|
|
102
|
+
results = {}
|
|
103
|
+
for loader, name, is_pkg in pkgutil.walk_packages(package.__path__, package.__name__ + "."):
|
|
104
|
+
results[name] = importlib.import_module(name)
|
|
105
|
+
return results
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def traverse_descriptor_file(
|
|
109
|
+
descriptor_path: str,
|
|
110
|
+
message_ops: Callable[[str, FileDesc, MessageDesc], ...],
|
|
111
|
+
field_ops: Callable[[str, FileDesc, MessageDesc, FieldDesc], ...],
|
|
112
|
+
):
|
|
113
|
+
"""
|
|
114
|
+
Traverses a protobuf descriptor file and applies operations on messages and fields.
|
|
115
|
+
|
|
116
|
+
:param descriptor_path: Path to the protobuf descriptor file.
|
|
117
|
+
:param message_ops: Callable that takes a qualified name, a file descriptor, and a message descriptor.
|
|
118
|
+
:param field_ops: Callable that takes a qualified name, a file descriptor, a message descriptor, and a field
|
|
119
|
+
descriptor.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def traverse(file_desc: FileDesc, message_desc: MessageDesc, parent_qualified_name: str):
|
|
123
|
+
qualified_name = parent_qualified_name + "." + message_desc.name if parent_qualified_name else message_desc.name
|
|
124
|
+
|
|
125
|
+
for field_desc in message_desc.field:
|
|
126
|
+
field_ops(qualified_name, file_desc, message_desc, field_desc)
|
|
127
|
+
message_ops(qualified_name, file_desc, message_desc)
|
|
128
|
+
|
|
129
|
+
for nested_message_desc in message_desc.nested_type:
|
|
130
|
+
traverse(file_desc, nested_message_desc, qualified_name)
|
|
131
|
+
|
|
132
|
+
with open(descriptor_path, "rb") as fh:
|
|
133
|
+
file_desc_set = FileDescSet()
|
|
134
|
+
file_desc_set.ParseFromString(fh.read())
|
|
135
|
+
|
|
136
|
+
for file_desc in file_desc_set.file:
|
|
137
|
+
for message_desc in file_desc.message_type:
|
|
138
|
+
traverse(file_desc, message_desc, "." + file_desc.package)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def plexus_msgs_omitted_fields(
|
|
142
|
+
descriptor_path: str = plexus_msgs_descriptor_path(),
|
|
143
|
+
) -> dict[str, tuple[FileDesc, MessageDesc, dict[str, FieldDesc]]]:
|
|
144
|
+
class OptionCollector(object):
|
|
145
|
+
def __init__(self, option_name: str, option_value_check: Callable[[Any], bool]):
|
|
146
|
+
self.option_name = option_name
|
|
147
|
+
self.option_value_check = option_value_check
|
|
148
|
+
self.collected_fields: list[FieldDesc] = []
|
|
149
|
+
self.collected_messages: list[tuple[str, FileDesc, MessageDesc, list[FieldDesc]]] = []
|
|
150
|
+
|
|
151
|
+
def message_ops(self, qualified_name: str, file_desc: FileDesc, message_desc: MessageDesc):
|
|
152
|
+
if self.collected_fields:
|
|
153
|
+
self.collected_messages.append((qualified_name, file_desc, message_desc, self.collected_fields))
|
|
154
|
+
self.collected_fields = []
|
|
155
|
+
|
|
156
|
+
def field_ops(
|
|
157
|
+
self,
|
|
158
|
+
qualified_name: str,
|
|
159
|
+
file_desc: FileDesc,
|
|
160
|
+
message_desc: MessageDesc,
|
|
161
|
+
field_desc: FieldDesc,
|
|
162
|
+
):
|
|
163
|
+
for option, option_value in field_desc.options.ListFields():
|
|
164
|
+
if getattr(option, "name", None) == self.option_name and self.option_value_check(option_value):
|
|
165
|
+
self.collected_fields.append(field_desc)
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
collector = OptionCollector("omitted", lambda x: x is True)
|
|
169
|
+
|
|
170
|
+
traverse_descriptor_file(descriptor_path, collector.message_ops, collector.field_ops)
|
|
171
|
+
|
|
172
|
+
return {qualified_name: (file_desc, message_desc, {field_desc.name: field_desc for field_desc in field_desc_list})
|
|
173
|
+
for qualified_name, file_desc, message_desc, field_desc_list in collector.collected_messages}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def plexus_msgs_composite_fields(
|
|
177
|
+
descriptor_path: str = plexus_msgs_descriptor_path(),
|
|
178
|
+
) -> dict[str, tuple[FileDesc, MessageDesc, dict[str, FieldDesc]]]:
|
|
179
|
+
class MessageTypeTreeCollector(object):
|
|
180
|
+
def __init__(self):
|
|
181
|
+
self.collected_fields: list[FieldDesc] = []
|
|
182
|
+
self.collected_messages: list[tuple[str, FileDesc, MessageDesc, list[FieldDesc]]] = []
|
|
183
|
+
|
|
184
|
+
def message_ops(self, qualified_name: str, file_desc: FileDesc, message_desc: MessageDesc):
|
|
185
|
+
if self.collected_fields:
|
|
186
|
+
self.collected_messages.append((qualified_name, file_desc, message_desc, self.collected_fields))
|
|
187
|
+
self.collected_fields = []
|
|
188
|
+
|
|
189
|
+
def field_ops(
|
|
190
|
+
self,
|
|
191
|
+
qualified_name: str,
|
|
192
|
+
file_desc: FileDesc,
|
|
193
|
+
message_desc: MessageDesc,
|
|
194
|
+
field_desc: FieldDesc,
|
|
195
|
+
):
|
|
196
|
+
if field_desc.type != FieldDesc.TYPE_MESSAGE:
|
|
197
|
+
return
|
|
198
|
+
self.collected_fields.append(field_desc)
|
|
199
|
+
|
|
200
|
+
collector = MessageTypeTreeCollector()
|
|
201
|
+
|
|
202
|
+
traverse_descriptor_file(descriptor_path, collector.message_ops, collector.field_ops)
|
|
203
|
+
|
|
204
|
+
return {qualified_name: (file_desc, message_desc, {field_desc.name: field_desc for field_desc in field_desc_list})
|
|
205
|
+
for qualified_name, file_desc, message_desc, field_desc_list in collector.collected_messages}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def collect_plexus_msg_tailor_specs(
|
|
209
|
+
descriptor_path: str = plexus_msgs_descriptor_path(),
|
|
210
|
+
) -> list[tuple[str, FileDesc, MessageDesc, list[FieldDesc], list[FieldDesc]]]:
|
|
211
|
+
omitted_fields = plexus_msgs_omitted_fields(descriptor_path)
|
|
212
|
+
composite_fields = plexus_msgs_composite_fields(descriptor_path)
|
|
213
|
+
|
|
214
|
+
def detect_omission(qualified_name: str, visited: set[str] = None) -> bool:
|
|
215
|
+
visited = visited or set()
|
|
216
|
+
if qualified_name in visited:
|
|
217
|
+
return False
|
|
218
|
+
if qualified_name in omitted_fields:
|
|
219
|
+
return True
|
|
220
|
+
|
|
221
|
+
if qualified_name in composite_fields:
|
|
222
|
+
_, _, field_desc_map = composite_fields[qualified_name]
|
|
223
|
+
return any(detect_omission(field_desc.type_name, visited | {qualified_name})
|
|
224
|
+
for field_desc in field_desc_map.values())
|
|
225
|
+
return False
|
|
226
|
+
|
|
227
|
+
specs = []
|
|
228
|
+
for qualified_name, (file_desc, message_desc, field_desc_map) in composite_fields.items():
|
|
229
|
+
if not detect_omission(qualified_name):
|
|
230
|
+
continue
|
|
231
|
+
_, _, omitted_field_desc_map = omitted_fields.get(qualified_name, (None, None, {}))
|
|
232
|
+
composite_field_desc_list = []
|
|
233
|
+
for field_desc in field_desc_map.values():
|
|
234
|
+
if not detect_omission(field_desc.type_name) or field_desc.name in omitted_field_desc_map:
|
|
235
|
+
continue
|
|
236
|
+
composite_field_desc_list.append(field_desc)
|
|
237
|
+
specs.append(
|
|
238
|
+
(
|
|
239
|
+
qualified_name,
|
|
240
|
+
file_desc,
|
|
241
|
+
message_desc,
|
|
242
|
+
list(omitted_field_desc_map.values()),
|
|
243
|
+
composite_field_desc_list,
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
return specs
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def generate_plexus_msg_tailor_python_code(
|
|
251
|
+
descriptor_path: str = plexus_msgs_descriptor_path(),
|
|
252
|
+
*,
|
|
253
|
+
tailor_function_name: str = "tailor_plexus_msg"
|
|
254
|
+
) -> str:
|
|
255
|
+
tailors = [
|
|
256
|
+
{
|
|
257
|
+
"qualified_name": qualified_name,
|
|
258
|
+
"tailor_function_name": "tailor" + "_".join(qualified_name.split(".")),
|
|
259
|
+
"omitted_fields": [field_desc.name for field_desc in omitted_field_desc_list],
|
|
260
|
+
"composite_fields": [
|
|
261
|
+
{
|
|
262
|
+
"name": field_desc.name,
|
|
263
|
+
"repeated": field_desc.label == FieldDesc.LABEL_REPEATED,
|
|
264
|
+
}
|
|
265
|
+
for field_desc in composite_field_desc_list],
|
|
266
|
+
}
|
|
267
|
+
for qualified_name, _, _, omitted_field_desc_list, composite_field_desc_list
|
|
268
|
+
in collect_plexus_msg_tailor_specs(descriptor_path)
|
|
269
|
+
]
|
|
270
|
+
|
|
271
|
+
template_str = textwrap.dedent(
|
|
272
|
+
"""
|
|
273
|
+
# AUTO-GENERATED FILE. DO NOT EDIT.
|
|
274
|
+
import copy
|
|
275
|
+
|
|
276
|
+
from google.protobuf.message import Message
|
|
277
|
+
|
|
278
|
+
{% for tailor in tailors %}
|
|
279
|
+
|
|
280
|
+
def {{ tailor.tailor_function_name }}(message: Message):
|
|
281
|
+
{% for field_name in tailor.omitted_fields %}
|
|
282
|
+
message.ClearField("{{ field_name }}")
|
|
283
|
+
{% endfor %}
|
|
284
|
+
{% for field in tailor.composite_fields %}
|
|
285
|
+
{% if field.repeated %}
|
|
286
|
+
for item in message.{{ field.name }}:
|
|
287
|
+
{{ settings.tailor_function_name }}(item)
|
|
288
|
+
{% else %}
|
|
289
|
+
if message.HasField("{{ field.name }}"):
|
|
290
|
+
{{ settings.tailor_function_name }}(message.{{ field.name }})
|
|
291
|
+
{% endif %}
|
|
292
|
+
{% endfor %}
|
|
293
|
+
return message
|
|
294
|
+
|
|
295
|
+
{% endfor %}
|
|
296
|
+
|
|
297
|
+
PLEXUS_MSG_TAILORS_REGISTRY = {
|
|
298
|
+
{% for tailor in tailors %}
|
|
299
|
+
"{{ tailor.qualified_name }}": {{ tailor.tailor_function_name }},
|
|
300
|
+
{% endfor %}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def {{ settings.tailor_function_name }}(message: Message):
|
|
305
|
+
qualified_name = "." + message.DESCRIPTOR.full_name
|
|
306
|
+
if qualified_name in PLEXUS_MSG_TAILORS_REGISTRY:
|
|
307
|
+
return PLEXUS_MSG_TAILORS_REGISTRY[qualified_name](message)
|
|
308
|
+
return message
|
|
309
|
+
"""
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
env = jinja2.Environment(trim_blocks=True, lstrip_blocks=True)
|
|
313
|
+
|
|
314
|
+
template = env.from_string(template_str)
|
|
315
|
+
rendered = template.render(tailors=tailors,
|
|
316
|
+
settings=dict(tailor_function_name=tailor_function_name))
|
|
317
|
+
|
|
318
|
+
return rendered
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def generate_plexus_msg_tailor_java_code(
|
|
322
|
+
descriptor_path: str = plexus_msgs_descriptor_path(),
|
|
323
|
+
*,
|
|
324
|
+
tailor_package_name: str = "plexus.msgs",
|
|
325
|
+
tailor_class_name: str = "PlexusMsgTailor",
|
|
326
|
+
tailer_method_name: str = "tailor",
|
|
327
|
+
) -> str:
|
|
328
|
+
def snake_case_to_camel_case(s: str) -> str:
|
|
329
|
+
return "".join(word.capitalize() for word in s.split("_"))
|
|
330
|
+
|
|
331
|
+
tailors = [
|
|
332
|
+
{
|
|
333
|
+
"qualified_name": qualified_name,
|
|
334
|
+
"message_class_name": qualified_name[1:],
|
|
335
|
+
"omitted_fields": [field_desc.name for field_desc in omitted_field_desc_list],
|
|
336
|
+
"composite_fields": [
|
|
337
|
+
{
|
|
338
|
+
"name": field_desc.name,
|
|
339
|
+
"repeated": field_desc.label == FieldDesc.LABEL_REPEATED,
|
|
340
|
+
}
|
|
341
|
+
for field_desc in composite_field_desc_list],
|
|
342
|
+
}
|
|
343
|
+
for qualified_name, file_desc, _, omitted_field_desc_list, composite_field_desc_list
|
|
344
|
+
in collect_plexus_msg_tailor_specs(descriptor_path)
|
|
345
|
+
]
|
|
346
|
+
|
|
347
|
+
template_str = textwrap.dedent(
|
|
348
|
+
"""
|
|
349
|
+
// AUTO-GENERATED FILE. DO NOT EDIT.
|
|
350
|
+
package {{ settings.tailor_package_name }};
|
|
351
|
+
|
|
352
|
+
public class {{ settings.tailor_class_name }} {
|
|
353
|
+
{% for tailor in tailors %}
|
|
354
|
+
|
|
355
|
+
public static {{ tailor.message_class_name }} tailorMessage({{ tailor.message_class_name }} message) {
|
|
356
|
+
var builder = message.toBuilder();
|
|
357
|
+
{% for field_name in tailor.omitted_fields %}
|
|
358
|
+
builder.clear{{ field_name|snake_case_to_camel_case }}();
|
|
359
|
+
{% endfor %}
|
|
360
|
+
{% for field in tailor.composite_fields %}
|
|
361
|
+
{% if field.repeated %}
|
|
362
|
+
for (int i = 0; i < builder.get{{ field.name|snake_case_to_camel_case }}Count(); i++) {
|
|
363
|
+
builder.set{{ field.name|snake_case_to_camel_case }}(
|
|
364
|
+
i, tailorMessage(builder.get{{ field.name|snake_case_to_camel_case }}(i)));
|
|
365
|
+
}
|
|
366
|
+
{% else %}
|
|
367
|
+
if (builder.has{{ field.name|snake_case_to_camel_case }}()) {
|
|
368
|
+
builder.set{{ field.name|snake_case_to_camel_case }}(
|
|
369
|
+
tailorMessage(message.get{{ field.name|snake_case_to_camel_case }}()));
|
|
370
|
+
}
|
|
371
|
+
{% endif %}
|
|
372
|
+
{% endfor %}
|
|
373
|
+
return builder.build();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
{% endfor %}
|
|
377
|
+
|
|
378
|
+
public static <T extends com.google.protobuf.Message> T {{ settings.tailer_method_name}}(T message) {
|
|
379
|
+
{% for tailor in tailors %}
|
|
380
|
+
if (message instanceof {{ tailor.message_class_name }})
|
|
381
|
+
return (T) tailorMessage(({{ tailor.message_class_name }}) message);
|
|
382
|
+
{% endfor %}
|
|
383
|
+
return message;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
"""
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
env = jinja2.Environment(trim_blocks=True, lstrip_blocks=True)
|
|
390
|
+
env.filters["snake_case_to_camel_case"] = snake_case_to_camel_case
|
|
391
|
+
|
|
392
|
+
template = env.from_string(template_str)
|
|
393
|
+
rendered = template.render(tailors=tailors,
|
|
394
|
+
settings=dict(tailor_package_name=tailor_package_name,
|
|
395
|
+
tailor_class_name=tailor_class_name,
|
|
396
|
+
tailer_method_name=tailer_method_name))
|
|
397
|
+
|
|
398
|
+
return rendered
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def generate_plexus_msg_tailor_cpp_code(
|
|
402
|
+
descriptor_path: str = plexus_msgs_descriptor_path(),
|
|
403
|
+
*,
|
|
404
|
+
tailor_namespace: str = "plexus::msgs",
|
|
405
|
+
tailor_class_name: str = "PlexusMsgTailor",
|
|
406
|
+
tailor_method_name: str = "Tailor",
|
|
407
|
+
) -> str:
|
|
408
|
+
def snake_case_to_pascal_case(s: str) -> str:
|
|
409
|
+
return "".join(word.capitalize() for word in s.split("_"))
|
|
410
|
+
|
|
411
|
+
tailors = [
|
|
412
|
+
{
|
|
413
|
+
"qualified_name": qualified_name,
|
|
414
|
+
"message_class_name": qualified_name.replace(".", "::"),
|
|
415
|
+
"omitted_fields": [field_desc.name for field_desc in omitted_field_desc_list],
|
|
416
|
+
"composite_fields": [
|
|
417
|
+
{
|
|
418
|
+
"name": field_desc.name,
|
|
419
|
+
"repeated": field_desc.label == FieldDesc.LABEL_REPEATED,
|
|
420
|
+
}
|
|
421
|
+
for field_desc in composite_field_desc_list],
|
|
422
|
+
}
|
|
423
|
+
for qualified_name, file_desc, _, omitted_field_desc_list, composite_field_desc_list
|
|
424
|
+
in collect_plexus_msg_tailor_specs(descriptor_path)
|
|
425
|
+
]
|
|
426
|
+
|
|
427
|
+
template_str = textwrap.dedent(
|
|
428
|
+
"""
|
|
429
|
+
// AUTO-GENERATED FILE. DO NOT EDIT.
|
|
430
|
+
#pragma once
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
namespace {{ settings.tailor_namespace }} {
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
class {{ settings.tailor_class_name }} {
|
|
437
|
+
public:
|
|
438
|
+
{% for tailor in tailors %}
|
|
439
|
+
|
|
440
|
+
static void TailorMessage({{ tailor.message_class_name }} &message) {
|
|
441
|
+
{% for field_name in tailor.omitted_fields %}
|
|
442
|
+
message.clear_{{ field_name }}();
|
|
443
|
+
{% endfor %}
|
|
444
|
+
{% for field in tailor.composite_fields %}
|
|
445
|
+
{% if field.repeated %}
|
|
446
|
+
for (int i = 0; i < message.mutable_{{ field.name }}()->size(); ++i) {
|
|
447
|
+
{{ settings.tailor_class_name }}::TailorMessage(message.mutable_{{ field.name }}()->at(i));
|
|
448
|
+
}
|
|
449
|
+
{% else %}
|
|
450
|
+
if (message.has_{{ field.name }}()) {
|
|
451
|
+
{{ settings.tailor_class_name }}::TailorMessage(*message.mutable_{{ field.name }}());
|
|
452
|
+
}
|
|
453
|
+
{% endif %}
|
|
454
|
+
{% endfor %}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
{% endfor %}
|
|
458
|
+
|
|
459
|
+
template <typename T>
|
|
460
|
+
static auto {{ settings.tailor_method_name }}(const T &message) {
|
|
461
|
+
return message;
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
{% for tailor in tailors %}
|
|
466
|
+
|
|
467
|
+
template <>
|
|
468
|
+
auto {{ settings.tailor_class_name }}::{{ settings.tailor_method_name }}(const {{ tailor.message_class_name }} &message) {
|
|
469
|
+
auto copy = message;
|
|
470
|
+
{{ settings.tailor_class_name }}::TailorMessage(copy);
|
|
471
|
+
return copy;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
{% endfor %}
|
|
475
|
+
|
|
476
|
+
} // namespace {{ settings.tailor_namespace }}
|
|
477
|
+
"""
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
env = jinja2.Environment(trim_blocks=True, lstrip_blocks=True)
|
|
481
|
+
env.filters["snake_case_to_pascal_case"] = snake_case_to_pascal_case
|
|
482
|
+
|
|
483
|
+
template = env.from_string(template_str)
|
|
484
|
+
rendered = template.render(tailors=tailors,
|
|
485
|
+
settings=dict(tailor_namespace=tailor_namespace,
|
|
486
|
+
tailor_class_name=tailor_class_name,
|
|
487
|
+
tailor_method_name=tailor_method_name))
|
|
488
|
+
|
|
489
|
+
return rendered
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def make_tailor_plexus_msg():
|
|
493
|
+
import_plexus_msgs()
|
|
494
|
+
|
|
495
|
+
codegen_module = ModuleType("codegen")
|
|
496
|
+
exec(generate_plexus_msg_tailor_python_code(), codegen_module.__dict__)
|
|
497
|
+
|
|
498
|
+
return codegen_module.tailor_plexus_msg
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
tailor_plexus_msg = make_tailor_plexus_msg()
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def tailor_plexus_msg_2(message: MessageT):
|
|
505
|
+
qualified_name = "." + message.DESCRIPTOR.full_name
|
|
506
|
+
_, _, omitted_field_desc_map = plexus_msgs_omitted_fields().get(qualified_name, (None, None, {}))
|
|
507
|
+
if not omitted_field_desc_map:
|
|
508
|
+
return message
|
|
509
|
+
|
|
510
|
+
for field_desc in message.DESCRIPTOR.fields:
|
|
511
|
+
if field_desc.name in omitted_field_desc_map:
|
|
512
|
+
message.ClearField(field_desc.name)
|
|
513
|
+
elif field_desc.type == FieldDesc.TYPE_MESSAGE:
|
|
514
|
+
value = getattr(message, field_desc.name)
|
|
515
|
+
if field_desc.label == FieldDesc.LABEL_REPEATED:
|
|
516
|
+
for item in value:
|
|
517
|
+
tailor_plexus_msg(item)
|
|
518
|
+
elif value is not None and value.ByteSize():
|
|
519
|
+
tailor_plexus_msg(value)
|
|
520
|
+
return message
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def copy_plexus_message(message: MessageT, *, tailored: bool = False):
|
|
524
|
+
message_copy = copy.deepcopy(message)
|
|
525
|
+
return tailor_plexus_msg(message_copy) if tailored else message_copy
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def copy_plexus_message_2(message: MessageT, *, tailored: bool = False) -> MessageT:
|
|
529
|
+
message_copy = copy.deepcopy(message)
|
|
530
|
+
return tailor_plexus_msg_2(message_copy) if tailored else message_copy
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
|
+
# source: plexus/msgs/vehicle/plexus_msgs_vehicle_can_messages.proto
|
|
5
|
+
# Protobuf Python Version: 6.31.1
|
|
6
|
+
"""Generated protocol buffer code."""
|
|
7
|
+
from google.protobuf import descriptor as _descriptor
|
|
8
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
9
|
+
from google.protobuf import runtime_version as _runtime_version
|
|
10
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
11
|
+
from google.protobuf.internal import builder as _builder
|
|
12
|
+
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
|
+
_runtime_version.Domain.PUBLIC,
|
|
14
|
+
6,
|
|
15
|
+
31,
|
|
16
|
+
1,
|
|
17
|
+
'',
|
|
18
|
+
'plexus/msgs/vehicle/plexus_msgs_vehicle_can_messages.proto'
|
|
19
|
+
)
|
|
20
|
+
# @@protoc_insertion_point(imports)
|
|
21
|
+
|
|
22
|
+
_sym_db = _symbol_database.Default()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n:plexus/msgs/vehicle/plexus_msgs_vehicle_can_messages.proto\x12\x13plexus.msgs.vehicle\"z\n\x0b\x43\x61nMessages\x12:\n\x08messages\x18\x01 \x03(\x0b\x32(.plexus.msgs.vehicle.CanMessages.Message\x1a/\n\x07Message\x12\x0f\n\x02id\x18\x01 \x01(\x05H\x00\x88\x01\x01\x12\x0c\n\x04\x64\x61ta\x18\x02 \x03(\rB\x05\n\x03_idB\x02P\x01\x62\x06proto3')
|
|
28
|
+
|
|
29
|
+
_globals = globals()
|
|
30
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
31
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'plexus.msgs.vehicle.plexus_msgs_vehicle_can_messages_pb2', _globals)
|
|
32
|
+
if not _descriptor._USE_C_DESCRIPTORS:
|
|
33
|
+
_globals['DESCRIPTOR']._loaded_options = None
|
|
34
|
+
_globals['DESCRIPTOR']._serialized_options = b'P\001'
|
|
35
|
+
_globals['_CANMESSAGES']._serialized_start=83
|
|
36
|
+
_globals['_CANMESSAGES']._serialized_end=205
|
|
37
|
+
_globals['_CANMESSAGES_MESSAGE']._serialized_start=158
|
|
38
|
+
_globals['_CANMESSAGES_MESSAGE']._serialized_end=205
|
|
39
|
+
# @@protoc_insertion_point(module_scope)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from google.protobuf.internal import containers as _containers
|
|
2
|
+
from google.protobuf import descriptor as _descriptor
|
|
3
|
+
from google.protobuf import message as _message
|
|
4
|
+
from collections.abc import Iterable as _Iterable, Mapping as _Mapping
|
|
5
|
+
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
|
|
6
|
+
|
|
7
|
+
DESCRIPTOR: _descriptor.FileDescriptor
|
|
8
|
+
|
|
9
|
+
class CanMessages(_message.Message):
|
|
10
|
+
__slots__ = ("messages",)
|
|
11
|
+
class Message(_message.Message):
|
|
12
|
+
__slots__ = ("id", "data")
|
|
13
|
+
ID_FIELD_NUMBER: _ClassVar[int]
|
|
14
|
+
DATA_FIELD_NUMBER: _ClassVar[int]
|
|
15
|
+
id: int
|
|
16
|
+
data: _containers.RepeatedScalarFieldContainer[int]
|
|
17
|
+
def __init__(self, id: _Optional[int] = ..., data: _Optional[_Iterable[int]] = ...) -> None: ...
|
|
18
|
+
MESSAGES_FIELD_NUMBER: _ClassVar[int]
|
|
19
|
+
messages: _containers.RepeatedCompositeFieldContainer[CanMessages.Message]
|
|
20
|
+
def __init__(self, messages: _Optional[_Iterable[_Union[CanMessages.Message, _Mapping]]] = ...) -> None: ...
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: plexus-msgs-python
|
|
3
|
+
Version: 1.0.4
|
|
4
|
+
Summary: Python APIs and utilities for Plexus ProtoBuf messages
|
|
5
|
+
Classifier: Programming Language :: Python :: 3
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
9
|
+
Requires-Python: <3.14,>=3.11
|
|
10
|
+
Requires-Dist: jinja2>=3.0
|
|
11
|
+
Requires-Dist: protobuf>=5.0
|
|
12
|
+
Requires-Dist: iker-python-common>=1.0
|
|
13
|
+
Provides-Extra: all
|
|
14
|
+
Requires-Dist: plexus-msgs-python; extra == "all"
|
|
15
|
+
Provides-Extra: test
|
|
16
|
+
Requires-Dist: ddt>=1.7; extra == "test"
|
|
17
|
+
Requires-Dist: pytest-cov>=5.0; extra == "test"
|
|
18
|
+
Requires-Dist: pytest-order>=1.3; extra == "test"
|
|
19
|
+
Requires-Dist: pytest>=8.3; extra == "test"
|
|
20
|
+
Requires-Dist: iker-python-common>=1.0; extra == "test"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
plexus/msgs/__init__.py,sha256=KSeCSv7WNBxHUsiW-n53CW1ZV7Xy5zomUECCs8m34gY,175
|
|
2
|
+
plexus/msgs/__main__.py,sha256=fyaPPvfB8Oi-Ih0MfX3xt6Tvc3CmNYi7lqy0Xxip01M,1520
|
|
3
|
+
plexus/msgs/descriptor.pb,sha256=0o7Ukil4ir1-KhsEA00UM3c9vEZkm0dJELY9mMB9Iws,144504
|
|
4
|
+
plexus/msgs/plexus_common_carto_pb2.py,sha256=XqqXceWk83171vr4uATmJam2gnwr05VNHhrHSwTSZy8,1994
|
|
5
|
+
plexus/msgs/plexus_common_carto_pb2.pyi,sha256=CKmVNQZuEbdoOFrh58P_BnjMr-Z3T2BKzvSOOlsx-AA,1260
|
|
6
|
+
plexus/msgs/plexus_common_entity_pb2.py,sha256=n2pN2tsDA7rTFB_f7FjED_K1Wk0VN1b2vGzis9-Pqfw,4020
|
|
7
|
+
plexus/msgs/plexus_common_entity_pb2.pyi,sha256=xOOf7WeEbbOTa-xHsEuR-h8mUTWrSaMXkzCAjz-0kME,5513
|
|
8
|
+
plexus/msgs/plexus_common_geom_pb2.py,sha256=HfXZ0_35udZtGXrPzVBk9O8vtTgtN9lomOmWy7sUuQo,5630
|
|
9
|
+
plexus/msgs/plexus_common_geom_pb2.pyi,sha256=tqtV83UHJAu5tzHMF9V7bkh-dpxz8iPeJK0ty1pPB-0,5957
|
|
10
|
+
plexus/msgs/plexus_common_message_pb2.py,sha256=iLbMEUh7QOOOW6XWY3XcjKcxEgZp-Kj7HwavVcSMz9E,2248
|
|
11
|
+
plexus/msgs/plexus_common_message_pb2.pyi,sha256=KrzJ7Mhst37q3lHlWc1Y09APWyTLPixeKRHzoTE8vnM,1377
|
|
12
|
+
plexus/msgs/plexus_common_pb2.py,sha256=yjj8iRNW9VRyRYshcQE2oZzc6YqPrHnkF-Cz0QhxH3o,6397
|
|
13
|
+
plexus/msgs/plexus_common_pb2.pyi,sha256=CW_kDDLX-ZJvvm2GXwNbQtwozhyHh2XWaBMz0c8VN_U,7570
|
|
14
|
+
plexus/msgs/plexus_pb2.py,sha256=V7IiyXRr3V-hx-QXcJHuib9qCCq3Tek24EyawkH4mU8,1967
|
|
15
|
+
plexus/msgs/plexus_pb2.pyi,sha256=xjEufjB6d1xzxkh8BTQXS7zrI047bwdVqqq_jzZMaU0,1098
|
|
16
|
+
plexus/msgs/utils.py,sha256=ZZFuwOIs92TiaeX83nmtjxZZ5lZDCUCvyiPvUfHMInk,20045
|
|
17
|
+
plexus/msgs/control/plexus_msgs_control_control_command_pb2.py,sha256=-_osho0zib5RjkT0j7BdKWMI91fhpHo_odXAiZXLeww,2733
|
|
18
|
+
plexus/msgs/control/plexus_msgs_control_control_command_pb2.pyi,sha256=YkJpNxRQszGzln56Zi0b0IGMMnWJQ6Ce8XH-Yqe_-TA,1993
|
|
19
|
+
plexus/msgs/control/plexus_msgs_control_dbw_command_pb2.py,sha256=u_qxJX3-TDETn5WjBOBC6zofIxcpGfNJm6LGjoAuuro,2861
|
|
20
|
+
plexus/msgs/control/plexus_msgs_control_dbw_command_pb2.pyi,sha256=JBEHEVGYwAPTs99BoErwxQOdOaFItYEe_7q1CchOqww,2306
|
|
21
|
+
plexus/msgs/localization/plexus_msgs_localization_odometry_pb2.py,sha256=Y10YlLeao_r0h3PrSNZ6uriC9FXZ8ROic5QY-pUMVeA,1846
|
|
22
|
+
plexus/msgs/localization/plexus_msgs_localization_odometry_pb2.pyi,sha256=lH_adAUMwoiMLSvWpevs_peU_kqxIuF849uJIakGRhg,751
|
|
23
|
+
plexus/msgs/localization/plexus_msgs_localization_positioning_pb2.py,sha256=M5JAIhGI1Tz3mv5Mg9n6w_-e2LoUkdZ-CHxv7DxKm_I,2130
|
|
24
|
+
plexus/msgs/localization/plexus_msgs_localization_positioning_pb2.pyi,sha256=_gYhMZ20ACOtAQ2CMrjK27mLFa-J9mlj1r3BmnFvxrk,1048
|
|
25
|
+
plexus/msgs/mapping/plexus_msgs_mapping_map_region_pb2.py,sha256=q3O3r5zzCeFStE57GYdjpNf7yXTNCFvCJPeh_ZhjnX8,6309
|
|
26
|
+
plexus/msgs/mapping/plexus_msgs_mapping_map_region_pb2.pyi,sha256=baADpOiWNjDGNL1JVOStXRXWJU3wickfq7O11i11Ct8,7007
|
|
27
|
+
plexus/msgs/perception/plexus_msgs_perception_lane_detection_pb2.py,sha256=0rgyKblnYukkTKzEUKkM8t1Dr1QJzOXYJueZPbwkYDc,6684
|
|
28
|
+
plexus/msgs/perception/plexus_msgs_perception_lane_detection_pb2.pyi,sha256=oX68nke32CKLqnv9peCgfCUAINmfSJDpYfhJJo_nuUw,8486
|
|
29
|
+
plexus/msgs/perception/plexus_msgs_perception_obstacle_detection_pb2.py,sha256=UnZvszkDqW1IGkhYiqo4zlMJRhT4j7poJYVFclIgd4A,6044
|
|
30
|
+
plexus/msgs/perception/plexus_msgs_perception_obstacle_detection_pb2.pyi,sha256=mH-nvuGDHNtPgUXG1LqQxKYgybMPs-gVJp2_6p1Jb6k,7072
|
|
31
|
+
plexus/msgs/planning/plexus_msgs_planning_driving_trajectory_pb2.py,sha256=UlAptS5auZHs1qDz7WbBb7B2s3q0UmmdiCM5EiK0hAg,2101
|
|
32
|
+
plexus/msgs/planning/plexus_msgs_planning_driving_trajectory_pb2.pyi,sha256=Fs5Mz1XAPBgNTZQm3pKfagnAD-jgEm8euR70N0RNRTE,937
|
|
33
|
+
plexus/msgs/planning/plexus_msgs_planning_obstacle_threat_pb2.py,sha256=jR2aJfb2kt0Puw3a0FFm5PhgM0DrfQsWDUodXsoimHg,3237
|
|
34
|
+
plexus/msgs/planning/plexus_msgs_planning_obstacle_threat_pb2.pyi,sha256=UL3RKWbtJFzMEx0Qohmzk6K9lpIPDa-XeR4CiJSHXtI,3844
|
|
35
|
+
plexus/msgs/routing/plexus_msgs_routing_navigation_info_pb2.py,sha256=3lATV0oqWS_vYatBjrgoWDoqFDMcwYPurPiXdZWJaQI,6177
|
|
36
|
+
plexus/msgs/routing/plexus_msgs_routing_navigation_info_pb2.pyi,sha256=z4aOMYCzFUD15_KGxgB5A6nZjggz18G_pyZmHtnzB1c,8892
|
|
37
|
+
plexus/msgs/testing/plexus_msgs_testing_dummy_pb2.py,sha256=x121-jzpntmeaBcHT_p_eOpH2Gin9iu_Gkg2svKXWOI,24230
|
|
38
|
+
plexus/msgs/testing/plexus_msgs_testing_dummy_pb2.pyi,sha256=1k8ygoalUTdRVaazdUB6NvQFrqR94n8FCUHhLsL47Mk,23796
|
|
39
|
+
plexus/msgs/vehicle/plexus_msgs_vehicle_can_messages_pb2.py,sha256=qTRo-uO9pSxOfuVKz8yzfXM5RMSGI5Q7X1d-OO4ezjo,1823
|
|
40
|
+
plexus/msgs/vehicle/plexus_msgs_vehicle_can_messages_pb2.pyi,sha256=syQUL-FMatbe9l6IGDw4cTJ7H3T_e8NJoZiwqSZzjpY,994
|
|
41
|
+
plexus_msgs_python-1.0.4.dist-info/METADATA,sha256=Fr4uTE8mtZse4j3op0F1dVmnAS8ZodlV6hmIV5sfqy0,782
|
|
42
|
+
plexus_msgs_python-1.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
43
|
+
plexus_msgs_python-1.0.4.dist-info/entry_points.txt,sha256=FHsZqentRWi8W_GpY6vJnSN-9Rmriolti9nkVNnXW_Q,65
|
|
44
|
+
plexus_msgs_python-1.0.4.dist-info/top_level.txt,sha256=ug_g7CVwaMQuas5UzAXbHUrQvKGCn8ezc6ZNvvRlJOE,7
|
|
45
|
+
plexus_msgs_python-1.0.4.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
plexus
|