stinger-ipc 0.0.1__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.
- stinger_ipc-0.0.1.dist-info/METADATA +170 -0
- stinger_ipc-0.0.1.dist-info/RECORD +13 -0
- stinger_ipc-0.0.1.dist-info/WHEEL +5 -0
- stinger_ipc-0.0.1.dist-info/licenses/LICENSE +504 -0
- stinger_ipc-0.0.1.dist-info/top_level.txt +1 -0
- stingeripc/__init__.py +4 -0
- stingeripc/args.py +116 -0
- stingeripc/asyncapi.py +501 -0
- stingeripc/components.py +964 -0
- stingeripc/connection.py +7 -0
- stingeripc/exceptions.py +3 -0
- stingeripc/interface.py +38 -0
- stingeripc/topic.py +67 -0
stingeripc/components.py
ADDED
@@ -0,0 +1,964 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from enum import Enum
|
3
|
+
import random
|
4
|
+
import stringcase
|
5
|
+
from abc import abstractmethod
|
6
|
+
from typing import Dict, List, Optional, Any, Union
|
7
|
+
from .topic import SignalTopicCreator, InterfaceTopicCreator, MethodTopicCreator, PropertyTopicCreator
|
8
|
+
from .args import ArgType, ArgPrimitiveType
|
9
|
+
from .exceptions import InvalidStingerStructure
|
10
|
+
from jacobsjinjatoo import stringmanip
|
11
|
+
|
12
|
+
|
13
|
+
class Arg:
|
14
|
+
def __init__(self, name: str, description: str|None = None):
|
15
|
+
self._name = name
|
16
|
+
self._description = description
|
17
|
+
self._default_value = None
|
18
|
+
self._type: ArgType = ArgType.UNKNOWN
|
19
|
+
self._optional: bool = False
|
20
|
+
|
21
|
+
def set_description(self, description: str) -> Arg:
|
22
|
+
self._description = description
|
23
|
+
return self
|
24
|
+
|
25
|
+
@property
|
26
|
+
def name(self) -> str:
|
27
|
+
return self._name
|
28
|
+
|
29
|
+
@property
|
30
|
+
def arg_type(self) -> ArgType:
|
31
|
+
return self._type
|
32
|
+
|
33
|
+
@property
|
34
|
+
def description(self) -> str|None:
|
35
|
+
return self._description
|
36
|
+
|
37
|
+
@property
|
38
|
+
def optional(self) -> bool:
|
39
|
+
return self._optional
|
40
|
+
|
41
|
+
@optional.setter
|
42
|
+
def optional(self, value: bool):
|
43
|
+
self._optional = value
|
44
|
+
|
45
|
+
@property
|
46
|
+
def python_type(self) -> str:
|
47
|
+
return self.name
|
48
|
+
|
49
|
+
@property
|
50
|
+
def python_class(self) -> str:
|
51
|
+
return self.python_type
|
52
|
+
|
53
|
+
@property
|
54
|
+
def python_local_type(self) -> str:
|
55
|
+
return self.python_type.split('.')[-1]
|
56
|
+
|
57
|
+
@property
|
58
|
+
def rust_type(self) -> str:
|
59
|
+
return self.name
|
60
|
+
|
61
|
+
@property
|
62
|
+
def rust_local_type(self) -> str:
|
63
|
+
return self.rust_type
|
64
|
+
|
65
|
+
@classmethod
|
66
|
+
def new_arg_from_stinger(cls, arg_spec: Dict[str, str], stinger_spec: StingerSpec|None=None) -> Arg:
|
67
|
+
if "type" not in arg_spec:
|
68
|
+
raise InvalidStingerStructure("No 'type' in arg structure")
|
69
|
+
if "name" not in arg_spec:
|
70
|
+
raise InvalidStingerStructure("No 'name' in arg structure")
|
71
|
+
|
72
|
+
if hasattr(ArgPrimitiveType, arg_spec["type"].upper()):
|
73
|
+
arg = ArgPrimitive.new_arg_primitive_from_stinger(arg_spec)
|
74
|
+
if opt := arg_spec.get('optional', False):
|
75
|
+
arg.optional = opt
|
76
|
+
return arg
|
77
|
+
else:
|
78
|
+
if stinger_spec is None:
|
79
|
+
raise RuntimeError("Need the root StingerSpec when creating an enum or struct Arg")
|
80
|
+
|
81
|
+
if arg_spec["type"] == 'enum':
|
82
|
+
if "enumName" not in arg_spec:
|
83
|
+
raise InvalidStingerStructure(f"Enum args need a 'enumName'")
|
84
|
+
if arg_spec["enumName"] not in stinger_spec.enums:
|
85
|
+
raise InvalidStingerStructure(f"Enum arg '{arg_spec['enumName']}' was not found in the list of stinger spec enums")
|
86
|
+
arg = ArgEnum(arg_spec["name"], stinger_spec.enums[arg_spec['enumName']])
|
87
|
+
if opt := arg_spec.get('optional', False):
|
88
|
+
arg.optional = opt
|
89
|
+
return arg
|
90
|
+
|
91
|
+
if arg_spec["type"] == "struct":
|
92
|
+
if "structName" not in arg_spec:
|
93
|
+
raise InvalidStingerStructure("Struct args need a 'structName'")
|
94
|
+
if arg_spec["structName"] not in stinger_spec.structs:
|
95
|
+
raise InvalidStingerStructure(f"Struct arg '{arg_spec["structName"]}' was not found in the list of stinger spec structs")
|
96
|
+
arg = ArgStruct(arg_spec["name"], stinger_spec.structs[arg_spec['structName']])
|
97
|
+
if opt := arg_spec.get('optional', False):
|
98
|
+
arg.optional = opt
|
99
|
+
return arg
|
100
|
+
raise RuntimeError("unknown arg type: {arg_spec['type']}")
|
101
|
+
|
102
|
+
@abstractmethod
|
103
|
+
def get_random_example_value(self, lang="python", seed:int=0):
|
104
|
+
...
|
105
|
+
|
106
|
+
class ArgEnum(Arg):
|
107
|
+
def __init__(self, name: str, enum: InterfaceEnum, description: str|None = None):
|
108
|
+
super().__init__(name, description)
|
109
|
+
self._enum = enum
|
110
|
+
self._type = ArgType.ENUM
|
111
|
+
|
112
|
+
@property
|
113
|
+
def enum(self) -> InterfaceEnum:
|
114
|
+
return self._enum
|
115
|
+
|
116
|
+
@property
|
117
|
+
def python_type(self) -> str:
|
118
|
+
if self.optional:
|
119
|
+
return f"{self._enum.python_type} | None"
|
120
|
+
return self._enum.python_type
|
121
|
+
|
122
|
+
@property
|
123
|
+
def python_class(self) -> str:
|
124
|
+
return self._enum.python_type
|
125
|
+
|
126
|
+
@property
|
127
|
+
def cpp_type(self) -> str:
|
128
|
+
if self.optional:
|
129
|
+
return f"boost::optional<{self._enum.cpp_type}>"
|
130
|
+
return self._enum.cpp_type
|
131
|
+
|
132
|
+
@property
|
133
|
+
def rust_type(self) -> str:
|
134
|
+
if self.optional:
|
135
|
+
return f"Option<{self._enum.rust_type}>"
|
136
|
+
return self._enum.rust_type
|
137
|
+
|
138
|
+
@property
|
139
|
+
def rust_local_type(self) -> str:
|
140
|
+
if self.optional:
|
141
|
+
return f"Option<{self._enum.rust_local_type}>"
|
142
|
+
return self._enum.rust_local_type
|
143
|
+
|
144
|
+
@property
|
145
|
+
def cpp_temp_type(self) -> str:
|
146
|
+
return self.cpp_type
|
147
|
+
|
148
|
+
@property
|
149
|
+
def cpp_data_type(self) -> str:
|
150
|
+
return self._enum.cpp_type
|
151
|
+
|
152
|
+
@property
|
153
|
+
def cpp_rapidjson_type(self) -> str:
|
154
|
+
return ArgPrimitiveType.to_cpp_rapidjson_type_str(ArgPrimitiveType.INTEGER)
|
155
|
+
|
156
|
+
@property
|
157
|
+
def markdown_type(self) -> str:
|
158
|
+
return f"[Enum {self._enum.class_name}](#enum-{self._enum.class_name})"
|
159
|
+
|
160
|
+
def get_random_example_value(self, lang="python", seed:int=2) -> str:
|
161
|
+
random_state = random.getstate()
|
162
|
+
random.seed(1)
|
163
|
+
value = random.choice(self._enum.values)
|
164
|
+
if lang == "python":
|
165
|
+
retval = f"{self._enum.get_module_alias()}.{self._enum.class_name}.{stringcase.constcase(value) }"
|
166
|
+
elif lang == "c++":
|
167
|
+
retval = f"{self._enum.class_name}::{stringcase.constcase(value)}"
|
168
|
+
elif lang == "rust":
|
169
|
+
if self.optional:
|
170
|
+
retval = f"Some(connection::payloads::{self._enum.class_name}::{value})"
|
171
|
+
else:
|
172
|
+
retval = f"connection::payloads::{self._enum.class_name}::{value}"
|
173
|
+
random.setstate(random_state)
|
174
|
+
return retval
|
175
|
+
|
176
|
+
def __repr__(self) -> str:
|
177
|
+
return f"<ArgEnum name={self._name}>"
|
178
|
+
|
179
|
+
|
180
|
+
class ArgPrimitive(Arg):
|
181
|
+
def __init__(self, name: str, arg_type: ArgPrimitiveType, description: str|None = None):
|
182
|
+
super().__init__(name, description)
|
183
|
+
self._arg_type = arg_type
|
184
|
+
self._type = ArgType.PRIMITIVE
|
185
|
+
|
186
|
+
@property
|
187
|
+
def type(self) -> ArgPrimitiveType:
|
188
|
+
return self._arg_type
|
189
|
+
|
190
|
+
@property
|
191
|
+
def python_type(self) -> str:
|
192
|
+
return ArgPrimitiveType.to_python_type(self._arg_type, optional=self._optional)
|
193
|
+
|
194
|
+
@property
|
195
|
+
def rust_type(self) -> str:
|
196
|
+
return ArgPrimitiveType.to_rust_type(self._arg_type, optional=self._optional)
|
197
|
+
|
198
|
+
@property
|
199
|
+
def cpp_type(self) -> str:
|
200
|
+
return ArgPrimitiveType.to_cpp_type(self._arg_type, optional=self._optional)
|
201
|
+
|
202
|
+
@property
|
203
|
+
def cpp_temp_type(self) -> str:
|
204
|
+
if self._arg_type == ArgPrimitiveType.STRING:
|
205
|
+
return "std::string"
|
206
|
+
else:
|
207
|
+
return self.cpp_type
|
208
|
+
|
209
|
+
@property
|
210
|
+
def json_type(self) -> str:
|
211
|
+
return ArgPrimitiveType.to_json_type(self._arg_type)
|
212
|
+
|
213
|
+
@property
|
214
|
+
def cpp_rapidjson_type(self) -> str:
|
215
|
+
return ArgPrimitiveType.to_cpp_rapidjson_type_str(self._arg_type)
|
216
|
+
|
217
|
+
def get_random_example_value(self, lang="python", seed:int=2) -> str|float|int|bool|None:
|
218
|
+
random_state = random.getstate()
|
219
|
+
random.seed(seed)
|
220
|
+
retval: str|float|int|bool|None = None
|
221
|
+
if self._arg_type == ArgPrimitiveType.BOOLEAN:
|
222
|
+
retval = random.choice([True, False])
|
223
|
+
if lang != "python":
|
224
|
+
retval = str(retval).lower()
|
225
|
+
elif self._arg_type == ArgPrimitiveType.FLOAT:
|
226
|
+
retval = random.choice([3.14, 1.0, 2.5, 97.9, 1.53])
|
227
|
+
elif self._arg_type == ArgPrimitiveType.INTEGER:
|
228
|
+
retval = random.choice([42, 1981, 2020, 2022, 1200, 5, 99, 123, 2025, 1955])
|
229
|
+
elif self._arg_type == ArgPrimitiveType.STRING:
|
230
|
+
retval = random.choice(['"apples"', '"Joe"', '"example"', '"foo"', '"bar"', '"tiger"'])
|
231
|
+
if lang == "rust":
|
232
|
+
retval = f"{retval}.to_string()"
|
233
|
+
if self.optional and lang == "rust":
|
234
|
+
retval = f"Some({retval})"
|
235
|
+
random.setstate(random_state)
|
236
|
+
return retval
|
237
|
+
|
238
|
+
def __repr__(self) -> str:
|
239
|
+
return f"<ArgPrimitive name={self._name} type={ArgPrimitiveType.to_python_type(self.type)}>"
|
240
|
+
|
241
|
+
@classmethod
|
242
|
+
def new_arg_primitive_from_stinger(cls, stinger: Dict[str, str]) -> ArgPrimitive:
|
243
|
+
if "type" not in stinger:
|
244
|
+
raise InvalidStingerStructure("No 'type' in arg structure")
|
245
|
+
if "name" not in stinger:
|
246
|
+
raise InvalidStingerStructure("No 'name' in arg structure")
|
247
|
+
|
248
|
+
arg_primitive_type = ArgPrimitiveType.from_string(stinger["type"])
|
249
|
+
arg: ArgPrimitive = cls(name=stinger["name"], arg_type=arg_primitive_type)
|
250
|
+
|
251
|
+
if "description" in stinger and isinstance(stinger["description"], str):
|
252
|
+
arg.set_description(stinger["description"])
|
253
|
+
return arg
|
254
|
+
|
255
|
+
|
256
|
+
class ArgStruct(Arg):
|
257
|
+
def __init__(self, name: str, iface_struct: InterfaceStruct):
|
258
|
+
super().__init__(name)
|
259
|
+
assert isinstance(iface_struct, InterfaceStruct), f"Passed {iface_struct=} is type {type(iface_struct)} which is not InterfaceStruct"
|
260
|
+
self._interface_struct: InterfaceStruct = iface_struct
|
261
|
+
self._type = ArgType.STRUCT
|
262
|
+
|
263
|
+
@property
|
264
|
+
def members(self) -> list[Arg]:
|
265
|
+
print(f"Struct: {self._interface_struct=}")
|
266
|
+
return self._interface_struct.members
|
267
|
+
|
268
|
+
@property
|
269
|
+
def cpp_type(self) -> str:
|
270
|
+
return self._interface_struct.cpp_type
|
271
|
+
|
272
|
+
@property
|
273
|
+
def python_type(self) -> str:
|
274
|
+
return f"stinger_types.{self._interface_struct.python_local_type}"
|
275
|
+
|
276
|
+
@property
|
277
|
+
def python_local_type(self) -> str:
|
278
|
+
return self._interface_struct.python_local_type
|
279
|
+
|
280
|
+
@property
|
281
|
+
def rust_type(self) -> str:
|
282
|
+
return self._interface_struct.rust_type
|
283
|
+
|
284
|
+
@property
|
285
|
+
def rust_local_type(self) -> str:
|
286
|
+
return self._interface_struct.rust_local_type
|
287
|
+
|
288
|
+
def get_random_example_value(self, lang="python", seed:int=2) -> str|None:
|
289
|
+
example_list: dict[str] = {a.name: str(a.get_random_example_value(lang, seed=seed)) for a in self.members}
|
290
|
+
if lang == 'c++':
|
291
|
+
return "{" + ", ".join(example_list.values()) + "}"
|
292
|
+
elif lang == 'python':
|
293
|
+
init_list = ", ".join([f"{k}={v}" for k, v in example_list.items()])
|
294
|
+
return f"{self.python_type}({init_list})"
|
295
|
+
elif lang == 'rust':
|
296
|
+
return "%s {%s}" % (self.rust_type, ", ".join([f'{k}: {v}' for k,v in example_list.items()]))
|
297
|
+
return None
|
298
|
+
|
299
|
+
def __str__(self) -> str:
|
300
|
+
return f"<ArgStruct name={self.name}>"
|
301
|
+
|
302
|
+
def __repr__(self):
|
303
|
+
return f"ArgStruct(name={self.name}, iface_struct={self._interface_struct})"
|
304
|
+
|
305
|
+
class Signal(object):
|
306
|
+
def __init__(self, topic_creator: SignalTopicCreator, name: str):
|
307
|
+
self._topic_creator = topic_creator
|
308
|
+
self._name = name
|
309
|
+
self._arg_list = [] # type: List[Arg]
|
310
|
+
|
311
|
+
def add_arg(self, arg: Arg) -> Signal:
|
312
|
+
if arg.name in [a.name for a in self._arg_list]:
|
313
|
+
raise InvalidStingerStructure(f"An arg named '{arg.name}' has been added.")
|
314
|
+
self._arg_list.append(arg)
|
315
|
+
return self
|
316
|
+
|
317
|
+
@property
|
318
|
+
def arg_list(self) -> list[Arg]:
|
319
|
+
return self._arg_list
|
320
|
+
|
321
|
+
@property
|
322
|
+
def name(self) -> str:
|
323
|
+
return self._name
|
324
|
+
|
325
|
+
@property
|
326
|
+
def topic(self) -> str:
|
327
|
+
return self._topic_creator.signal_topic(self.name)
|
328
|
+
|
329
|
+
@classmethod
|
330
|
+
def new_signal_from_stinger(
|
331
|
+
cls, topic_creator: SignalTopicCreator, name: str, signal_spec: Dict[str, str], stinger_spec: StingerSpec|None=None
|
332
|
+
) -> "Signal":
|
333
|
+
"""Alternative constructor from a Stinger signal structure."""
|
334
|
+
signal = cls(topic_creator, name)
|
335
|
+
if "payload" not in signal_spec:
|
336
|
+
raise InvalidStingerStructure("Signal specification must have 'payload'")
|
337
|
+
if not isinstance(signal_spec['payload'], list):
|
338
|
+
raise InvalidStingerStructure(f"Payload must be a list. It is '{type(signal_spec['payload'])}' ")
|
339
|
+
|
340
|
+
for arg_spec in signal_spec["payload"]:
|
341
|
+
if 'name' not in arg_spec or 'type' not in arg_spec:
|
342
|
+
raise InvalidStingerStructure("Arg must have name and type.")
|
343
|
+
new_arg = Arg.new_arg_from_stinger(arg_spec, stinger_spec)
|
344
|
+
signal.add_arg(new_arg)
|
345
|
+
|
346
|
+
return signal
|
347
|
+
|
348
|
+
class Method(object):
|
349
|
+
def __init__(self, topic_creator: MethodTopicCreator, name: str):
|
350
|
+
self._topic_creator = topic_creator
|
351
|
+
self._name = name
|
352
|
+
self._arg_list = [] # type: List[Arg]
|
353
|
+
self._return_value: Arg|list[Arg]|None = None
|
354
|
+
|
355
|
+
def add_arg(self, arg: Arg) -> Method:
|
356
|
+
if arg.name in [a.name for a in self._arg_list]:
|
357
|
+
raise InvalidStingerStructure(f"An arg named '{arg.name}' has been added.")
|
358
|
+
self._arg_list.append(arg)
|
359
|
+
return self
|
360
|
+
|
361
|
+
def add_return_value(self, value: Arg) -> Method:
|
362
|
+
if self._return_value is None:
|
363
|
+
self._return_value = value
|
364
|
+
elif isinstance(self._return_value, list):
|
365
|
+
if value.name in [a.name for a in self._return_value]:
|
366
|
+
raise InvalidStingerStructure(f"A return value named '{value.name}' has been already added.")
|
367
|
+
self._return_value.append(value)
|
368
|
+
elif isinstance(self._return_value, Arg):
|
369
|
+
if value.name == self._return_value.name:
|
370
|
+
raise InvalidStingerStructure(f"Attempt to add '{value.name}' to return value when it is already been added.")
|
371
|
+
self._return_value = [self._return_value, value]
|
372
|
+
return self
|
373
|
+
|
374
|
+
@property
|
375
|
+
def arg_list(self) -> list[Arg]:
|
376
|
+
return self._arg_list
|
377
|
+
|
378
|
+
@property
|
379
|
+
def return_value(self) -> Arg|list[Arg]|None:
|
380
|
+
return self._return_value
|
381
|
+
|
382
|
+
@property
|
383
|
+
def return_value_name(self) -> str:
|
384
|
+
return f"{self.name} return value"
|
385
|
+
|
386
|
+
@property
|
387
|
+
def return_value_cpp_class(self) -> str:
|
388
|
+
if self._return_value is None:
|
389
|
+
return "null"
|
390
|
+
elif isinstance(self._return_value, Arg):
|
391
|
+
return self._return_value.cpp_type
|
392
|
+
elif isinstance(self._return_value, list):
|
393
|
+
return stringmanip.upper_camel_case(self.return_value_name)
|
394
|
+
|
395
|
+
@property
|
396
|
+
def return_value_rust_type(self) -> str:
|
397
|
+
if self._return_value is None:
|
398
|
+
return "None"
|
399
|
+
elif isinstance(self._return_value, Arg):
|
400
|
+
return self._return_value.rust_type
|
401
|
+
elif isinstance(self._return_value, list):
|
402
|
+
return stringmanip.upper_camel_case(self.return_value_name)
|
403
|
+
|
404
|
+
@property
|
405
|
+
def return_value_python_type(self):
|
406
|
+
if self._return_value is None:
|
407
|
+
return "None"
|
408
|
+
elif isinstance(self._return_value, Arg):
|
409
|
+
return self._return_value.python_type
|
410
|
+
elif isinstance(self._return_value, list):
|
411
|
+
return f"stinger_types.{stringmanip.upper_camel_case(self.return_value_name)}"
|
412
|
+
|
413
|
+
@property
|
414
|
+
def return_value_python_local_type(self):
|
415
|
+
if self._return_value is None:
|
416
|
+
return "None"
|
417
|
+
elif isinstance(self._return_value, Arg):
|
418
|
+
return self._return_value.python_local_type
|
419
|
+
elif isinstance(self._return_value, list):
|
420
|
+
return stringmanip.upper_camel_case(self.return_value_name)
|
421
|
+
|
422
|
+
@property
|
423
|
+
def return_value_property_name(self) -> str:
|
424
|
+
if isinstance(self._return_value, Arg):
|
425
|
+
return self._return_value.name
|
426
|
+
else:
|
427
|
+
return self.name
|
428
|
+
|
429
|
+
@property
|
430
|
+
def return_value_type(self) -> str|bool:
|
431
|
+
if self._return_value is None:
|
432
|
+
return False
|
433
|
+
elif isinstance(self._return_value, Arg):
|
434
|
+
return self._return_value.arg_type.name.lower()
|
435
|
+
elif isinstance(self._return_value, list):
|
436
|
+
return "struct"
|
437
|
+
raise RuntimeError("Method return value type was not recognized")
|
438
|
+
|
439
|
+
def get_return_value_random_example_value(self, lang: str="python", seed: int=2):
|
440
|
+
if lang == "python":
|
441
|
+
if self._return_value is None:
|
442
|
+
return "None"
|
443
|
+
elif isinstance(self._return_value, Arg):
|
444
|
+
return self._return_value.get_random_example_value(lang, seed)
|
445
|
+
elif isinstance(self._return_value, list):
|
446
|
+
return f"{[a.get_random_example_value(lang,seed) for a in self._return_value]}"
|
447
|
+
if lang == "c++" or lang == "cpp":
|
448
|
+
if self._return_value is None:
|
449
|
+
return "null"
|
450
|
+
elif isinstance(self._return_value, Arg):
|
451
|
+
return self._return_value.get_random_example_value(lang, seed)
|
452
|
+
elif isinstance(self._return_value, list):
|
453
|
+
return ", ".join([str(a.get_random_example_value(lang,seed)) for a in self._return_value])
|
454
|
+
raise RuntimeError(f"No random example for return value for {lang}")
|
455
|
+
|
456
|
+
@property
|
457
|
+
def name(self) -> str:
|
458
|
+
return self._name
|
459
|
+
|
460
|
+
@property
|
461
|
+
def topic(self) -> str:
|
462
|
+
return self._topic_creator.method_topic(self.name)
|
463
|
+
|
464
|
+
def response_topic(self, client_id) -> str:
|
465
|
+
return self._topic_creator.method_response_topic(self.name, client_id)
|
466
|
+
|
467
|
+
@classmethod
|
468
|
+
def new_method_from_stinger(
|
469
|
+
cls, topic_creator: MethodTopicCreator, name: str, method_spec: Dict[str, str], stinger_spec: StingerSpec|None=None
|
470
|
+
) -> "Method":
|
471
|
+
"""Alternative constructor from a Stinger method structure."""
|
472
|
+
method = cls(topic_creator, name)
|
473
|
+
if "arguments" not in method_spec:
|
474
|
+
raise InvalidStingerStructure("Method specification must have 'arguments'")
|
475
|
+
if not isinstance(method_spec['arguments'], list):
|
476
|
+
raise InvalidStingerStructure(f"Arguments must be a list. It is '{type(method_spec['arguments'])}' ")
|
477
|
+
|
478
|
+
for arg_spec in method_spec["arguments"]:
|
479
|
+
if 'name' not in arg_spec or 'type' not in arg_spec:
|
480
|
+
raise InvalidStingerStructure("Arg must have name and type.")
|
481
|
+
new_arg = Arg.new_arg_from_stinger(arg_spec, stinger_spec)
|
482
|
+
method.add_arg(new_arg)
|
483
|
+
|
484
|
+
if "returnValues" in method_spec:
|
485
|
+
if not isinstance(method_spec['returnValues'], list):
|
486
|
+
raise InvalidStingerStructure(f"ReturnValues must be a list.")
|
487
|
+
|
488
|
+
for arg_spec in method_spec["returnValues"]:
|
489
|
+
if 'name' not in arg_spec or 'type' not in arg_spec:
|
490
|
+
raise InvalidStingerStructure("Return value must have name and type.")
|
491
|
+
new_arg = Arg.new_arg_from_stinger(arg_spec, stinger_spec)
|
492
|
+
method.add_return_value(new_arg)
|
493
|
+
|
494
|
+
return method
|
495
|
+
|
496
|
+
class Property:
|
497
|
+
|
498
|
+
def __init__(self, topic_creator: PropertyTopicCreator, name: str):
|
499
|
+
self._topic_creator = topic_creator
|
500
|
+
self._name = name
|
501
|
+
self._arg_list = [] # type: List[Arg]
|
502
|
+
self._read_only = False
|
503
|
+
|
504
|
+
def add_arg(self, arg: Arg) -> Property:
|
505
|
+
if arg.name in [a.name for a in self._arg_list]:
|
506
|
+
raise InvalidStingerStructure(f"An arg named '{arg.name}' has been added.")
|
507
|
+
self._arg_list.append(arg)
|
508
|
+
return self
|
509
|
+
|
510
|
+
@property
|
511
|
+
def python_local_type(self) -> str:
|
512
|
+
return self.python_class.split('.')[-1]
|
513
|
+
|
514
|
+
@property
|
515
|
+
def python_class(self) -> str:
|
516
|
+
if len(self._arg_list) == 1:
|
517
|
+
return self._arg_list[0].python_class
|
518
|
+
else:
|
519
|
+
return f"stinger_types.{stringmanip.upper_camel_case(self.name)}Property"
|
520
|
+
|
521
|
+
@property
|
522
|
+
def rust_local_type(self) -> str:
|
523
|
+
if len(self._arg_list) == 1:
|
524
|
+
return self._arg_list[0].rust_local_type
|
525
|
+
else:
|
526
|
+
return f"{stringmanip.upper_camel_case(self.name)}Property"
|
527
|
+
|
528
|
+
@property
|
529
|
+
def rust_type(self) -> str:
|
530
|
+
if len(self._arg_list) == 1:
|
531
|
+
return self._arg_list[0].rust_type
|
532
|
+
else:
|
533
|
+
return f"connection::payloads::{self.rust_local_type}"
|
534
|
+
|
535
|
+
@property
|
536
|
+
def arg_list(self) -> list[Arg]:
|
537
|
+
return self._arg_list
|
538
|
+
|
539
|
+
@property
|
540
|
+
def name(self) -> str:
|
541
|
+
return self._name
|
542
|
+
|
543
|
+
@property
|
544
|
+
def value_topic(self) -> str:
|
545
|
+
return self._topic_creator.property_value_topic(self.name)
|
546
|
+
|
547
|
+
@property
|
548
|
+
def update_topic(self) -> str:
|
549
|
+
return self._topic_creator.property_update_topic(self.name)
|
550
|
+
|
551
|
+
@property
|
552
|
+
def read_only(self) -> bool:
|
553
|
+
return self._read_only
|
554
|
+
|
555
|
+
@classmethod
|
556
|
+
def new_method_from_stinger(
|
557
|
+
cls, topic_creator: PropertyTopicCreator, name: str, prop_spec: Dict[str, str], stinger_spec: StingerSpec|None=None
|
558
|
+
) -> "Property":
|
559
|
+
"""Alternative constructor from a Stinger method structure."""
|
560
|
+
prop_obj = cls(topic_creator, name)
|
561
|
+
if "values" not in prop_spec:
|
562
|
+
raise InvalidStingerStructure("Property specification must have 'values'")
|
563
|
+
if not isinstance(prop_spec['values'], list):
|
564
|
+
raise InvalidStingerStructure(f"Values must be a list. It is '{type(prop_spec['values'])}' ")
|
565
|
+
|
566
|
+
for arg_spec in prop_spec["values"]:
|
567
|
+
if 'name' not in arg_spec or 'type' not in arg_spec:
|
568
|
+
raise InvalidStingerStructure("Arg must have name and type.")
|
569
|
+
new_arg = Arg.new_arg_from_stinger(arg_spec, stinger_spec)
|
570
|
+
prop_obj.add_arg(new_arg)
|
571
|
+
|
572
|
+
if r_o := prop_spec.get("readOnly", False):
|
573
|
+
prop_obj._read_only = r_o
|
574
|
+
|
575
|
+
return prop_obj
|
576
|
+
|
577
|
+
def __str__(self) -> str:
|
578
|
+
return f"Property<name={self.name} values=[{', '.join([a.name for a in self.arg_list])}]>"
|
579
|
+
|
580
|
+
class InterfaceEnum:
|
581
|
+
|
582
|
+
def __init__(self, name: str):
|
583
|
+
self._name = name
|
584
|
+
self._values: list[Any] = []
|
585
|
+
|
586
|
+
def add_value(self, value: str):
|
587
|
+
self._values.append(value)
|
588
|
+
|
589
|
+
@property
|
590
|
+
def name(self):
|
591
|
+
return self._name
|
592
|
+
|
593
|
+
@property
|
594
|
+
def class_name(self):
|
595
|
+
return stringmanip.upper_camel_case(self.name)
|
596
|
+
|
597
|
+
@staticmethod
|
598
|
+
def get_module_name(lang="python") -> str:
|
599
|
+
return "interface_types"
|
600
|
+
|
601
|
+
@staticmethod
|
602
|
+
def get_module_alias(lang="python") -> str:
|
603
|
+
return "stinger_types"
|
604
|
+
|
605
|
+
@property
|
606
|
+
def python_type(self) -> str:
|
607
|
+
return f"{self.get_module_alias()}.{stringmanip.upper_camel_case(self.name)}"
|
608
|
+
|
609
|
+
@property
|
610
|
+
def rust_local_type(self) -> str:
|
611
|
+
return stringmanip.upper_camel_case(self.name)
|
612
|
+
|
613
|
+
@property
|
614
|
+
def rust_type(self) -> str:
|
615
|
+
return f"connection::payloads::{self.rust_local_type}"
|
616
|
+
|
617
|
+
@property
|
618
|
+
def cpp_type(self) -> str:
|
619
|
+
return stringmanip.upper_camel_case(self.name)
|
620
|
+
|
621
|
+
@property
|
622
|
+
def values(self):
|
623
|
+
return self._values
|
624
|
+
|
625
|
+
@classmethod
|
626
|
+
def new_enum_from_stinger(cls, name, values: List[Dict[str, str]]) -> InterfaceEnum:
|
627
|
+
ie = cls(name)
|
628
|
+
for enum_obj in values:
|
629
|
+
if "name" in enum_obj:
|
630
|
+
ie.add_value(enum_obj["name"])
|
631
|
+
else:
|
632
|
+
raise InvalidStingerStructure("InterfaceEnum item must have a name")
|
633
|
+
return ie
|
634
|
+
|
635
|
+
|
636
|
+
class InterfaceStruct:
|
637
|
+
|
638
|
+
def __init__(self, name: str):
|
639
|
+
self._name = name
|
640
|
+
self._members: list[Arg] = []
|
641
|
+
|
642
|
+
def add_member(self, arg: Arg):
|
643
|
+
self._members.append(arg)
|
644
|
+
|
645
|
+
@property
|
646
|
+
def name(self):
|
647
|
+
return self._name
|
648
|
+
|
649
|
+
@property
|
650
|
+
def class_name(self):
|
651
|
+
return stringmanip.upper_camel_case(self.name)
|
652
|
+
|
653
|
+
@staticmethod
|
654
|
+
def get_module_name(lang="python") -> str:
|
655
|
+
return "interface_types"
|
656
|
+
|
657
|
+
@staticmethod
|
658
|
+
def get_module_alias(lang="python") -> str:
|
659
|
+
return "stinger_types"
|
660
|
+
|
661
|
+
@property
|
662
|
+
def python_type(self) -> str:
|
663
|
+
return f"{self.get_module_alias()}.{stringmanip.upper_camel_case(self.name)}"
|
664
|
+
|
665
|
+
@property
|
666
|
+
def python_local_type(self) -> str:
|
667
|
+
return stringmanip.upper_camel_case(self.name)
|
668
|
+
|
669
|
+
@property
|
670
|
+
def rust_local_type(self) -> str:
|
671
|
+
return stringmanip.upper_camel_case(self.name)
|
672
|
+
|
673
|
+
@property
|
674
|
+
def rust_type(self) -> str:
|
675
|
+
return f"connection::payloads::{self.rust_local_type}"
|
676
|
+
|
677
|
+
@property
|
678
|
+
def cpp_type(self) -> str:
|
679
|
+
return stringmanip.upper_camel_case(self.name)
|
680
|
+
|
681
|
+
@property
|
682
|
+
def values(self) -> list[Arg]:
|
683
|
+
return self._members
|
684
|
+
|
685
|
+
@property
|
686
|
+
def members(self) -> list[Arg]:
|
687
|
+
return self._members
|
688
|
+
|
689
|
+
@classmethod
|
690
|
+
def new_struct_from_stinger(cls, name, spec: Dict[str, str|List[Dict[str,str]]], stinger_spec: StingerSpec) -> InterfaceStruct:
|
691
|
+
istruct = cls(name)
|
692
|
+
for memb in spec.get('members', []):
|
693
|
+
arg = Arg.new_arg_from_stinger(memb, stinger_spec=stinger_spec)
|
694
|
+
istruct.add_member(arg)
|
695
|
+
return istruct
|
696
|
+
|
697
|
+
def __str__(self) -> str:
|
698
|
+
return f"<InterfaceStruct members={[m.name for m in self.members]}>"
|
699
|
+
|
700
|
+
def __repr__(self):
|
701
|
+
return f"InterfaceStruct(name={self.name})"
|
702
|
+
|
703
|
+
class MqttTransportProtocol(Enum):
|
704
|
+
TCP = 0
|
705
|
+
WEBSOCKETS = 1
|
706
|
+
|
707
|
+
|
708
|
+
class Broker:
|
709
|
+
def __init__(self, name: str="Default"):
|
710
|
+
self._name: str = name
|
711
|
+
self._host: str|None = None
|
712
|
+
self._port: int|None = None
|
713
|
+
self._auth = None
|
714
|
+
self._transport_protocol: MqttTransportProtocol = MqttTransportProtocol.TCP
|
715
|
+
|
716
|
+
@property
|
717
|
+
def name(self) -> str:
|
718
|
+
return self._name
|
719
|
+
|
720
|
+
@property
|
721
|
+
def class_name(self) -> str:
|
722
|
+
return f"{stringcase.pascalcase(self.name)}Connection"
|
723
|
+
|
724
|
+
@property
|
725
|
+
def hostname(self) -> str|None:
|
726
|
+
return self._host
|
727
|
+
|
728
|
+
@hostname.setter
|
729
|
+
def hostname(self, value: str):
|
730
|
+
self._host = value
|
731
|
+
|
732
|
+
@property
|
733
|
+
def port(self) -> int|None:
|
734
|
+
return self._port
|
735
|
+
|
736
|
+
@port.setter
|
737
|
+
def port(self, port: int):
|
738
|
+
self._port = port
|
739
|
+
|
740
|
+
@classmethod
|
741
|
+
def new_broker_from_stinger(cls, name: str, spec: Dict[str, Any]) -> Broker:
|
742
|
+
new_broker = cls(name=name)
|
743
|
+
if 'host' in spec:
|
744
|
+
new_broker.hostname = spec['host']
|
745
|
+
if 'port' in spec:
|
746
|
+
new_broker.port = int(spec['port'])
|
747
|
+
return new_broker
|
748
|
+
|
749
|
+
def __repr__(self) -> str:
|
750
|
+
return f"<Broker name={self.name} host={self.hostname}:{self.port}>"
|
751
|
+
|
752
|
+
|
753
|
+
class StingerSpec:
|
754
|
+
def __init__(self, topic_creator: InterfaceTopicCreator, interface):
|
755
|
+
self._topic_creator = topic_creator
|
756
|
+
try:
|
757
|
+
self._name: str = interface["name"]
|
758
|
+
self._version: str = interface["version"]
|
759
|
+
except KeyError as e:
|
760
|
+
raise InvalidStingerStructure(
|
761
|
+
f"Missing interface property in {interface}: {e}"
|
762
|
+
)
|
763
|
+
except TypeError:
|
764
|
+
raise InvalidStingerStructure(
|
765
|
+
f"Interface didn't appear to have a correct type"
|
766
|
+
)
|
767
|
+
|
768
|
+
self._summary = interface['summary'] if 'summary' in interface else None
|
769
|
+
self._title = interface['title'] if 'title' in interface else None
|
770
|
+
|
771
|
+
self.signals: dict[str, Signal] = {}
|
772
|
+
self.properties: dict[str, Any] = {}
|
773
|
+
self.methods: dict[str, Method] = {}
|
774
|
+
self.enums: dict[str, InterfaceEnum] = {}
|
775
|
+
self.structs: dict[str, InterfaceStruct] = {}
|
776
|
+
self._brokers: dict[str, Broker] = {}
|
777
|
+
|
778
|
+
@property
|
779
|
+
def method_return_codes(self) -> dict[int, str]:
|
780
|
+
return {
|
781
|
+
0: "Success",
|
782
|
+
1: "Client Error",
|
783
|
+
2: "Server Error",
|
784
|
+
3: "Transport Error",
|
785
|
+
4: "Payload Error",
|
786
|
+
5: "Timeout",
|
787
|
+
6: "Unknown Error",
|
788
|
+
7: "Not Implemented",
|
789
|
+
}
|
790
|
+
|
791
|
+
@property
|
792
|
+
def interface_info(self) -> tuple[str, dict[str, Any]]:
|
793
|
+
info = {
|
794
|
+
"name": self._name,
|
795
|
+
"version": self._version,
|
796
|
+
"title": self._title or self._name,
|
797
|
+
"summary": self._summary or '',
|
798
|
+
}
|
799
|
+
return (
|
800
|
+
self._topic_creator.interface_info_topic(),
|
801
|
+
info
|
802
|
+
)
|
803
|
+
|
804
|
+
def add_broker(self, broker: Broker):
|
805
|
+
assert broker is not None
|
806
|
+
self._brokers[broker.name] = broker
|
807
|
+
|
808
|
+
@property
|
809
|
+
def brokers(self) -> Dict[str, Broker]:
|
810
|
+
if len(self._brokers) == 0:
|
811
|
+
default_broker = Broker()
|
812
|
+
return {default_broker.name: default_broker}
|
813
|
+
else:
|
814
|
+
return self._brokers
|
815
|
+
|
816
|
+
def get_example_broker(self) -> Broker|None:
|
817
|
+
for broker in self.brokers.values():
|
818
|
+
return broker
|
819
|
+
return None
|
820
|
+
|
821
|
+
def add_signal(self, signal: Signal):
|
822
|
+
assert isinstance(signal, Signal)
|
823
|
+
self.signals[signal.name] = signal
|
824
|
+
|
825
|
+
def add_method(self, method: Method):
|
826
|
+
assert isinstance(method, Method)
|
827
|
+
self.methods[method.name] = method
|
828
|
+
|
829
|
+
def add_property(self, prop: Property):
|
830
|
+
assert isinstance(prop, Property)
|
831
|
+
self.properties[prop.name] = prop
|
832
|
+
|
833
|
+
def add_enum(self, interface_enum: InterfaceEnum):
|
834
|
+
assert interface_enum is not None
|
835
|
+
self.enums[interface_enum.name] = interface_enum
|
836
|
+
|
837
|
+
def add_struct(self, interface_struct: InterfaceStruct):
|
838
|
+
assert interface_struct is not None
|
839
|
+
self.structs[interface_struct.name] = interface_struct
|
840
|
+
print(f"All structs so far: {self.structs=}")
|
841
|
+
|
842
|
+
def uses_enums(self) -> bool:
|
843
|
+
return bool(self.enums)
|
844
|
+
|
845
|
+
@property
|
846
|
+
def name(self):
|
847
|
+
return self._name
|
848
|
+
|
849
|
+
@property
|
850
|
+
def version(self):
|
851
|
+
return self._version
|
852
|
+
|
853
|
+
@staticmethod
|
854
|
+
def get_enum_module_name() -> str:
|
855
|
+
return InterfaceEnum.get_module_name()
|
856
|
+
|
857
|
+
@staticmethod
|
858
|
+
def get_enum_module_alias() -> str:
|
859
|
+
return InterfaceEnum.get_module_alias()
|
860
|
+
|
861
|
+
@classmethod
|
862
|
+
def new_spec_from_stinger(cls, topic_creator, stinger: Dict[str, Any]) -> StingerSpec:
|
863
|
+
if "stingeripc" not in stinger:
|
864
|
+
raise InvalidStingerStructure("Missing 'stingeripc' format version")
|
865
|
+
if "version" not in stinger["stingeripc"]:
|
866
|
+
raise InvalidStingerStructure("Stinger spec version not present")
|
867
|
+
if stinger["stingeripc"]["version"] not in ["0.0.6"]:
|
868
|
+
raise InvalidStingerStructure(
|
869
|
+
f"Unsupported stinger spec version {stinger['stingeripc']['version']}"
|
870
|
+
)
|
871
|
+
|
872
|
+
stinger_spec = StingerSpec(topic_creator, stinger["interface"])
|
873
|
+
|
874
|
+
# Enums must come before other components because other components may use enum values.
|
875
|
+
try:
|
876
|
+
if "enums" in stinger:
|
877
|
+
for enum_name, enum_spec in stinger["enums"].items():
|
878
|
+
ie = InterfaceEnum.new_enum_from_stinger(enum_name, enum_spec)
|
879
|
+
assert (ie is not None), f"Did not create enum from {enum_name} and {enum_spec}"
|
880
|
+
stinger_spec.add_enum(ie)
|
881
|
+
except TypeError as e:
|
882
|
+
raise InvalidStingerStructure(
|
883
|
+
f"Signal specification appears to be invalid: {e}"
|
884
|
+
)
|
885
|
+
|
886
|
+
try:
|
887
|
+
if "structures" in stinger:
|
888
|
+
for struct_name, struct_spec in stinger["structures"].items():
|
889
|
+
istruct = InterfaceStruct.new_struct_from_stinger(struct_name, struct_spec, stinger_spec)
|
890
|
+
assert (istruct is not None), f"Did not create struct from {struct_name} and {struct_spec}"
|
891
|
+
print(f"Created interface struct {istruct=}")
|
892
|
+
stinger_spec.add_struct(istruct)
|
893
|
+
print(f"The created structure is {stinger_spec.structs[struct_name]}")
|
894
|
+
except TypeError as e:
|
895
|
+
raise InvalidStingerStructure(
|
896
|
+
f"Struct specification appears to be invalid: {e}"
|
897
|
+
)
|
898
|
+
|
899
|
+
try:
|
900
|
+
if "brokers" in stinger:
|
901
|
+
for broker_name, broker_spec in stinger["brokers"].items():
|
902
|
+
broker = Broker.new_broker_from_stinger(broker_name, broker_spec)
|
903
|
+
assert (broker is not None), f"Did not create broker from {broker_name} and {broker_spec}"
|
904
|
+
stinger_spec.add_broker(broker)
|
905
|
+
except TypeError as e:
|
906
|
+
raise InvalidStingerStructure(
|
907
|
+
f"Broker specification appears to be invalid: {e}"
|
908
|
+
)
|
909
|
+
|
910
|
+
try:
|
911
|
+
if "signals" in stinger:
|
912
|
+
for signal_name, signal_spec in stinger["signals"].items():
|
913
|
+
signal = Signal.new_signal_from_stinger(
|
914
|
+
topic_creator.signal_topic_creator(),
|
915
|
+
signal_name,
|
916
|
+
signal_spec,
|
917
|
+
stinger_spec
|
918
|
+
)
|
919
|
+
assert (
|
920
|
+
signal is not None
|
921
|
+
), f"Did not create signal from {signal_name} and {signal_spec}"
|
922
|
+
stinger_spec.add_signal(signal)
|
923
|
+
except TypeError as e:
|
924
|
+
raise InvalidStingerStructure(
|
925
|
+
f"Signal specification appears to be invalid: {e}"
|
926
|
+
)
|
927
|
+
|
928
|
+
try:
|
929
|
+
if "methods" in stinger:
|
930
|
+
for method_name, method_spec in stinger["methods"].items():
|
931
|
+
method = Method.new_method_from_stinger(
|
932
|
+
topic_creator.method_topic_creator(),
|
933
|
+
method_name,
|
934
|
+
method_spec,
|
935
|
+
stinger_spec
|
936
|
+
)
|
937
|
+
assert (
|
938
|
+
method is not None
|
939
|
+
), f"Did not create method from {method_name} and {method_spec}"
|
940
|
+
stinger_spec.add_method(method)
|
941
|
+
except TypeError as e:
|
942
|
+
raise InvalidStingerStructure(
|
943
|
+
f"Method specification appears to be invalid: {e}"
|
944
|
+
)
|
945
|
+
|
946
|
+
try:
|
947
|
+
if "properties" in stinger:
|
948
|
+
for prop_name, prop_spec in stinger["properties"].items():
|
949
|
+
prop = Property.new_method_from_stinger(
|
950
|
+
topic_creator.property_topic_creator(),
|
951
|
+
prop_name,
|
952
|
+
prop_spec,
|
953
|
+
stinger_spec
|
954
|
+
)
|
955
|
+
assert (
|
956
|
+
prop is not None
|
957
|
+
), f"Did not create property from {prop_name} and {prop_spec}"
|
958
|
+
stinger_spec.add_property(prop)
|
959
|
+
except TypeError as e:
|
960
|
+
raise InvalidStingerStructure(
|
961
|
+
f"Property specification appears to be invalid: {e}"
|
962
|
+
)
|
963
|
+
|
964
|
+
return stinger_spec
|