stinger-ipc 0.0.1__py3-none-any.whl → 0.0.3__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.
Files changed (52) hide show
  1. {stinger_ipc-0.0.1.dist-info → stinger_ipc-0.0.3.dist-info}/METADATA +4 -4
  2. stinger_ipc-0.0.3.dist-info/RECORD +52 -0
  3. stinger_ipc-0.0.3.dist-info/entry_points.txt +4 -0
  4. stingeripc/args.py +6 -5
  5. stingeripc/asyncapi.py +249 -167
  6. stingeripc/components.py +301 -136
  7. stingeripc/connection.py +2 -1
  8. stingeripc/exceptions.py +1 -2
  9. stingeripc/interface.py +8 -4
  10. stingeripc/lang_symb.py +68 -0
  11. stingeripc/templates/cpp/CMakeLists.txt.jinja2 +26 -0
  12. stingeripc/templates/cpp/examples/client_main.cpp.jinja2 +47 -0
  13. stingeripc/templates/cpp/examples/server_main.cpp.jinja2 +35 -0
  14. stingeripc/templates/cpp/include/broker.hpp.jinja2 +132 -0
  15. stingeripc/templates/cpp/include/client.hpp.jinja2 +53 -0
  16. stingeripc/templates/cpp/include/enums.hpp.jinja2 +17 -0
  17. stingeripc/templates/cpp/include/ibrokerconnection.hpp.jinja2 +42 -0
  18. stingeripc/templates/cpp/include/return_types.hpp.jinja2 +14 -0
  19. stingeripc/templates/cpp/include/server.hpp.jinja2 +44 -0
  20. stingeripc/templates/cpp/include/structs.hpp.jinja2 +13 -0
  21. stingeripc/templates/cpp/src/broker.cpp.jinja2 +243 -0
  22. stingeripc/templates/cpp/src/client.cpp.jinja2 +202 -0
  23. stingeripc/templates/cpp/src/server.cpp.jinja2 +170 -0
  24. stingeripc/templates/markdown/index.md.jinja2 +142 -0
  25. stingeripc/templates/python/__init__.py.jinja2 +1 -0
  26. stingeripc/templates/python/client.py.jinja2 +309 -0
  27. stingeripc/templates/python/connection.py.jinja2 +164 -0
  28. stingeripc/templates/python/interface_types.py.jinja2 +48 -0
  29. stingeripc/templates/python/method_codes.py.jinja2 +30 -0
  30. stingeripc/templates/python/pyproject.toml.jinja2 +9 -0
  31. stingeripc/templates/python/server.py.jinja2 +214 -0
  32. stingeripc/templates/rust/Cargo.toml.jinja2 +4 -0
  33. stingeripc/templates/rust/client/Cargo.toml.jinja2 +25 -0
  34. stingeripc/templates/rust/client/examples/client.rs.jinja2 +53 -0
  35. stingeripc/templates/rust/client/src/lib.rs.jinja2 +247 -0
  36. stingeripc/templates/rust/connection/Cargo.toml.jinja2 +21 -0
  37. stingeripc/templates/rust/connection/examples/pub_and_recv.rs.jinja2 +44 -0
  38. stingeripc/templates/rust/connection/src/handler.rs.jinja2 +0 -0
  39. stingeripc/templates/rust/connection/src/lib.rs.jinja2 +262 -0
  40. stingeripc/templates/rust/connection/src/payloads.rs.jinja2 +131 -0
  41. stingeripc/templates/rust/server/Cargo.toml.jinja2 +19 -0
  42. stingeripc/templates/rust/server/examples/server.rs.jinja2 +83 -0
  43. stingeripc/templates/rust/server/src/lib.rs.jinja2 +272 -0
  44. stingeripc/tools/__init__.py +0 -0
  45. stingeripc/tools/markdown_generator.py +25 -0
  46. stingeripc/tools/python_generator.py +41 -0
  47. stingeripc/tools/rust_generator.py +50 -0
  48. stingeripc/topic.py +11 -8
  49. stinger_ipc-0.0.1.dist-info/RECORD +0 -13
  50. {stinger_ipc-0.0.1.dist-info → stinger_ipc-0.0.3.dist-info}/WHEEL +0 -0
  51. {stinger_ipc-0.0.1.dist-info → stinger_ipc-0.0.3.dist-info}/licenses/LICENSE +0 -0
  52. {stinger_ipc-0.0.1.dist-info → stinger_ipc-0.0.3.dist-info}/top_level.txt +0 -0
stingeripc/components.py CHANGED
@@ -4,14 +4,26 @@ import random
4
4
  import stringcase
5
5
  from abc import abstractmethod
6
6
  from typing import Dict, List, Optional, Any, Union
7
- from .topic import SignalTopicCreator, InterfaceTopicCreator, MethodTopicCreator, PropertyTopicCreator
7
+ from .topic import (
8
+ SignalTopicCreator,
9
+ InterfaceTopicCreator,
10
+ MethodTopicCreator,
11
+ PropertyTopicCreator,
12
+ )
13
+ from .lang_symb import *
8
14
  from .args import ArgType, ArgPrimitiveType
9
15
  from .exceptions import InvalidStingerStructure
10
16
  from jacobsjinjatoo import stringmanip
11
17
 
18
+ YamlArg = dict[str, str | bool]
19
+ YamlArgList = list[YamlArg]
20
+ YamlIfaceEnum = dict[str, str | YamlArgList]
21
+ YamlIfaceEnums = dict[str, YamlIfaceEnum]
22
+ YamlIfaceProperty = dict[str, str | bool | YamlArgList]
23
+
12
24
 
13
25
  class Arg:
14
- def __init__(self, name: str, description: str|None = None):
26
+ def __init__(self, name: str, description: str | None = None):
15
27
  self._name = name
16
28
  self._description = description
17
29
  self._default_value = None
@@ -22,6 +34,11 @@ class Arg:
22
34
  self._description = description
23
35
  return self
24
36
 
37
+ def try_set_description_from_spec(self, spec: dict[str, Any]) -> Arg:
38
+ if "description" in spec and isinstance(spec["description"], str):
39
+ self._description = spec["description"]
40
+ return self
41
+
25
42
  @property
26
43
  def name(self) -> str:
27
44
  return self._name
@@ -31,7 +48,7 @@ class Arg:
31
48
  return self._type
32
49
 
33
50
  @property
34
- def description(self) -> str|None:
51
+ def description(self) -> str | None:
35
52
  return self._description
36
53
 
37
54
  @property
@@ -52,7 +69,7 @@ class Arg:
52
69
 
53
70
  @property
54
71
  def python_local_type(self) -> str:
55
- return self.python_type.split('.')[-1]
72
+ return self.python_type.split(".")[-1]
56
73
 
57
74
  @property
58
75
  def rust_type(self) -> str:
@@ -63,48 +80,67 @@ class Arg:
63
80
  return self.rust_type
64
81
 
65
82
  @classmethod
66
- def new_arg_from_stinger(cls, arg_spec: Dict[str, str], stinger_spec: StingerSpec|None=None) -> Arg:
83
+ def new_arg_from_stinger(
84
+ cls, arg_spec: YamlArg, stinger_spec: StingerSpec | None = None
85
+ ) -> Arg:
67
86
  if "type" not in arg_spec:
68
87
  raise InvalidStingerStructure("No 'type' in arg structure")
69
88
  if "name" not in arg_spec:
70
89
  raise InvalidStingerStructure("No 'name' in arg structure")
90
+ if not isinstance(arg_spec["type"], str):
91
+ raise InvalidStingerStructure("'type' in arg structure must be a string")
92
+ if not isinstance(arg_spec["name"], str):
93
+ raise InvalidStingerStructure("'name' in arg structure must be a string")
71
94
 
72
95
  if hasattr(ArgPrimitiveType, arg_spec["type"].upper()):
73
96
  arg = ArgPrimitive.new_arg_primitive_from_stinger(arg_spec)
74
- if opt := arg_spec.get('optional', False):
97
+ if opt := arg_spec.get("optional", False):
98
+ assert isinstance(opt, bool), "Optional field must be a boolean"
75
99
  arg.optional = opt
76
100
  return arg
77
101
  else:
78
102
  if stinger_spec is None:
79
- raise RuntimeError("Need the root StingerSpec when creating an enum or struct Arg")
103
+ raise RuntimeError(
104
+ "Need the root StingerSpec when creating an enum or struct Arg"
105
+ )
80
106
 
81
- if arg_spec["type"] == 'enum':
107
+ if arg_spec["type"] == "enum":
82
108
  if "enumName" not in arg_spec:
83
109
  raise InvalidStingerStructure(f"Enum args need a 'enumName'")
84
110
  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):
111
+ raise InvalidStingerStructure(
112
+ f"Enum arg '{arg_spec['enumName']}' was not found in the list of stinger spec enums"
113
+ )
114
+ arg = ArgEnum(
115
+ arg_spec["name"], stinger_spec.get_interface_enum(arg_spec["enumName"])
116
+ )
117
+ if opt := arg_spec.get("optional", False):
88
118
  arg.optional = opt
119
+ arg.try_set_description_from_spec(arg_spec)
89
120
  return arg
90
-
121
+
91
122
  if arg_spec["type"] == "struct":
92
123
  if "structName" not in arg_spec:
93
124
  raise InvalidStingerStructure("Struct args need a 'structName'")
94
125
  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):
126
+ raise InvalidStingerStructure(
127
+ f"Struct arg '{arg_spec["structName"]}' was not found in the list of stinger spec structs"
128
+ )
129
+ arg = ArgStruct(
130
+ arg_spec["name"], stinger_spec.structs[arg_spec["structName"]]
131
+ )
132
+ if opt := arg_spec.get("optional", False):
98
133
  arg.optional = opt
134
+ arg.try_set_description_from_spec(arg_spec)
99
135
  return arg
100
- raise RuntimeError("unknown arg type: {arg_spec['type']}")
136
+ raise RuntimeError(f"unknown arg type: {arg_spec['type']}")
101
137
 
102
138
  @abstractmethod
103
- def get_random_example_value(self, lang="python", seed:int=0):
104
- ...
139
+ def get_random_example_value(self, lang="python", seed: int = 0): ...
140
+
105
141
 
106
142
  class ArgEnum(Arg):
107
- def __init__(self, name: str, enum: InterfaceEnum, description: str|None = None):
143
+ def __init__(self, name: str, enum: InterfaceEnum, description: str | None = None):
108
144
  super().__init__(name, description)
109
145
  self._enum = enum
110
146
  self._type = ArgType.ENUM
@@ -157,19 +193,19 @@ class ArgEnum(Arg):
157
193
  def markdown_type(self) -> str:
158
194
  return f"[Enum {self._enum.class_name}](#enum-{self._enum.class_name})"
159
195
 
160
- def get_random_example_value(self, lang="python", seed:int=2) -> str:
196
+ def get_random_example_value(self, lang="python", seed: int = 2) -> str:
161
197
  random_state = random.getstate()
162
198
  random.seed(1)
163
- value = random.choice(self._enum.values)
199
+ value = random.choice(self._enum.values)
164
200
  if lang == "python":
165
201
  retval = f"{self._enum.get_module_alias()}.{self._enum.class_name}.{stringcase.constcase(value) }"
166
202
  elif lang == "c++":
167
203
  retval = f"{self._enum.class_name}::{stringcase.constcase(value)}"
168
204
  elif lang == "rust":
169
205
  if self.optional:
170
- retval = f"Some(connection::payloads::{self._enum.class_name}::{value})"
206
+ retval = f"Some(connection::payloads::{self._enum.class_name}::{stringmanip.upper_camel_case(value)})"
171
207
  else:
172
- retval = f"connection::payloads::{self._enum.class_name}::{value}"
208
+ retval = f"connection::payloads::{self._enum.class_name}::{stringmanip.upper_camel_case(value)}"
173
209
  random.setstate(random_state)
174
210
  return retval
175
211
 
@@ -178,7 +214,9 @@ class ArgEnum(Arg):
178
214
 
179
215
 
180
216
  class ArgPrimitive(Arg):
181
- def __init__(self, name: str, arg_type: ArgPrimitiveType, description: str|None = None):
217
+ def __init__(
218
+ self, name: str, arg_type: ArgPrimitiveType, description: str | None = None
219
+ ):
182
220
  super().__init__(name, description)
183
221
  self._arg_type = arg_type
184
222
  self._type = ArgType.PRIMITIVE
@@ -205,7 +243,7 @@ class ArgPrimitive(Arg):
205
243
  return "std::string"
206
244
  else:
207
245
  return self.cpp_type
208
-
246
+
209
247
  @property
210
248
  def json_type(self) -> str:
211
249
  return ArgPrimitiveType.to_json_type(self._arg_type)
@@ -214,10 +252,12 @@ class ArgPrimitive(Arg):
214
252
  def cpp_rapidjson_type(self) -> str:
215
253
  return ArgPrimitiveType.to_cpp_rapidjson_type_str(self._arg_type)
216
254
 
217
- def get_random_example_value(self, lang="python", seed:int=2) -> str|float|int|bool|None:
255
+ def get_random_example_value(
256
+ self, lang="python", seed: int = 2
257
+ ) -> str | float | int | bool | None:
218
258
  random_state = random.getstate()
219
259
  random.seed(seed)
220
- retval: str|float|int|bool|None = None
260
+ retval: str | float | int | bool | None = None
221
261
  if self._arg_type == ArgPrimitiveType.BOOLEAN:
222
262
  retval = random.choice([True, False])
223
263
  if lang != "python":
@@ -227,7 +267,9 @@ class ArgPrimitive(Arg):
227
267
  elif self._arg_type == ArgPrimitiveType.INTEGER:
228
268
  retval = random.choice([42, 1981, 2020, 2022, 1200, 5, 99, 123, 2025, 1955])
229
269
  elif self._arg_type == ArgPrimitiveType.STRING:
230
- retval = random.choice(['"apples"', '"Joe"', '"example"', '"foo"', '"bar"', '"tiger"'])
270
+ retval = random.choice(
271
+ ['"apples"', '"Joe"', '"example"', '"foo"', '"bar"', '"tiger"']
272
+ )
231
273
  if lang == "rust":
232
274
  retval = f"{retval}.to_string()"
233
275
  if self.optional and lang == "rust":
@@ -239,30 +281,32 @@ class ArgPrimitive(Arg):
239
281
  return f"<ArgPrimitive name={self._name} type={ArgPrimitiveType.to_python_type(self.type)}>"
240
282
 
241
283
  @classmethod
242
- def new_arg_primitive_from_stinger(cls, stinger: Dict[str, str]) -> ArgPrimitive:
243
- if "type" not in stinger:
284
+ def new_arg_primitive_from_stinger(
285
+ cls, arg_spec: dict[str, str | bool]
286
+ ) -> ArgPrimitive:
287
+ if "type" not in arg_spec:
244
288
  raise InvalidStingerStructure("No 'type' in arg structure")
245
- if "name" not in stinger:
289
+ if "name" not in arg_spec:
246
290
  raise InvalidStingerStructure("No 'name' in arg structure")
247
291
 
248
- arg_primitive_type = ArgPrimitiveType.from_string(stinger["type"])
249
- arg: ArgPrimitive = cls(name=stinger["name"], arg_type=arg_primitive_type)
292
+ arg_primitive_type = ArgPrimitiveType.from_string(arg_spec["type"])
293
+ arg: ArgPrimitive = cls(name=arg_spec["name"], arg_type=arg_primitive_type)
250
294
 
251
- if "description" in stinger and isinstance(stinger["description"], str):
252
- arg.set_description(stinger["description"])
295
+ arg.try_set_description_from_spec(arg_spec)
253
296
  return arg
254
297
 
255
298
 
256
299
  class ArgStruct(Arg):
257
300
  def __init__(self, name: str, iface_struct: InterfaceStruct):
258
301
  super().__init__(name)
259
- assert isinstance(iface_struct, InterfaceStruct), f"Passed {iface_struct=} is type {type(iface_struct)} which is not InterfaceStruct"
302
+ assert isinstance(
303
+ iface_struct, InterfaceStruct
304
+ ), f"Passed {iface_struct=} is type {type(iface_struct)} which is not InterfaceStruct"
260
305
  self._interface_struct: InterfaceStruct = iface_struct
261
306
  self._type = ArgType.STRUCT
262
307
 
263
308
  @property
264
309
  def members(self) -> list[Arg]:
265
- print(f"Struct: {self._interface_struct=}")
266
310
  return self._interface_struct.members
267
311
 
268
312
  @property
@@ -280,32 +324,69 @@ class ArgStruct(Arg):
280
324
  @property
281
325
  def rust_type(self) -> str:
282
326
  return self._interface_struct.rust_type
283
-
327
+
284
328
  @property
285
329
  def rust_local_type(self) -> str:
286
330
  return self._interface_struct.rust_local_type
287
331
 
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++':
332
+ @property
333
+ def markdown_type(self) -> str:
334
+ return f"[Struct {self._interface_struct.class_name}](#enum-{self._interface_struct.class_name})"
335
+
336
+ def get_random_example_value(self, lang="python", seed: int = 2) -> str | None:
337
+ example_list: dict[str, str] = {
338
+ a.name: str(a.get_random_example_value(lang, seed=seed))
339
+ for a in self.members
340
+ }
341
+ if lang == "c++":
291
342
  return "{" + ", ".join(example_list.values()) + "}"
292
- elif lang == 'python':
343
+ elif lang == "python":
293
344
  init_list = ", ".join([f"{k}={v}" for k, v in example_list.items()])
294
345
  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()]))
346
+ elif lang == "rust":
347
+ return "%s {%s}" % (
348
+ self.rust_type,
349
+ ", ".join([f"{k}: {v}" for k, v in example_list.items()]),
350
+ )
297
351
  return None
298
352
 
299
353
  def __str__(self) -> str:
300
354
  return f"<ArgStruct name={self.name}>"
301
-
355
+
302
356
  def __repr__(self):
303
357
  return f"ArgStruct(name={self.name}, iface_struct={self._interface_struct})"
304
358
 
305
- class Signal(object):
359
+
360
+ class InterfaceComponent:
361
+
362
+ def __init__(self, name: str):
363
+ self._name = name
364
+ self._documentation: str | None = None
365
+
366
+ @property
367
+ def name(self) -> str:
368
+ return self._name
369
+
370
+ @property
371
+ def documentation(self) -> str | None:
372
+ return self._documentation
373
+
374
+ def set_documentation(self, documentation: str) -> InterfaceComponent:
375
+ self._documentation = documentation
376
+ return self
377
+
378
+ def try_set_documentation_from_spec(
379
+ self, spec: dict[str, Any]
380
+ ) -> InterfaceComponent:
381
+ if "documentation" in spec and isinstance(spec["documentation"], str):
382
+ self._documentation = spec["documentation"]
383
+ return self
384
+
385
+
386
+ class Signal(InterfaceComponent):
306
387
  def __init__(self, topic_creator: SignalTopicCreator, name: str):
388
+ super().__init__(name)
307
389
  self._topic_creator = topic_creator
308
- self._name = name
309
390
  self._arg_list = [] # type: List[Arg]
310
391
 
311
392
  def add_arg(self, arg: Arg) -> Signal:
@@ -318,39 +399,50 @@ class Signal(object):
318
399
  def arg_list(self) -> list[Arg]:
319
400
  return self._arg_list
320
401
 
321
- @property
322
- def name(self) -> str:
323
- return self._name
324
-
325
402
  @property
326
403
  def topic(self) -> str:
327
404
  return self._topic_creator.signal_topic(self.name)
328
405
 
329
406
  @classmethod
330
407
  def new_signal_from_stinger(
331
- cls, topic_creator: SignalTopicCreator, name: str, signal_spec: Dict[str, str], stinger_spec: StingerSpec|None=None
408
+ cls,
409
+ topic_creator: SignalTopicCreator,
410
+ name: str,
411
+ signal_spec: Dict[str, str],
412
+ stinger_spec: StingerSpec | None = None,
332
413
  ) -> "Signal":
333
414
  """Alternative constructor from a Stinger signal structure."""
334
415
  signal = cls(topic_creator, name)
335
416
  if "payload" not in signal_spec:
336
417
  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'])}' ")
418
+ if not isinstance(signal_spec["payload"], list):
419
+ raise InvalidStingerStructure(
420
+ f"Payload must be a list. It is '{type(signal_spec['payload'])}' "
421
+ )
339
422
 
340
423
  for arg_spec in signal_spec["payload"]:
341
- if 'name' not in arg_spec or 'type' not in arg_spec:
424
+ if "name" not in arg_spec or "type" not in arg_spec:
342
425
  raise InvalidStingerStructure("Arg must have name and type.")
343
426
  new_arg = Arg.new_arg_from_stinger(arg_spec, stinger_spec)
344
427
  signal.add_arg(new_arg)
345
428
 
429
+ signal.try_set_documentation_from_spec(signal_spec)
430
+
346
431
  return signal
347
432
 
348
- class Method(object):
433
+
434
+ class Method(InterfaceComponent):
435
+
349
436
  def __init__(self, topic_creator: MethodTopicCreator, name: str):
437
+ super().__init__(name)
438
+ self._python = PythonMethodSymbols(self)
350
439
  self._topic_creator = topic_creator
351
- self._name = name
352
440
  self._arg_list = [] # type: List[Arg]
353
- self._return_value: Arg|list[Arg]|None = None
441
+ self._return_value: Arg | list[Arg] | None = None
442
+
443
+ @property
444
+ def python(self) -> PythonMethodSymbols:
445
+ return self._python
354
446
 
355
447
  def add_arg(self, arg: Arg) -> Method:
356
448
  if arg.name in [a.name for a in self._arg_list]:
@@ -363,11 +455,15 @@ class Method(object):
363
455
  self._return_value = value
364
456
  elif isinstance(self._return_value, list):
365
457
  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.")
458
+ raise InvalidStingerStructure(
459
+ f"A return value named '{value.name}' has been already added."
460
+ )
367
461
  self._return_value.append(value)
368
462
  elif isinstance(self._return_value, Arg):
369
463
  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.")
464
+ raise InvalidStingerStructure(
465
+ f"Attempt to add '{value.name}' to return value when it is already been added."
466
+ )
371
467
  self._return_value = [self._return_value, value]
372
468
  return self
373
469
 
@@ -376,7 +472,7 @@ class Method(object):
376
472
  return self._arg_list
377
473
 
378
474
  @property
379
- def return_value(self) -> Arg|list[Arg]|None:
475
+ def return_value(self) -> Arg | list[Arg] | None:
380
476
  return self._return_value
381
477
 
382
478
  @property
@@ -395,7 +491,7 @@ class Method(object):
395
491
  @property
396
492
  def return_value_rust_type(self) -> str:
397
493
  if self._return_value is None:
398
- return "None"
494
+ return "()"
399
495
  elif isinstance(self._return_value, Arg):
400
496
  return self._return_value.rust_type
401
497
  elif isinstance(self._return_value, list):
@@ -408,7 +504,11 @@ class Method(object):
408
504
  elif isinstance(self._return_value, Arg):
409
505
  return self._return_value.python_type
410
506
  elif isinstance(self._return_value, list):
411
- return f"stinger_types.{stringmanip.upper_camel_case(self.return_value_name)}"
507
+ return (
508
+ f"stinger_types.{stringmanip.upper_camel_case(self.return_value_name)}"
509
+ )
510
+ else:
511
+ raise RuntimeError(f"Did not handle return value type for: {self._return_value}")
412
512
 
413
513
  @property
414
514
  def return_value_python_local_type(self):
@@ -427,7 +527,7 @@ class Method(object):
427
527
  return self.name
428
528
 
429
529
  @property
430
- def return_value_type(self) -> str|bool:
530
+ def return_value_type(self) -> str | bool:
431
531
  if self._return_value is None:
432
532
  return False
433
533
  elif isinstance(self._return_value, Arg):
@@ -436,27 +536,30 @@ class Method(object):
436
536
  return "struct"
437
537
  raise RuntimeError("Method return value type was not recognized")
438
538
 
439
- def get_return_value_random_example_value(self, lang: str="python", seed: int=2):
539
+ def get_return_value_random_example_value(
540
+ self, lang: str = "python", seed: int = 2
541
+ ):
440
542
  if lang == "python":
441
543
  if self._return_value is None:
442
544
  return "None"
443
545
  elif isinstance(self._return_value, Arg):
444
546
  return self._return_value.get_random_example_value(lang, seed)
445
547
  elif isinstance(self._return_value, list):
446
- return f"{[a.get_random_example_value(lang,seed) for a in self._return_value]}"
548
+ return f"{[a.get_random_example_value(lang,seed) for a in self._return_value]}"
447
549
  if lang == "c++" or lang == "cpp":
448
550
  if self._return_value is None:
449
551
  return "null"
450
552
  elif isinstance(self._return_value, Arg):
451
553
  return self._return_value.get_random_example_value(lang, seed)
452
554
  elif isinstance(self._return_value, list):
453
- return ", ".join([str(a.get_random_example_value(lang,seed)) for a in self._return_value])
555
+ return ", ".join(
556
+ [
557
+ str(a.get_random_example_value(lang, seed))
558
+ for a in self._return_value
559
+ ]
560
+ )
454
561
  raise RuntimeError(f"No random example for return value for {lang}")
455
562
 
456
- @property
457
- def name(self) -> str:
458
- return self._name
459
-
460
563
  @property
461
564
  def topic(self) -> str:
462
565
  return self._topic_creator.method_topic(self.name)
@@ -466,38 +569,51 @@ class Method(object):
466
569
 
467
570
  @classmethod
468
571
  def new_method_from_stinger(
469
- cls, topic_creator: MethodTopicCreator, name: str, method_spec: Dict[str, str], stinger_spec: StingerSpec|None=None
572
+ cls,
573
+ topic_creator: MethodTopicCreator,
574
+ name: str,
575
+ method_spec: Dict[str, str],
576
+ stinger_spec: StingerSpec | None = None,
470
577
  ) -> "Method":
471
578
  """Alternative constructor from a Stinger method structure."""
472
579
  method = cls(topic_creator, name)
473
580
  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'])}' ")
581
+ raise InvalidStingerStructure(
582
+ f"Method '{name}' specification must have 'arguments'"
583
+ )
584
+ if not isinstance(method_spec["arguments"], list):
585
+ raise InvalidStingerStructure(
586
+ f"Arguments for '{name}' method must be a list. It is '{type(method_spec['arguments'])}' "
587
+ )
477
588
 
478
589
  for arg_spec in method_spec["arguments"]:
479
- if 'name' not in arg_spec or 'type' not in arg_spec:
590
+ if "name" not in arg_spec or "type" not in arg_spec:
480
591
  raise InvalidStingerStructure("Arg must have name and type.")
481
592
  new_arg = Arg.new_arg_from_stinger(arg_spec, stinger_spec)
482
593
  method.add_arg(new_arg)
483
594
 
484
595
  if "returnValues" in method_spec:
485
- if not isinstance(method_spec['returnValues'], list):
596
+ if not isinstance(method_spec["returnValues"], list):
486
597
  raise InvalidStingerStructure(f"ReturnValues must be a list.")
487
-
598
+
488
599
  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.")
600
+ if "name" not in arg_spec or "type" not in arg_spec:
601
+ raise InvalidStingerStructure(
602
+ "Return value must have name and type."
603
+ )
491
604
  new_arg = Arg.new_arg_from_stinger(arg_spec, stinger_spec)
492
605
  method.add_return_value(new_arg)
493
606
 
607
+ method.try_set_documentation_from_spec(method_spec)
608
+
494
609
  return method
495
610
 
496
- class Property:
611
+
612
+ class Property(InterfaceComponent):
497
613
 
498
614
  def __init__(self, topic_creator: PropertyTopicCreator, name: str):
615
+ super().__init__(name)
499
616
  self._topic_creator = topic_creator
500
- self._name = name
501
617
  self._arg_list = [] # type: List[Arg]
502
618
  self._read_only = False
503
619
 
@@ -509,7 +625,7 @@ class Property:
509
625
 
510
626
  @property
511
627
  def python_local_type(self) -> str:
512
- return self.python_class.split('.')[-1]
628
+ return self.python_class.split(".")[-1]
513
629
 
514
630
  @property
515
631
  def python_class(self) -> str:
@@ -536,10 +652,6 @@ class Property:
536
652
  def arg_list(self) -> list[Arg]:
537
653
  return self._arg_list
538
654
 
539
- @property
540
- def name(self) -> str:
541
- return self._name
542
-
543
655
  @property
544
656
  def value_topic(self) -> str:
545
657
  return self._topic_creator.property_value_topic(self.name)
@@ -554,17 +666,23 @@ class Property:
554
666
 
555
667
  @classmethod
556
668
  def new_method_from_stinger(
557
- cls, topic_creator: PropertyTopicCreator, name: str, prop_spec: Dict[str, str], stinger_spec: StingerSpec|None=None
669
+ cls,
670
+ topic_creator: PropertyTopicCreator,
671
+ name: str,
672
+ prop_spec: YamlIfaceProperty,
673
+ stinger_spec: StingerSpec | None = None,
558
674
  ) -> "Property":
559
675
  """Alternative constructor from a Stinger method structure."""
560
676
  prop_obj = cls(topic_creator, name)
561
677
  if "values" not in prop_spec:
562
678
  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'])}' ")
679
+ if not isinstance(prop_spec["values"], list):
680
+ raise InvalidStingerStructure(
681
+ f"Values must be a list. It is '{type(prop_spec['values'])}' "
682
+ )
565
683
 
566
684
  for arg_spec in prop_spec["values"]:
567
- if 'name' not in arg_spec or 'type' not in arg_spec:
685
+ if "name" not in arg_spec or "type" not in arg_spec:
568
686
  raise InvalidStingerStructure("Arg must have name and type.")
569
687
  new_arg = Arg.new_arg_from_stinger(arg_spec, stinger_spec)
570
688
  prop_obj.add_arg(new_arg)
@@ -572,24 +690,34 @@ class Property:
572
690
  if r_o := prop_spec.get("readOnly", False):
573
691
  prop_obj._read_only = r_o
574
692
 
693
+ prop_obj.try_set_documentation_from_spec(prop_spec)
694
+
575
695
  return prop_obj
576
696
 
577
697
  def __str__(self) -> str:
578
698
  return f"Property<name={self.name} values=[{', '.join([a.name for a in self.arg_list])}]>"
579
699
 
700
+
580
701
  class InterfaceEnum:
581
702
 
582
703
  def __init__(self, name: str):
583
704
  self._name = name
584
- self._values: list[Any] = []
705
+ self._values: list[str] = []
706
+ self._value_descriptions: list[str | None] = []
707
+ self._description: str | None = None
585
708
 
586
- def add_value(self, value: str):
709
+ def add_value(self, value: str, description: str | None = None):
587
710
  self._values.append(value)
711
+ self._value_descriptions.append(description)
588
712
 
589
713
  @property
590
714
  def name(self):
591
715
  return self._name
592
716
 
717
+ @property
718
+ def description(self) -> str | None:
719
+ return self._description
720
+
593
721
  @property
594
722
  def class_name(self):
595
723
  return stringmanip.upper_camel_case(self.name)
@@ -623,13 +751,19 @@ class InterfaceEnum:
623
751
  return self._values
624
752
 
625
753
  @classmethod
626
- def new_enum_from_stinger(cls, name, values: List[Dict[str, str]]) -> InterfaceEnum:
754
+ def new_enum_from_stinger(cls, name, enum_spec: YamlIfaceEnum) -> InterfaceEnum:
627
755
  ie = cls(name)
628
- for enum_obj in values:
629
- if "name" in enum_obj:
630
- ie.add_value(enum_obj["name"])
756
+ for enum_obj in enum_spec.get("values", []):
757
+ assert isinstance(enum_obj, dict), f"Enum values must be a dicts."
758
+ if "name" in enum_obj and isinstance(enum_obj["name"], str):
759
+ ie.add_value(enum_obj["name"], enum_obj.get("description", None))
631
760
  else:
632
- raise InvalidStingerStructure("InterfaceEnum item must have a name")
761
+ raise InvalidStingerStructure(
762
+ f"InterfaceEnum '{name}' items must have string names."
763
+ )
764
+ description = enum_spec.get("description", None)
765
+ if description is not None and isinstance(description, str):
766
+ ie._description = description
633
767
  return ie
634
768
 
635
769
 
@@ -638,6 +772,7 @@ class InterfaceStruct:
638
772
  def __init__(self, name: str):
639
773
  self._name = name
640
774
  self._members: list[Arg] = []
775
+ self._description: str | None = None
641
776
 
642
777
  def add_member(self, arg: Arg):
643
778
  self._members.append(arg)
@@ -646,6 +781,10 @@ class InterfaceStruct:
646
781
  def name(self):
647
782
  return self._name
648
783
 
784
+ @property
785
+ def description(self) -> str | None:
786
+ return self._description
787
+
649
788
  @property
650
789
  def class_name(self):
651
790
  return stringmanip.upper_camel_case(self.name)
@@ -681,48 +820,55 @@ class InterfaceStruct:
681
820
  @property
682
821
  def values(self) -> list[Arg]:
683
822
  return self._members
684
-
823
+
685
824
  @property
686
825
  def members(self) -> list[Arg]:
687
826
  return self._members
688
827
 
689
828
  @classmethod
690
- def new_struct_from_stinger(cls, name, spec: Dict[str, str|List[Dict[str,str]]], stinger_spec: StingerSpec) -> InterfaceStruct:
829
+ def new_struct_from_stinger(
830
+ cls,
831
+ name,
832
+ spec: Dict[str, str | List[Dict[str, str]]],
833
+ stinger_spec: StingerSpec,
834
+ ) -> InterfaceStruct:
691
835
  istruct = cls(name)
692
- for memb in spec.get('members', []):
836
+ for memb in spec.get("members", []):
693
837
  arg = Arg.new_arg_from_stinger(memb, stinger_spec=stinger_spec)
694
838
  istruct.add_member(arg)
839
+ istruct._description = spec.get("description", None)
695
840
  return istruct
696
841
 
697
842
  def __str__(self) -> str:
698
843
  return f"<InterfaceStruct members={[m.name for m in self.members]}>"
699
-
844
+
700
845
  def __repr__(self):
701
846
  return f"InterfaceStruct(name={self.name})"
702
847
 
848
+
703
849
  class MqttTransportProtocol(Enum):
704
850
  TCP = 0
705
851
  WEBSOCKETS = 1
706
852
 
707
853
 
708
854
  class Broker:
709
- def __init__(self, name: str="Default"):
855
+ def __init__(self, name: str = "Default"):
710
856
  self._name: str = name
711
- self._host: str|None = None
712
- self._port: int|None = None
857
+ self._host: str | None = None
858
+ self._port: int | None = None
713
859
  self._auth = None
714
860
  self._transport_protocol: MqttTransportProtocol = MqttTransportProtocol.TCP
715
-
861
+
716
862
  @property
717
863
  def name(self) -> str:
718
864
  return self._name
719
865
 
720
866
  @property
721
867
  def class_name(self) -> str:
722
- return f"{stringcase.pascalcase(self.name)}Connection"
868
+ return f"{stringmanip.upper_camel_case(self.name)}Connection"
723
869
 
724
870
  @property
725
- def hostname(self) -> str|None:
871
+ def hostname(self) -> str | None:
726
872
  return self._host
727
873
 
728
874
  @hostname.setter
@@ -730,7 +876,7 @@ class Broker:
730
876
  self._host = value
731
877
 
732
878
  @property
733
- def port(self) -> int|None:
879
+ def port(self) -> int | None:
734
880
  return self._port
735
881
 
736
882
  @port.setter
@@ -740,10 +886,10 @@ class Broker:
740
886
  @classmethod
741
887
  def new_broker_from_stinger(cls, name: str, spec: Dict[str, Any]) -> Broker:
742
888
  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'])
889
+ if "host" in spec:
890
+ new_broker.hostname = spec["host"]
891
+ if "port" in spec:
892
+ new_broker.port = int(spec["port"])
747
893
  return new_broker
748
894
 
749
895
  def __repr__(self) -> str:
@@ -756,6 +902,8 @@ class StingerSpec:
756
902
  try:
757
903
  self._name: str = interface["name"]
758
904
  self._version: str = interface["version"]
905
+ self._python = PythonInterfaceSymbols(self)
906
+ self._rust = RustInterfaceSymbols(self)
759
907
  except KeyError as e:
760
908
  raise InvalidStingerStructure(
761
909
  f"Missing interface property in {interface}: {e}"
@@ -764,12 +912,12 @@ class StingerSpec:
764
912
  raise InvalidStingerStructure(
765
913
  f"Interface didn't appear to have a correct type"
766
914
  )
767
-
768
- self._summary = interface['summary'] if 'summary' in interface else None
769
- self._title = interface['title'] if 'title' in interface else None
915
+
916
+ self._summary = interface["summary"] if "summary" in interface else None
917
+ self._title = interface["title"] if "title" in interface else None
770
918
 
771
919
  self.signals: dict[str, Signal] = {}
772
- self.properties: dict[str, Any] = {}
920
+ self.properties: dict[str, Property] = {}
773
921
  self.methods: dict[str, Method] = {}
774
922
  self.enums: dict[str, InterfaceEnum] = {}
775
923
  self.structs: dict[str, InterfaceStruct] = {}
@@ -791,15 +939,12 @@ class StingerSpec:
791
939
  @property
792
940
  def interface_info(self) -> tuple[str, dict[str, Any]]:
793
941
  info = {
794
- "name": self._name,
942
+ "name": self._name,
795
943
  "version": self._version,
796
944
  "title": self._title or self._name,
797
- "summary": self._summary or '',
945
+ "summary": self._summary or "",
798
946
  }
799
- return (
800
- self._topic_creator.interface_info_topic(),
801
- info
802
- )
947
+ return (self._topic_creator.interface_info_topic(), info)
803
948
 
804
949
  def add_broker(self, broker: Broker):
805
950
  assert broker is not None
@@ -813,7 +958,7 @@ class StingerSpec:
813
958
  else:
814
959
  return self._brokers
815
960
 
816
- def get_example_broker(self) -> Broker|None:
961
+ def get_example_broker(self) -> Broker | None:
817
962
  for broker in self.brokers.values():
818
963
  return broker
819
964
  return None
@@ -837,11 +982,15 @@ class StingerSpec:
837
982
  def add_struct(self, interface_struct: InterfaceStruct):
838
983
  assert interface_struct is not None
839
984
  self.structs[interface_struct.name] = interface_struct
840
- print(f"All structs so far: {self.structs=}")
841
985
 
842
986
  def uses_enums(self) -> bool:
843
987
  return bool(self.enums)
844
988
 
989
+ def get_interface_enum(self, name: str) -> InterfaceEnum:
990
+ if name in self.enums:
991
+ return self.enums[name]
992
+ raise InvalidStingerStructure(f"Enum '{name}' not found in stinger spec")
993
+
845
994
  @property
846
995
  def name(self):
847
996
  return self._name
@@ -850,6 +999,14 @@ class StingerSpec:
850
999
  def version(self):
851
1000
  return self._version
852
1001
 
1002
+ @property
1003
+ def python(self) -> PythonInterfaceSymbols:
1004
+ return self._python
1005
+
1006
+ @property
1007
+ def rust(self) -> RustInterfaceSymbols:
1008
+ return self._rust
1009
+
853
1010
  @staticmethod
854
1011
  def get_enum_module_name() -> str:
855
1012
  return InterfaceEnum.get_module_name()
@@ -859,12 +1016,14 @@ class StingerSpec:
859
1016
  return InterfaceEnum.get_module_alias()
860
1017
 
861
1018
  @classmethod
862
- def new_spec_from_stinger(cls, topic_creator, stinger: Dict[str, Any]) -> StingerSpec:
1019
+ def new_spec_from_stinger(
1020
+ cls, topic_creator, stinger: Dict[str, Any]
1021
+ ) -> StingerSpec:
863
1022
  if "stingeripc" not in stinger:
864
1023
  raise InvalidStingerStructure("Missing 'stingeripc' format version")
865
1024
  if "version" not in stinger["stingeripc"]:
866
1025
  raise InvalidStingerStructure("Stinger spec version not present")
867
- if stinger["stingeripc"]["version"] not in ["0.0.6"]:
1026
+ if stinger["stingeripc"]["version"] not in ["0.0.7"]:
868
1027
  raise InvalidStingerStructure(
869
1028
  f"Unsupported stinger spec version {stinger['stingeripc']['version']}"
870
1029
  )
@@ -876,7 +1035,9 @@ class StingerSpec:
876
1035
  if "enums" in stinger:
877
1036
  for enum_name, enum_spec in stinger["enums"].items():
878
1037
  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}"
1038
+ assert (
1039
+ ie is not None
1040
+ ), f"Did not create enum from {enum_name} and {enum_spec}"
880
1041
  stinger_spec.add_enum(ie)
881
1042
  except TypeError as e:
882
1043
  raise InvalidStingerStructure(
@@ -886,11 +1047,13 @@ class StingerSpec:
886
1047
  try:
887
1048
  if "structures" in stinger:
888
1049
  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=}")
1050
+ istruct = InterfaceStruct.new_struct_from_stinger(
1051
+ struct_name, struct_spec, stinger_spec
1052
+ )
1053
+ assert (
1054
+ istruct is not None
1055
+ ), f"Did not create struct from {struct_name} and {struct_spec}"
892
1056
  stinger_spec.add_struct(istruct)
893
- print(f"The created structure is {stinger_spec.structs[struct_name]}")
894
1057
  except TypeError as e:
895
1058
  raise InvalidStingerStructure(
896
1059
  f"Struct specification appears to be invalid: {e}"
@@ -900,7 +1063,9 @@ class StingerSpec:
900
1063
  if "brokers" in stinger:
901
1064
  for broker_name, broker_spec in stinger["brokers"].items():
902
1065
  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}"
1066
+ assert (
1067
+ broker is not None
1068
+ ), f"Did not create broker from {broker_name} and {broker_spec}"
904
1069
  stinger_spec.add_broker(broker)
905
1070
  except TypeError as e:
906
1071
  raise InvalidStingerStructure(
@@ -914,7 +1079,7 @@ class StingerSpec:
914
1079
  topic_creator.signal_topic_creator(),
915
1080
  signal_name,
916
1081
  signal_spec,
917
- stinger_spec
1082
+ stinger_spec,
918
1083
  )
919
1084
  assert (
920
1085
  signal is not None
@@ -932,7 +1097,7 @@ class StingerSpec:
932
1097
  topic_creator.method_topic_creator(),
933
1098
  method_name,
934
1099
  method_spec,
935
- stinger_spec
1100
+ stinger_spec,
936
1101
  )
937
1102
  assert (
938
1103
  method is not None
@@ -950,7 +1115,7 @@ class StingerSpec:
950
1115
  topic_creator.property_topic_creator(),
951
1116
  prop_name,
952
1117
  prop_spec,
953
- stinger_spec
1118
+ stinger_spec,
954
1119
  )
955
1120
  assert (
956
1121
  prop is not None