stinger-ipc 0.0.25__py3-none-any.whl → 0.0.27__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stinger-ipc
3
- Version: 0.0.25
3
+ Version: 0.0.27
4
4
  Summary: Tools to create code to do IPC over MQTT
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown
@@ -1,4 +1,4 @@
1
- stinger_ipc-0.0.25.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
1
+ stinger_ipc-0.0.27.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
2
2
  stingeripc/__init__.py,sha256=PTr5WfMfB-GL4vp3-XMU8IwGv3Q5RXQ24H7JuEo3hdk,133
3
3
  stingeripc/args.py,sha256=x3P8GRu9-jyiMl62t0FPqbWY18FbhMVN4eSa2UzUv6c,3960
4
4
  stingeripc/asyncapi.py,sha256=yyHDIM2Hm8stAPGmcuCostQ7Tk_YzVOqA2jAiqyp7j4,23445
@@ -24,7 +24,7 @@ stingeripc/templates/cpp/partials/args.jinja2,sha256=jJYp_290Q21schBItazlf8bd60v
24
24
  stingeripc/templates/cpp/partials/deserialize.jinja2,sha256=VwmsnpdHEQAXG7hD_MrmaiVjV6HazwoIXsjeoMfSSNY,1492
25
25
  stingeripc/templates/cpp/partials/serialize.jinja2,sha256=iT1pNwT1m9ECb068HGn4U94CVVGBqgGpDPocpK0VuIA,826
26
26
  stingeripc/templates/cpp/src/broker.cpp.jinja2,sha256=bBSSLjnJXpHHWhghf-SljJ825nsrQaTOBoOIzoXLbp4,10368
27
- stingeripc/templates/cpp/src/client.cpp.jinja2,sha256=kEHOIBMLLzkdh0AyWK56hN5FDm0Emi5-xBNBwDd_Pzs,12309
27
+ stingeripc/templates/cpp/src/client.cpp.jinja2,sha256=BQujzcyN9XnZZ-92U-psggkFNayj5NL6LZxsJeqQCdM,12368
28
28
  stingeripc/templates/cpp/src/property_structs.cpp.jinja2,sha256=e7K3tGhZZ9iekEYOJvcPmhWCAonOTVMKwCQI39tfyZ4,939
29
29
  stingeripc/templates/cpp/src/server.cpp.jinja2,sha256=c9XeBzfC2uIxpkpRGPmKHZV11tpSWmRX19ZGkMDO9lo,7001
30
30
  stingeripc/templates/cpp/src/structs.cpp.jinja2,sha256=w4DSPqWf7r01lhuwwCbyoVwmUr5W1GqC2G8x_G3O_Gk,493
@@ -38,7 +38,7 @@ stingeripc/templates/python/connection.py.jinja2,sha256=mTiiC_p4AEwIGH9mvyzYDdu_
38
38
  stingeripc/templates/python/interface_types.py.jinja2,sha256=BcCD0uCzDn1TAn1IvWhHiOsai6MczIBu12Xg0snTHk8,1711
39
39
  stingeripc/templates/python/method_codes.py.jinja2,sha256=74i8iKk4Fu9_nE2SYTRrRK2G4mogfJx88ihbmjyfYVQ,1089
40
40
  stingeripc/templates/python/pyproject.toml.jinja2,sha256=R-rgSv6RTdgPINvhCwJ180Vzjy9-WkqtLbLgnCov3CI,195
41
- stingeripc/templates/python/server.py.jinja2,sha256=pxjzLPE7k3DG6zE-zXyK_I16ZF8k-xgbdh_182Fx_E8,11217
41
+ stingeripc/templates/python/server.py.jinja2,sha256=oK5fy2YfkJXfpzM46zLann9sATRUzP_qJxuol5GGDWI,18253
42
42
  stingeripc/templates/rust/Cargo.toml.jinja2,sha256=teIu5y2AeqSkk0eUPT8CemvzmhRoblCUP5jdmonLfY4,70
43
43
  stingeripc/templates/rust/client/Cargo.toml.jinja2,sha256=oymbdtF3apgD4nVxt-fa9__hSZaUFsvjA0AxJarPMGw,858
44
44
  stingeripc/templates/rust/client/examples/client.rs.jinja2,sha256=pMLR-xyEJrK4289XUbFEB18HKe3kaRJDoeFBQ89SLU4,2075
@@ -47,17 +47,17 @@ stingeripc/templates/rust/payloads/Cargo.toml.jinja2,sha256=t6WQ5BCBvjagCW8AgK4V
47
47
  stingeripc/templates/rust/payloads/examples/pub_and_recv.rs.jinja2,sha256=cIbunwEwYGbkRO1lzi2nGNX2dGdFhAJ2ZZqFhphY7CY,1461
48
48
  stingeripc/templates/rust/payloads/src/lib.rs.jinja2,sha256=3vBn2OGm_j7BoyHRHLcH1e0hKRBiqtbgHsz04rTaVMM,86
49
49
  stingeripc/templates/rust/payloads/src/payloads.rs.jinja2,sha256=1PUSzmHLMBYsVaCR8gOMdHWK8dmODmLM3WSu9iysN-c,4380
50
- stingeripc/templates/rust/server/Cargo.toml.jinja2,sha256=t2MIpoe34RYOtxvpS6ma7W0A9sLlMQBYblpALU805TQ,573
51
- stingeripc/templates/rust/server/examples/server.rs.jinja2,sha256=L8Cgs1Wb1HFLyMqF0EARUtrgqoxXly6u4kaSElaNrHo,4851
52
- stingeripc/templates/rust/server/src/lib.rs.jinja2,sha256=rhackDGeDndEXoNXoldsc-jr7I-nP2uYKTZSnWXOaIU,15675
50
+ stingeripc/templates/rust/server/Cargo.toml.jinja2,sha256=QwPnGhPWvYZit5ldr9hZ2AC9xn9dAVj_3Td-dO-Befw,593
51
+ stingeripc/templates/rust/server/examples/server.rs.jinja2,sha256=WhkHXu45PQyAePv-yQdE_p_xYcuqIMSEAzD4Pn7U1A4,4752
52
+ stingeripc/templates/rust/server/src/lib.rs.jinja2,sha256=0PRSB8OJIlbv1TFM8UjuD3bApbANW8fvUOi-pHJRMlQ,15637
53
53
  stingeripc/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
54
  stingeripc/tools/cli.py,sha256=RqeL6FcoeUCvle86jmox-4A25SL4Br2CSZXPKbaJwlY,3372
55
55
  stingeripc/tools/cpp_generator.py,sha256=jYIRVjykI4corEI65CsA7q42psFxfkgskasYcSXSbqY,3111
56
56
  stingeripc/tools/markdown_generator.py,sha256=HVEWcMjAfVnuWY29kxfAW1mvSxQSF-rdYFA2kWdLOn0,1174
57
57
  stingeripc/tools/python_generator.py,sha256=GJwGI6DntkJPJjHN4xSeTD60KgY32gI43IvIByxWSsw,1845
58
58
  stingeripc/tools/rust_generator.py,sha256=Im0EdNifxkGDnZy2JCuvKzmn1h2suk6EJ_xtpGZazGk,2240
59
- stinger_ipc-0.0.25.dist-info/METADATA,sha256=6p0baAyWwAgHzkq_mPK9Yii6XLYEq4nnbBIdflR9ynQ,6079
60
- stinger_ipc-0.0.25.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
61
- stinger_ipc-0.0.25.dist-info/entry_points.txt,sha256=bh8k8mvtAog4MKTd82ALSHqQkuD069VrZ-vNlde6XjE,204
62
- stinger_ipc-0.0.25.dist-info/top_level.txt,sha256=mSNwAf83_1qiTP_vu7XEPBrZu-fDusT1FFyQZzCrRcU,11
63
- stinger_ipc-0.0.25.dist-info/RECORD,,
59
+ stinger_ipc-0.0.27.dist-info/METADATA,sha256=523F72GNhrTsIxzv4DuC0F6d3ZTutmCdcSHbMXecnmk,6079
60
+ stinger_ipc-0.0.27.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
61
+ stinger_ipc-0.0.27.dist-info/entry_points.txt,sha256=bh8k8mvtAog4MKTd82ALSHqQkuD069VrZ-vNlde6XjE,204
62
+ stinger_ipc-0.0.27.dist-info/top_level.txt,sha256=mSNwAf83_1qiTP_vu7XEPBrZu-fDusT1FFyQZzCrRcU,11
63
+ stinger_ipc-0.0.27.dist-info/RECORD,,
@@ -45,7 +45,7 @@ constexpr const char {{stinger.cpp.client_class_name}}::INTERFACE_VERSION[];
45
45
  {%-endfor%}
46
46
 
47
47
  {%-for prop_name, prop in stinger.properties.items() %}
48
- _{{prop_name | lowerCamelCase}}PropertySubscriptionId = _broker->Subscribe("{{prop.update_topic}}", 1);
48
+ _{{prop_name | lowerCamelCase}}PropertySubscriptionId = _broker->Subscribe("{{prop.value_topic}}", 1);
49
49
  {%-endfor%}
50
50
  }
51
51
 
@@ -116,7 +116,7 @@ void {{stinger.cpp.client_class_name}}::_receiveMessage(
116
116
  {%-endfor%}{# for methods #}
117
117
 
118
118
  {%-for prop_name, prop in stinger.properties.items() %}
119
- {%if not loop.first%}else {%endif%}if ((mqttProps.subscriptionId && (*mqttProps.subscriptionId == _{{prop_name | lowerCamelCase}}PropertySubscriptionId)) || topic == "{{prop.update_topic}}")
119
+ {%if not loop.first%}else {%endif%}if ((mqttProps.subscriptionId && (*mqttProps.subscriptionId == _{{prop_name | lowerCamelCase}}PropertySubscriptionId)) || topic == "{{prop.value_topic}}")
120
120
  {
121
121
  _receive{{prop_name | UpperCamelCase}}PropertyUpdate(topic, payload, mqttProps.propertyVersion);
122
122
  }
@@ -164,6 +164,7 @@ void {{stinger.cpp.client_class_name}}::_handle{{method_name|UpperCamelCase}}Res
164
164
  const std::string &correlationId)
165
165
  {
166
166
  std::cout << "In response handler for " << topic << " with correlationId=" << correlationId << std::endl;
167
+ {%if method.return_value_type is not false%}
167
168
  rapidjson::Document doc;
168
169
  rapidjson::ParseResult ok = doc.Parse(payload.c_str());
169
170
  if (!ok)
@@ -171,11 +172,10 @@ void {{stinger.cpp.client_class_name}}::_handle{{method_name|UpperCamelCase}}Res
171
172
  //Log("Could not JSON parse {{method_name}} signal payload.");
172
173
  throw std::runtime_error(rapidjson::GetParseError_En(ok.Code()));
173
174
  }
174
-
175
175
  if (!doc.IsObject()) {
176
176
  throw std::runtime_error("Received payload is not an object");
177
177
  }
178
-
178
+ {%endif%}
179
179
  boost::uuids::uuid correlationIdUuid = boost::lexical_cast<boost::uuids::uuid>(correlationId);
180
180
  auto promiseItr = _pending{{method_name|UpperCamelCase}}MethodCalls.find(correlationIdUuid);
181
181
  if (promiseItr != _pending{{method_name|UpperCamelCase}}MethodCalls.end())
@@ -7,15 +7,31 @@ This is the Server for the {{stinger.name}} interface.
7
7
 
8
8
  import json
9
9
  import logging
10
-
10
+ import threading
11
+ from dataclasses import dataclass, field
11
12
  logging.basicConfig(level=logging.DEBUG)
12
13
 
13
- from typing import Callable, Dict, Any, Optional, List
14
+ from typing import Callable, Dict, Any, Optional, List, Generic, TypeVar
14
15
  from connection import BrokerConnection
15
16
  from method_codes import *
16
17
  import {{stinger.get_enum_module_name()}} as {{stinger.get_enum_module_alias()}}
17
18
  {%macro method_type_annotation(method) %}Callable[[{%if method.arg_list | length > 0%}{%for arg in method.arg_list%}{{arg.python_type}}{%if not loop.last%}, {%endif%}{%endfor%}{%else%}None{%endif%}], {{method.return_value_python_type}}]{%endmacro%}
18
19
 
20
+ T = TypeVar('T')
21
+
22
+ @dataclass
23
+ class PropertyControls(Generic[T]):
24
+ value: T | None = None
25
+ mutex = threading.Lock()
26
+ version: int = -1
27
+ subscription_id: int | None = None
28
+ callbacks: List[Callable[[T], None]] = field(default_factory=list)
29
+
30
+ @dataclass
31
+ class MethodControls:
32
+ subscription_id: int | None = None
33
+ callback: Optional[Callable] = None
34
+
19
35
  class {{stinger.python.server_class_name}}:
20
36
 
21
37
  def __init__(self, connection: BrokerConnection):
@@ -25,18 +41,15 @@ class {{stinger.python.server_class_name}}:
25
41
  self._conn = connection
26
42
  self._conn.set_message_callback(self._receive_message)
27
43
  self._conn.set_last_will(topic="{{stinger.interface_info.0}}", payload=None, qos=1, retain=True)
28
- {%for prop_name, prop_spec in stinger.properties.items()-%}
29
- self._property_{{prop_name}} = None
30
- self._conn.subscribe("{{prop_spec.update_topic}}")
31
- self.changed_value_callback_for_{{pprop_name}} = None
32
- self._publish_interface_info()
44
+ {%for prop_name, prop in stinger.properties.items()%}
45
+ self._property_{{prop_name|snake_case}}: PropertyControls[{%if prop.arg_list | length == 1%}{{prop.arg_list[0].python_class}}{%else%}{{prop.python_class}}{%endif%}, {%for arg in prop.arg_list %}{{arg.python_type}}{%if not loop.last%}, {%endif%}{%endfor%}] = PropertyControls()
46
+ self._property_{{prop_name|snake_case}}.subscription_id = self._conn.subscribe("{{prop.update_topic}}")
33
47
  {%endfor-%}
34
- {%-for method in stinger.methods.values()%}
35
- self._conn.subscribe("{{method.topic}}")
36
- {%endfor-%}
37
- {%for method_name, method in stinger.methods.items()-%}
38
- self._{{method_name|snake_case}}_method_handler: Optional[{{method_type_annotation(method)}}] = None
48
+ {%-for method_name, method in stinger.methods.items()%}
49
+ self._method_{{method_name|snake_case}} = MethodControls()
50
+ self._method_{{method_name|snake_case}}.subscription_id = self._conn.subscribe("{{method.topic}}")
39
51
  {%endfor%}
52
+ self._publish_interface_info()
40
53
 
41
54
  def _receive_message(self, topic: str, payload: str, properties: Dict[str, Any]):
42
55
  """ This is the callback that is called whenever any message is received on a subscribed topic.
@@ -44,7 +57,7 @@ class {{stinger.python.server_class_name}}:
44
57
  self._logger.debug("Received message to %s", topic)
45
58
  {%if stinger.methods | length > 0 -%}
46
59
  {%for method_name, method in stinger.methods.items()-%}
47
- {%if not loop.first%}el{%endif%}if self._conn.is_topic_sub(topic, "{{method.topic}}"):
60
+ {%if not loop.first%}el{%endif%}if (properties.get('SubscriptionId', -1) == self._method_{{method_name|snake_case}}.subscription_id) or self._conn.is_topic_sub(topic, "{{method.topic}}"):
48
61
  try:
49
62
  payload_obj = json.loads(payload)
50
63
  except json.decoder.JSONDecodeError:
@@ -52,7 +65,25 @@ class {{stinger.python.server_class_name}}:
52
65
  else:
53
66
  self._process_{{method_name | snake_case}}_call(topic, payload_obj, properties)
54
67
  {%endfor%}
55
- {%-else%}pass{%-endif%}
68
+ {%-endif%}
69
+ {%for prop_name, prop in stinger.properties.items()%}
70
+ {%if not loop.first%}el{%endif%}if (properties.get('SubscriptionId', -1) == self._property_{{prop_name|snake_case}}.subscription_id) or self._conn.is_topic_sub(topic, "{{prop.update_topic}}"):
71
+ {%if prop.arg_list | length > 1 -%}
72
+ prop_value = {{prop.python_class}}.model_validate_json(payload)
73
+ {%else-%}
74
+ payload_obj = json.loads(payload)
75
+ prop_value = {{prop.arg_list[0].python_class}}(payload_obj["{{prop.arg_list[0].name}}"])
76
+ {%endif-%}
77
+ with self._property_{{prop_name|snake_case}}.mutex:
78
+ self._property_{{prop_name|snake_case}}.value = prop_value
79
+ self._property_{{prop_name|snake_case}}.version += 1
80
+ for callback in self._property_{{prop_name|snake_case}}.callbacks:
81
+ {%-if prop.arg_list | length > 1%}
82
+ callback({%for arg in prop.arg_list %}prop_value.{{arg.name|snake_case}}{%if not loop.last%}, {%endif%}{%endfor%})
83
+ {%-else%}
84
+ callback(prop_value)
85
+ {%-endif%}
86
+ {%endfor%}
56
87
 
57
88
  def _publish_interface_info(self):
58
89
  self._conn.publish("{{stinger.interface_info.0}}", '''{{stinger.interface_info.1 | tojson}}''', qos=1, retain=True)
@@ -78,8 +109,8 @@ class {{stinger.python.server_class_name}}:
78
109
  def handle_{{method_name | snake_case}}(self, handler: {{method_type_annotation(method)}}):
79
110
  """ This is a decorator to decorate a method that will handle the '{{method_name}}' method calls.
80
111
  """
81
- if self._{{method_name|snake_case}}_method_handler is None and handler is not None:
82
- self._{{method_name|snake_case}}_method_handler = handler
112
+ if self._method_{{method_name|snake_case}}.callback is None and handler is not None:
113
+ self._method_{{method_name|snake_case}}.callback = handler
83
114
  else:
84
115
  raise Exception("Method handler already set")
85
116
 
@@ -90,7 +121,7 @@ class {{stinger.python.server_class_name}}:
90
121
  correlation_id = properties.get('CorrelationData') # type: Optional[bytes]
91
122
  response_topic = properties.get('ResponseTopic') # type: Optional[str]
92
123
  self._logger.info("Correlation Data %s", correlation_id)
93
- if self._{{method_name|snake_case}}_method_handler is not None:
124
+ if self._method_{{method_name|snake_case}}.callback is not None:
94
125
  method_args = [] # type: List[Any]
95
126
  {%for arg in method.arg_list -%}
96
127
  if "{{arg.name}}" in payload:
@@ -114,7 +145,7 @@ class {{stinger.python.server_class_name}}:
114
145
  return_json = ""
115
146
  debug_msg = None # type: Optional[str]
116
147
  try:
117
- return_struct = self._{{method_name|snake_case}}_method_handler(*method_args)
148
+ return_struct = self._method_{{method_name|snake_case}}.callback(*method_args)
118
149
  self._logger.debug("Return value is %s", return_struct)
119
150
  {%if method.return_value is false%}
120
151
  return_json = "{}"
@@ -147,7 +178,65 @@ class {{stinger.python.server_class_name}}:
147
178
  self._conn.publish(response_topic, return_json, qos=1, retain=False,
148
179
  correlation_id=correlation_id, return_value=return_code, debug_info=debug_msg)
149
180
  {%endfor%}
181
+ {%for prop_name, prop in stinger.properties.items()%}
182
+ @property
183
+ def {{prop_name | snake_case}}(self) -> {{prop.python_class}}|None:
184
+ """ This property returns the last received value for the '{{prop_name}}' property.
185
+ """
186
+ with self._property_{{prop_name|snake_case}}_mutex:
187
+ return self._property_{{prop_name|snake_case}}
150
188
 
189
+ @{{prop_name | snake_case}}.setter
190
+ def {{prop_name | snake_case}}(self, {%if prop.arg_list | length > 1%}value{%else%}{{prop.arg_list[0].name|snake_case}}{%endif%}: {{prop.python_class}}):
191
+ """ This property sets (publishes) a new value for the '{{prop_name}}' property.
192
+ """
193
+ if not isinstance({%if prop.arg_list | length > 1%}value{%else%}{{prop.arg_list[0].name|snake_case}}{%endif%}, {{prop.python_class}}):
194
+ raise ValueError(f"The value must be {{prop.python_class}}.")
195
+
196
+ {%if prop.arg_list | length > 1%}
197
+ payload = value.model_dump_json()
198
+ {%else%}
199
+ payload = json.dumps({ "{{prop.arg_list[0].name}}": {{prop.arg_list[0].name|snake_case}} })
200
+ {%endif%}
201
+
202
+ if {%if prop.arg_list | length > 1%}value{%else%}{{prop.arg_list[0].name|snake_case}}{%endif%} != self._property_{{prop_name|snake_case}}.value:
203
+ with self._property_{{prop_name|snake_case}}.mutex:
204
+ self._property_{{prop_name|snake_case}}.value = {%if prop.arg_list | length > 1%}value{%else%}{{prop.arg_list[0].name|snake_case}}{%endif%}
205
+ self._property_{{prop_name|snake_case}}.version += 1
206
+ self._conn.publish("{{prop.value_topic}}", payload, qos=1, retain=True)
207
+ for callback in self._property_{{prop_name|snake_case}}.callbacks:
208
+ {%-if prop.arg_list | length > 1%}
209
+ callback({%for arg in prop.arg_list %}value.{{arg.name|snake_case}}{%if not loop.last%}, {%endif%}{%endfor%})
210
+ {%-else%}
211
+ callback({{prop.arg_list[0].name|snake_case}})
212
+ {%-endif%}
213
+
214
+ def set_{{prop_name | snake_case}}(self, {%for arg in prop.arg_list %}{{arg.name|snake_case}}: {{arg.python_type}}{%if not loop.last%}, {%endif%}{%endfor%}):
215
+ """ This method sets (publishes) a new value for the '{{prop_name}}' property.
216
+ """
217
+ {%-for arg in prop.arg_list%}
218
+ if not isinstance({{arg.name|snake_case}}, {{arg.python_class}}){%if arg.optional%} and {{arg.name|snake_case}} is not None{%endif%}:
219
+ raise ValueError(f"The '{{arg.name|snake_case}}' value must be {{arg.python_type}}.")
220
+ {%-endfor%}
221
+
222
+ {%if prop.arg_list | length > 1%}
223
+ obj = stinger_types.{{prop.python_local_type}}({%for arg in prop.arg_list%}
224
+ {{arg.name}}={{arg.name|snake_case}},
225
+ {%endfor%}
226
+ )
227
+ {%else%}
228
+ obj = {{prop.arg_list[0].name | snake_case}}
229
+ {%endif%}
230
+
231
+ # Use the property.setter to do that actual work.
232
+ self.{{prop_name|snake_case}} = obj
233
+
234
+ def on_{{prop_name | snake_case}}_updates(self, handler: Callable[[{%for arg in prop.arg_list %}{{arg.python_type}}{%if not loop.last%}, {%endif%}{%endfor%}], None]):
235
+ """ This method registers a callback to be called whenever a new '{{prop_name}}' property update is received.
236
+ """
237
+ if handler is not None:
238
+ self._property_{{prop_name|snake_case}}.callbacks.append(handler)
239
+ {%endfor%}
151
240
 
152
241
  class {{stinger.python.server_class_name}}Builder:
153
242
  """
@@ -159,6 +248,9 @@ class {{stinger.python.server_class_name}}Builder:
159
248
  {%for method_name, method in stinger.methods.items()%}
160
249
  self._{{method_name|snake_case}}_method_handler: Optional[{{method_type_annotation(method)}}] = None
161
250
  {%-endfor%}
251
+ {%for prop_name, property in stinger.properties.items()%}
252
+ self._{{prop_name|snake_case}}_property_callbacks: List[Callable[[{%for arg in property.arg_list %}{{arg.python_type}}{%if not loop.last%}, {%endif%}{%endfor%}], None]] = []
253
+ {%-endfor%}
162
254
  {%for method_name, method in stinger.methods.items()%}
163
255
  def handle_{{method_name | snake_case}}(self, handler: {{method_type_annotation(method)}}):
164
256
  if self._{{method_name|snake_case}}_method_handler is None and handler is not None:
@@ -166,12 +258,22 @@ class {{stinger.python.server_class_name}}Builder:
166
258
  else:
167
259
  raise Exception("Method handler already set")
168
260
  {%endfor%}
261
+ {%for prop_name, prop in stinger.properties.items()%}
262
+ def on_{{prop_name | snake_case}}_updates(self, handler: Callable[[{%for arg in prop.arg_list %}{{arg.python_type}}{%if not loop.last%}, {%endif%}{%endfor%}], None]):
263
+ """ This method registers a callback to be called whenever a new '{{prop_name}}' property update is received.
264
+ """
265
+ self._{{prop_name|snake_case}}_property_callbacks.append(handler)
266
+ {%endfor%}
169
267
  def build(self) -> {{stinger.python.server_class_name}}:
170
268
  new_server = {{stinger.python.server_class_name}}(self._conn)
171
269
  {%for method_name, method in stinger.methods.items()%}
172
270
  if self._{{method_name|snake_case}}_method_handler is not None:
173
271
  new_server.handle_{{method_name|snake_case}}(self._{{method_name|snake_case}}_method_handler)
174
272
  {%-endfor%}
273
+ {%for prop_name, prop in stinger.properties.items()%}
274
+ for callback in self._{{prop_name|snake_case}}_property_callbacks:
275
+ new_server.on_{{prop_name|snake_case}}_updates(callback)
276
+ {%endfor%}
175
277
  return new_server
176
278
 
177
279
  if __name__ == '__main__':
@@ -187,6 +289,18 @@ if __name__ == '__main__':
187
289
  conn = {{broker.class_name}}({%if broker.hostname is none%}'localhost', 1883{%endif%})
188
290
  server = {{stinger.python.server_class_name}}(conn)
189
291
 
292
+ {%for prop_name, prop in stinger.properties.items()-%}
293
+ {%if prop.arg_list | length > 1%}
294
+ server.{{prop_name | snake_case}} = {{prop.python_class}}(
295
+ {%for arg in prop.arg_list%}
296
+ {{arg.name}}={{arg.get_random_example_value()}},
297
+ {%endfor%}
298
+ )
299
+ {%else%}
300
+ server.{{prop_name | snake_case}} = {{prop.arg_list[0].get_random_example_value()}}
301
+ {%endif%}
302
+ {%endfor%}
303
+
190
304
  {%for method_name, method in stinger.methods.items()%}
191
305
  @server.handle_{{method_name | snake_case}}
192
306
  def {{method_name | snake_case}}({%for arg in method.arg_list%}{{arg.name}}: {{arg.python_type}}{%if not loop.last%}, {%endif%}{%endfor%}) -> {{method.return_value_python_type}}:
@@ -195,6 +309,12 @@ if __name__ == '__main__':
195
309
  return {{method.get_return_value_random_example_value('python')}}
196
310
  {%endfor%}
197
311
 
312
+ {%for prop_name, prop in stinger.properties.items()%}
313
+ @server.on_{{prop_name | snake_case}}_updates
314
+ def on_{{prop_name | snake_case}}_update({%for arg in prop.arg_list %}{{arg.name}}: {{arg.python_type}}{%if not loop.last%}, {%endif%}{%endfor%}):
315
+ print(f"Received update for '{{prop_name}}' property: {%for arg in prop.arg_list %}{ {{arg.name}}= }{%if not loop.last%}, {%endif%}{%endfor%}")
316
+ {%endfor%}
317
+
198
318
  print("Ctrl-C will stop the program.")
199
319
 
200
320
  while True:
@@ -15,6 +15,7 @@ serde = { version = "1.0.219", features = ["derive"] }
15
15
  serde_json = "1.0.142"
16
16
  log = "0.4"
17
17
  env_logger = "0.10"
18
+ async-trait = "0.1"
18
19
 
19
20
  [[example]]
20
21
  name = "{{stinger.name | snake_case}}_server_demo"
@@ -10,9 +10,10 @@ use mqttier::MqttierClient;
10
10
  use {{stinger.rust.server_package_name}}::{ {{stinger.rust.server_struct_name}}{%if stinger.methods|length>0%}, {{stinger.name | UpperCamelCase }}MethodHandlers{%endif%} };
11
11
  use tokio::time::{sleep, Duration};
12
12
  {%if stinger.methods|length > 0%}
13
- //use {{stinger.rust.server_package_name}}::handler::{{stinger.name | UpperCamelCase }}MethodHandlers;
14
- //use {{stinger.rust.server_package_name}}::init::Initializable;
15
- use std::sync::{Arc, Mutex};{%endif%}
13
+ use std::sync::Arc;
14
+ use tokio::sync::Mutex;
15
+ use async_trait::async_trait;
16
+ {%endif%}
16
17
  #[allow(unused_imports)]
17
18
  use {{stinger.rust.common_package_name}}::payloads::{MethodResultCode, *};
18
19
 
@@ -29,15 +30,16 @@ impl {{stinger.name | UpperCamelCase }}MethodImpl {
29
30
  }
30
31
  }
31
32
 
33
+ #[async_trait]
32
34
  impl {{stinger.name | UpperCamelCase }}MethodHandlers for {{stinger.name | UpperCamelCase }}MethodImpl {
33
35
 
34
- fn initialize(&mut self, server: {{stinger.rust.server_struct_name}}) -> Result<(), MethodResultCode> {
36
+ async fn initialize(&mut self, server: {{stinger.rust.server_struct_name}}) -> Result<(), MethodResultCode> {
35
37
  self.server = Some(server.clone());
36
38
  Ok(())
37
39
  }
38
40
 
39
41
  {%for method_name, method in stinger.methods.items()%}
40
- fn handle_{{method_name|snake_case}}(&self, {%for arg in method.arg_list%}_{{arg.name|snake_case}}: {{arg.rust_type}}{%if not loop.last%}, {%endif%}{%endfor%}) -> Result<{{method.return_value_rust_type}}, MethodResultCode> {
42
+ async fn handle_{{method_name|snake_case}}(&self, {%for arg in method.arg_list%}_{{arg.name|snake_case}}: {{arg.rust_type}}{%if not loop.last%}, {%endif%}{%endfor%}) -> Result<{{method.return_value_rust_type}}, MethodResultCode> {
41
43
  println!("Handling {{method_name}}");
42
44
  {%-if method.return_value_type == 'struct'%}
43
45
  let rv = {{method.return_value_rust_type}} {
@@ -58,12 +60,8 @@ impl {{stinger.name | UpperCamelCase }}MethodHandlers for {{stinger.name | Upper
58
60
  self
59
61
  }
60
62
  }
61
-
62
-
63
63
  {%endif%}
64
64
 
65
-
66
-
67
65
  #[tokio::main]
68
66
  async fn main() {
69
67
  env_logger::Builder::from_default_env()
@@ -13,9 +13,8 @@ use mqttier::{MqttierClient{%if stinger.methods|length > 0 or stinger.properties
13
13
  use {{stinger.rust.common_package_name}}::payloads::{*, MethodResultCode};
14
14
  use std::any::Any;
15
15
  {%if stinger.methods|length > 0 %}
16
- //pub mod handler;
17
- //pub mod init;
18
- //pub use handler::{{stinger.name | UpperCamelCase }}MethodHandlers;
16
+ use async_trait::async_trait;
17
+ use tokio::sync::Mutex as AsyncMutex;
19
18
  use std::sync::{Arc, Mutex};
20
19
  {%endif%}
21
20
  {%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
@@ -68,7 +67,7 @@ pub struct {{stinger.rust.server_struct_name}} {
68
67
  {%endif%}
69
68
  {%if stinger.methods|length > 0 %}
70
69
  /// Struct contains all the method handlers.
71
- method_handlers: Arc<Mutex<Box<dyn {{stinger.name | UpperCamelCase }}MethodHandlers>>>,
70
+ method_handlers: Arc<AsyncMutex<Box<dyn {{stinger.name | UpperCamelCase }}MethodHandlers>>>,
72
71
  {%endif%}
73
72
  {%if stinger.properties|length > 0%}
74
73
  /// Struct contains all the properties.
@@ -85,7 +84,7 @@ pub struct {{stinger.rust.server_struct_name}} {
85
84
  }
86
85
 
87
86
  impl {{stinger.rust.server_struct_name}} {
88
- pub async fn new(connection: &mut MqttierClient{%if stinger.methods|length > 0%}, method_handlers: Arc<Mutex<Box<dyn {{stinger.name | UpperCamelCase }}MethodHandlers>>> {%endif%}) -> Self {
87
+ pub async fn new(connection: &mut MqttierClient{%if stinger.methods|length > 0%}, method_handlers: Arc<AsyncMutex<Box<dyn {{stinger.name | UpperCamelCase }}MethodHandlers>>> {%endif%}) -> Self {
89
88
  {%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
90
89
  // Create a channel for messages to get from the MqttierClient object to this {{stinger.rust.server_struct_name}} object.
91
90
  // The Connection object uses a clone of the tx side of the channel.
@@ -152,7 +151,7 @@ impl {{stinger.rust.server_struct_name}} {
152
151
 
153
152
  {%for method_name, method in stinger.methods.items()-%}
154
153
  /// Handles a request message for the {{method_name}} method.
155
- async fn handle_{{method_name|snake_case}}_request(publisher: MqttierClient, handlers: Arc<Mutex<Box<dyn {{stinger.name | UpperCamelCase }}MethodHandlers>>>, msg: ReceivedMessage) {
154
+ async fn handle_{{method_name|snake_case}}_request(publisher: MqttierClient, handlers: Arc<AsyncMutex<Box<dyn {{stinger.name | UpperCamelCase }}MethodHandlers>>>, msg: ReceivedMessage) {
156
155
  let opt_corr_data = msg.correlation_data;
157
156
  let opt_resp_topic = msg.response_topic;
158
157
  {%if method.arg_list | length > 0 -%}
@@ -161,8 +160,8 @@ impl {{stinger.rust.server_struct_name}} {
161
160
  {%endif%}{# has arg_list #}
162
161
  // call the method handler
163
162
  let rv: Result<{{method.return_value_rust_type}}, MethodResultCode> = {
164
- let handler_guard = handlers.lock().unwrap();
165
- (*handler_guard).handle_{{method_name|snake_case}}({%for arg in method.arg_list%}payload.{{arg.name}}{%if not loop.last%}, {%endif%}{%endfor%})
163
+ let handler_guard = handlers.lock().await;
164
+ handler_guard.handle_{{method_name|snake_case}}({%for arg in method.arg_list%}payload.{{arg.name}}{%if not loop.last%}, {%endif%}{%endfor%}).await
166
165
  };
167
166
 
168
167
  if let Some(resp_topic) = opt_resp_topic {
@@ -267,7 +266,7 @@ impl {{stinger.rust.server_struct_name}} {
267
266
 
268
267
  {%if stinger.methods|length > 0 -%}
269
268
  let method_handlers = self.method_handlers.clone();
270
- self.method_handlers.lock().unwrap().initialize(self.clone()).expect("Failed to initialize method handlers");
269
+ self.method_handlers.lock().await.initialize(self.clone()).await;
271
270
  {%endif-%}
272
271
 
273
272
  let sub_ids = self.subscription_ids.clone();
@@ -304,13 +303,14 @@ impl {{stinger.rust.server_struct_name}} {
304
303
 
305
304
  }
306
305
  {%if stinger.methods|length > 0 %}
306
+ #[async_trait]
307
307
  pub trait {{stinger.name | UpperCamelCase }}MethodHandlers: Send + Sync {
308
308
 
309
- fn initialize(&mut self, server: {{stinger.rust.server_struct_name}}) -> Result<(), MethodResultCode>;
309
+ async fn initialize(&mut self, server: {{stinger.rust.server_struct_name}}) -> Result<(), MethodResultCode>;
310
310
 
311
311
  {%for method_name, method in stinger.methods.items()-%}
312
312
  /// Pointer to a function to handle the {{method_name}} method request.
313
- fn handle_{{method_name|snake_case}}(&self, {%for arg in method.arg_list%}{{arg.name|snake_case}}: {{arg.rust_type}}{%if not loop.last%}, {%endif%}{%endfor%}) -> Result<{{method.return_value_rust_type}}, MethodResultCode>;
313
+ async fn handle_{{method_name|snake_case}}(&self, {%for arg in method.arg_list%}{{arg.name|snake_case}}: {{arg.rust_type}}{%if not loop.last%}, {%endif%}{%endfor%}) -> Result<{{method.return_value_rust_type}}, MethodResultCode>;
314
314
 
315
315
  {%endfor%}
316
316