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.
@@ -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