11# -*- coding: utf-8 -*-
2- # (c) 2014-2022 The mqttwarn developers
2+ # (c) 2014-2023 The mqttwarn developers
33import logging
44import typing as t
55
66import attr
77
88from mqttwarn .configuration import Config
9+ from mqttwarn .model import Service , TdataType , TopicTargetType
910from mqttwarn .util import load_function , sanitize_function_name
1011
1112logger = logging .getLogger (__name__ )
@@ -20,9 +21,9 @@ class RuntimeContext:
2021 """
2122
2223 config : Config = attr .ib ()
23- invoker : t . Optional [ "FunctionInvoker" ] = attr .ib ()
24+ invoker : "FunctionInvoker" = attr .ib ()
2425
25- def get_sections (self ):
26+ def get_sections (self ) -> t . List [ str ] :
2627 sections = []
2728 for section in self .config .sections ():
2829 if section == "defaults" :
@@ -39,57 +40,57 @@ def get_sections(self):
3940 logger .warning ("Section `%s' has no targets defined" % section )
4041 return sections
4142
42- def get_topic (self , section ) :
43+ def get_topic (self , section : str ) -> str :
4344 if self .config .has_option (section , "topic" ):
4445 return self .config .get (section , "topic" )
4546 return section
4647
47- def get_qos (self , section ) :
48+ def get_qos (self , section : str ) -> int :
4849 qos = 0
4950 if self .config .has_option (section , "qos" ):
5051 qos = int (self .config .get (section , "qos" ))
5152 return qos
5253
53- def get_config (self , section , name ) :
54+ def get_config (self , section : str , name : str ) -> t . Any :
5455 value = None
5556 if self .config .has_option (section , name ):
5657 value = self .config .get (section , name )
5758 return value
5859
59- def is_filtered (self , section , topic , payload ) :
60+ def is_filtered (self , section : str , topic : str , payload : t . AnyStr ) -> bool :
6061 if self .config .has_option (section , "filter" ):
61- filterfunc = sanitize_function_name (self .config .get (section , "filter" ))
6262 try :
63- return self .invoker .filter (filterfunc , topic , payload , section )
63+ name = sanitize_function_name (self .config .get (section , "filter" ))
64+ return self .invoker .filter (name , topic , payload , section )
6465 except Exception as e :
65- logger .exception ("Cannot invoke filter function '%s' defined in '%s': %s" % (filterfunc , section , e ))
66+ logger .exception ("Cannot invoke filter function '%s' defined in '%s': %s" % (name , section , e ))
6667 return False
6768
68- def get_topic_data (self , section , topic ) :
69+ def get_topic_data (self , section : str , data : TdataType ) -> t . Optional [ TdataType ] :
6970 if self .config .has_option (section , "datamap" ):
70- name = sanitize_function_name (self .config .get (section , "datamap" ))
7171 try :
72- return self .invoker .datamap (name , topic )
72+ name = sanitize_function_name (self .config .get (section , "datamap" ))
73+ return self .invoker .datamap (name , data )
7374 except Exception as e :
7475 logger .exception ("Cannot invoke datamap function '%s' defined in '%s': %s" % (name , section , e ))
7576 return None
7677
77- def get_all_data (self , section , topic , data ) :
78+ def get_all_data (self , section : str , topic : str , data : TdataType ) -> t . Optional [ TdataType ] :
7879 if self .config .has_option (section , "alldata" ):
79- name = sanitize_function_name (self .config .get (section , "alldata" ))
8080 try :
81+ name = sanitize_function_name (self .config .get (section , "alldata" ))
8182 return self .invoker .alldata (name , topic , data )
8283 except Exception as e :
8384 logger .exception ("Cannot invoke alldata function '%s' defined in '%s': %s" % (name , section , e ))
8485 return None
8586
86- def get_topic_targets (self , section , topic , data ) :
87+ def get_topic_targets (self , section : str , topic : str , data : TdataType ) -> TopicTargetType :
8788 """
8889 Topic targets function invoker.
8990 """
9091 if self .config .has_option (section , "targets" ):
91- name = sanitize_function_name (self .config .get (section , "targets" ))
9292 try :
93+ name = sanitize_function_name (self .config .get (section , "targets" ))
9394 return self .invoker .topic_target_list (name , topic , data )
9495 except Exception as ex :
9596 error = repr (ex )
@@ -99,20 +100,31 @@ def get_topic_targets(self, section, topic, data):
99100 )
100101 return None
101102
102- def get_service_config (self , service ) :
103+ def get_service_config (self , service : str ) -> t . Dict [ str , t . Any ] :
103104 config = self .config .config ("config:" + service )
104105 if config is None :
105106 return {}
106107 return dict (config )
107108
108- def get_service_targets (self , service ):
109- # Be more graceful with jobs w/o any target address information (2021-10-18 [amo]).
109+ def get_service_targets (self , service : str ) -> t .List [TopicTargetType ]:
110+ """
111+ Resolve target address descriptor.
112+
113+ 2021-10-18 [amo]: Be more graceful with jobs w/o any target address information.
114+ """
115+ targets : t .List [TopicTargetType ] = []
110116 try :
111- targets = self .config .getdict ("config:" + service , "targets" ) or [None ]
112- return targets
117+ targets = self .config .getdict ("config:" + service , "targets" )
113118 except :
114119 logger .exception ("Unable to access targets for service `%s'" % service )
115120
121+ # TODO: The target address descriptor may be of any type these days,
122+ # and not necessarily a list.
123+ # TODO: Currently, this makes sure to always return one element.
124+ # Verify if this is really needed.
125+ targets = targets or [None ]
126+ return targets
127+
116128
117129@attr .s
118130class FunctionInvoker :
@@ -121,31 +133,32 @@ class FunctionInvoker:
121133 functions from a configured Python source code file.
122134 """
123135
124- config = attr .ib ()
125- srv = attr .ib ()
136+ config : Config = attr .ib ()
137+ srv : Service = attr .ib ()
126138
127- def datamap (self , name , topic ) :
139+ def datamap (self , name : str , data : TdataType ) -> TdataType :
128140 """
129141 Invoke function "name" loaded from the "functions" Python module.
130142
131143 :param name: Function name to invoke
132- :param topic : Topic to pass to the invoked function
144+ :param data : Data to pass to the invoked function
133145 :return: Return value of function invocation
134146 """
135147
136148 val = None
149+
137150 try :
138151 func = load_function (name = name , py_mod = self .config .functions )
139152 try :
140- val = func (topic , self .srv ) # new version
153+ val = func (data , self .srv ) # new version
141154 except TypeError :
142- val = func (topic ) # legacy
155+ val = func (data ) # legacy
143156 except :
144157 raise
145158
146159 return val
147160
148- def alldata (self , name , topic , data ) :
161+ def alldata (self , name : str , topic : str , data : TdataType ) -> TdataType :
149162 """
150163 Invoke function "name" loaded from the "functions" Python module.
151164
@@ -164,7 +177,7 @@ def alldata(self, name, topic, data):
164177
165178 return val
166179
167- def topic_target_list (self , name , topic , data ) :
180+ def topic_target_list (self , name : str , topic : str , data : TdataType ) -> TopicTargetType :
168181 """
169182 Invoke function "name" loaded from the "functions" Python module.
170183 Computes dynamic topic subscription targets.
@@ -185,7 +198,7 @@ def topic_target_list(self, name, topic, data):
185198
186199 return val
187200
188- def filter (self , name , topic , payload , section = None ): # noqa:A003
201+ def filter (self , name : str , topic : str , payload : t . AnyStr , section : t . Optional [ str ] = None ) -> bool : # noqa:A003
189202 """
190203 Invoke function "name" loaded from the "functions" Python module.
191204 Return that function's True/False.
@@ -199,15 +212,17 @@ def filter(self, name, topic, payload, section=None): # noqa:A003
199212 # Filtering currently only works on text.
200213 # TODO: To let filtering also work on binary data, this line would need to go elsewhere. But where?
201214 if isinstance (payload , bytes ):
202- payload = payload .decode ("utf-8" )
215+ payload_decoded = payload .decode ("utf-8" )
216+ else :
217+ payload_decoded = payload
203218
204219 rc = False
205220 try :
206221 func = load_function (name = name , py_mod = self .config .functions )
207222 try :
208- rc = func (topic , payload , section , self .srv ) # new version
223+ rc = func (topic , payload_decoded , section , self .srv ) # new version
209224 except TypeError :
210- rc = func (topic , payload ) # legacy signature
225+ rc = func (topic , payload_decoded ) # legacy signature
211226 except :
212227 raise
213228
0 commit comments