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.
- {stinger_ipc-0.0.1.dist-info → stinger_ipc-0.0.3.dist-info}/METADATA +4 -4
- stinger_ipc-0.0.3.dist-info/RECORD +52 -0
- stinger_ipc-0.0.3.dist-info/entry_points.txt +4 -0
- stingeripc/args.py +6 -5
- stingeripc/asyncapi.py +249 -167
- stingeripc/components.py +301 -136
- stingeripc/connection.py +2 -1
- stingeripc/exceptions.py +1 -2
- stingeripc/interface.py +8 -4
- stingeripc/lang_symb.py +68 -0
- stingeripc/templates/cpp/CMakeLists.txt.jinja2 +26 -0
- stingeripc/templates/cpp/examples/client_main.cpp.jinja2 +47 -0
- stingeripc/templates/cpp/examples/server_main.cpp.jinja2 +35 -0
- stingeripc/templates/cpp/include/broker.hpp.jinja2 +132 -0
- stingeripc/templates/cpp/include/client.hpp.jinja2 +53 -0
- stingeripc/templates/cpp/include/enums.hpp.jinja2 +17 -0
- stingeripc/templates/cpp/include/ibrokerconnection.hpp.jinja2 +42 -0
- stingeripc/templates/cpp/include/return_types.hpp.jinja2 +14 -0
- stingeripc/templates/cpp/include/server.hpp.jinja2 +44 -0
- stingeripc/templates/cpp/include/structs.hpp.jinja2 +13 -0
- stingeripc/templates/cpp/src/broker.cpp.jinja2 +243 -0
- stingeripc/templates/cpp/src/client.cpp.jinja2 +202 -0
- stingeripc/templates/cpp/src/server.cpp.jinja2 +170 -0
- stingeripc/templates/markdown/index.md.jinja2 +142 -0
- stingeripc/templates/python/__init__.py.jinja2 +1 -0
- stingeripc/templates/python/client.py.jinja2 +309 -0
- stingeripc/templates/python/connection.py.jinja2 +164 -0
- stingeripc/templates/python/interface_types.py.jinja2 +48 -0
- stingeripc/templates/python/method_codes.py.jinja2 +30 -0
- stingeripc/templates/python/pyproject.toml.jinja2 +9 -0
- stingeripc/templates/python/server.py.jinja2 +214 -0
- stingeripc/templates/rust/Cargo.toml.jinja2 +4 -0
- stingeripc/templates/rust/client/Cargo.toml.jinja2 +25 -0
- stingeripc/templates/rust/client/examples/client.rs.jinja2 +53 -0
- stingeripc/templates/rust/client/src/lib.rs.jinja2 +247 -0
- stingeripc/templates/rust/connection/Cargo.toml.jinja2 +21 -0
- stingeripc/templates/rust/connection/examples/pub_and_recv.rs.jinja2 +44 -0
- stingeripc/templates/rust/connection/src/handler.rs.jinja2 +0 -0
- stingeripc/templates/rust/connection/src/lib.rs.jinja2 +262 -0
- stingeripc/templates/rust/connection/src/payloads.rs.jinja2 +131 -0
- stingeripc/templates/rust/server/Cargo.toml.jinja2 +19 -0
- stingeripc/templates/rust/server/examples/server.rs.jinja2 +83 -0
- stingeripc/templates/rust/server/src/lib.rs.jinja2 +272 -0
- stingeripc/tools/__init__.py +0 -0
- stingeripc/tools/markdown_generator.py +25 -0
- stingeripc/tools/python_generator.py +41 -0
- stingeripc/tools/rust_generator.py +50 -0
- stingeripc/topic.py +11 -8
- stinger_ipc-0.0.1.dist-info/RECORD +0 -13
- {stinger_ipc-0.0.1.dist-info → stinger_ipc-0.0.3.dist-info}/WHEEL +0 -0
- {stinger_ipc-0.0.1.dist-info → stinger_ipc-0.0.3.dist-info}/licenses/LICENSE +0 -0
- {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
|
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(
|
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(
|
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(
|
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(
|
103
|
+
raise RuntimeError(
|
104
|
+
"Need the root StingerSpec when creating an enum or struct Arg"
|
105
|
+
)
|
80
106
|
|
81
|
-
if arg_spec["type"] ==
|
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(
|
86
|
-
|
87
|
-
|
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(
|
96
|
-
|
97
|
-
|
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__(
|
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(
|
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(
|
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(
|
243
|
-
|
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
|
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(
|
249
|
-
arg: ArgPrimitive = cls(name=
|
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
|
-
|
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(
|
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
|
-
|
289
|
-
|
290
|
-
|
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 ==
|
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 ==
|
296
|
-
return "%s {%s}" % (
|
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
|
-
|
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,
|
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[
|
338
|
-
raise InvalidStingerStructure(
|
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
|
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
|
-
|
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(
|
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(
|
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 "
|
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
|
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(
|
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(
|
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,
|
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(
|
475
|
-
|
476
|
-
|
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
|
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[
|
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
|
490
|
-
raise InvalidStingerStructure(
|
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
|
-
|
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(
|
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,
|
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[
|
564
|
-
raise InvalidStingerStructure(
|
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
|
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[
|
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,
|
754
|
+
def new_enum_from_stinger(cls, name, enum_spec: YamlIfaceEnum) -> InterfaceEnum:
|
627
755
|
ie = cls(name)
|
628
|
-
for enum_obj in values:
|
629
|
-
|
630
|
-
|
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(
|
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(
|
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(
|
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"{
|
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
|
744
|
-
new_broker.hostname = spec[
|
745
|
-
if
|
746
|
-
new_broker.port = int(spec[
|
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[
|
769
|
-
self._title = interface[
|
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,
|
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(
|
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.
|
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 (
|
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(
|
890
|
-
|
891
|
-
|
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 (
|
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
|